Files
aare/python/aare/PixelHistogramCUDA.py
T
kferjaoui ea32b530e8
Build on RHEL8 / build (push) Successful in 3m48s
Build on RHEL9 / build (push) Successful in 4m5s
Run tests using data on local RHEL8 / build (push) Failing after 4m48s
python: bind PixelHistogramCUDA
PixelHistogramCUDA_<input>_<storage> for uint16, float32 and float64
frames and the six storage types of PixelHistogram, behind an
aare.PixelHistogramCUDA() factory. Frames are never converted: a wrong
dtype raises TypeError, so a registered buffer is always the one copied.
The GIL is released during GPU work.

- C++: fill_histogram() and submit_batch() take const views, so
  read-only arrays (np.memmap mode="r") work; register_output_buffer()
  pins the destination of values(out).
- Python: register_input_buffer()/register_output_buffer() keep their
  array alive; values(out=...) writes into a given array. With a pinned
  out, reading back a 640 MB histogram takes 28 ms instead of 230 ms.
- Tests in test_PixelHistogramCUDA.py; PixelHistogramCUDA.ipynb compares
  CPU and GPU on 100k Moench frames (identical, ~13x faster on the GPU).
2026-10-21 17:35:42 +02:00

86 lines
3.1 KiB
Python

# SPDX-License-Identifier: MPL-2.0
import numpy as np
from . import _aare
from .ClusterFinder import _cuda_available
# numpy dtype -> class name suffix, for the frames and for the counts.
_INPUT_SUFFIX = {
np.dtype(np.uint16): "u16",
np.dtype(np.float32): "f",
np.dtype(np.float64): "d",
}
_STORAGE_SUFFIX = {
np.dtype(np.uint8): "u8",
np.dtype(np.uint16): "u16",
np.dtype(np.uint32): "u32",
np.dtype(np.uint64): "u64",
np.dtype(np.float32): "f",
np.dtype(np.float64): "d",
}
def _suffix(table, dtype, what):
try:
return table[np.dtype(dtype)]
except (KeyError, TypeError):
supported = ", ".join(str(d) for d in table)
raise ValueError(
f"Unsupported {what}: {dtype}. Supported: {supported}."
) from None
def PixelHistogramCUDA(*, rows, cols, n_bins, xmin, xmax, dtype,
storage_dtype=np.uint32, batch_size=64):
"""
Per-pixel histograms filled on the GPU.
Parameters
----------
rows, cols : int
Frame shape.
n_bins : int
Number of bins per pixel.
xmin, xmax : float
Histogram range, [xmin, xmax). Values outside it, and NaN, are
dropped.
dtype : numpy dtype
dtype of the frames you will pass: np.uint16, np.float32 or
np.float64. Frames are copied to the GPU as they are and are never
converted, so fill_histogram() raises TypeError for any other dtype.
The copy usually limits the speed: uint16 frames move half the bytes
of float32 and a quarter of float64. Use float32 rather than float64
for processed frames when its precision is enough.
storage_dtype : numpy dtype, optional
dtype of the counts returned by values(): np.uint8, np.uint16,
np.uint32, np.uint64, np.float32 or np.float64. Integer counts are
capped at the type's maximum. Default np.uint32.
batch_size : int, optional
Frames copied and processed per step. Two batches are held on the
GPU, 2 * batch_size * rows * cols * itemsize bytes. Default 64.
Example
-------
.. code-block:: python
import numpy as np
from aare import PixelHistogramCUDA
frames = np.fromfile(path, dtype=np.uint16).reshape(-1, 400, 400)
hist = PixelHistogramCUDA(rows=400, cols=400, n_bins=1000,
xmin=-0.5, xmax=999.5, dtype=frames.dtype)
hist.register_input_buffer(frames) # optional: faster copies
hist.fill_histogram(frames)
counts = hist.values() # (400, 400, 1000)
"""
if not _cuda_available():
raise RuntimeError(
"PixelHistogramCUDA is not available in this build of aare. "
"Rebuild with -DAARE_CUDA=ON (and -DAARE_PYTHON_BINDINGS=ON)."
)
name = (f"PixelHistogramCUDA_{_suffix(_INPUT_SUFFIX, dtype, 'dtype')}_"
f"{_suffix(_STORAGE_SUFFIX, storage_dtype, 'storage_dtype')}")
cls = getattr(_aare, name)
return cls(rows=rows, cols=cols, n_bins=n_bins, xmin=xmin, xmax=xmax,
batch_size=batch_size)