Slide 30 reported an 8/11 cluster mismatch between ClusterFinder and
ClusterFinderFrozen without naming its cause. Two experiments now do.
Instrumented: 974 divergent pixels partition onto the three decisions with
no remainder -- Test1 619 flips / 0 clusters, Test3 347 / 11, local-max gate
8 / 8. Ablated: the 11 go to exactly zero. Corrects a claim the data refuted
-- Test1 initiates independently, ~7x slower, and never creates a cluster.
AARE_BRANCH_TRACE defaults to 0 and folds away at compile time, so the CPU
baseline the deck quotes is unaffected. AARE_TEST3_ENABLED defaults to 1.
New annex A7 (2 slides): part 1 restores the third test slide 4 omits and
derives c3 from variance addition; part 2 carries the measurement.
fig_test3 painted a completed branch map with a "scan is here" cursor over
it -- now the finished frame, arrows meaning raster order, shadow given its
own fill so a photon stops looking 3x4 wide, frozen in red so amber keeps
one meaning. fig_overlap_9x9: row spacing now clears the tag, not the lane.
New docs/deck/QA.md: the code and algorithm questions this raised, with
where each is settled. Also retired 3 figures placed on no slide; annex
count in report and README was stale (6/53, now 7/55).
- Diagnostic twin of ClusterFinder: identical decisions, but the pedestal is
frozen per frame (snapshot at frame start, updates deferred to frame end),
matching the CUDA kernel's update model.
- Isolates pedestal-update timing as the sole remaining CPU/GPU mismatch.
- Adds class + bindings + factory, validation notebook, and helper utilities.
- CUDA Graph variant of ClusterFinderCUDA: one pre-recorded graph per stream
(memset + H2D + kernel + D2H), with per-frame src/dst pointers swapped via
cudaGraphExecMemcpyNodeSetParams to cut per-frame launch overhead.
- Exposed through the _aare_cuda bindings and a ClusterFinderCUDAGraph factory.
- DEVICE_PED_TYPE for device pedestal/variance (shipped double/double to match
the double CPU ClusterFinder).
- Local-max suppression in Test 1/Test 3: non-peak pixels no longer store or
update, mirroring ClusterFinder's `value < max -> continue`.
- device_pedestal()/device_noise() accessors (+ bindings) for the in-kernel
decision-time pedestal.
- Move Chi2.hpp from include/aare/ to src/ (private)
- Pimpl on FitModel<Model>: MnUserParameters/MnStrategy behind opaque
src/FitModelImpl.hpp, no Minuit2 includes in public headers
- Move fit_pixel/fit_3d bodies to Fit.cpp with explicit instantiations
for all 8 models; drop FCN template param from public API
- CMake: aare::Minuit2 wrapped in $<BUILD_INTERFACE:...> (hidden from
exported targets, same pattern as lmfit), MINUIT2_INSTALL OFF, Chi2.hpp
removed from PUBLICHEADERS
- Update python bindings and benchmark callsites accordingly
---------
Co-authored-by: Erik Fröjdh <erik.frojdh@psi.ch>
Co-authored-by: Alice <alice.mazzoleni@psi.ch>
Collection of small improvements in usability:
- NDView works for large arrays
- Direct subtraction of Pedestal from np.array
- len() support for python bindings of files
- reshape image directly in decoder such that first dimension is num
counters
- take into account chip artefact in decoder.
---------
Co-authored-by: Erik Fröjdh <erik.frojdh@psi.ch>
If AARE_FETCH_MINUIT is set to OFF we first look for a standalone
Minuit2 and if that is not found we try to find Minuit2 as a part of
ROOT.
In both cases we make an alias to allow for simpler use of the target
later.
It still doesn't solve the issue that we install Minuit to when we fetch
it but that can be addressed in a separate PR.
closes #316
Multi threaded filling of per pixel histograms for example for detector calibration
1. PixelHistogram - Generic variant expects already pedestal subtracted
data
2. PedestalTrackingHistogram - Terrible name, useful class. Keeps it's
own pedestal and does conversion and pedestal tracking in the worker
threads.
---------
Co-authored-by: Lars Erik Fröjd <froejdh_e@pc-jungfrau-02.psi.ch>
- Eliminate the ~200–300 µs inter-batch idle gap by allowing two batches
to be in-flight simultaneously:
- submit_batch() enqueues H2D+kernel+D2H without blocking
- collect() syncs via cudaEventSynchronize (not
cudaStreamSynchronize) so a queued second batch runs uninterrupted.
- Two ping-pong output slots (NUM_SLOTS=2) with per-slot pinned buffers
and cudaEventDisableTiming sync events.
- find_clusters_batched() keeps its direct implementation.
* Measured: 0.026 -> 0.022 ms/frame (~18%).
- Device pedestal arrays (mean/sum/sum2) are now float instead of
double: halves global-memory bandwidth for pedestal reads/writes and
eliminates FP64 arithmetic in the kernel (3.3x kernel speedup,
15µs -> 4.6µs).
- Replace the per-cluster push_back loop in the D2H drain with a
single resize()+memcpy().
Rework the multi-stream pipeline to eliminate per-frame sync barriers and
fix the D2H staging architecture.
Sync reduction:
- Replace one cudaStreamSynchronize per frame with one per stream per batch,
cutting synchronisation calls from O(n_frames x n_streams) to O(n_streams)
- Introduce a unified per-frame D2H output layout [uint32_t count | clusters[max]]
stored in a single class-level lazy-allocated pinned pool (h_output_pinned),
replacing the per-stream separate cluster/count device buffers
- Move CUDA event pool from per-stream fixed-size to per-frame-slot lazy-allocated,
enabling correct kernel timing across any batch size
Pinned H2D without CPU-side copy:
- Add register_input_buffer(ptr, bytes) / unregister_input_buffer() wrapping
cudaHostRegister so callers can pin their existing batch buffer once; all
find_clusters_batched() slices then transfer at DMA speed (~22 GB/s) instead
of ~15 GB/s for pageable, with no extra memcpy or WC-memory penalty
Result (RTX 4090, 400x400 uint16, 3x3 clusters, batch=2000, 5 streams):
Before: ~34 µs/frame -> After: ~28 µs/frame (−18 %)
Add options for var cluster_finder_X:
1. number of neighbors (for better segmentation of clusters)
2. option to empty the surrounding pixels
---------
Co-authored-by: xiangyu.xie <xiangyu.xie@psi.ch>
- Use per-stream pinned host staging buffers for truly async CUDA transfers.
- Avoid reserving full device capacity per result frame.
- Reduce kernel work by delaying cluster payload construction.
- Use squared comparisons and removing per-pixel sqrtf() ops.
- Stencil arithmetic and shared memory use float (COMPUTE_TYPE alias).
- Pedestal accumulation stays double to preserve variance accuracy.
Notes:
- On RTX 4090, FP32 throughput is ~64× higher than FP64, so moving
stencil math to float improves performance.
- Using float also avoids shared memory bank conflicts: stride-18 maps
to distinct banks for 32-bit values, but caused conflicts with 64-bit.
- Allowing the users more flexibility to play around with custom eta
functions without touching the c++ code
- passing vector of eta values to ``transform_eta_values``
```
from aare import Interpolator, ClusterVector, Etai, Cluster
import numpy as np
def custom_eta(cluster_pixel_coordinate_x, cluster_pixel_coordinate_y, cluster_data):
# dummy custom eta function that just returns the sum of the cluster data
eta = Etai()
eta.x = 0.1 # dummy x value
eta.y = 0.1 # dummy y value
eta.sum = np.sum(cluster_data) # sum of the cluster data as the "energy
return eta
# Create a dummy eta distribution and bins
eta_distribution = np.zeros((10, 10, 1)) # dummy eta distribution
etax_bins = np.linspace(0, 1.0, 11)
etay_bins = np.linspace(0, 1.0, 11)
e_bins = np.array([0., 10.]) # dummy energy bins
# Create the interpolator
interpolator = Interpolator(eta_distribution, etax_bins, etay_bins, e_bins)
# Create a dummy cluster vector
cluster_vector = ClusterVector()
cluster_vector.push_back(Cluster(10, 5, np.ones(shape=9, dtype = np.int32)))
cluster_vector.push_back(Cluster(20, 10, np.ones(shape=9, dtype = np.int32)))
# Create dummy etas for the clusters
cluster_array = np.array(cluster_vector)
etas = np.array([custom_eta(cluster["x"], cluster["y"], cluster["data"]) for cluster in cluster_array])
# transform eta values to uniform coordinates
uniform_coordinates = interpolator.transform_eta_values(etas)
# Interpolate to get the photon coordinates e.g. apply interpolation logic
photon_coordinates_x = cluster_array["x"] + uniform_coordinates["x"] # add to pixel coordinate
photon_coordinates_y = cluster_array["y"] + uniform_coordinates["y"] # add to pixel coordinate
```
advantage: full control over interpolation logic,
downside: inefficient quite some loops in python
- passing pre computed eta values to interpolate function
```
Interpolator.interpolate(cluster_vector, etas)
```
downside: less flexibility in interpolation logic.
downside: People might misuse it instead of using interpolate directly
with a pre compiled eta function implemented in c++
- After upgrading to pybind11 3, duplicate registration of cluster-related
types across `_aare` and `_aare_cuda` started failing.
- Mark the `Cluster` and `ClusterVector` bindings as `py::module_local()` so
each extension owns its local registration.
Note: cluster objects from CPU and CUDA bindings are now distinct Python types.
- Add bind_ClusterFinderCUDA.hpp with pybind11 bindings for
ClusterFinderCUDA
- Build CUDA bindings as separate _aare_cuda.so to avoid
segfaults from mixing nvcc and gcc compiled code in the
same shared object
- Re-export CUDA classes onto _aare in __init__.py so user
code uses `from aare import ClusterFinderCUDA` regardless
of which .so hosts the class
- Factory in ClusterFinder.py selects backend; RuntimeError
if GPU requested on CPU-only build
- Update python/CMakeLists.txt: _aare_cuda module gated
behind AARE_CUDA and AARE_PYTHON_BINDINGS
- Add validation notebook: ~20x speedup vs sequential ClusterFinder
To improve codebase quality and reduce human error, this PR introduces
the pre-commit framework. This ensures that all code adheres to project
standards before it is even committed, maintaining a consistent style
and catching common mistakes early.
Key Changes:
- Code Formatting: Automated C++ formatting using clang-format (based on
the project's .clang-format file).
- Syntax Validation: Basic checks for file integrity and syntax.
- Spell Check: Automated scanning for typos in source code and comments.
- CMake Formatting: Standardization of CMakeLists.txt and .cmake
configuration files.
- GitHub Workflow: Added a CI action that validates every Pull Request
against the pre-commit configuration to ensure compliance.
The configuration includes a [ci] block to handle automated fixes within
the PR. Currently, this is disabled. If we want the CI to automatically
commit formatting fixes back to the PR branch, this can be toggled to
true in .pre-commit-config.yaml.
```yaml
ci:
autofix_commit_msg: [pre-commit] auto fixes from pre-commit hooks
autofix_prs: false
autoupdate_schedule: monthly
```
The last large commit with the fit functions, for example, was not
formatted according to the clang-format rules. This PR would allow to
avoid similar mistakes in the future.
Python fomat with `ruff` for tests and sanitiser for `.ipynb` notebooks
can be added as well.
- Set parameter starting values, limits or fix through name as well as
index
- Updated parameter names for the scurve
- Fast approximation to erf function (~10% speedup of fitting)
---------
Co-authored-by: Khalil Ferjaoui <khalilferjaoui@yahoo.fr>
## Unified Minuit2 fitting framework with FitModel API
### Models (`Models.hpp`)
Consolidate all model structs (Gaussian, RisingScurve, FallingScurve)
into a
single header. Each model provides: `eval`, `eval_and_grad`, `is_valid`,
`estimate_par`, `compute_steps`, and `param_info` metadata. No Minuit2
dependency.
### Chi2 functors (`Chi2.hpp`)
Generic `Chi2Model1DGrad` (analytic gradient) templated on the model
struct.
Replaces the separate Chi2Gaussian, Chi2GaussianGradient,
Chi2Scurves, and Chi2ScurvesGradient headers.
### FitModel (`FitModel.hpp`)
Configuration object wrapping `MnUserParameters`, strategy, tolerance,
and
user-override tracking. User constraints (fixed parameters, start
values, limits)
always take precedence over automatic data-driven estimates.
### Fit functions (`Fit.hpp`)
- `fit_pixel<Model, FCN>(model, x, y, y_err)` -> single-pixel,
self-contained
- `fit_pixel<Model, FCN>(model, upar_local, x, y, y_err)` -> pre-cloned
upar for hot loops
- `fit_3d<Model, FCN>(model, x, y, y_err, ..., n_threads)` ->
row-parallel over pixel grid
### Python bindings
- `Pol1`, `Pol2`, `Gaussian`, `RisingScurve`, `FallingScurve` model
classes with
`FixParameter`, `SetParLimits`, `SetParameter`, and properties for
`max_calls`, `tolerance`, `compute_errors`
- Single `fit(model, x, y, y_err, n_threads)` dispatch replacing the old
`fit_gaus_minuit`, `fit_gaus_minuit_grad`, `fit_scurve_minuit_grad`,
etc.
### Benchmarks
- Updated `fit_benchmark.cpp` (Google Benchmark) to use the new FitModel
API
- Jupyter notebooks for 1D and 3D S-curve fitting (lmfit vs Minuit2
analytic)
- ~1.8x speedup over lmfit, near-linear thread scaling up to physical
core count
---------
Co-authored-by: Erik Fröjdh <erik.frojdh@psi.ch>
Matterhorn10 Transform
some other Transformations from pyctbGUI
added method get_reading_mode for easier error handling in decoders
## TODO:
- proper error handling for all other decoders
- proper documentation for all other decoders
- refactoring all other decoders to store hard coded values in a Struct
ChipSpecification
Reading multiple ROI's for aare
- read_frame, read_n etc throws for multiple ROIs
- new functions read_ROIs, read_n_ROIs
- read_roi_into (used for python bindings - to not copy)
all these functions use get_frame or get_frame_into where one passes the
roi_index
## Refactoring:
- each roi keeps track of its subfiles that one has to open e.g.
subfiles can be opened several times
- refactored class DetectorGeometry - keep track of the updated module
geometries in new class ROIGeometry.
- ModuleGeometry updates based on ROI
## ROIGeometry:
- stores number of modules overlapping with ROI and its indices
- size of ROI
Note: only tested size of the resulting frames not the actual values
---------
Co-authored-by: Erik Fröjdh <erik.frojdh@psi.ch>
Co-authored-by: Erik Fröjdh <erik.frojdh@gmail.com>
- automatically run python tests
- automatically run test using data files on local runner from gitea
- fixed some of the workflows
---------
Co-authored-by: Erik Fröjdh <erik.frojdh@psi.ch>
- New aare:to_string/string_to similar to what we have in
slsDetectorPackage
- Added members period and exptime to RawMasterFile
- Parsing exposure time and period for json and raw master file formats
- Parsing of RawMasterFile from string stream to enable test without
files
Comments:
- to_string is at the moment not a public header. Can make it later if
needed. This gives us full freedom with the API
- FileConfig should probably be deprecated need to look into it.
Meanwhile removed python bindings and string conv