mirror of
https://github.com/slsdetectorgroup/aare.git
synced 2026-10-31 13:41:27 +01:00
Merge branch 'main' into feature/cuda_clusterfinder
This commit is contained in:
100 files changed
+4748
-2464
No files matched your search
@@ -68,8 +68,10 @@ set(PYTHON_FILES
|
||||
aare/ClusterFinder.py
|
||||
aare/ClusterVector.py
|
||||
aare/Cluster.py
|
||||
aare/FastPedestal.py
|
||||
aare/calibration.py
|
||||
aare/func.py
|
||||
aare/factory.py
|
||||
aare/experimental.py
|
||||
aare/RawFile.py
|
||||
aare/transform.py
|
||||
aare/ScanParameters.py
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from . import _aare
|
||||
import numpy as np
|
||||
from .ClusterFinder import _type_to_char
|
||||
from .factory import _type_to_char
|
||||
|
||||
|
||||
def Cluster(x : int, y : int, data, cluster_size=(3,3), dtype = np.int32):
|
||||
@@ -21,4 +21,4 @@ def Cluster(x : int, y : int, data, cluster_size=(3,3), dtype = np.int32):
|
||||
except AttributeError:
|
||||
raise ValueError(f"Unsupported combination of type and cluster size: {dtype}/{cluster_size} when requesting {class_name}")
|
||||
|
||||
return cls(x, y, data)
|
||||
return cls(x, y, data)
|
||||
@@ -1,21 +1,10 @@
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
from . import _aare
|
||||
import numpy as np
|
||||
from .factory import _type_to_char
|
||||
|
||||
_supported_cluster_sizes = [(2,2), (3,3), (5,5), (7,7), (9,9),]
|
||||
|
||||
def _type_to_char(dtype):
|
||||
if dtype == np.int32:
|
||||
return 'i'
|
||||
elif dtype == np.float32:
|
||||
return 'f'
|
||||
elif dtype == np.float64:
|
||||
return 'd'
|
||||
elif dtype == np.int16:
|
||||
return 'i16'
|
||||
else:
|
||||
raise ValueError(f"Unsupported dtype: {dtype}. Only np.int32, np.float32, and np.float64 are supported.")
|
||||
|
||||
def _get_class(name, cluster_size, dtype):
|
||||
"""
|
||||
Helper function to get the class based on the name, cluster size, and dtype.
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
import numpy as np
|
||||
|
||||
from .factory import _get_typed_class
|
||||
|
||||
|
||||
def _get_fast_pedestal_class(dtype):
|
||||
return _get_typed_class("FastPedestal", dtype)
|
||||
|
||||
|
||||
def FastPedestal(rows, cols, n_samples=1000, dtype=np.float64):
|
||||
"""Create an empty per-pixel running pedestal.
|
||||
|
||||
This factory hides the dtype suffix used by the templated C++ bindings.
|
||||
Call ``add_init_frame()`` exactly ``n_samples`` times before using the
|
||||
statistics or calling ``push_ema()``. Subsequent frames have weight
|
||||
``1 / n_samples`` in the running mean and population variance.
|
||||
|
||||
Args:
|
||||
rows: Number of image rows.
|
||||
cols: Number of image columns.
|
||||
n_samples: Initialization frame count and steady-state update-weight
|
||||
denominator.
|
||||
dtype: Output dtype for the mean, variance, and standard deviation.
|
||||
Supported values are ``np.float64``, ``np.float32``, and
|
||||
``np.int16``.
|
||||
"""
|
||||
cls = _get_fast_pedestal_class(dtype)
|
||||
return cls(rows, cols, n_samples)
|
||||
|
||||
|
||||
def from_file(filename, n_samples=1000, skip_first=0, dtype=np.float64):
|
||||
"""Create a FastPedestal from frames in a file.
|
||||
|
||||
After ignoring ``skip_first`` frames, the next ``n_samples`` frames
|
||||
initialize the pedestal. Every remaining frame is then applied as a
|
||||
steady-state update. Input frames are read as uint16 data.
|
||||
|
||||
Args:
|
||||
filename: Input image file.
|
||||
n_samples: Number of frames used for initialization.
|
||||
skip_first: Number of leading frames to ignore.
|
||||
dtype: Output dtype for the mean, variance, and standard deviation.
|
||||
|
||||
Raises:
|
||||
RuntimeError: If fewer than ``n_samples`` frames remain after
|
||||
``skip_first`` or ``n_samples`` is zero.
|
||||
"""
|
||||
cls = _get_fast_pedestal_class(dtype)
|
||||
return cls.from_file(
|
||||
filename, n_samples=n_samples, skip_first=skip_first
|
||||
)
|
||||
|
||||
|
||||
FastPedestal.from_file = from_file
|
||||
+20
-7
@@ -20,8 +20,24 @@ except ImportError:
|
||||
pass
|
||||
|
||||
from . import transform
|
||||
from . import experimental
|
||||
|
||||
from ._aare import File, RawMasterFile, RawSubFile, JungfrauDataFile
|
||||
from ._aare import (
|
||||
FastPedestal_d,
|
||||
FastPedestal_f,
|
||||
FastPedestal_i16,
|
||||
Pedestal_d,
|
||||
Pedestal_f,
|
||||
Pedestal_i16,
|
||||
ClusterFinder_Cluster3x3i,
|
||||
VarClusterFinder,
|
||||
)
|
||||
from ._aare import (
|
||||
File,
|
||||
JungfrauDataFile,
|
||||
RawMasterFile,
|
||||
RawSubFile,
|
||||
)
|
||||
from ._aare import Pedestal_d, Pedestal_f, ClusterFinder_Cluster3x3i, VarClusterFinder
|
||||
from ._aare import DetectorType, ReadoutMode
|
||||
from ._aare import hitmap
|
||||
@@ -31,14 +47,14 @@ from ._aare import corner
|
||||
# from ._aare import ClusterFinderMT, ClusterCollector, ClusterFileSink, ClusterVector_i
|
||||
|
||||
from ._version import __version__
|
||||
from .ClusterFinder import ClusterFinder, ClusterFinderFrozen, ClusterCollector, ClusterFinderMT, ClusterFileSink, ClusterFile
|
||||
from .ClusterFinder import ClusterFinderCUDA, ClusterFinderCUDAGraph, _cuda_available, find_cluster_views_batched_iter
|
||||
from .FastPedestal import FastPedestal
|
||||
from .ClusterFinder import (ClusterFinder, ClusterFinderFrozen, ClusterCollector, ClusterFinderMT, ClusterFileSink, ClusterFile,
|
||||
ClusterFinderCUDA, ClusterFinderCUDAGraph, _cuda_available, find_cluster_views_batched_iter)
|
||||
from .ClusterVector import ClusterVector
|
||||
from .Cluster import Cluster
|
||||
|
||||
from ._aare import Gaussian, RisingScurve, FallingScurve, Pol1, Pol2, GaussianErfcPlateau, GaussianChargeSharing, GaussianChargeSharingKb
|
||||
from ._aare import fit
|
||||
from ._aare import fit_gaus, fit_pol1, fit_scurve, fit_scurve2
|
||||
from ._aare import Interpolator
|
||||
from ._aare import calculate_eta2, calculate_eta3, calculate_cross_eta3, calculate_full_eta2
|
||||
from ._aare import reduce_to_2x2, reduce_to_3x3
|
||||
@@ -54,9 +70,6 @@ from .ScanParameters import ScanParameters
|
||||
from .utils import random_pixels, random_pixel, flat_list, add_colorbar, Timer
|
||||
|
||||
|
||||
#make functions available in the top level API
|
||||
from .func import *
|
||||
|
||||
from .calibration import *
|
||||
from ._aare import apply_calibration, count_switching_pixels
|
||||
from ._aare import calculate_pedestal, calculate_pedestal_float, calculate_pedestal_g0, calculate_pedestal_g0_float
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
"""Experimental APIs that may change without notice."""
|
||||
|
||||
from ._aare.experimental import MultiThreadedFileReader
|
||||
|
||||
__all__ = ["MultiThreadedFileReader"]
|
||||
@@ -0,0 +1,36 @@
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
import numpy as np
|
||||
|
||||
from . import _aare
|
||||
|
||||
|
||||
_TYPE_TO_CHAR = {
|
||||
np.dtype(np.int32): "i",
|
||||
np.dtype(np.float32): "f",
|
||||
np.dtype(np.float64): "d",
|
||||
np.dtype(np.int16): "i16",
|
||||
}
|
||||
|
||||
|
||||
def _type_to_char(dtype):
|
||||
"""Return the suffix used by bindings instantiated for ``dtype``."""
|
||||
try:
|
||||
return _TYPE_TO_CHAR[np.dtype(dtype)]
|
||||
except (KeyError, TypeError):
|
||||
supported = ", ".join(str(dtype) for dtype in _TYPE_TO_CHAR)
|
||||
raise ValueError(
|
||||
f"Unsupported dtype: {dtype}. Supported dtypes are {supported}."
|
||||
) from None
|
||||
|
||||
|
||||
def _get_typed_class(name, dtype):
|
||||
"""Return a bound class named ``<name>_<dtype suffix>``."""
|
||||
class_name = f"{name}_{_type_to_char(dtype)}"
|
||||
try:
|
||||
return getattr(_aare, class_name)
|
||||
except AttributeError:
|
||||
raise ValueError(
|
||||
f"Unsupported dtype for {name}: {dtype} "
|
||||
f"(binding {class_name} is not available)."
|
||||
) from None
|
||||
@@ -1,2 +0,0 @@
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
from ._aare import gaus, pol1, scurve, scurve2
|
||||
+42
-70
@@ -1,106 +1,78 @@
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import sys
|
||||
sys.path.insert(0, '/home/kferjaoui/sw/aare/build')
|
||||
from aare import fit_gaus, fit_pol1
|
||||
from aare import Gaussian, fit
|
||||
from aare import pol1
|
||||
|
||||
textpm = f"±" #
|
||||
textmu = f"μ" #
|
||||
textsigma = f"σ" #
|
||||
from aare import Gaussian, Pol1
|
||||
|
||||
textpm = "±"
|
||||
textmu = "μ"
|
||||
textsigma = "σ"
|
||||
|
||||
|
||||
# ================================= Gauss fit =================================
|
||||
# Parameters
|
||||
mu = np.random.uniform(1, 100) # Mean of Gaussian
|
||||
sigma = np.random.uniform(4, 20) # Standard deviation
|
||||
num_points = 10000 # Number of points for smooth distribution
|
||||
# noise_sigma = 10
|
||||
|
||||
# Generate Gaussian distribution
|
||||
data = np.random.normal(mu, sigma, num_points)
|
||||
mu = np.random.uniform(1, 100)
|
||||
sigma = np.random.uniform(4, 20)
|
||||
data = np.random.normal(mu, sigma, 10000)
|
||||
counts, edges = np.histogram(data, bins=100)
|
||||
|
||||
x = 0.5 * (edges[:-1] + edges[1:]) # proper bin centers
|
||||
x = 0.5 * (edges[:-1] + edges[1:])
|
||||
y = counts.astype(np.float64)
|
||||
|
||||
# Poisson noise
|
||||
yerr = np.sqrt(np.maximum(y, 1))
|
||||
# yerr = np.abs(np.random.normal(0, noise_sigma, len(x)))
|
||||
|
||||
# Create subplot
|
||||
fig0, ax0 = plt.subplots(1, 1, num=0, figsize=(12, 8))
|
||||
|
||||
# Add the errors as error bars in the step plot
|
||||
ax0.errorbar(x, y, yerr=yerr, fmt=". ", capsize=5)
|
||||
ax0.grid()
|
||||
|
||||
# Fit with lmfit
|
||||
result_lm = fit_gaus(x, y, yerr)
|
||||
par_lm = result_lm["par"]
|
||||
err_lm = result_lm["par_err"]
|
||||
chi2_lm = result_lm["chi2"]
|
||||
print("[lmfit] fit_gaus: ", par_lm, err_lm, chi2_lm)
|
||||
gaussian = Gaussian(compute_errors=True)
|
||||
result = gaussian.fit(x, y, yerr)
|
||||
par = result["par"]
|
||||
err = result["par_err"]
|
||||
chi2 = result["chi2"]
|
||||
print(f"Gaussian.fit: par={par}, err={err}, chi2={chi2}")
|
||||
|
||||
# Fit with Minuit2 + analytic gradient + Hesse errors
|
||||
gaussian = Gaussian()
|
||||
gaussian.compute_errors = True
|
||||
result_m2 = gaussian.fit(x, y, yerr)
|
||||
par_m2 = result_m2['par']
|
||||
err_m2 = result_m2['par_err']
|
||||
chi2_m2 = result_m2['chi2']
|
||||
print(f"[minuit2] gaussian.fit: par={par_m2}, err={err_m2}, chi2={chi2_m2}")
|
||||
|
||||
x = np.linspace(x[0], x[-1], 1000)
|
||||
ax0.plot(x, gaussian(x, par_lm), marker="", label="fit_gaus")
|
||||
ax0.plot(x, gaussian(x, par_m2), marker="", linestyle=":", label="fit_gaus_minuit_grad")
|
||||
x_plot = np.linspace(x[0], x[-1], 1000)
|
||||
ax0.plot(x_plot, gaussian(x_plot, par), marker="", label="Gaussian.fit")
|
||||
ax0.legend()
|
||||
ax0.set(xlabel="x", ylabel="Counts",
|
||||
ax0.set(
|
||||
xlabel="x",
|
||||
ylabel="Counts",
|
||||
title=(
|
||||
f"fit_gaus: A={par_lm[0]:0.2f}{textpm}{err_lm[0]:0.2f} "
|
||||
f"{textmu}={par_lm[1]:0.2f}{textpm}{err_lm[1]:0.2f} "
|
||||
f"{textsigma}={par_lm[2]:0.2f}{textpm}{err_lm[2]:0.2f}\n"
|
||||
f"minuit_grad: A={par_m2[0]:0.2f}{textpm}{err_m2[0]:0.2f} "
|
||||
f"{textmu}={par_m2[1]:0.2f}{textpm}{err_m2[1]:0.2f} "
|
||||
f"{textsigma}={par_m2[2]:0.2f}{textpm}{err_m2[2]:0.2f}\n"
|
||||
f"A={par[0]:0.2f}{textpm}{err[0]:0.2f} "
|
||||
f"{textmu}={par[1]:0.2f}{textpm}{err[1]:0.2f} "
|
||||
f"{textsigma}={par[2]:0.2f}{textpm}{err[2]:0.2f}\n"
|
||||
f"(truth: {textmu}={mu:0.2f}, {textsigma}={sigma:0.2f})"
|
||||
),
|
||||
)
|
||||
fig0.tight_layout()
|
||||
|
||||
|
||||
|
||||
# ================================= pol1 fit =================================
|
||||
# Parameters
|
||||
# ================================= Pol1 fit =================================
|
||||
n_points = 40
|
||||
|
||||
# Generate random slope and intercept (origin)
|
||||
slope = np.random.uniform(-10, 10) # Random slope between 0.5 and 2.0
|
||||
intercept = np.random.uniform(-10, 10) # Random intercept between -10 and 10
|
||||
|
||||
# Generate random x values
|
||||
slope = np.random.uniform(-10, 10)
|
||||
intercept = np.random.uniform(-10, 10)
|
||||
x_values = np.random.uniform(-10, 10, n_points)
|
||||
|
||||
# Calculate y values based on the linear function y = mx + b + error
|
||||
errors = np.abs(np.random.normal(0, np.random.uniform(1, 5), n_points))
|
||||
var_points = np.random.normal(0, np.random.uniform(0.1, 2), n_points)
|
||||
y_values = slope * x_values + intercept + var_points
|
||||
y_values = slope * x_values + intercept + np.random.normal(0, 1, n_points)
|
||||
|
||||
fig1, ax1 = plt.subplots(1, 1, num=1, figsize=(12, 8))
|
||||
ax1.errorbar(x_values, y_values, yerr=errors, fmt=". ", capsize=5)
|
||||
result_pol = fit_pol1(x_values, y_values, errors)
|
||||
par = result_pol["par"]
|
||||
err = result_pol["par_err"]
|
||||
|
||||
x = np.linspace(np.min(x_values), np.max(x_values), 1000)
|
||||
ax1.plot(x, pol1(x, par), marker="")
|
||||
ax1.set(xlabel="x", ylabel="y", title=f"a = {par[0]:0.2f}{textpm}{err[0]:0.2f}\n"
|
||||
f"b = {par[1]:0.2f}{textpm}{err[1]:0.2f}\n"
|
||||
f"(init: {slope:0.2f}, {intercept:0.2f})")
|
||||
pol1 = Pol1(compute_errors=True)
|
||||
result = pol1.fit(x_values, y_values, errors)
|
||||
par = result["par"]
|
||||
err = result["par_err"]
|
||||
|
||||
x_plot = np.linspace(np.min(x_values), np.max(x_values), 1000)
|
||||
ax1.plot(x_plot, pol1(x_plot, par), marker="")
|
||||
ax1.set(
|
||||
xlabel="x",
|
||||
ylabel="y",
|
||||
title=(
|
||||
f"intercept = {par[0]:0.2f}{textpm}{err[0]:0.2f}\n"
|
||||
f"slope = {par[1]:0.2f}{textpm}{err[1]:0.2f}\n"
|
||||
f"(truth: {intercept:0.2f}, {slope:0.2f})"
|
||||
),
|
||||
)
|
||||
fig1.tight_layout()
|
||||
|
||||
plt.show()
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include <pybind11/stl_bind.h>
|
||||
|
||||
namespace py = pybind11;
|
||||
using pd_type = double;
|
||||
|
||||
using namespace aare;
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include "aare/ClusterVector.hpp"
|
||||
#include "aare/NDView.hpp"
|
||||
#include "aare/Pedestal.hpp"
|
||||
|
||||
#include "module_config.hpp"
|
||||
#include "np_helper.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
@@ -15,7 +17,6 @@
|
||||
#include <pybind11/stl_bind.h>
|
||||
|
||||
namespace py = pybind11;
|
||||
using pd_type = double;
|
||||
|
||||
using namespace aare;
|
||||
|
||||
@@ -30,7 +31,7 @@ void define_ClusterCollector(py::module &m, const std::string &typestr) {
|
||||
using ClusterType = Cluster<T, ClusterSizeX, ClusterSizeY, CoordType>;
|
||||
|
||||
py::class_<ClusterCollector<ClusterType>>(m, class_name.c_str())
|
||||
.def(py::init<ClusterFinderMT<ClusterType, uint16_t, double> *>())
|
||||
.def(py::init<ClusterFinderMT<ClusterType, uint16_t, pd_type> *>())
|
||||
.def("stop", &ClusterCollector<ClusterType>::stop)
|
||||
.def(
|
||||
"steal_clusters",
|
||||
|
||||
@@ -47,6 +47,8 @@ void define_ClusterFile(py::module &m, const std::string &typestr) {
|
||||
})
|
||||
.def("set_roi", &ClusterFile<ClusterType>::set_roi, py::arg("roi"))
|
||||
.def("tell", &ClusterFile<ClusterType>::tell)
|
||||
.def("estimate_n_clusters",
|
||||
&ClusterFile<ClusterType>::estimate_n_clusters)
|
||||
.def(
|
||||
"set_noise_map",
|
||||
[](ClusterFile<ClusterType> &self, py::array_t<int32_t> noise_map) {
|
||||
@@ -82,4 +84,4 @@ void define_ClusterFile(py::module &m, const std::string &typestr) {
|
||||
});
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
#pragma GCC diagnostic pop
|
||||
@@ -15,8 +15,6 @@
|
||||
#include <pybind11/stl_bind.h>
|
||||
|
||||
namespace py = pybind11;
|
||||
using pd_type = double;
|
||||
|
||||
using namespace aare;
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
@@ -29,6 +27,8 @@ void define_ClusterFileSink(py::module &m, const std::string &typestr) {
|
||||
|
||||
using ClusterType = Cluster<T, ClusterSizeX, ClusterSizeY, CoordType>;
|
||||
|
||||
// TODO! adapt to set pedestal type (needs templating of ClusterFileSink)
|
||||
// or maybe access through base class?
|
||||
py::class_<ClusterFileSink<ClusterType>>(m, class_name.c_str())
|
||||
.def(py::init<ClusterFinderMT<ClusterType, uint16_t, double> *,
|
||||
const std::filesystem::path &>())
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include "aare/ClusterVector.hpp"
|
||||
#include "aare/NDView.hpp"
|
||||
#include "aare/Pedestal.hpp"
|
||||
|
||||
#include "module_config.hpp"
|
||||
#include "np_helper.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
@@ -15,7 +17,6 @@
|
||||
#include <pybind11/stl_bind.h>
|
||||
|
||||
namespace py = pybind11;
|
||||
using pd_type = double;
|
||||
|
||||
using namespace aare;
|
||||
|
||||
@@ -48,6 +49,8 @@ void define_ClusterFinder(py::module &m, const std::string &typestr) {
|
||||
})
|
||||
.def("clear_pedestal",
|
||||
&ClusterFinder<ClusterType, uint16_t, pd_type>::clear_pedestal)
|
||||
.def("update_threshold",
|
||||
&ClusterFinder<ClusterType, uint16_t, pd_type>::update_threshold)
|
||||
.def_property_readonly(
|
||||
"pedestal",
|
||||
[](ClusterFinder<ClusterType, uint16_t, pd_type> &self) {
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include "aare/ClusterVector.hpp"
|
||||
#include "aare/NDView.hpp"
|
||||
#include "aare/Pedestal.hpp"
|
||||
|
||||
#include "module_config.hpp"
|
||||
#include "np_helper.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
@@ -15,7 +17,6 @@
|
||||
#include <pybind11/stl_bind.h>
|
||||
|
||||
namespace py = pybind11;
|
||||
using pd_type = double;
|
||||
|
||||
using namespace aare;
|
||||
|
||||
@@ -31,9 +32,10 @@ void define_ClusterFinderMT(py::module &m, const std::string &typestr) {
|
||||
|
||||
py::class_<ClusterFinderMT<ClusterType, uint16_t, pd_type>>(
|
||||
m, class_name.c_str())
|
||||
.def(py::init<Shape<2>, pd_type, size_t, size_t>(),
|
||||
.def(py::init<Shape<2>, pd_type, size_t, size_t, size_t>(),
|
||||
py::arg("image_size"), py::arg("n_sigma") = 5.0,
|
||||
py::arg("capacity") = 2048, py::arg("n_threads") = 3)
|
||||
py::arg("capacity") = 2048, py::arg("n_threads") = 3,
|
||||
py::arg("queue_depth") = 16)
|
||||
.def("push_pedestal_frame",
|
||||
[](ClusterFinderMT<ClusterType, uint16_t, pd_type> &self,
|
||||
py::array_t<uint16_t> frame) {
|
||||
@@ -46,7 +48,6 @@ void define_ClusterFinderMT(py::module &m, const std::string &typestr) {
|
||||
py::array_t<uint16_t> frame, uint64_t frame_number) {
|
||||
auto view = make_view_2d(frame);
|
||||
self.find_clusters(view, frame_number);
|
||||
return;
|
||||
},
|
||||
py::arg(), py::arg("frame_number") = 0)
|
||||
.def_property_readonly(
|
||||
@@ -56,6 +57,8 @@ void define_ClusterFinderMT(py::module &m, const std::string &typestr) {
|
||||
})
|
||||
.def("clear_pedestal",
|
||||
&ClusterFinderMT<ClusterType, uint16_t, pd_type>::clear_pedestal)
|
||||
.def("update_threshold",
|
||||
&ClusterFinderMT<ClusterType, uint16_t, pd_type>::update_threshold)
|
||||
.def("sync", &ClusterFinderMT<ClusterType, uint16_t, pd_type>::sync)
|
||||
.def("stop", &ClusterFinderMT<ClusterType, uint16_t, pd_type>::stop)
|
||||
.def("start", &ClusterFinderMT<ClusterType, uint16_t, pd_type>::start)
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
#include <pybind11/stl_bind.h>
|
||||
|
||||
namespace py = pybind11;
|
||||
using pd_type = double;
|
||||
|
||||
using namespace aare;
|
||||
|
||||
@@ -72,6 +71,7 @@ void define_ClusterVector(py::module &m, const std::string &typestr) {
|
||||
R"(calculates sum of 2x2 subcluster with highest energy and index relative to cluster center 0: top_left, 1: top_right, 2: bottom_left, 3: bottom_right
|
||||
)")
|
||||
.def_property_readonly("size", &ClusterVector<ClusterType>::size)
|
||||
.def("empty", &ClusterVector<ClusterType>::empty)
|
||||
.def("item_size", &ClusterVector<ClusterType>::item_size)
|
||||
.def_property_readonly("fmt",
|
||||
[typestr](ClusterVector<ClusterType> &self) {
|
||||
@@ -169,4 +169,4 @@ void define_3x3_reduction(py::module &m) {
|
||||
py::arg("clustervector"));
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
#pragma GCC diagnostic pop
|
||||
@@ -0,0 +1,147 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
#include "aare/FastPedestal.hpp"
|
||||
#include "np_helper.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <pybind11/numpy.h>
|
||||
#include <pybind11/pybind11.h>
|
||||
#include <pybind11/stl.h>
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
template <typename SUM_TYPE>
|
||||
void define_fast_pedestal_bindings(py::module &m, const std::string &name) {
|
||||
|
||||
py::class_<FastPedestal<SUM_TYPE>>(
|
||||
m, name.c_str(),
|
||||
"Maintain a per-pixel running mean, population variance, and "
|
||||
"standard deviation.",
|
||||
py::buffer_protocol())
|
||||
.def(py::init<uint32_t, uint32_t, uint32_t>(), py::arg("rows"),
|
||||
py::arg("cols"), py::arg("n_samples"),
|
||||
"Construct an empty pedestal. It becomes ready after n_samples "
|
||||
"calls to add_init_frame().")
|
||||
.def(py::init<uint32_t, uint32_t>(), py::arg("rows"), py::arg("cols"),
|
||||
"Construct an empty pedestal with n_samples=1000.")
|
||||
|
||||
.def(
|
||||
"mean",
|
||||
[](FastPedestal<SUM_TYPE> &self) {
|
||||
auto mean = new NDArray<SUM_TYPE, 2>{};
|
||||
*mean = self.mean();
|
||||
return return_image_data(mean);
|
||||
},
|
||||
"Return a copy of the cached mean. The pedestal must be ready.")
|
||||
.def(
|
||||
"var",
|
||||
[](FastPedestal<SUM_TYPE> &self) {
|
||||
auto variance = new NDArray<SUM_TYPE, 2>{};
|
||||
*variance = self.variance();
|
||||
return return_image_data(variance);
|
||||
},
|
||||
"Return the population variance, normalized by n_samples, as a "
|
||||
"NumPy array. The pedestal must be ready.")
|
||||
.def(
|
||||
"std",
|
||||
[](FastPedestal<SUM_TYPE> &self) {
|
||||
auto standard_deviation = new NDArray<SUM_TYPE, 2>{};
|
||||
*standard_deviation = self.std();
|
||||
return return_image_data(standard_deviation);
|
||||
},
|
||||
"Return the population standard deviation as a NumPy array. The "
|
||||
"pedestal must be ready.")
|
||||
.def(
|
||||
"view",
|
||||
[](py::object self_py) {
|
||||
return py::module_::import("numpy").attr("asarray")(self_py);
|
||||
},
|
||||
"Return a non-owning, non-writable NumPy view of the cached mean. "
|
||||
"The pedestal must be ready.")
|
||||
|
||||
// We need to buffer protocol to allow for numpy operations using the
|
||||
// pedestal mean
|
||||
.def_buffer([](FastPedestal<SUM_TYPE> &self) {
|
||||
auto mean = self.view();
|
||||
return py::buffer_info(
|
||||
const_cast<SUM_TYPE *>(mean.data()), sizeof(SUM_TYPE),
|
||||
py::format_descriptor<SUM_TYPE>::format(), 2,
|
||||
{static_cast<py::ssize_t>(mean.shape(0)),
|
||||
static_cast<py::ssize_t>(mean.shape(1))},
|
||||
{static_cast<py::ssize_t>(mean.strides()[0] * sizeof(SUM_TYPE)),
|
||||
static_cast<py::ssize_t>(mean.strides()[1] *
|
||||
sizeof(SUM_TYPE))},
|
||||
true);
|
||||
})
|
||||
// Subtracting a FastPedestal from a NumPy array
|
||||
.def(
|
||||
"__array_ufunc__",
|
||||
[](py::object self, py::object ufunc, const std::string &method,
|
||||
py::args inputs, py::kwargs kwargs) -> py::object {
|
||||
if (method != "__call__" || inputs.size() != 2 ||
|
||||
inputs[1].ptr() != self.ptr() ||
|
||||
py::cast<std::string>(ufunc.attr("__name__")) !=
|
||||
"subtract") {
|
||||
return py::reinterpret_borrow<py::object>(
|
||||
Py_NotImplemented);
|
||||
}
|
||||
|
||||
auto mean =
|
||||
py::module_::import("builtins").attr("memoryview")(self);
|
||||
return ufunc(inputs[0], mean, **kwargs);
|
||||
},
|
||||
"Support subtracting a FastPedestal from a NumPy array.")
|
||||
.def("clear", py::overload_cast<>(&FastPedestal<SUM_TYPE>::clear),
|
||||
"Reset all statistics and initialization state to zero.")
|
||||
.def_property_readonly("rows", &FastPedestal<SUM_TYPE>::rows,
|
||||
"Number of image rows.")
|
||||
.def_property_readonly("cols", &FastPedestal<SUM_TYPE>::cols,
|
||||
"Number of image columns.")
|
||||
.def_property_readonly("cur_samples",
|
||||
&FastPedestal<SUM_TYPE>::cur_samples,
|
||||
"Number of initialization frames accumulated. "
|
||||
"Steady-state pushes do not change it.")
|
||||
.def_property_readonly(
|
||||
"ready", &FastPedestal<SUM_TYPE>::ready,
|
||||
"Whether n_samples initialization frames have been accumulated.")
|
||||
.def_property_readonly(
|
||||
"n_samples", &FastPedestal<SUM_TYPE>::n_samples,
|
||||
"Initialization frame count and steady-state update-weight "
|
||||
"denominator.")
|
||||
.def(
|
||||
"clone",
|
||||
[](FastPedestal<SUM_TYPE> &pedestal) {
|
||||
return FastPedestal<SUM_TYPE>(pedestal);
|
||||
},
|
||||
"Return an independent copy of the pedestal and its state.")
|
||||
.def(
|
||||
"push_ema",
|
||||
[](FastPedestal<SUM_TYPE> &pedestal,
|
||||
py::array_t<uint16_t, py::array::c_style> &frame) {
|
||||
pedestal.push_ema(make_view_2d(frame));
|
||||
},
|
||||
py::arg("frame").noconvert(),
|
||||
"Update exponential moving average. The pedstal must "
|
||||
"already be ready for this update.")
|
||||
.def(
|
||||
"add_init_frame",
|
||||
[](FastPedestal<SUM_TYPE> &pedestal,
|
||||
py::array_t<uint16_t, py::array::c_style> &frame) {
|
||||
pedestal.add_init_frame(make_view_2d(frame));
|
||||
},
|
||||
py::arg("frame").noconvert(),
|
||||
"Accumulate one uint16 initialization frame. Call exactly "
|
||||
"n_samples times to make the pedestal ready.")
|
||||
.def_static(
|
||||
"from_file",
|
||||
[](const std::filesystem::path &filename, uint32_t n_samples,
|
||||
uint32_t skip_first) {
|
||||
return FastPedestal<SUM_TYPE>::template from_file<uint16_t>(
|
||||
filename, n_samples, skip_first);
|
||||
},
|
||||
py::arg("filename"), py::arg("n_samples") = 1000,
|
||||
py::arg("skip_first") = 0,
|
||||
"Create a pedestal from a uint16 file. Skip skip_first frames, use "
|
||||
"the next n_samples for initialization, then apply every remaining "
|
||||
"frame as a steady-state update.");
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
#pragma once
|
||||
|
||||
#include "aare/MultiThreadedFileReader.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
#include <pybind11/numpy.h>
|
||||
#include <pybind11/pybind11.h>
|
||||
#include <pybind11/stl.h>
|
||||
#include <pybind11/stl/filesystem.h>
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
inline py::dtype multi_threaded_reader_numpy_dtype(const aare::Dtype &dtype) {
|
||||
using aare::Dtype;
|
||||
if (dtype == Dtype::INT8)
|
||||
return py::dtype::of<int8_t>();
|
||||
if (dtype == Dtype::UINT8)
|
||||
return py::dtype::of<uint8_t>();
|
||||
if (dtype == Dtype::INT16)
|
||||
return py::dtype::of<int16_t>();
|
||||
if (dtype == Dtype::UINT16)
|
||||
return py::dtype::of<uint16_t>();
|
||||
if (dtype == Dtype::INT32)
|
||||
return py::dtype::of<int32_t>();
|
||||
if (dtype == Dtype::UINT32)
|
||||
return py::dtype::of<uint32_t>();
|
||||
if (dtype == Dtype::INT64)
|
||||
return py::dtype::of<int64_t>();
|
||||
if (dtype == Dtype::UINT64)
|
||||
return py::dtype::of<uint64_t>();
|
||||
if (dtype == Dtype::FLOAT)
|
||||
return py::dtype::of<float>();
|
||||
if (dtype == Dtype::DOUBLE)
|
||||
return py::dtype::of<double>();
|
||||
throw std::runtime_error("Unsupported pixel data type");
|
||||
}
|
||||
|
||||
inline py::array
|
||||
multi_threaded_reader_read(aare::experimental::MultiThreadedFileReader &reader,
|
||||
bool read_all) {
|
||||
const size_t n_frames =
|
||||
read_all ? reader.remaining_frames() : reader.next_read_frames();
|
||||
const std::vector<py::ssize_t> shape{
|
||||
static_cast<py::ssize_t>(n_frames),
|
||||
static_cast<py::ssize_t>(reader.rows()),
|
||||
static_cast<py::ssize_t>(reader.cols())};
|
||||
|
||||
py::array image(multi_threaded_reader_numpy_dtype(reader.dtype()), shape);
|
||||
auto *destination = reinterpret_cast<std::byte *>(image.mutable_data());
|
||||
{
|
||||
py::gil_scoped_release release;
|
||||
if (read_all) {
|
||||
size_t offset = 0;
|
||||
while (reader.remaining_frames() != 0) {
|
||||
const size_t frames_read =
|
||||
reader.read_into(destination + offset);
|
||||
offset += frames_read * reader.bytes_per_frame();
|
||||
}
|
||||
} else {
|
||||
reader.read_into(destination);
|
||||
}
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
inline void define_multi_threaded_file_reader_bindings(py::module_ &m) {
|
||||
using aare::experimental::MultiThreadedFileReader;
|
||||
|
||||
auto reader =
|
||||
py::class_<MultiThreadedFileReader>(m, "MultiThreadedFileReader");
|
||||
reader.attr("__module__") = "aare.experimental";
|
||||
reader
|
||||
.def(py::init<std::filesystem::path, size_t, size_t,
|
||||
std::optional<size_t>>(),
|
||||
py::arg("fname"), py::arg("n_threads"), py::arg("chunk_size"),
|
||||
py::arg("total_frames") = py::none(),
|
||||
R"doc(
|
||||
Read chunks of detector frames concurrently.
|
||||
|
||||
Each worker opens an independent File. The returned array is
|
||||
ordered by frame index even though chunks are read in parallel.
|
||||
|
||||
Args:
|
||||
fname: Path accepted by File.
|
||||
n_threads: Maximum number of worker threads.
|
||||
chunk_size: Number of frames read per claimed chunk.
|
||||
total_frames: Optional frame limit. None reads all frames.
|
||||
)doc")
|
||||
.def(
|
||||
"read",
|
||||
[](MultiThreadedFileReader &self) {
|
||||
return multi_threaded_reader_read(self, false);
|
||||
},
|
||||
R"doc(
|
||||
Read one chunk per active worker into a NumPy array.
|
||||
|
||||
Returns:
|
||||
An array containing at most n_threads * chunk_size frames.
|
||||
An empty array is returned at the end of the configured
|
||||
frame range. The GIL is released while file data is read.
|
||||
)doc")
|
||||
.def(
|
||||
"read_all",
|
||||
[](MultiThreadedFileReader &self) {
|
||||
return multi_threaded_reader_read(self, true);
|
||||
},
|
||||
R"doc(
|
||||
Read all frames remaining from the current position.
|
||||
)doc")
|
||||
.def_property_readonly("n_threads", &MultiThreadedFileReader::n_threads)
|
||||
.def_property_readonly("chunk_size",
|
||||
&MultiThreadedFileReader::chunk_size)
|
||||
.def_property_readonly("total_frames",
|
||||
&MultiThreadedFileReader::total_frames)
|
||||
.def_property_readonly("source_total_frames",
|
||||
&MultiThreadedFileReader::source_total_frames)
|
||||
.def_property_readonly("rows", &MultiThreadedFileReader::rows)
|
||||
.def_property_readonly("cols", &MultiThreadedFileReader::cols)
|
||||
.def_property_readonly("bitdepth", &MultiThreadedFileReader::bitdepth)
|
||||
.def_property_readonly("dtype",
|
||||
[](const MultiThreadedFileReader &self) {
|
||||
return multi_threaded_reader_numpy_dtype(
|
||||
self.dtype());
|
||||
})
|
||||
.def_property_readonly("bytes_per_frame",
|
||||
&MultiThreadedFileReader::bytes_per_frame)
|
||||
.def_property_readonly("total_bytes",
|
||||
&MultiThreadedFileReader::total_bytes)
|
||||
.def_property_readonly("remaining_frames",
|
||||
&MultiThreadedFileReader::remaining_frames)
|
||||
.def_property_readonly("next_read_frames",
|
||||
&MultiThreadedFileReader::next_read_frames)
|
||||
.def_property_readonly("next_read_bytes",
|
||||
&MultiThreadedFileReader::next_read_bytes)
|
||||
.def("seek", &MultiThreadedFileReader::seek, py::arg("frame_index"))
|
||||
.def("tell", &MultiThreadedFileReader::tell)
|
||||
.def("close", &MultiThreadedFileReader::close,
|
||||
"Close all worker files. Safe to call more than once.")
|
||||
.def_property_readonly(
|
||||
"closed",
|
||||
[](const MultiThreadedFileReader &self) { return !self.is_open(); })
|
||||
.def("__len__", &MultiThreadedFileReader::total_frames)
|
||||
.def(
|
||||
"__enter__",
|
||||
[](MultiThreadedFileReader &self) -> MultiThreadedFileReader * {
|
||||
if (!self.is_open()) {
|
||||
throw std::runtime_error(
|
||||
"Cannot enter a closed MultiThreadedFileReader");
|
||||
}
|
||||
return &self;
|
||||
},
|
||||
py::return_value_policy::reference_internal)
|
||||
.def("__exit__",
|
||||
[](MultiThreadedFileReader &self, const py::object &,
|
||||
const py::object &, const py::object &) {
|
||||
self.close();
|
||||
return false;
|
||||
})
|
||||
.def(
|
||||
"__iter__", [](MultiThreadedFileReader &self) { return &self; },
|
||||
py::return_value_policy::reference_internal)
|
||||
.def("__next__", [](MultiThreadedFileReader &self) {
|
||||
if (self.remaining_frames() == 0) {
|
||||
throw py::stop_iteration();
|
||||
}
|
||||
return multi_threaded_reader_read(self, false);
|
||||
});
|
||||
}
|
||||
@@ -153,9 +153,13 @@ void define_pedestal_tracking_pixel_histogram_bindings(py::module &m) {
|
||||
Args:
|
||||
file_path: Path to the file to fill from
|
||||
max_frames: Maximum number of frames to fill from the file (default: -1)
|
||||
reader_threads: Number of parallel file reader workers (default: 2)
|
||||
reader_chunk_size: Frames claimed by each reader worker per batch (default: 4)
|
||||
)",
|
||||
py::call_guard<py::gil_scoped_release>(), py::arg("fname"),
|
||||
py::arg("max_frames") = -1, py::arg("verbose") = false)
|
||||
py::arg("max_frames") = -1, py::arg("verbose") = false,
|
||||
py::arg("reader_threads") = std::size_t{2},
|
||||
py::arg("reader_chunk_size") = std::size_t{4})
|
||||
.def("process_pedestal_file",
|
||||
&PedestalTrackingPixelHistogram::process_pedestal_file,
|
||||
R"(
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
|
||||
#include "aare/decode.hpp"
|
||||
#include "aare/defs.hpp"
|
||||
// #include "aare/fClusterFileV2.hpp"
|
||||
|
||||
#include "np_helper.hpp"
|
||||
|
||||
|
||||
+1
-50
@@ -7,7 +7,6 @@
|
||||
#include "aare/RawSubFile.hpp"
|
||||
|
||||
#include "aare/defs.hpp"
|
||||
// #include "aare/fClusterFileV2.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
@@ -195,52 +194,4 @@ void define_file_io_bindings(py::module &m) {
|
||||
});
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
// py::class_<ClusterHeader>(m, "ClusterHeader")
|
||||
// .def(py::init<>())
|
||||
// .def_readwrite("frame_number", &ClusterHeader::frame_number)
|
||||
// .def_readwrite("n_clusters", &ClusterHeader::n_clusters)
|
||||
// .def("__repr__", [](const ClusterHeader &a) { return "<ClusterHeader:
|
||||
// " + a.to_string() + ">"; });
|
||||
|
||||
// py::class_<ClusterV2_>(m, "ClusterV2_")
|
||||
// .def(py::init<>())
|
||||
// .def_readwrite("x", &ClusterV2_::x)
|
||||
// .def_readwrite("y", &ClusterV2_::y)
|
||||
// .def_readwrite("data", &ClusterV2_::data)
|
||||
// .def("__repr__", [](const ClusterV2_ &a) { return "<ClusterV2_: " +
|
||||
// a.to_string(false) + ">"; });
|
||||
|
||||
// py::class_<ClusterV2>(m, "ClusterV2")
|
||||
// .def(py::init<>())
|
||||
// .def_readwrite("cluster", &ClusterV2::cluster)
|
||||
// .def_readwrite("frame_number", &ClusterV2::frame_number)
|
||||
// .def("__repr__", [](const ClusterV2 &a) { return "<ClusterV2: " +
|
||||
// a.to_string() + ">"; });
|
||||
|
||||
// py::class_<ClusterFileV2>(m, "ClusterFileV2")
|
||||
// .def(py::init<const std::filesystem::path &, const std::string &>())
|
||||
// .def("read", py::overload_cast<>(&ClusterFileV2::read))
|
||||
// .def("read", py::overload_cast<int>(&ClusterFileV2::read))
|
||||
// .def("frame_number", &ClusterFileV2::frame_number)
|
||||
// .def("write", py::overload_cast<std::vector<ClusterV2> const
|
||||
// &>(&ClusterFileV2::write))
|
||||
|
||||
// .def("close", &ClusterFileV2::close);
|
||||
|
||||
// m.def("to_clustV2", [](std::vector<DynamicCluster> &clusters, const int
|
||||
// frame_number) {
|
||||
// std::vector<ClusterV2> clusters_;
|
||||
// for (auto &c : clusters) {
|
||||
// ClusterV2 cluster;
|
||||
// cluster.cluster.x = c.x;
|
||||
// cluster.cluster.y = c.y;
|
||||
// int i=0;
|
||||
// for(auto &d : cluster.cluster.data) {
|
||||
// d=c.get<double>(i++);
|
||||
// }
|
||||
// cluster.frame_number = frame_number;
|
||||
// clusters_.push_back(cluster);
|
||||
// }
|
||||
// return clusters_;
|
||||
// });
|
||||
}
|
||||
}
|
||||
+1
-447
@@ -227,452 +227,6 @@ fit_dispatch(const aare::FitModel<Model> &model,
|
||||
}
|
||||
|
||||
void define_fit_bindings(py::module &m) {
|
||||
|
||||
// TODO! Evaluate without converting to double
|
||||
m.def(
|
||||
"gaus",
|
||||
[](py::array_t<double, py::array::c_style | py::array::forcecast> x,
|
||||
py::array_t<double, py::array::c_style | py::array::forcecast> par) {
|
||||
auto x_view = make_view_1d(x);
|
||||
auto par_view = make_view_1d(par);
|
||||
auto y = new NDArray<double, 1>{aare::func::gaus(x_view, par_view)};
|
||||
return return_image_data(y);
|
||||
},
|
||||
R"(
|
||||
Evaluate a 1D Gaussian function for all points in x using parameters par.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x : array_like
|
||||
The points at which to evaluate the Gaussian function.
|
||||
par : array_like
|
||||
The parameters of the Gaussian function. The first element is the amplitude, the second element is the mean, and the third element is the standard deviation.
|
||||
)",
|
||||
py::arg("x"), py::arg("par"));
|
||||
|
||||
m.def(
|
||||
"pol1",
|
||||
[](py::array_t<double, py::array::c_style | py::array::forcecast> x,
|
||||
py::array_t<double, py::array::c_style | py::array::forcecast> par) {
|
||||
auto x_view = make_view_1d(x);
|
||||
auto par_view = make_view_1d(par);
|
||||
auto y = new NDArray<double, 1>{aare::func::pol1(x_view, par_view)};
|
||||
return return_image_data(y);
|
||||
},
|
||||
R"(
|
||||
Evaluate a 1D polynomial function for all points in x using parameters par. (p0+p1*x)
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x : array_like
|
||||
The points at which to evaluate the polynomial function.
|
||||
par : array_like
|
||||
The parameters of the polynomial function. The first element is the intercept, and the second element is the slope.
|
||||
)",
|
||||
py::arg("x"), py::arg("par"));
|
||||
|
||||
m.def(
|
||||
"scurve",
|
||||
[](py::array_t<double, py::array::c_style | py::array::forcecast> x,
|
||||
py::array_t<double, py::array::c_style | py::array::forcecast> par) {
|
||||
auto x_view = make_view_1d(x);
|
||||
auto par_view = make_view_1d(par);
|
||||
auto y =
|
||||
new NDArray<double, 1>{aare::func::scurve(x_view, par_view)};
|
||||
return return_image_data(y);
|
||||
},
|
||||
R"(
|
||||
Evaluate a 1D scurve function for all points in x using parameters par.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x : array_like
|
||||
The points at which to evaluate the scurve function.
|
||||
par : array_like
|
||||
The parameters of the scurve function. The first element is the background slope, the second element is the background intercept, the third element is the mean, the fourth element is the standard deviation, the fifth element is inflexion point count number, and the sixth element is C.
|
||||
)",
|
||||
py::arg("x"), py::arg("par"));
|
||||
|
||||
m.def(
|
||||
"scurve2",
|
||||
[](py::array_t<double, py::array::c_style | py::array::forcecast> x,
|
||||
py::array_t<double, py::array::c_style | py::array::forcecast> par) {
|
||||
auto x_view = make_view_1d(x);
|
||||
auto par_view = make_view_1d(par);
|
||||
auto y =
|
||||
new NDArray<double, 1>{aare::func::scurve2(x_view, par_view)};
|
||||
return return_image_data(y);
|
||||
},
|
||||
R"(
|
||||
Evaluate a 1D scurve2 function for all points in x using parameters par.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x : array_like
|
||||
The points at which to evaluate the scurve function.
|
||||
par : array_like
|
||||
The parameters of the scurve2 function. The first element is the background slope, the second element is the background intercept, the third element is the mean, the fourth element is the standard deviation, the fifth element is inflexion point count number, and the sixth element is C.
|
||||
)",
|
||||
py::arg("x"), py::arg("par"));
|
||||
|
||||
m.def(
|
||||
"fit_gaus",
|
||||
[](py::array_t<double, py::array::c_style | py::array::forcecast> x,
|
||||
py::array_t<double, py::array::c_style | py::array::forcecast> y,
|
||||
int n_threads) {
|
||||
if (y.ndim() == 3) {
|
||||
auto par = new NDArray<double, 3>{};
|
||||
auto y_view = make_view_3d(y);
|
||||
auto x_view = make_view_1d(x);
|
||||
*par = aare::fit_gaus(x_view, y_view, n_threads);
|
||||
return return_image_data(par);
|
||||
} else if (y.ndim() == 1) {
|
||||
auto par = new NDArray<double, 1>{};
|
||||
auto y_view = make_view_1d(y);
|
||||
auto x_view = make_view_1d(x);
|
||||
*par = aare::fit_gaus(x_view, y_view);
|
||||
return return_image_data(par);
|
||||
} else {
|
||||
throw std::runtime_error("Data must be 1D or 3D");
|
||||
}
|
||||
},
|
||||
R"(
|
||||
Fit a 1D Gaussian to data.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x : array_like
|
||||
The x values.
|
||||
y : array_like
|
||||
The y values.
|
||||
n_threads : int, optional
|
||||
The number of threads to use. Default is 4.
|
||||
)",
|
||||
py::arg("x"), py::arg("y"), py::arg("n_threads") = 4);
|
||||
|
||||
m.def(
|
||||
"fit_gaus",
|
||||
[](py::array_t<double, py::array::c_style | py::array::forcecast> x,
|
||||
py::array_t<double, py::array::c_style | py::array::forcecast> y,
|
||||
py::array_t<double, py::array::c_style | py::array::forcecast> y_err,
|
||||
int n_threads) {
|
||||
if (y.ndim() == 3) {
|
||||
// Allocate memory for the output
|
||||
// Need to have pointers to allow python to manage
|
||||
// the memory
|
||||
auto par = new NDArray<double, 3>({y.shape(0), y.shape(1), 3});
|
||||
auto par_err =
|
||||
new NDArray<double, 3>({y.shape(0), y.shape(1), 3});
|
||||
auto chi2 = new NDArray<double, 2>({y.shape(0), y.shape(1)});
|
||||
|
||||
// Make views of the numpy arrays
|
||||
auto y_view = make_view_3d(y);
|
||||
auto y_view_err = make_view_3d(y_err);
|
||||
auto x_view = make_view_1d(x);
|
||||
|
||||
aare::fit_gaus(x_view, y_view, y_view_err, par->view(),
|
||||
par_err->view(), chi2->view(), n_threads);
|
||||
|
||||
return py::dict("par"_a = return_image_data(par),
|
||||
"par_err"_a = return_image_data(par_err),
|
||||
"chi2"_a = return_image_data(chi2),
|
||||
"Ndf"_a = y.shape(2) - 3);
|
||||
} else if (y.ndim() == 1) {
|
||||
// Allocate memory for the output
|
||||
// Need to have pointers to allow python to manage
|
||||
// the memory
|
||||
auto par = new NDArray<double, 1>({3});
|
||||
auto par_err = new NDArray<double, 1>({3});
|
||||
|
||||
// Decode the numpy arrays
|
||||
auto y_view = make_view_1d(y);
|
||||
auto y_view_err = make_view_1d(y_err);
|
||||
auto x_view = make_view_1d(x);
|
||||
|
||||
double chi2 = 0;
|
||||
aare::fit_gaus(x_view, y_view, y_view_err, par->view(),
|
||||
par_err->view(), chi2);
|
||||
|
||||
return py::dict("par"_a = return_image_data(par),
|
||||
"par_err"_a = return_image_data(par_err),
|
||||
"chi2"_a = chi2, "Ndf"_a = y.size() - 3);
|
||||
|
||||
} else {
|
||||
throw std::runtime_error("Data must be 1D or 3D");
|
||||
}
|
||||
},
|
||||
R"(
|
||||
Fit a 1D Gaussian to data with error estimates.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x : array_like
|
||||
The x values.
|
||||
y : array_like
|
||||
The y values.
|
||||
y_err : array_like
|
||||
The error in the y values.
|
||||
n_threads : int, optional
|
||||
The number of threads to use. Default is 4.
|
||||
)",
|
||||
py::arg("x"), py::arg("y"), py::arg("y_err"), py::arg("n_threads") = 4);
|
||||
|
||||
m.def(
|
||||
"fit_pol1",
|
||||
[](py::array_t<double, py::array::c_style | py::array::forcecast> x,
|
||||
py::array_t<double, py::array::c_style | py::array::forcecast> y,
|
||||
int n_threads) {
|
||||
if (y.ndim() == 3) {
|
||||
auto par = new NDArray<double, 3>{};
|
||||
|
||||
auto x_view = make_view_1d(x);
|
||||
auto y_view = make_view_3d(y);
|
||||
*par = aare::fit_pol1(x_view, y_view, n_threads);
|
||||
return return_image_data(par);
|
||||
} else if (y.ndim() == 1) {
|
||||
auto par = new NDArray<double, 1>{};
|
||||
auto x_view = make_view_1d(x);
|
||||
auto y_view = make_view_1d(y);
|
||||
*par = aare::fit_pol1(x_view, y_view);
|
||||
return return_image_data(par);
|
||||
} else {
|
||||
throw std::runtime_error("Data must be 1D or 3D");
|
||||
}
|
||||
},
|
||||
py::arg("x"), py::arg("y"), py::arg("n_threads") = 4);
|
||||
|
||||
m.def(
|
||||
"fit_pol1",
|
||||
[](py::array_t<double, py::array::c_style | py::array::forcecast> x,
|
||||
py::array_t<double, py::array::c_style | py::array::forcecast> y,
|
||||
py::array_t<double, py::array::c_style | py::array::forcecast> y_err,
|
||||
int n_threads) {
|
||||
if (y.ndim() == 3) {
|
||||
auto par = new NDArray<double, 3>({y.shape(0), y.shape(1), 2});
|
||||
|
||||
auto par_err =
|
||||
new NDArray<double, 3>({y.shape(0), y.shape(1), 2});
|
||||
|
||||
auto y_view = make_view_3d(y);
|
||||
auto y_view_err = make_view_3d(y_err);
|
||||
auto x_view = make_view_1d(x);
|
||||
|
||||
auto chi2 = new NDArray<double, 2>({y.shape(0), y.shape(1)});
|
||||
|
||||
aare::fit_pol1(x_view, y_view, y_view_err, par->view(),
|
||||
par_err->view(), chi2->view(), n_threads);
|
||||
return py::dict("par"_a = return_image_data(par),
|
||||
"par_err"_a = return_image_data(par_err),
|
||||
"chi2"_a = return_image_data(chi2),
|
||||
"Ndf"_a = y.shape(2) - 2);
|
||||
|
||||
} else if (y.ndim() == 1) {
|
||||
auto par = new NDArray<double, 1>({2});
|
||||
auto par_err = new NDArray<double, 1>({2});
|
||||
|
||||
auto y_view = make_view_1d(y);
|
||||
auto y_view_err = make_view_1d(y_err);
|
||||
auto x_view = make_view_1d(x);
|
||||
|
||||
double chi2 = 0;
|
||||
|
||||
aare::fit_pol1(x_view, y_view, y_view_err, par->view(),
|
||||
par_err->view(), chi2);
|
||||
return py::dict("par"_a = return_image_data(par),
|
||||
"par_err"_a = return_image_data(par_err),
|
||||
"chi2"_a = chi2, "Ndf"_a = y.size() - 2);
|
||||
|
||||
} else {
|
||||
throw std::runtime_error("Data must be 1D or 3D");
|
||||
}
|
||||
},
|
||||
R"(
|
||||
Fit a 1D polynomial to data with error estimates.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x : array_like
|
||||
The x values.
|
||||
y : array_like
|
||||
The y values.
|
||||
y_err : array_like
|
||||
The error in the y values.
|
||||
n_threads : int, optional
|
||||
The number of threads to use. Default is 4.
|
||||
)",
|
||||
py::arg("x"), py::arg("y"), py::arg("y_err"), py::arg("n_threads") = 4);
|
||||
|
||||
//=========
|
||||
m.def(
|
||||
"fit_scurve",
|
||||
[](py::array_t<double, py::array::c_style | py::array::forcecast> x,
|
||||
py::array_t<double, py::array::c_style | py::array::forcecast> y,
|
||||
int n_threads) {
|
||||
if (y.ndim() == 3) {
|
||||
auto par = new NDArray<double, 3>{};
|
||||
|
||||
auto x_view = make_view_1d(x);
|
||||
auto y_view = make_view_3d(y);
|
||||
*par = aare::fit_scurve(x_view, y_view, n_threads);
|
||||
return return_image_data(par);
|
||||
} else if (y.ndim() == 1) {
|
||||
auto par = new NDArray<double, 1>{};
|
||||
auto x_view = make_view_1d(x);
|
||||
auto y_view = make_view_1d(y);
|
||||
*par = aare::fit_scurve(x_view, y_view);
|
||||
return return_image_data(par);
|
||||
} else {
|
||||
throw std::runtime_error("Data must be 1D or 3D");
|
||||
}
|
||||
},
|
||||
py::arg("x"), py::arg("y"), py::arg("n_threads") = 4);
|
||||
|
||||
m.def(
|
||||
"fit_scurve",
|
||||
[](py::array_t<double, py::array::c_style | py::array::forcecast> x,
|
||||
py::array_t<double, py::array::c_style | py::array::forcecast> y,
|
||||
py::array_t<double, py::array::c_style | py::array::forcecast> y_err,
|
||||
int n_threads) {
|
||||
if (y.ndim() == 3) {
|
||||
auto par = new NDArray<double, 3>({y.shape(0), y.shape(1), 6});
|
||||
|
||||
auto par_err =
|
||||
new NDArray<double, 3>({y.shape(0), y.shape(1), 6});
|
||||
|
||||
auto y_view = make_view_3d(y);
|
||||
auto y_view_err = make_view_3d(y_err);
|
||||
auto x_view = make_view_1d(x);
|
||||
|
||||
auto chi2 = new NDArray<double, 2>({y.shape(0), y.shape(1)});
|
||||
|
||||
aare::fit_scurve(x_view, y_view, y_view_err, par->view(),
|
||||
par_err->view(), chi2->view(), n_threads);
|
||||
return py::dict("par"_a = return_image_data(par),
|
||||
"par_err"_a = return_image_data(par_err),
|
||||
"chi2"_a = return_image_data(chi2),
|
||||
"Ndf"_a = y.shape(2) - 2);
|
||||
|
||||
} else if (y.ndim() == 1) {
|
||||
auto par = new NDArray<double, 1>({2});
|
||||
auto par_err = new NDArray<double, 1>({2});
|
||||
|
||||
auto y_view = make_view_1d(y);
|
||||
auto y_view_err = make_view_1d(y_err);
|
||||
auto x_view = make_view_1d(x);
|
||||
|
||||
double chi2 = 0;
|
||||
|
||||
aare::fit_scurve(x_view, y_view, y_view_err, par->view(),
|
||||
par_err->view(), chi2);
|
||||
return py::dict("par"_a = return_image_data(par),
|
||||
"par_err"_a = return_image_data(par_err),
|
||||
"chi2"_a = chi2, "Ndf"_a = y.size() - 2);
|
||||
|
||||
} else {
|
||||
throw std::runtime_error("Data must be 1D or 3D");
|
||||
}
|
||||
},
|
||||
R"(
|
||||
Fit a 1D polynomial to data with error estimates.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x : array_like
|
||||
The x values.
|
||||
y : array_like
|
||||
The y values.
|
||||
y_err : array_like
|
||||
The error in the y values.
|
||||
n_threads : int, optional
|
||||
The number of threads to use. Default is 4.
|
||||
)",
|
||||
py::arg("x"), py::arg("y"), py::arg("y_err"), py::arg("n_threads") = 4);
|
||||
|
||||
m.def(
|
||||
"fit_scurve2",
|
||||
[](py::array_t<double, py::array::c_style | py::array::forcecast> x,
|
||||
py::array_t<double, py::array::c_style | py::array::forcecast> y,
|
||||
int n_threads) {
|
||||
if (y.ndim() == 3) {
|
||||
auto par = new NDArray<double, 3>{};
|
||||
|
||||
auto x_view = make_view_1d(x);
|
||||
auto y_view = make_view_3d(y);
|
||||
*par = aare::fit_scurve2(x_view, y_view, n_threads);
|
||||
return return_image_data(par);
|
||||
} else if (y.ndim() == 1) {
|
||||
auto par = new NDArray<double, 1>{};
|
||||
auto x_view = make_view_1d(x);
|
||||
auto y_view = make_view_1d(y);
|
||||
*par = aare::fit_scurve2(x_view, y_view);
|
||||
return return_image_data(par);
|
||||
} else {
|
||||
throw std::runtime_error("Data must be 1D or 3D");
|
||||
}
|
||||
},
|
||||
py::arg("x"), py::arg("y"), py::arg("n_threads") = 4);
|
||||
|
||||
m.def(
|
||||
"fit_scurve2",
|
||||
[](py::array_t<double, py::array::c_style | py::array::forcecast> x,
|
||||
py::array_t<double, py::array::c_style | py::array::forcecast> y,
|
||||
py::array_t<double, py::array::c_style | py::array::forcecast> y_err,
|
||||
int n_threads) {
|
||||
if (y.ndim() == 3) {
|
||||
auto par = new NDArray<double, 3>({y.shape(0), y.shape(1), 6});
|
||||
|
||||
auto par_err =
|
||||
new NDArray<double, 3>({y.shape(0), y.shape(1), 6});
|
||||
|
||||
auto y_view = make_view_3d(y);
|
||||
auto y_view_err = make_view_3d(y_err);
|
||||
auto x_view = make_view_1d(x);
|
||||
|
||||
auto chi2 = new NDArray<double, 2>({y.shape(0), y.shape(1)});
|
||||
|
||||
aare::fit_scurve2(x_view, y_view, y_view_err, par->view(),
|
||||
par_err->view(), chi2->view(), n_threads);
|
||||
return py::dict("par"_a = return_image_data(par),
|
||||
"par_err"_a = return_image_data(par_err),
|
||||
"chi2"_a = return_image_data(chi2),
|
||||
"Ndf"_a = y.shape(2) - 2);
|
||||
|
||||
} else if (y.ndim() == 1) {
|
||||
auto par = new NDArray<double, 1>({6});
|
||||
auto par_err = new NDArray<double, 1>({6});
|
||||
|
||||
auto y_view = make_view_1d(y);
|
||||
auto y_view_err = make_view_1d(y_err);
|
||||
auto x_view = make_view_1d(x);
|
||||
|
||||
double chi2 = 0;
|
||||
|
||||
aare::fit_scurve2(x_view, y_view, y_view_err, par->view(),
|
||||
par_err->view(), chi2);
|
||||
return py::dict("par"_a = return_image_data(par),
|
||||
"par_err"_a = return_image_data(par_err),
|
||||
"chi2"_a = chi2, "Ndf"_a = y.size() - 2);
|
||||
|
||||
} else {
|
||||
throw std::runtime_error("Data must be 1D or 3D");
|
||||
}
|
||||
},
|
||||
R"(
|
||||
Fit a 1D polynomial to data with error estimates.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x : array_like
|
||||
The x values.
|
||||
y : array_like
|
||||
The y values.
|
||||
y_err : array_like
|
||||
The error in the y values.
|
||||
n_threads : int, optional
|
||||
The number of threads to use. Default is 4.
|
||||
)",
|
||||
py::arg("x"), py::arg("y"), py::arg("y_err"), py::arg("n_threads") = 4);
|
||||
|
||||
// ── Bind model classes ──────────────────────────────────────────
|
||||
bind_fit_model<aare::model::Gaussian>(m, "Gaussian");
|
||||
bind_fit_model<aare::model::GaussianErfcPlateau>(m, "GaussianErfcPlateau");
|
||||
@@ -794,4 +348,4 @@ void define_fit_bindings(py::module &m) {
|
||||
)",
|
||||
py::arg("model"), py::arg("x"), py::arg("y"),
|
||||
py::arg("y_err") = py::none(), py::arg("n_threads") = 4);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Files with bindings to the different classes
|
||||
|
||||
#include "module_config.hpp"
|
||||
|
||||
// New style file naming
|
||||
#include "bind_Cluster.hpp"
|
||||
#include "bind_ClusterCollector.hpp"
|
||||
@@ -12,7 +14,9 @@
|
||||
#include "bind_ClusterVector.hpp"
|
||||
#include "bind_Defs.hpp"
|
||||
#include "bind_Eta.hpp"
|
||||
#include "bind_FastPedestal.hpp"
|
||||
#include "bind_Interpolator.hpp"
|
||||
#include "bind_MultiThreadedFileReader.hpp"
|
||||
#include "bind_PedestalTrackingPixelHistogram.hpp"
|
||||
#include "bind_PixelHistogram.hpp"
|
||||
#include "bind_PixelMap.hpp"
|
||||
@@ -30,6 +34,7 @@
|
||||
#include "var_cluster.hpp"
|
||||
|
||||
// Pybind stuff
|
||||
#include <cstdint>
|
||||
#include <pybind11/pybind11.h>
|
||||
#include <pybind11/stl.h>
|
||||
|
||||
@@ -61,7 +66,11 @@ double, 'f' for float)
|
||||
define_ClusterCollector<T, N, M, U>(m, "Cluster" #N "x" #M #TYPE_CODE);
|
||||
|
||||
PYBIND11_MODULE(_aare, m) {
|
||||
auto experimental = m.def_submodule(
|
||||
"experimental", "Experimental APIs that may change without notice");
|
||||
|
||||
define_file_io_bindings(m);
|
||||
define_multi_threaded_file_reader_bindings(experimental);
|
||||
define_raw_file_io_bindings(m);
|
||||
define_raw_sub_file_io_bindings(m);
|
||||
define_ctb_raw_file_io_bindings(m);
|
||||
@@ -72,6 +81,10 @@ PYBIND11_MODULE(_aare, m) {
|
||||
define_pedestal_tracking_pixel_histogram_bindings(m);
|
||||
define_pedestal_bindings<double>(m, "Pedestal_d");
|
||||
define_pedestal_bindings<float>(m, "Pedestal_f");
|
||||
define_pedestal_bindings<int16_t>(m, "Pedestal_i16");
|
||||
define_fast_pedestal_bindings<double>(m, "FastPedestal_d");
|
||||
define_fast_pedestal_bindings<float>(m, "FastPedestal_f");
|
||||
define_fast_pedestal_bindings<int16_t>(m, "FastPedestal_i16");
|
||||
define_fit_bindings(m);
|
||||
define_interpolation_bindings(m);
|
||||
define_jungfrau_data_file_io_bindings(m);
|
||||
@@ -103,6 +116,7 @@ PYBIND11_MODULE(_aare, m) {
|
||||
DEFINE_BINDINGS_CLUSTERFINDER(int, 3, 3, uint16_t, i);
|
||||
DEFINE_BINDINGS_CLUSTERFINDER(double, 3, 3, uint16_t, d);
|
||||
DEFINE_BINDINGS_CLUSTERFINDER(float, 3, 3, uint16_t, f);
|
||||
DEFINE_BINDINGS_CLUSTERFINDER(int16_t, 3, 3, uint16_t, i16);
|
||||
|
||||
DEFINE_BINDINGS_CLUSTERFINDER(int, 5, 5, uint16_t, i);
|
||||
DEFINE_BINDINGS_CLUSTERFINDER(double, 5, 5, uint16_t, d);
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
// Configure module wide pedestal type for cluster finding
|
||||
using pd_type = double;
|
||||
@@ -52,6 +52,12 @@ void define_pedestal_bindings(py::module &m, const std::string &name) {
|
||||
*std = self.std();
|
||||
return return_image_data(std);
|
||||
})
|
||||
.def("cached_std",
|
||||
[](Pedestal<SUM_TYPE> &self) {
|
||||
auto standard_deviation = new NDArray<SUM_TYPE, 2>{};
|
||||
*standard_deviation = self.cached_std();
|
||||
return return_image_data(standard_deviation);
|
||||
})
|
||||
.def(
|
||||
"__array_ufunc__",
|
||||
[](py::object self, py::object ufunc, const std::string &method,
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include "aare/RawSubFile.hpp"
|
||||
|
||||
#include "aare/defs.hpp"
|
||||
// #include "aare/fClusterFileV2.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "aare/RawSubFile.hpp"
|
||||
|
||||
#include "aare/defs.hpp"
|
||||
// #include "aare/fClusterFileV2.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
#include "aare/VarClusterFinder.hpp"
|
||||
#include "np_helper.hpp"
|
||||
// #include "aare/defs.hpp"
|
||||
// #include "aare/fClusterFileV2.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
// #include <filesystem>
|
||||
#include <pybind11/numpy.h>
|
||||
// #include <pybind11/iostream.h>
|
||||
#include <pybind11/pybind11.h>
|
||||
#include <pybind11/stl.h>
|
||||
// #include <pybind11/stl/filesystem.h>
|
||||
// #include <string>
|
||||
|
||||
namespace py = pybind11;
|
||||
using namespace ::aare;
|
||||
|
||||
@@ -103,15 +103,18 @@ def test_max_sum():
|
||||
|
||||
def test_cluster_finder():
|
||||
"""Test ClusterFinder"""
|
||||
shape = [100,100]
|
||||
cf = _aare.ClusterFinder_Cluster3x3i(shape)
|
||||
|
||||
clusterfinder = _aare.ClusterFinder_Cluster3x3i([100,100])
|
||||
#Push 1000 frames to the pedestal
|
||||
for i in range(1000):
|
||||
frame = np.random.normal(loc = 100, scale = 5, size = shape).astype(np.uint16)
|
||||
cf.push_pedestal_frame(frame)
|
||||
cf.update_threshold()
|
||||
frame = np.zeros(shape=shape, dtype=np.uint16)
|
||||
cf.find_clusters(frame)
|
||||
|
||||
#frame = np.random.rand(100,100)
|
||||
frame = np.zeros(shape=[100,100])
|
||||
|
||||
clusterfinder.find_clusters(frame)
|
||||
|
||||
clusters = clusterfinder.steal_clusters(False) #conversion does not work
|
||||
clusters = cf.steal_clusters(False) #conversion does not work
|
||||
|
||||
assert clusters.size == 0
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ from conftest import test_data_path
|
||||
def test_cluster_file(test_data_path):
|
||||
"""Test ClusterFile"""
|
||||
f = ClusterFile(test_data_path / "clust/single_frame_97_clustrers.clust")
|
||||
assert f.estimate_n_clusters() == 97
|
||||
assert f.tell() == 0
|
||||
cv = f.read_clusters(10) #conversion does not work
|
||||
|
||||
|
||||
@@ -62,4 +64,4 @@ def test_read_clusters_and_fill_histogram(test_data_path):
|
||||
hist_py = pickle.load(f)
|
||||
|
||||
#Compare the two histograms
|
||||
assert hist_aare == hist_py
|
||||
assert hist_aare == hist_py
|
||||
@@ -16,6 +16,7 @@ def test_create_cluster_vector():
|
||||
assert cv.cluster_size_x == 3
|
||||
assert cv.cluster_size_y == 3
|
||||
assert cv.size == 0
|
||||
assert cv.empty()
|
||||
|
||||
|
||||
def test_push_back_on_cluster_vector():
|
||||
@@ -27,6 +28,7 @@ def test_push_back_on_cluster_vector():
|
||||
cluster = _aare.Cluster2x2i(19, 22, np.ones(4, dtype=np.int32))
|
||||
cv.push_back(cluster)
|
||||
assert cv.size == 1
|
||||
assert not cv.empty()
|
||||
|
||||
arr = np.array(cv, copy=False)
|
||||
assert arr[0]['x'] == 19
|
||||
@@ -127,4 +129,4 @@ def test_masking():
|
||||
|
||||
assert cv_masked_array[0]["x"] == 1
|
||||
assert cv_masked_array[0]["y"] == 2
|
||||
assert (cv_masked_array[0]["data"] == np.ones((3,3),dtype=np.int32)).all()
|
||||
assert (cv_masked_array[0]["data"] == np.ones((3,3),dtype=np.int32)).all()
|
||||
@@ -0,0 +1,145 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from aare import (
|
||||
FastPedestal,
|
||||
FastPedestal_d,
|
||||
FastPedestal_f,
|
||||
FastPedestal_i16,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("dtype", "pedestal_type"),
|
||||
[
|
||||
(np.float64, FastPedestal_d),
|
||||
(np.float32, FastPedestal_f),
|
||||
(np.int16, FastPedestal_i16),
|
||||
],
|
||||
)
|
||||
def test_fast_pedestal_factory(dtype, pedestal_type):
|
||||
pedestal = FastPedestal(2, 3, n_samples=4, dtype=dtype)
|
||||
|
||||
assert isinstance(pedestal, pedestal_type)
|
||||
assert pedestal.rows == 2
|
||||
assert pedestal.cols == 3
|
||||
assert pedestal.n_samples == 4
|
||||
|
||||
|
||||
def test_fast_pedestal_factory_defaults_to_double():
|
||||
assert isinstance(FastPedestal(2, 3), FastPedestal_d)
|
||||
|
||||
|
||||
def test_fast_pedestal_factory_rejects_unbound_dtype():
|
||||
with pytest.raises(ValueError, match="Unsupported dtype for FastPedestal"):
|
||||
FastPedestal(2, 3, dtype=np.int32)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("kwargs", "expected_n_samples"),
|
||||
[
|
||||
({"rows": 2, "cols": 3}, 1000),
|
||||
({"rows": 2, "cols": 3, "n_samples": 4}, 4),
|
||||
],
|
||||
)
|
||||
def test_fast_pedestal_binding_accepts_constructor_keywords(
|
||||
kwargs, expected_n_samples
|
||||
):
|
||||
pedestal = FastPedestal_d(**kwargs)
|
||||
|
||||
assert pedestal.rows == 2
|
||||
assert pedestal.cols == 3
|
||||
assert pedestal.n_samples == expected_n_samples
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("dtype", "pedestal_type", "expected_dtype"),
|
||||
[
|
||||
(np.float64, FastPedestal_d, np.float64),
|
||||
(np.float32, FastPedestal_f, np.float32),
|
||||
(np.int16, FastPedestal_i16, np.int16),
|
||||
],
|
||||
)
|
||||
def test_fast_pedestal_factory_from_file(
|
||||
tmp_path, dtype, pedestal_type, expected_dtype
|
||||
):
|
||||
frames = np.array(
|
||||
[[[100, 100]], [[2, 4]], [[4, 6]], [[5, 7]]], dtype=np.uint16
|
||||
)
|
||||
filename = tmp_path / "frames.npy"
|
||||
np.save(filename, frames)
|
||||
|
||||
pedestal = FastPedestal.from_file(
|
||||
filename, n_samples=2, skip_first=1, dtype=dtype
|
||||
)
|
||||
|
||||
assert isinstance(pedestal, pedestal_type)
|
||||
assert pedestal.ready
|
||||
assert pedestal.cur_samples == 2
|
||||
assert pedestal.mean().dtype == expected_dtype
|
||||
np.testing.assert_array_equal(pedestal.mean(), [[4, 6]])
|
||||
|
||||
|
||||
def test_fast_pedestal_factory_from_file_rejects_unbound_dtype():
|
||||
with pytest.raises(ValueError, match="Unsupported dtype for FastPedestal"):
|
||||
FastPedestal.from_file("unused.npy", dtype=np.int32)
|
||||
|
||||
|
||||
def test_fast_pedestal_from_file_rejects_skip_beyond_end(tmp_path):
|
||||
filename = tmp_path / "frames.npy"
|
||||
np.save(filename, np.zeros((1, 1, 1), dtype=np.uint16))
|
||||
|
||||
with pytest.raises(RuntimeError, match="less frames"):
|
||||
FastPedestal.from_file(filename, n_samples=1, skip_first=2)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("pedestal_type", "expected_dtype"),
|
||||
[(FastPedestal_d, np.float64), (FastPedestal_f, np.float32)],
|
||||
)
|
||||
def test_fast_pedestal_initialization(pedestal_type, expected_dtype):
|
||||
pedestal = pedestal_type(2, 3, 2)
|
||||
first = np.array([[2, 4, 6], [8, 10, 12]], dtype=np.uint16)
|
||||
second = np.array([[4, 6, 8], [10, 12, 14]], dtype=np.uint16)
|
||||
|
||||
pedestal.add_init_frame(first)
|
||||
pedestal.add_init_frame(second)
|
||||
|
||||
|
||||
expected_mean = np.array(
|
||||
[[3, 5, 7], [9, 11, 13]], dtype=expected_dtype
|
||||
)
|
||||
np.testing.assert_array_equal(pedestal.mean(), expected_mean)
|
||||
np.testing.assert_array_equal(pedestal.std(), np.ones((2, 3)))
|
||||
|
||||
|
||||
def test_fast_pedestal_steady_state_push_ema():
|
||||
pedestal = FastPedestal_d(1, 2, 2)
|
||||
pedestal.add_init_frame(np.array([[2, 4]], dtype=np.uint16))
|
||||
pedestal.add_init_frame(np.array([[4, 6]], dtype=np.uint16))
|
||||
|
||||
|
||||
pedestal.push_ema(np.array([[6, 8]], dtype=np.uint16))
|
||||
|
||||
np.testing.assert_array_equal(pedestal.mean(), [[4.5, 6.5]])
|
||||
|
||||
|
||||
def test_fast_pedestal_exposes_read_only_buffer_and_subtraction():
|
||||
pedestal = FastPedestal_d(1, 2, 1)
|
||||
pedestal.add_init_frame(np.array([[2, 4]], dtype=np.uint16))
|
||||
|
||||
|
||||
view = np.asarray(pedestal)
|
||||
result = np.array([[12, 14]], dtype=np.uint16) - pedestal
|
||||
|
||||
np.testing.assert_array_equal(view, [[2, 4]])
|
||||
np.testing.assert_array_equal(result, [[10, 10]])
|
||||
assert np.shares_memory(view, pedestal.view())
|
||||
assert not view.flags.writeable
|
||||
|
||||
|
||||
def test_fast_pedestal_rejects_wrong_shape():
|
||||
pedestal = FastPedestal_d(2, 3)
|
||||
|
||||
with pytest.raises(RuntimeError, match="shape"):
|
||||
pedestal.add_init_frame(np.zeros((2, 2), dtype=np.uint16))
|
||||
@@ -0,0 +1,15 @@
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
import numpy as np
|
||||
import aare
|
||||
|
||||
|
||||
|
||||
def test_gaussian_model_evaluates_and_fits_data():
|
||||
x = np.linspace(-5.0, 5.0, 51)
|
||||
expected = np.array([20.0, 0.5, 1.2])
|
||||
model = aare.Gaussian()
|
||||
|
||||
y = model(x, expected)
|
||||
result = model.fit(x, y)
|
||||
|
||||
np.testing.assert_allclose(result["par"], expected, atol=2e-3)
|
||||
@@ -1,488 +1,413 @@
|
||||
{
|
||||
"cells":
|
||||
[
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "efef8e20-6571-4561-8f0b-6048b57907de",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import time\n",
|
||||
"import random\n",
|
||||
"import numpy as np\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
"from matplotlib.gridspec import GridSpec\n",
|
||||
"import sys\n",
|
||||
"sys.path.insert(0, '/home/ferjao_k/aare/build')\n",
|
||||
"from aare import fit_gaus # lmfit\n",
|
||||
"from aare import Gaussian, fit # minuit2"
|
||||
]
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "efef8e20-6571-4561-8f0b-6048b57907de",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import time\n",
|
||||
"import random\n",
|
||||
"import numpy as np\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
"from matplotlib.gridspec import GridSpec\n",
|
||||
"from aare import Gaussian\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "35772e2c-37c6-4986-a9c0-7dca4a034827",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"ROWS = 100\n",
|
||||
"COLS = 100\n",
|
||||
"N_SCAN = 100\n",
|
||||
"NOISE_FRAC = 0.05\n",
|
||||
"SEED = 42\n",
|
||||
"N_THREADS = 4\n",
|
||||
"\n",
|
||||
"N_REPEATS = 7\n",
|
||||
"N_WARMUP = 3 # untimed iterations (icache + branch predictor warmup)\n",
|
||||
"COOLDOWN = 2.0 # seconds between (method, thread_count) pairs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "be455445-7df8-47cc-9f39-d2a38856e0bd",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Data generator"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "06c8fddb-56d9-4f84-8b21-4ffd8b7bd26f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def generate_3d_data(rows, cols, n_scan, noise_frac, seed):\n",
|
||||
" \"\"\"\n",
|
||||
" Generate a synthetic detector image stack where each pixel has a\n",
|
||||
" Gaussian response curve with per-pixel variation in A, mu, sigma.\n",
|
||||
"\n",
|
||||
" Returns x (n_scan,), y (rows, cols, n_scan), y_err (rows, cols, n_scan),\n",
|
||||
" and the ground-truth parameter arrays.\n",
|
||||
" \"\"\"\n",
|
||||
" rng = np.random.default_rng(seed)\n",
|
||||
"\n",
|
||||
" # Per-pixel true params each of shape: [rows, cols, 1]\n",
|
||||
" A_true = rng.uniform(200, 1000, size=(rows, cols))\n",
|
||||
" mu_true = rng.uniform(20, 80, size=(rows, cols))\n",
|
||||
" sig_true = rng.uniform(3, 12, size=(rows, cols))\n",
|
||||
" \n",
|
||||
" # One common binned energy array\n",
|
||||
" x = np.linspace(0, 100, n_scan) # shape [1, 1, nscan]\n",
|
||||
"\n",
|
||||
" # Build ground truth signals per-pixel\n",
|
||||
" exponent = -0.5 * ((x[None, None, :] - mu_true[:, :, None]) / sig_true[:,:, None])**2 # shape [rows, cols, nscan]\n",
|
||||
" y_clean = A_true[:, :, None] * np.exp(exponent)\n",
|
||||
"\n",
|
||||
" # Perturb with noise\n",
|
||||
" noise_sigma = noise_frac * A_true[:, :, None] * np.ones_like(y_clean) # shape [rows, cols, nscan]\n",
|
||||
" noise = rng.normal(0, noise_sigma)\n",
|
||||
" y = y_clean + noise\n",
|
||||
"\n",
|
||||
" y_err = noise_sigma.copy()\n",
|
||||
"\n",
|
||||
" return x, y, y_err, A_true, mu_true, sig_true "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "30ed6bb8-4798-497f-9990-4c518f885855",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Profiling function"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "dc0b52b6-9fc3-453d-b6eb-e325a2b8342e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def bench(fn, n_warmup=N_WARMUP, n_repeats=N_REPEATS):\n",
|
||||
" \"\"\"\n",
|
||||
" Warmup then time `fn` over `n_repeats` calls.\n",
|
||||
" Returns (last_result, list_of_walltimes_in_seconds).\n",
|
||||
" \"\"\"\n",
|
||||
" # warmup: primes icache, branch predictor, and lets CPU ramp to boost clock\n",
|
||||
" for _ in range(n_warmup):\n",
|
||||
" res = fn()\n",
|
||||
"\n",
|
||||
" times = []\n",
|
||||
" for _ in range(n_repeats):\n",
|
||||
" t0 = time.perf_counter()\n",
|
||||
" res = fn()\n",
|
||||
" t1 = time.perf_counter()\n",
|
||||
" times.append(t1 - t0)\n",
|
||||
" return res, times"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "377fc820-95b2-48aa-b104-a272c50e4103",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Quick check on small (2x2) frame"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "9cc999b9-e0d8-4deb-ae29-e8bd0141534b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Generate 2 x 2 dataset of Gaussian-like profiles for each pixel\n",
|
||||
"x2, y2, yerr2, true_A2, true_mu2, true_sig2 = generate_3d_data(\n",
|
||||
" 2, 2, N_SCAN, NOISE_FRAC, SEED\n",
|
||||
")\n",
|
||||
"model_g = Gaussian()\n",
|
||||
"model_g.compute_errors = True\n",
|
||||
"result = model_g.fit(x2, y2, yerr2)\n",
|
||||
"\n",
|
||||
"from pprint import pprint\n",
|
||||
"print(\"== True Gaussian params == \")\n",
|
||||
"print(\"A_true = \\n\", true_A2)\n",
|
||||
"print(\"mu_true = \\n\", true_mu2)\n",
|
||||
"print(\"sig_true = \\n\",true_sig2)\n",
|
||||
"print(\"\\n\")\n",
|
||||
"\n",
|
||||
"print(\"== Fit results ==\")\n",
|
||||
"par = result['par']\n",
|
||||
"# print(par)\n",
|
||||
"A_fit = par[:, :, 0]\n",
|
||||
"mu_fit = par[:, :, 1]\n",
|
||||
"sig_fit = par[:, :, 2]\n",
|
||||
"print(\"A_fit = \\n\", A_fit)\n",
|
||||
"print(\"mu_fit = \\n\", mu_fit)\n",
|
||||
"print(\"sig_fit = \\n\", sig_fit)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a98a41f3-fd2e-4bfc-9ec6-0d23dd38e896",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"fig, ax = plt.subplots(2, 2, figsize=(12,8))\n",
|
||||
"\n",
|
||||
"# Gaussians in 2x2 frame: True vs Fit\n",
|
||||
"for row in range(2):\n",
|
||||
" for col in range(2):\n",
|
||||
" ax[row, col].plot(x2, y2[row, col,:], label=\"data\")\n",
|
||||
" ax[row, col].plot(x2, model_g(x2, result['par'][row, col,:]), linewidth=1, color=\"green\", label=\"minuit\")\n",
|
||||
" ax[row, col].set_title(f\"Gaussian Fit to data in pixel [{row}, {col}]\")\n",
|
||||
" ax[row, col].legend()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "fcf52481-0278-4f95-8676-829a6d61eff8",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Fit data with different backends"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "1f6bc651-80c1-41dd-8a05-7f15aba006aa",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# ===============\n",
|
||||
"# DATA GENERATION\n",
|
||||
"# ===============\n",
|
||||
"print(f\"Generating synthetic data: {ROWS}x{COLS} pixels, \"\n",
|
||||
" f\"{N_SCAN} scan points, noise_frac={NOISE_FRAC}\\n\")\n",
|
||||
"\n",
|
||||
"x, y, yerr, true_A, true_mu, true_sig = generate_3d_data(\n",
|
||||
" ROWS, COLS, N_SCAN, NOISE_FRAC, SEED\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"model = Gaussian()\n",
|
||||
"print(f\"model.max_calls = {model.max_calls}\")\n",
|
||||
"print(f\"model.tolerance = {model.tolerance}\")\n",
|
||||
"print(\"model.compute_errors =\", model.compute_errors)\n",
|
||||
"METHOD_DEFS = [\n",
|
||||
" (\"Minuit2 (obj API)\",\n",
|
||||
" lambda nt: lambda: model.fit(x, y, n_threads=nt),\n",
|
||||
" \"#FF9800\", {\"linewidth\": 2.5, \"linestyle\": \":\"}),\n",
|
||||
"]\n",
|
||||
"\n",
|
||||
"colors = {label: c for label, _, c, _ in METHOD_DEFS}\n",
|
||||
"styles = {label: s for label, _, _, s in METHOD_DEFS}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "5a417145-ce42-4c3a-a7bf-05ff97ba0450",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# ====================================\n",
|
||||
"# SINGLE-CALL BENCHMARK (at N_THREADS)\n",
|
||||
"# ====================================\n",
|
||||
"def extract_result(label, res):\n",
|
||||
" \"\"\"Normalize return values across fitters into a common dict.\"\"\"\n",
|
||||
" if isinstance(res, dict):\n",
|
||||
" out = {\"par\": res[\"par\"]}\n",
|
||||
" if \"par_err\" in res:\n",
|
||||
" out[\"par_err\"] = res[\"par_err\"]\n",
|
||||
" if \"chi2\" in res:\n",
|
||||
" out[\"chi2\"] = res[\"chi2\"]\n",
|
||||
" return out\n",
|
||||
" \n",
|
||||
"methods = {}\n",
|
||||
"for label, factory, _, _ in METHOD_DEFS:\n",
|
||||
" time.sleep(COOLDOWN)\n",
|
||||
" res, times = bench(factory(N_THREADS))\n",
|
||||
" entry = extract_result(label, res)\n",
|
||||
" entry[\"times\"] = times\n",
|
||||
" methods[label] = entry\n",
|
||||
"\n",
|
||||
"# ---- Print summary ----\n",
|
||||
"ndf = N_SCAN - 3\n",
|
||||
"print(f\"{'Method':24s} {'time (ms)':>10s} {'med|dA|':>10s} {'med|dMu|':>10s} {'med|dSig|':>10s}\")\n",
|
||||
"print(\"-\" * 80)\n",
|
||||
"for name, m in methods.items():\n",
|
||||
" par = m[\"par\"]\n",
|
||||
" med_t = np.median(m[\"times\"]) * 1e3\n",
|
||||
" dA = np.median(np.abs(par[:,:,0] - true_A))\n",
|
||||
" dMu = np.median(np.abs(par[:,:,1] - true_mu))\n",
|
||||
" dSig = np.median(np.abs(par[:,:,2] - true_sig))\n",
|
||||
"\n",
|
||||
" chi2_str = \"\"\n",
|
||||
" if \"chi2\" in m:\n",
|
||||
" chi2_str = f\" chi2/ndf={np.median(m['chi2'] / ndf):.4f}\"\n",
|
||||
"\n",
|
||||
" print(f\"[{name:22s}] {med_t:8.2f} ms \"\n",
|
||||
" f\"{dA:10.3f} {dMu:10.4f} {dSig:10.4f}{chi2_str}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "55ecc77c-0823-408d-a811-8e4f4900f332",
|
||||
"metadata": {
|
||||
"scrolled": true
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "35772e2c-37c6-4986-a9c0-7dca4a034827",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"ROWS = 100\n",
|
||||
"COLS = 100\n",
|
||||
"N_SCAN = 100\n",
|
||||
"NOISE_FRAC = 0.05\n",
|
||||
"SEED = 42\n",
|
||||
"N_THREADS = 4\n",
|
||||
"\n",
|
||||
"N_REPEATS = 7\n",
|
||||
"N_WARMUP = 3 # untimed iterations (icache + branch predictor warmup)\n",
|
||||
"COOLDOWN = 2.0 # seconds between (method, thread_count) pairs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "be455445-7df8-47cc-9f39-d2a38856e0bd",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Data generator"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "06c8fddb-56d9-4f84-8b21-4ffd8b7bd26f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def generate_3d_data(rows, cols, n_scan, noise_frac, seed):\n",
|
||||
" \"\"\"\n",
|
||||
" Generate a synthetic detector image stack where each pixel has a\n",
|
||||
" Gaussian response curve with per-pixel variation in A, mu, sigma.\n",
|
||||
"\n",
|
||||
" Returns x (n_scan,), y (rows, cols, n_scan), y_err (rows, cols, n_scan),\n",
|
||||
" and the ground-truth parameter arrays.\n",
|
||||
" \"\"\"\n",
|
||||
" rng = np.random.default_rng(seed)\n",
|
||||
"\n",
|
||||
" # Per-pixel true params each of shape: [rows, cols, 1]\n",
|
||||
" A_true = rng.uniform(200, 1000, size=(rows, cols))\n",
|
||||
" mu_true = rng.uniform(20, 80, size=(rows, cols))\n",
|
||||
" sig_true = rng.uniform(3, 12, size=(rows, cols))\n",
|
||||
" \n",
|
||||
" # One common binned energy array\n",
|
||||
" x = np.linspace(0, 100, n_scan) # shape [1, 1, nscan]\n",
|
||||
"\n",
|
||||
" # Build ground truth signals per-pixel\n",
|
||||
" exponent = -0.5 * ((x[None, None, :] - mu_true[:, :, None]) / sig_true[:,:, None])**2 # shape [rows, cols, nscan]\n",
|
||||
" y_clean = A_true[:, :, None] * np.exp(exponent)\n",
|
||||
"\n",
|
||||
" # Perturb with noise\n",
|
||||
" noise_sigma = noise_frac * A_true[:, :, None] * np.ones_like(y_clean) # shape [rows, cols, nscan]\n",
|
||||
" noise = rng.normal(0, noise_sigma)\n",
|
||||
" y = y_clean + noise\n",
|
||||
"\n",
|
||||
" y_err = noise_sigma.copy()\n",
|
||||
"\n",
|
||||
" return x, y, y_err, A_true, mu_true, sig_true "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "30ed6bb8-4798-497f-9990-4c518f885855",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Profiling function"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "dc0b52b6-9fc3-453d-b6eb-e325a2b8342e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def bench(fn, n_warmup=N_WARMUP, n_repeats=N_REPEATS):\n",
|
||||
" \"\"\"\n",
|
||||
" Warmup then time `fn` over `n_repeats` calls.\n",
|
||||
" Returns (last_result, list_of_walltimes_in_seconds).\n",
|
||||
" \"\"\"\n",
|
||||
" # warmup: primes icache, branch predictor, and lets CPU ramp to boost clock\n",
|
||||
" for _ in range(n_warmup):\n",
|
||||
" res = fn()\n",
|
||||
"\n",
|
||||
" times = []\n",
|
||||
" for _ in range(n_repeats):\n",
|
||||
" t0 = time.perf_counter()\n",
|
||||
" res = fn()\n",
|
||||
" t1 = time.perf_counter()\n",
|
||||
" times.append(t1 - t0)\n",
|
||||
" return res, times"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "377fc820-95b2-48aa-b104-a272c50e4103",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Quick check on small (2x2) frame"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "9cc999b9-e0d8-4deb-ae29-e8bd0141534b",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"== True Gaussian params == \n",
|
||||
"A_true = \n",
|
||||
" [[819.16483884 551.1027518 ]\n",
|
||||
" [886.87833593 757.89442325]]\n",
|
||||
"mu_true = \n",
|
||||
" [[25.65064087 78.5373411 ]\n",
|
||||
" [65.66838212 67.16385832]]\n",
|
||||
"sig_true = \n",
|
||||
" [[ 4.15302269 7.05347344]\n",
|
||||
" [ 6.33718222 11.3408849 ]]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"== Fit results ==\n",
|
||||
"A_fit = \n",
|
||||
" [[812.09277132 559.04069721]\n",
|
||||
" [899.09335849 759.24481682]]\n",
|
||||
"mu_fit = \n",
|
||||
" [[25.6598209 78.40461782]\n",
|
||||
" [65.52261318 66.84540995]]\n",
|
||||
"sig_fit = \n",
|
||||
" [[ 4.2778026 7.041045 ]\n",
|
||||
" [ 6.29190225 11.34233504]]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Generate 2 x 2 dataset of Gaussian-like profiles for each pixel\n",
|
||||
"x2, y2, yerr2, true_A2, true_mu2, true_sig2 = generate_3d_data(\n",
|
||||
" 2, 2, N_SCAN, NOISE_FRAC, SEED\n",
|
||||
")\n",
|
||||
"model_g = Gaussian()\n",
|
||||
"model_g.compute_errors = True\n",
|
||||
"result = model_g.fit(x2, y2, yerr2)\n",
|
||||
"\n",
|
||||
"from pprint import pprint\n",
|
||||
"print(\"== True Gaussian params == \")\n",
|
||||
"print(\"A_true = \\n\", true_A2)\n",
|
||||
"print(\"mu_true = \\n\", true_mu2)\n",
|
||||
"print(\"sig_true = \\n\",true_sig2)\n",
|
||||
"print(\"\\n\")\n",
|
||||
"\n",
|
||||
"print(\"== Fit results ==\")\n",
|
||||
"par = result['par']\n",
|
||||
"# print(par)\n",
|
||||
"A_fit = par[:, :, 0]\n",
|
||||
"mu_fit = par[:, :, 1]\n",
|
||||
"sig_fit = par[:, :, 2]\n",
|
||||
"print(\"A_fit = \\n\", A_fit)\n",
|
||||
"print(\"mu_fit = \\n\", mu_fit)\n",
|
||||
"print(\"sig_fit = \\n\", sig_fit)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "a98a41f3-fd2e-4bfc-9ec6-0d23dd38e896",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"image/png":
|
||||
"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 truncated
|
||||
"text/plain": [
|
||||
"<Figure size 1200x800 with 4 Axes>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"fig, ax = plt.subplots(2, 2, figsize=(12,8))\n",
|
||||
"\n",
|
||||
"# Gaussians in 2x2 frame: True vs Fit\n",
|
||||
"for row in range(2):\n",
|
||||
" for col in range(2):\n",
|
||||
" ax[row, col].plot(x2, y2[row, col,:], label=\"data\")\n",
|
||||
" ax[row, col].plot(x2, model_g(x2, result['par'][row, col,:]), linewidth=1, color=\"green\", label=\"minuit\")\n",
|
||||
" ax[row, col].set_title(f\"Gaussian Fit to data in pixel [{row}, {col}]\")\n",
|
||||
" ax[row, col].legend()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "fcf52481-0278-4f95-8676-829a6d61eff8",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Fit data with different backends"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"id": "1f6bc651-80c1-41dd-8a05-7f15aba006aa",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Generating synthetic data: 100x100 pixels, 100 scan points, noise_frac=0.05\n",
|
||||
"\n",
|
||||
"model.max_calls = 100\n",
|
||||
"model.tolerance = 0.5\n",
|
||||
"model.compute_errors = False\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# ===============\n",
|
||||
"# DATA GENERATION\n",
|
||||
"# ===============\n",
|
||||
"print(f\"Generating synthetic data: {ROWS}x{COLS} pixels, \"\n",
|
||||
" f\"{N_SCAN} scan points, noise_frac={NOISE_FRAC}\\n\")\n",
|
||||
"\n",
|
||||
"x, y, yerr, true_A, true_mu, true_sig = generate_3d_data(\n",
|
||||
" ROWS, COLS, N_SCAN, NOISE_FRAC, SEED\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"model = Gaussian()\n",
|
||||
"print(f\"model.max_calls = {model.max_calls}\")\n",
|
||||
"print(f\"model.tolerance = {model.tolerance}\")\n",
|
||||
"print(\"model.compute_errors =\", model.compute_errors)\n",
|
||||
"METHOD_DEFS = [\n",
|
||||
" (\"lmfit (LM)\",\n",
|
||||
" lambda nt: lambda: fit_gaus(x, y, n_threads=nt),\n",
|
||||
" \"#2196F3\", {\"linewidth\": 3.0, \"linestyle\": \"-\"}),\n",
|
||||
"\n",
|
||||
" (\"Minuit2 (obj API)\",\n",
|
||||
" lambda nt: lambda: model.fit(x, y, n_threads=nt),\n",
|
||||
" \"#FF9800\", {\"linewidth\": 2.5, \"linestyle\": \":\"}),\n",
|
||||
"]\n",
|
||||
"\n",
|
||||
"colors = {label: c for label, _, c, _ in METHOD_DEFS}\n",
|
||||
"styles = {label: s for label, _, _, s in METHOD_DEFS}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"id": "5a417145-ce42-4c3a-a7bf-05ff97ba0450",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Method time (ms) med|dA| med|dMu| med|dSig|\n",
|
||||
"--------------------------------------------------------------------------------\n",
|
||||
"[lmfit (LM) ] 90.30 ms 6.272 0.0940 0.0949\n",
|
||||
"[Minuit2 (obj API) ] 59.60 ms 6.272 0.0940 0.0949 chi2/ndf=880.9946\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source":
|
||||
[
|
||||
"# ====================================\n",
|
||||
"# SINGLE-CALL BENCHMARK (at N_THREADS)\n",
|
||||
"# ====================================\n",
|
||||
"def extract_result(label, res):\n",
|
||||
" \"\"\"Normalize return values across fitters into a common dict.\"\"\"\n",
|
||||
" if isinstance(res, dict):\n",
|
||||
" out = {\"par\": res[\"par\"]}\n",
|
||||
" if \"par_err\" in res:\n",
|
||||
" out[\"par_err\"] = res[\"par_err\"]\n",
|
||||
" if \"chi2\" in res:\n",
|
||||
" out[\"chi2\"] = res[\"chi2\"]\n",
|
||||
" return out\n",
|
||||
" # fit_gaus without y_err returns a raw array\n",
|
||||
" return {\"par\": res}\n",
|
||||
" \n",
|
||||
"methods = {}\n",
|
||||
"for label, factory, _, _ in METHOD_DEFS:\n",
|
||||
" time.sleep(COOLDOWN)\n",
|
||||
" res, times = bench(factory(N_THREADS))\n",
|
||||
" entry = extract_result(label, res)\n",
|
||||
" entry[\"times\"] = times\n",
|
||||
" methods[label] = entry\n",
|
||||
"\n",
|
||||
"# ---- Print summary ----\n",
|
||||
"ndf = N_SCAN - 3\n",
|
||||
"print(f\"{'Method':24s} {'time (ms)':>10s} {'med|dA|':>10s} {'med|dMu|':>10s} {'med|dSig|':>10s}\")\n",
|
||||
"print(\"-\" * 80)\n",
|
||||
"for name, m in methods.items():\n",
|
||||
" par = m[\"par\"]\n",
|
||||
" med_t = np.median(m[\"times\"]) * 1e3\n",
|
||||
" dA = np.median(np.abs(par[:,:,0] - true_A))\n",
|
||||
" dMu = np.median(np.abs(par[:,:,1] - true_mu))\n",
|
||||
" dSig = np.median(np.abs(par[:,:,2] - true_sig))\n",
|
||||
"\n",
|
||||
" chi2_str = \"\"\n",
|
||||
" if \"chi2\" in m:\n",
|
||||
" chi2_str = f\" chi2/ndf={np.median(m['chi2'] / ndf):.4f}\"\n",
|
||||
"\n",
|
||||
" print(f\"[{name:22s}] {med_t:8.2f} ms \"\n",
|
||||
" f\"{dA:10.3f} {dMu:10.4f} {dSig:10.4f}{chi2_str}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "55ecc77c-0823-408d-a811-8e4f4900f332",
|
||||
"metadata": {
|
||||
"scrolled": true
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# ===============\n",
|
||||
"# THREAD SCALING\n",
|
||||
"# ===============\n",
|
||||
"thread_counts = [1, 2, 4, 8, 16]\n",
|
||||
"\n",
|
||||
"thread_times = {label: [] for label, _, _, _ in METHOD_DEFS}\n",
|
||||
"ttimes_stddev = {label: [] for label, _, _, _ in METHOD_DEFS}\n",
|
||||
"\n",
|
||||
"for nt in thread_counts:\n",
|
||||
" # shuffle method order per thread count to decorrelate thermal bias\n",
|
||||
" run_order = list(METHOD_DEFS)\n",
|
||||
" random.shuffle(run_order)\n",
|
||||
"\n",
|
||||
" for label, factory, _, _ in run_order:\n",
|
||||
" time.sleep(COOLDOWN)\n",
|
||||
" _, times = bench(factory(nt))\n",
|
||||
"\n",
|
||||
" med = np.median(times) * 1e3\n",
|
||||
" std = np.std(times) * 1e3\n",
|
||||
" thread_times[label].append(med)\n",
|
||||
" ttimes_stddev[label].append(std)\n",
|
||||
"\n",
|
||||
" per_px = med / (ROWS * COLS) * 1e3\n",
|
||||
" per_px_std = std / (ROWS * COLS) * 1e3\n",
|
||||
" print(f\" {label:22s} n_threads={nt:2d} \"\n",
|
||||
" f\"{med:8.2f} ± {std:6.2f} ms \"\n",
|
||||
" f\"({per_px:.4f} ± {per_px_std:.4f} μs/pixel)\")\n",
|
||||
" print(\"\\n\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "d5f3152d-be84-420e-8045-9c42ac5c24cb",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# =============================\n",
|
||||
"# FIGURE 1: Residual histograms\n",
|
||||
"# =============================\n",
|
||||
"param_names = [\"A\", \"μ\", \"σ\"]\n",
|
||||
"param_truths = [true_A, true_mu, true_sig]\n",
|
||||
"\n",
|
||||
"fig1, axes1 = plt.subplots(1, 3, figsize=(15, 5))\n",
|
||||
"fig1.suptitle(f\"Parameter Residuals — {ROWS}×{COLS} pixels, {N_SCAN} scan points\",\n",
|
||||
" fontsize=14, fontweight=\"bold\")\n",
|
||||
"\n",
|
||||
"for col, (pname, truth) in enumerate(zip(param_names, param_truths)):\n",
|
||||
" ax = axes1[col]\n",
|
||||
"\n",
|
||||
" # collect residuals across all methods for shared bin edges\n",
|
||||
" res_by_method = {}\n",
|
||||
" all_res = []\n",
|
||||
" for mname, m in methods.items():\n",
|
||||
" residual = (m[\"par\"][:, :, col] - truth).ravel()\n",
|
||||
" res_by_method[mname] = residual\n",
|
||||
" all_res.append(residual)\n",
|
||||
" all_res = np.concatenate(all_res)\n",
|
||||
"\n",
|
||||
" lo, hi = np.percentile(all_res, [0.5, 99.5])\n",
|
||||
" edges = np.linspace(lo, hi, 101)\n",
|
||||
"\n",
|
||||
" for mname, r in res_by_method.items():\n",
|
||||
" ax.hist(r, bins=edges, histtype=\"step\", label=mname,\n",
|
||||
" color=colors[mname],\n",
|
||||
" linewidth=styles[mname][\"linewidth\"],\n",
|
||||
" linestyle=styles[mname][\"linestyle\"])\n",
|
||||
"\n",
|
||||
" ax.axvline(0, color=\"k\", linestyle=\"--\", linewidth=1, alpha=0.7)\n",
|
||||
" ax.set_xlabel(f\"Fitted {pname} − True {pname}\")\n",
|
||||
" ax.set_ylabel(\"Pixel count\")\n",
|
||||
" ax.set_title(f\"Δ{pname}\")\n",
|
||||
" ax.legend(fontsize=8)\n",
|
||||
" ax.grid(alpha=0.3)\n",
|
||||
"\n",
|
||||
"fig1.tight_layout()\n",
|
||||
"# fig1.savefig(\"fig1_residual_histograms.png\", dpi=150, bbox_inches=\"tight\")\n",
|
||||
"# print(\"\\nSaved fig1_residual_histograms.png\")\n",
|
||||
"\n",
|
||||
"# ====================================================\n",
|
||||
"# FIGURE 2: Performance — bar chart + thread scaling\n",
|
||||
"# ====================================================\n",
|
||||
"fig2 = plt.figure(figsize=(14, 5))\n",
|
||||
"gs = GridSpec(1, 2, figure=fig2, width_ratios=[1, 1.3])\n",
|
||||
"\n",
|
||||
"# -- Left: bar chart at N_THREADS --\n",
|
||||
"ax2a = fig2.add_subplot(gs[0])\n",
|
||||
"names = list(methods.keys())\n",
|
||||
"medians = [np.median(methods[n][\"times\"]) * 1e3 for n in names]\n",
|
||||
"bars = ax2a.barh(names, medians,\n",
|
||||
" color=[colors[n] for n in names],\n",
|
||||
" edgecolor=\"white\", height=0.5)\n",
|
||||
"ax2a.set_xlabel(\"Median wall time (ms)\")\n",
|
||||
"ax2a.set_title(f\"Single call — {ROWS}×{COLS} px, {N_THREADS} threads\")\n",
|
||||
"for bar, val in zip(bars, medians):\n",
|
||||
" ax2a.text(bar.get_width() + max(medians) * 0.02,\n",
|
||||
" bar.get_y() + bar.get_height() / 2,\n",
|
||||
" f\"{val:.1f} ms\", va=\"center\", fontsize=10)\n",
|
||||
"ax2a.grid(axis=\"x\", alpha=0.3)\n",
|
||||
"ax2a.set_xlim(0, max(medians) * 1.25)\n",
|
||||
"\n",
|
||||
"# -- Right: thread scaling with error bars --\n",
|
||||
"ax2b = fig2.add_subplot(gs[1])\n",
|
||||
"for label, _, _, _ in METHOD_DEFS:\n",
|
||||
" tt = thread_times[label]\n",
|
||||
" sd = ttimes_stddev[label]\n",
|
||||
" speedup = [tt[0] / t for t in tt]\n",
|
||||
" # propagate uncertainty: S = t0/t → δS/S = sqrt((δt0/t0)² + (δt/t)²)\n",
|
||||
" speedup_err = [\n",
|
||||
" s * np.sqrt((sd[0] / tt[0])**2 + (sd[i] / tt[i])**2)\n",
|
||||
" for i, s in enumerate(speedup)\n",
|
||||
" ]\n",
|
||||
" ax2b.errorbar(thread_counts, speedup, yerr=speedup_err,\n",
|
||||
" fmt=\"o-\", label=label, color=colors[label],\n",
|
||||
" linewidth=2, markersize=7, capsize=4)\n",
|
||||
"\n",
|
||||
"ax2b.plot(thread_counts, thread_counts, \"k--\", alpha=0.4, label=\"Ideal linear\")\n",
|
||||
"ax2b.set_xlabel(\"Number of threads\")\n",
|
||||
"ax2b.set_ylabel(\"Speedup vs 1 thread\")\n",
|
||||
"ax2b.set_title(\"Thread scaling\")\n",
|
||||
"ax2b.set_xticks(thread_counts)\n",
|
||||
"ax2b.legend(fontsize=9)\n",
|
||||
"ax2b.grid(alpha=0.3)\n",
|
||||
"\n",
|
||||
"fig2.tight_layout()\n",
|
||||
"# fig2.savefig(\"fig2_performance.png\", dpi=150, bbox_inches=\"tight\")\n",
|
||||
"# print(\"Saved fig2_performance.png\")\n",
|
||||
"\n",
|
||||
"plt.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "92a8c724-e139-45d4-a354-1b8408637ead",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# ===============\n",
|
||||
"# THREAD SCALING\n",
|
||||
"# ===============\n",
|
||||
"thread_counts = [1, 2, 4, 8, 16]\n",
|
||||
"\n",
|
||||
"thread_times = {label: [] for label, _, _, _ in METHOD_DEFS}\n",
|
||||
"ttimes_stddev = {label: [] for label, _, _, _ in METHOD_DEFS}\n",
|
||||
"\n",
|
||||
"for nt in thread_counts:\n",
|
||||
" # shuffle method order per thread count to decorrelate thermal bias\n",
|
||||
" run_order = list(METHOD_DEFS)\n",
|
||||
" random.shuffle(run_order)\n",
|
||||
"\n",
|
||||
" for label, factory, _, _ in run_order:\n",
|
||||
" time.sleep(COOLDOWN)\n",
|
||||
" _, times = bench(factory(nt))\n",
|
||||
"\n",
|
||||
" med = np.median(times) * 1e3\n",
|
||||
" std = np.std(times) * 1e3\n",
|
||||
" thread_times[label].append(med)\n",
|
||||
" ttimes_stddev[label].append(std)\n",
|
||||
"\n",
|
||||
" per_px = med / (ROWS * COLS) * 1e3\n",
|
||||
" per_px_std = std / (ROWS * COLS) * 1e3\n",
|
||||
" print(f\" {label:22s} n_threads={nt:2d} \"\n",
|
||||
" f\"{med:8.2f} \u00b1 {std:6.2f} ms \"\n",
|
||||
" f\"({per_px:.4f} \u00b1 {per_px_std:.4f} \u03bcs/pixel)\")\n",
|
||||
" print(\"\\n\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "d5f3152d-be84-420e-8045-9c42ac5c24cb",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# =============================\n",
|
||||
"# FIGURE 1: Residual histograms\n",
|
||||
"# =============================\n",
|
||||
"param_names = [\"A\", \"\u03bc\", \"\u03c3\"]\n",
|
||||
"param_truths = [true_A, true_mu, true_sig]\n",
|
||||
"\n",
|
||||
"fig1, axes1 = plt.subplots(1, 3, figsize=(15, 5))\n",
|
||||
"fig1.suptitle(f\"Parameter Residuals \u2014 {ROWS}\u00d7{COLS} pixels, {N_SCAN} scan points\",\n",
|
||||
" fontsize=14, fontweight=\"bold\")\n",
|
||||
"\n",
|
||||
"for col, (pname, truth) in enumerate(zip(param_names, param_truths)):\n",
|
||||
" ax = axes1[col]\n",
|
||||
"\n",
|
||||
" # collect residuals across all methods for shared bin edges\n",
|
||||
" res_by_method = {}\n",
|
||||
" all_res = []\n",
|
||||
" for mname, m in methods.items():\n",
|
||||
" residual = (m[\"par\"][:, :, col] - truth).ravel()\n",
|
||||
" res_by_method[mname] = residual\n",
|
||||
" all_res.append(residual)\n",
|
||||
" all_res = np.concatenate(all_res)\n",
|
||||
"\n",
|
||||
" lo, hi = np.percentile(all_res, [0.5, 99.5])\n",
|
||||
" edges = np.linspace(lo, hi, 101)\n",
|
||||
"\n",
|
||||
" for mname, r in res_by_method.items():\n",
|
||||
" ax.hist(r, bins=edges, histtype=\"step\", label=mname,\n",
|
||||
" color=colors[mname],\n",
|
||||
" linewidth=styles[mname][\"linewidth\"],\n",
|
||||
" linestyle=styles[mname][\"linestyle\"])\n",
|
||||
"\n",
|
||||
" ax.axvline(0, color=\"k\", linestyle=\"--\", linewidth=1, alpha=0.7)\n",
|
||||
" ax.set_xlabel(f\"Fitted {pname} \u2212 True {pname}\")\n",
|
||||
" ax.set_ylabel(\"Pixel count\")\n",
|
||||
" ax.set_title(f\"\u0394{pname}\")\n",
|
||||
" ax.legend(fontsize=8)\n",
|
||||
" ax.grid(alpha=0.3)\n",
|
||||
"\n",
|
||||
"fig1.tight_layout()\n",
|
||||
"# fig1.savefig(\"fig1_residual_histograms.png\", dpi=150, bbox_inches=\"tight\")\n",
|
||||
"# print(\"\\nSaved fig1_residual_histograms.png\")\n",
|
||||
"\n",
|
||||
"# ====================================================\n",
|
||||
"# FIGURE 2: Performance \u2014 bar chart + thread scaling\n",
|
||||
"# ====================================================\n",
|
||||
"fig2 = plt.figure(figsize=(14, 5))\n",
|
||||
"gs = GridSpec(1, 2, figure=fig2, width_ratios=[1, 1.3])\n",
|
||||
"\n",
|
||||
"# -- Left: bar chart at N_THREADS --\n",
|
||||
"ax2a = fig2.add_subplot(gs[0])\n",
|
||||
"names = list(methods.keys())\n",
|
||||
"medians = [np.median(methods[n][\"times\"]) * 1e3 for n in names]\n",
|
||||
"bars = ax2a.barh(names, medians,\n",
|
||||
" color=[colors[n] for n in names],\n",
|
||||
" edgecolor=\"white\", height=0.5)\n",
|
||||
"ax2a.set_xlabel(\"Median wall time (ms)\")\n",
|
||||
"ax2a.set_title(f\"Single call \u2014 {ROWS}\u00d7{COLS} px, {N_THREADS} threads\")\n",
|
||||
"for bar, val in zip(bars, medians):\n",
|
||||
" ax2a.text(bar.get_width() + max(medians) * 0.02,\n",
|
||||
" bar.get_y() + bar.get_height() / 2,\n",
|
||||
" f\"{val:.1f} ms\", va=\"center\", fontsize=10)\n",
|
||||
"ax2a.grid(axis=\"x\", alpha=0.3)\n",
|
||||
"ax2a.set_xlim(0, max(medians) * 1.25)\n",
|
||||
"\n",
|
||||
"# -- Right: thread scaling with error bars --\n",
|
||||
"ax2b = fig2.add_subplot(gs[1])\n",
|
||||
"for label, _, _, _ in METHOD_DEFS:\n",
|
||||
" tt = thread_times[label]\n",
|
||||
" sd = ttimes_stddev[label]\n",
|
||||
" speedup = [tt[0] / t for t in tt]\n",
|
||||
" # propagate uncertainty: S = t0/t \u2192 \u03b4S/S = sqrt((\u03b4t0/t0)\u00b2 + (\u03b4t/t)\u00b2)\n",
|
||||
" speedup_err = [\n",
|
||||
" s * np.sqrt((sd[0] / tt[0])**2 + (sd[i] / tt[i])**2)\n",
|
||||
" for i, s in enumerate(speedup)\n",
|
||||
" ]\n",
|
||||
" ax2b.errorbar(thread_counts, speedup, yerr=speedup_err,\n",
|
||||
" fmt=\"o-\", label=label, color=colors[label],\n",
|
||||
" linewidth=2, markersize=7, capsize=4)\n",
|
||||
"\n",
|
||||
"ax2b.plot(thread_counts, thread_counts, \"k--\", alpha=0.4, label=\"Ideal linear\")\n",
|
||||
"ax2b.set_xlabel(\"Number of threads\")\n",
|
||||
"ax2b.set_ylabel(\"Speedup vs 1 thread\")\n",
|
||||
"ax2b.set_title(\"Thread scaling\")\n",
|
||||
"ax2b.set_xticks(thread_counts)\n",
|
||||
"ax2b.legend(fontsize=9)\n",
|
||||
"ax2b.grid(alpha=0.3)\n",
|
||||
"\n",
|
||||
"fig2.tight_layout()\n",
|
||||
"# fig2.savefig(\"fig2_performance.png\", dpi=150, bbox_inches=\"tight\")\n",
|
||||
"# print(\"Saved fig2_performance.png\")\n",
|
||||
"\n",
|
||||
"plt.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "92a8c724-e139-45d4-a354-1b8408637ead",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
|
||||
@@ -2,26 +2,19 @@
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"execution_count": null,
|
||||
"id": "efef8e20-6571-4561-8f0b-6048b57907de",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import time\n",
|
||||
"import random\n",
|
||||
"import numpy as np\n",
|
||||
"np.set_printoptions(suppress=True, precision=6)\n",
|
||||
"\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
"from scipy.special import erf\n",
|
||||
"\n",
|
||||
"import sys\n",
|
||||
"sys.path.insert(0, '/home/ferjao_k/sw/aare/build')\n",
|
||||
"\n",
|
||||
"from aare import fit_scurve, fit_scurve2 # lmfit\n",
|
||||
"from aare import RisingScurve, FallingScurve, fit # minuit2 (object based API)\n",
|
||||
"\n",
|
||||
"from pprint import pprint"
|
||||
"from aare import RisingScurve, FallingScurve\n",
|
||||
"from pprint import pprint\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -34,7 +27,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"execution_count": null,
|
||||
"id": "35772e2c-37c6-4986-a9c0-7dca4a034827",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -58,7 +51,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"execution_count": null,
|
||||
"id": "06c8fddb-56d9-4f84-8b21-4ffd8b7bd26f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -92,22 +85,10 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"execution_count": null,
|
||||
"id": "9a0b3292-7c57-4483-aecc-7ef683ff0b62",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"image/png":
|
||||
"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 truncated
|
||||
"text/plain": [
|
||||
"<Figure size 1200x400 with 2 Axes>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"fig, ax = plt.subplots(1, 2, figsize=(12,4), sharex=True)\n",
|
||||
"\n",
|
||||
@@ -134,38 +115,11 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 18,
|
||||
"execution_count": null,
|
||||
"id": "37d2fda4-40b8-4ea2-8f2c-6c3f2384d158",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Paramter list of the Scurve:\n",
|
||||
"['p0', 'p1', 'mu', 'sigma', 'A', 'C']\n",
|
||||
"6 \n",
|
||||
"\n",
|
||||
"== Tuned fit settings ==\n",
|
||||
"max_calls : 500\n",
|
||||
"tolerance : 0.01\n",
|
||||
"compute_erros: True\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"== Results ==\n",
|
||||
"True rising params: [100. 0.25 60. 6. 120. 1. ]\n",
|
||||
"lmfit rising result: [ 99.467831 0.287017 60.078405 5.715461 117.257939 0.967234]\n",
|
||||
"minuit_grad (obj api ) rising result:\n",
|
||||
"{'par': array([100. , 0.269137, 60.050193, 5.729932, 117.777635,\n",
|
||||
" 0.984073]),\n",
|
||||
" 'par_err': array([1. , 0.054406, 0.747753, 0.909864, 7.887499, 0.174058]),\n",
|
||||
" 'chi2': array([7.351743])}\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"res_r_lmfit = fit_scurve(x, y_rising)\n",
|
||||
"\n",
|
||||
"model_r = RisingScurve()\n",
|
||||
"print(\"Paramter list of the Scurve:\")\n",
|
||||
"# print(model_r.GetParNames())\n",
|
||||
@@ -187,7 +141,6 @@
|
||||
"res_r_munuit_obj = model_r.fit(x, y_rising, np.sqrt(y_rising))\n",
|
||||
"\n",
|
||||
"print(\"True rising params: \", p_true_rising)\n",
|
||||
"print(\"lmfit rising result: \", res_r_lmfit)\n",
|
||||
"print(\"minuit_grad (obj api ) rising result:\")\n",
|
||||
"pprint(res_r_munuit_obj, sort_dicts=False)"
|
||||
]
|
||||
@@ -202,26 +155,11 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 19,
|
||||
"execution_count": null,
|
||||
"id": "5ce22d2d-1adb-47d9-bf36-6cb535fa8b77",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"True falling params: [100. 0.25 60. 6. 120. -1. ]\n",
|
||||
"lmfit falling result: [101.73448 0.225786 60.251971 6.092915 118.476735 -0.998677]\n",
|
||||
"minuit_grad (obj api ) falling result\n",
|
||||
"{'par': array([101.741754, 0.225723, 60.251604, 6.092887, 118.473268,\n",
|
||||
" -0.998565]),\n",
|
||||
" 'chi2': array([1865.728797])}\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"res_f_lmfit = fit_scurve2(x, y_falling)\n",
|
||||
"\n",
|
||||
"model_f = FallingScurve()\n",
|
||||
"model_f.SetParameter(2, 20) # set start p2 = 20\n",
|
||||
"model_f.SetParameter(4, 90) # set start p4 = 90\n",
|
||||
@@ -230,36 +168,22 @@
|
||||
"res_f_munuit_obj = model_f.fit(x, y_falling)\n",
|
||||
"\n",
|
||||
"print(\"True falling params: \", p_true_falling)\n",
|
||||
"print(\"lmfit falling result: \", res_f_lmfit)\n",
|
||||
"print(\"minuit_grad (obj api ) falling result\")\n",
|
||||
"pprint(res_f_munuit_obj, sort_dicts=False)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 20,
|
||||
"execution_count": null,
|
||||
"id": "fe72503a-fc77-4b17-9b17-b98055e2fc87",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"image/png":
|
||||
"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p06duuR9appGmzZt8i2rVasWhw4d8jxft26dp015Pfnkk5e0D7PZzMKFC9m5cyeTJk3iiSee4OTJk4wdO5aqVauyZ88ewD1P2uFw5HsA7N27l3///ZdevXrh5eV1ycd2MQ888EC+JHMANWvW5K677mLmzJmeZbt27eKPP/6gZ8+enmU//vgjNWrU4M4778zX3hYtWly37O533303sbGxvP3222zcuBG73X7N9ymEEOLKzZ49m02bNuV7GAwGHA4H77zzDtWqVcNkMmEwGDCZTOzbt49du3YV2f6bNm2aL1dKREQE4eHhnnN+ZmYmf/75J+3atcNkMnnW8/PzO+9aoTD16tVj//79LF26lDfeeIMGDRqwatUqunXrxqOPPurJy5Lbu5/7yJ2//fPPPwPQr1+/IjnmXB06dDhvWc+ePcnOzmb+/PmeZTNnzsRsNtOlSxfAPax/9+7dPPXUUwD52tyqVSvi4+M91zHXyp133onJZKJPnz7MmjWLAwcOXNP9iVuPBNrippd7Al69ejXPPfccu3btuqRg9umnn2bGjBkcOnSIDh06EB4ezj333MOKFSsu+l4fH5/zglez2YzVavU8P336NBEREee9t6BlF1K1alUGDhzInDlzOHz4MBMnTuT06dO8+eabAMyaNQuj0ZjvAe65yECBw6uvRmFzyHr27Mlvv/3G7t27gbMn3bw/ixMnTrBt27bz2muxWFBKXdbNjis1f/58unfvzvTp02nQoAHBwcF069aNhISEa75vIYQQl69q1arUrVs33wNg0KBBvPnmm7Rr147Fixfz+++/s2nTJu644w6ys7OLbP8hISHnLTObzZ59JCcno5S66nO+0WikRYsWjB07lmXLlnHkyBGaNGnCjz/+6AmkH3zwwXznz9yb2SdPnkSv1xMZGXklh1iogs751atXp169ep6b606nkzlz5tC2bVuCg4OBs3lyhgwZct45v2/fvgDX/JxfsWJFVq5cSXh4OP369aNixYpUrFiRyZMnX9P9iluHzNEWN73cEzC47zo7nU6mT5/OggUL6Nix4wXf+8wzz/DMM8+QmZnJL7/8wsiRI2ndujV79+4lJibmqtoVEhLCH3/8cd7yqwnoNE3j5ZdfZsyYMZ4M3G3atGHTpk3nrZs7J/no0aMX3KaXl9d5Sdyg8BNgYXPGnnzySQYNGkRsbCxjx47liy++oF27dvl6v0NDQ/H29mbGjBkFbiM0NPSCbS0KoaGhfPDBB3zwwQccPnyYH374gddff53ExESWLl16zfcvhBCiaMyZM4du3brxzjvv5Ft+6tSp61rmKSgoCE3TCpyPfTXn/JCQEAYOHMjatWvZvn07rVq14pNPPiE9Pd2zTuLine truncated
|
||||
"text/plain": [
|
||||
"<Figure size 1200x400 with 2 Axes>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"fig, ax = plt.subplots(1, 2, figsize=(12,4))\n",
|
||||
"\n",
|
||||
"# rising\n",
|
||||
"ax[0].plot(x, y_true_rising, label=\"true\")\n",
|
||||
"ax[0].scatter(x, y_rising, s=12, label=\"data\")\n",
|
||||
"ax[0].scatter(x, scurve(x, res_r_lmfit[:6]), s=20, marker=\"x\", label=\"lmfit\")\n",
|
||||
"ax[0].plot(x, model_r(x, res_r_munuit_obj['par']), linewidth=1, color=\"green\", label=\"minuit\")\n",
|
||||
"ax[0].set_title(\"Rising S-curve fits\")\n",
|
||||
"ax[0].legend()\n",
|
||||
@@ -267,7 +191,6 @@
|
||||
"# falling\n",
|
||||
"ax[1].plot(x, y_true_falling, label=\"true\")\n",
|
||||
"ax[1].scatter(x, y_falling, s=12, label=\"data\")\n",
|
||||
"ax[1].scatter(x, scurve2(x, res_f_lmfit[:6]), s=20, marker=\"x\", label=\"lmfit\")\n",
|
||||
"ax[1].plot(x, model_f(x, res_f_munuit_obj['par']), linewidth=1, color=\"green\", label=\"minuit\")\n",
|
||||
"ax[1].set_title(\"Falling S-curve fits\")\n",
|
||||
"ax[1].legend()\n",
|
||||
@@ -285,29 +208,13 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 21,
|
||||
"execution_count": null,
|
||||
"id": "3ba40e9a-0c64-4ef6-ba90-b1116f99b862",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Rising abs error lmfit : [0.532169 0.037017 0.078405 0.284539 2.742061 0.032766]\n",
|
||||
"Rising abs error minuit_grad: [0. 0.019137 0.050193 0.270068 2.222365 0.015927]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Falling abs error lmfit : [1.73448 0.024214 0.251971 0.092915 1.523265 0.001323]\n",
|
||||
"Falling abs error minuit_grad: [1.741754 0.024277 0.251604 0.092887 1.526732 0.001435]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"print(\"Rising abs error lmfit : \", np.abs(res_r_lmfit[:6] - p_true_rising))\n",
|
||||
"print(\"Rising abs error minuit_grad: \", np.abs(res_r_munuit_obj['par'] - p_true_rising))\n",
|
||||
"print(\"\\n\")\n",
|
||||
"print(\"Falling abs error lmfit : \", np.abs(res_f_lmfit[:6] - p_true_falling))\n",
|
||||
"print(\"Falling abs error minuit_grad: \", np.abs(res_f_munuit_obj['par'] - p_true_falling))"
|
||||
"print(\"Rising absolute parameter error:\", np.abs(res_r_munuit_obj['par'] - p_true_rising))\n",
|
||||
"print(\"Falling absolute parameter error:\", np.abs(res_f_munuit_obj['par'] - p_true_falling))\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -320,25 +227,12 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 22,
|
||||
"execution_count": null,
|
||||
"id": "dc0b52b6-9fc3-453d-b6eb-e325a2b8342e",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Rising lmfit : 0.334 ms\n",
|
||||
"Rising minuit2 : 0.239 ms\n",
|
||||
"\n",
|
||||
"Falling lmfit : 0.294 ms\n",
|
||||
"Falling minuit2 : 0.214 ms\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def bench(fn, n_repeats=200):\n",
|
||||
" # warmup\n",
|
||||
" for _ in range(3):\n",
|
||||
" fn()\n",
|
||||
"\n",
|
||||
@@ -348,20 +242,14 @@
|
||||
" t1 = time.perf_counter()\n",
|
||||
" return res, (t1 - t0) / n_repeats\n",
|
||||
"\n",
|
||||
"model_rising = RisingScurve()\n",
|
||||
"model_rising = RisingScurve()\n",
|
||||
"model_falling = FallingScurve()\n",
|
||||
"\n",
|
||||
"res_r_lmfit, t_r_lmfit = bench(lambda: fit_scurve(x, y_rising))\n",
|
||||
"res_r_minuit, t_r_minuit_grad = bench(lambda: model_rising.fit(x, y_rising))\n",
|
||||
"_, t_rising = bench(lambda: model_rising.fit(x, y_rising))\n",
|
||||
"_, t_falling = bench(lambda: model_falling.fit(x, y_falling))\n",
|
||||
"\n",
|
||||
"res_f_lmfit, t_f_lmfit = bench(lambda: fit_scurve2(x, y_falling))\n",
|
||||
"res_f_minuit, t_f_minuit_grad = bench(lambda: model_falling.fit(x, y_falling))\n",
|
||||
"\n",
|
||||
"print(f\"Rising lmfit : {1e3*t_r_lmfit:.3f} ms\")\n",
|
||||
"print(f\"Rising minuit2 : {1e3*t_r_minuit_grad:.3f} ms\")\n",
|
||||
"print()\n",
|
||||
"print(f\"Falling lmfit : {1e3*t_f_lmfit:.3f} ms\")\n",
|
||||
"print(f\"Falling minuit2 : {1e3*t_f_minuit_grad:.3f} ms\")"
|
||||
"print(f\"Rising Minuit2: {1e3*t_rising:.3f} ms\")\n",
|
||||
"print(f\"Falling Minuit2: {1e3*t_falling:.3f} ms\")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"execution_count": null,
|
||||
"id": "efef8e20-6571-4561-8f0b-6048b57907de",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -13,30 +13,15 @@
|
||||
"from scipy.special import erf\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
"from matplotlib.gridspec import GridSpec\n",
|
||||
"import sys\n",
|
||||
"sys.path.insert(0, '/home/ferjao_k/aare/build')\n",
|
||||
"\n",
|
||||
"from aare import fit_scurve2\n",
|
||||
"from aare import FallingScurve, fit"
|
||||
"from aare import FallingScurve\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"execution_count": null,
|
||||
"id": "2536939d-8f34-438e-850a-6f3bdf48f658",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"'/home/ferjao_k/aare/build/aare/__init__.py'"
|
||||
]
|
||||
},
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import aare\n",
|
||||
"aare.__file__"
|
||||
@@ -44,7 +29,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"execution_count": null,
|
||||
"id": "35772e2c-37c6-4986-a9c0-7dca4a034827",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -71,7 +56,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"execution_count": null,
|
||||
"id": "06c8fddb-56d9-4f84-8b21-4ffd8b7bd26f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -127,7 +112,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"execution_count": null,
|
||||
"id": "dc0b52b6-9fc3-453d-b6eb-e325a2b8342e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -154,19 +139,10 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"execution_count": null,
|
||||
"id": "13bcd5a6-8f95-4300-a002-04023d0f9bab",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Generating synthetic data: 100x100 pixels, 100 scan points, noise_frac=0.05\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"print(f\"Generating synthetic data: {ROWS}x{COLS} pixels, \"\n",
|
||||
" f\"{N_SCAN} scan points, noise_frac={NOISE_FRAC}\\n\")\n",
|
||||
@@ -186,17 +162,13 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"execution_count": null,
|
||||
"id": "1f6bc651-80c1-41dd-8a05-7f15aba006aa",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"model = FallingScurve()\n",
|
||||
"METHOD_DEFS = [\n",
|
||||
" (\"lmfit (LM)\",\n",
|
||||
" lambda nt: lambda: fit_scurve2(x, y, n_threads=nt),\n",
|
||||
" \"#FF9800\", {\"linewidth\": 3.0, \"linestyle\": \"-\"}),\n",
|
||||
"\n",
|
||||
" (\"Minuit2 (analytic)\",\n",
|
||||
" lambda nt: lambda: model.fit(x, y, n_threads=nt),\n",
|
||||
" \"#4CAF50\", {\"linewidth\": 4.0, \"linestyle\": \":\"}),\n",
|
||||
@@ -216,27 +188,13 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"execution_count": null,
|
||||
"id": "5a417145-ce42-4c3a-a7bf-05ff97ba0450",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Method time (ms) med|dp0| med|dp1| med|dp2| med|dp3| med|dp4| med|dp5|\n",
|
||||
"--------------------------------------------------------------------------------------------------------------\n",
|
||||
"[lmfit (LM) ] 359.31 ms 18.6946 0.2249 0.2177 0.2663 11.9347 0.3635\n",
|
||||
"[Minuit2 (analytic) ] 257.18 ms 17.1035 0.2147 0.2293 0.2668 11.9449 0.3518 chi2/ndf=596.8385\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"PARAM_NAMES = [\"p0\", \"p1\", \"p2\", \"p3\", \"p4\", \"p5\"]\n",
|
||||
"ndf = N_SCAN - len(PARAM_NAMES)\n",
|
||||
"# NOTE: fit_scurve returns Ndf = n_scan - 2 in its dict, which looks wrong\n",
|
||||
"# for a 6-parameter model. We use our own ndf = n_scan - 6 everywhere.\n",
|
||||
"\n",
|
||||
"def extract_result(label, res):\n",
|
||||
" if isinstance(res, dict):\n",
|
||||
" out = {\"par\": res[\"par\"]}\n",
|
||||
@@ -245,8 +203,6 @@
|
||||
" if \"chi2\" in res:\n",
|
||||
" out[\"chi2\"] = res[\"chi2\"]\n",
|
||||
" return out\n",
|
||||
" # fallback: raw array, assume shape (rows, cols, 6)\n",
|
||||
" return {\"par\": res}\n",
|
||||
"\n",
|
||||
"methods = {}\n",
|
||||
"for label, factory, _, _ in METHOD_DEFS:\n",
|
||||
@@ -288,35 +244,12 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"execution_count": null,
|
||||
"id": "55ecc77c-0823-408d-a811-8e4f4900f332",
|
||||
"metadata": {
|
||||
"scrolled": true
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
" lmfit (LM) n_threads= 1 1404.39 ± 11.48 ms (140.4392 ± 1.1478 µs/pixel)\n",
|
||||
" Minuit2 (analytic) n_threads= 1 1022.42 ± 1.69 ms (102.2417 ± 0.1690 µs/pixel)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
" lmfit (LM) n_threads= 2 708.78 ± 6.14 ms (70.8775 ± 0.6142 µs/pixel)\n",
|
||||
" Minuit2 (analytic) n_threads= 2 521.15 ± 0.96 ms (52.1151 ± 0.0962 µs/pixel)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
" Minuit2 (analytic) n_threads= 4 256.73 ± 2.40 ms (25.6728 ± 0.2399 µs/pixel)\n",
|
||||
" lmfit (LM) n_threads= 4 352.83 ± 0.77 ms (35.2833 ± 0.0767 µs/pixel)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
" lmfit (LM) n_threads= 8 226.62 ± 1.48 ms (22.6616 ± 0.1479 µs/pixel)\n",
|
||||
" Minuit2 (analytic) n_threads= 8 164.39 ± 0.51 ms (16.4386 ± 0.0509 µs/pixel)\n",
|
||||
"\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"thread_counts = [1, 2, 4, 8]\n",
|
||||
" \n",
|
||||
@@ -339,8 +272,8 @@
|
||||
" per_px = med / (ROWS * COLS) * 1e3\n",
|
||||
" per_px_std = std / (ROWS * COLS) * 1e3\n",
|
||||
" print(f\" {label:22s} n_threads={nt:2d} \"\n",
|
||||
" f\"{med:8.2f} ± {std:6.2f} ms \"\n",
|
||||
" f\"({per_px:.4f} ± {per_px_std:.4f} µs/pixel)\")\n",
|
||||
" f\"{med:8.2f} \u00b1 {std:6.2f} ms \"\n",
|
||||
" f\"({per_px:.4f} \u00b1 {per_px_std:.4f} \u00b5s/pixel)\")\n",
|
||||
" \n",
|
||||
" print(\"\\n\")"
|
||||
]
|
||||
@@ -355,49 +288,17 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"execution_count": null,
|
||||
"id": "d5f3152d-be84-420e-8045-9c42ac5c24cb",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"image/png":
|
||||
"iVBORw0KGgoAAAANSUhEUgAABjUAAAN2CAYAAACxdwtsAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3Qd0VNXWwPGdQioBQkjo3RCQoogIogKKFBXQh4oVbE95gigKYsGCigqIgB9gQyyAgKgPlfcUxQYqVREVxYCCCNIC0tOT+dY+vDvMnUxCyiRT8v+tdTOZO3fu3DYzZ84+Z58Qh8PhEAAAAAAAAAAAAD8X6usNAAAAAAAAAAAAKA6CGgAAAAAAAAAAICAQ1AAAAAAAAAAAAAGBoAYAAAAAAAAAAAgIBDUAAAAAAAAAAEBAIKgBAAAAAAAAAAACAkENAAAAAAAAAAAQEAhqAAAAAAAAAACAgEBQAwAAAAAAAAAABASCGgAAACjUjTfeKCEhIWbq3r17sY+ULms9T9fhC19++aVzG3T6448/fLIdQGFef/112zXqC66vr9sDBNr3DQAAqHwIagAA4KfcK2Rdp6pVq8qpp54qw4cPly1btvh6UwOCv1bcFXaew8LCpEaNGnLGGWfIfffdJ7t37/b1psLHli5dKnfddZd06dJFYmJiShSwWbhwofTs2VMSEhIkMjJSmjRpIrfccots3ry50Ofs2rXLvF5ycrJER0dLfHy8nHvuufLyyy9LXl5eOeyhyMyZM+Xmm2+Wtm3bSnh4uHP/dHuLkpmZKRMnTpQOHTpItWrVJDY2Vtq0aSMPPfSQHDp0qNDnffvtt3L11VdLvXr1zHGpXbu29O/fXz799NNy2Dt4cvDgQZkwYYJceeWV5jy7XtcnC4iW9hotzfsBgcn1mho7dqyvNwcAAHhJuLdWBAAAKs6xY8dk48aNZnr11Vfl/ffflwsvvJBTEETy8/NNZez3339vptmzZ8uaNWukYcOGFbodWuGrlcOqol8bdjNmzDDv9ZJwOBxy0003yRtvvGGbv23bNvPZMW/ePHn33Xfl4osvLlDZ37t3b/n7779tgYNvvvnGTP/+97/lvffek6ioKK+epnvvvbfIIIQn+/fvNxXU+j5x9fPPP5vpzTfflM8//1yaNm1qe/yVV16RIUOGmPeaZe/evbJ48WIzPfLII/LYY49JeerYsaM888wzUplpQO7+++8v8fNKc42W9v2AisH3DQAAKC6CGgAABIirrrpKzjzzTMnOzpaVK1fKf/7zHzM/PT1dBg0aZCqGtMWpt+Xm5kpOTo5pBYuS0Qo27XFRpUqVEp/nw4cPmwq5n376yczXnhpTpkyRyZMnV+hp6NOnj5nge9rSuEGDBub60FboWvFenECIawWuVhpqL68FCxbIL7/8Yq7Ra6+91vyvvRWsoOkVV1zhrCzWYJZWBOs1OGvWLPPaH3/8sTz88MMFKuR1ff/85z9lzJgx0qpVK4/bpM9r3769DBgwoMBj+n7R5+k+6rW/fv36k+6jBiasgIZ+Tul9rcjWXh8a8NDPxmuuuUZWrFghoaHHO6rrem+//XZnQKNz587St29fUxn+0UcfmXmPP/64nHXWWXLJJZdIeWndurWZKjs9X+3atTPnff78+XLgwIEily/tNVqa9wMqDt83AACg2BwAAMAvffHFFw79qram1157zfb4ddddZ3v8s88+M/OXLl3quOmmmxynn366o3bt2o6IiAhHdHS045RTTjHzf/zxxwKvdcMNNzjX061bN8fvv//uGDhwoCMhIcEREhJitkW9+OKLjiuuuMKRkpJiHgsPD3fExcWZ17rvvvscaWlpBdbduHFj57offfRRx4cffujo3Lmz2ab69es7xowZ48jOzjbLvvDCC45WrVo5IiMjHU2bNnU8+eSTjvz8/ALrzM3Ndbz++uuOHj16OGrVqmW2IzEx0dG/f3/H559/bltW98f1OLlPun2udu7cafalbdu2jqpVq5ptSU5Odtx9992OXbt2FdgW1/XrcVy3bp3joosuctSoUcPM27p1a6nP88GDB835sx7r3bt3oeu48sorHQ0aNDDLV6tWzXHuuec6XnnlFUdeXl6B5ZcvX+647LLLHPXq1XNUqVLFERsba45Dnz59zDnS1y3s2nD373//29GxY0dHVFSUIykpyXHzzTc79uzZU+C4FLa/7sensGOh6xw1apTj/PPPdzRq1MicG912fc2ePXs65syZU+BaKeq1jh496njssccc7du3N+uyrqHTTjvN8c9//tPx0UcfOfxNenq68389NkUdR+t9oufYWubaa691PrZ//37z3rUe02ve8vzzzzvn6/v/119/dT724IMPOh/Tc/7333/bXvOqq64yj9WpU8eRmppaYJse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 truncated
|
||||
"text/plain": [
|
||||
"<Figure size 1600x900 with 6 Axes>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<Figure size 1400x500 with 0 Axes>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"image/png":
|
||||
"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 truncated
|
||||
"text/plain": [
|
||||
"<Figure size 1400x500 with 2 Axes>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# FIGURE 1: Residual histograms (6 panels)\n",
|
||||
"truth_arrays = [truths[pn] for pn in PARAM_NAMES]\n",
|
||||
" \n",
|
||||
"fig1, axes1 = plt.subplots(2, 3, figsize=(16, 9))\n",
|
||||
"fig1.suptitle(\n",
|
||||
" f\"Parameter Residuals — {ROWS}×{COLS} pixels, {N_SCAN} scan points\",\n",
|
||||
" f\"Parameter Residuals \u2014 {ROWS}\u00d7{COLS} pixels, {N_SCAN} scan points\",\n",
|
||||
" fontsize=14, fontweight=\"bold\")\n",
|
||||
" \n",
|
||||
"for idx, (pname, truth) in enumerate(zip(PARAM_NAMES, truth_arrays)):\n",
|
||||
@@ -421,9 +322,9 @@
|
||||
" linestyle=styles[mname][\"linestyle\"])\n",
|
||||
" \n",
|
||||
" ax.axvline(0, color=\"k\", linestyle=\"--\", linewidth=1, alpha=0.7)\n",
|
||||
" ax.set_xlabel(f\"Fitted {pname} − True {pname}\")\n",
|
||||
" ax.set_xlabel(f\"Fitted {pname} \u2212 True {pname}\")\n",
|
||||
" ax.set_ylabel(\"Pixel count\")\n",
|
||||
" ax.set_title(f\"Δ{pname}\")\n",
|
||||
" ax.set_title(f\"\u0394{pname}\")\n",
|
||||
" ax.legend(fontsize=8)\n",
|
||||
" ax.grid(alpha=0.3)\n",
|
||||
" \n",
|
||||
@@ -444,7 +345,7 @@
|
||||
" color=[colors[n] for n in names],\n",
|
||||
" edgecolor=\"white\", height=0.5)\n",
|
||||
"ax2a.set_xlabel(\"Median wall time (ms)\")\n",
|
||||
"ax2a.set_title(f\"Single call — {ROWS}×{COLS} px, {N_THREADS} threads\")\n",
|
||||
"ax2a.set_title(f\"Single call \u2014 {ROWS}\u00d7{COLS} px, {N_THREADS} threads\")\n",
|
||||
"for bar, val in zip(bars, medians):\n",
|
||||
" ax2a.text(bar.get_width() + max(medians) * 0.02,\n",
|
||||
" bar.get_y() + bar.get_height() / 2,\n",
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
import aare
|
||||
from aare.experimental import MultiThreadedFileReader
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def frame_file(tmp_path):
|
||||
data = np.arange(10 * 2 * 3, dtype=np.uint16).reshape(10, 2, 3)
|
||||
path = tmp_path / "frames.npy"
|
||||
np.save(path, data)
|
||||
return path, data
|
||||
|
||||
|
||||
def test_experimental_import_path():
|
||||
assert aare.experimental.MultiThreadedFileReader is MultiThreadedFileReader
|
||||
assert MultiThreadedFileReader.__module__ == "aare.experimental"
|
||||
assert not hasattr(aare, "MultiThreadedFileReader")
|
||||
|
||||
|
||||
def test_reads_all_frames_in_order(frame_file):
|
||||
path, expected = frame_file
|
||||
reader = MultiThreadedFileReader(path, n_threads=2, chunk_size=3)
|
||||
|
||||
first = reader.read()
|
||||
second = reader.read()
|
||||
exhausted = reader.read()
|
||||
|
||||
assert np.array_equal(first, expected[:6])
|
||||
assert np.array_equal(second, expected[6:])
|
||||
assert exhausted.shape == (0, 2, 3)
|
||||
assert first.dtype == np.uint16
|
||||
assert reader.n_threads == 2
|
||||
assert reader.chunk_size == 3
|
||||
assert reader.total_frames == 10
|
||||
assert reader.source_total_frames == 10
|
||||
assert reader.rows == 2
|
||||
assert reader.cols == 3
|
||||
assert reader.bitdepth == 16
|
||||
assert reader.dtype == np.dtype(np.uint16)
|
||||
assert reader.bytes_per_frame == 12
|
||||
assert reader.total_bytes == expected.nbytes
|
||||
assert len(reader) == 10
|
||||
assert reader.tell() == 10
|
||||
assert reader.remaining_frames == 0
|
||||
assert reader.next_read_frames == 0
|
||||
assert reader.next_read_bytes == 0
|
||||
|
||||
|
||||
def test_total_frame_limit(frame_file):
|
||||
path, expected = frame_file
|
||||
reader = MultiThreadedFileReader(
|
||||
path, n_threads=2, chunk_size=2, total_frames=7
|
||||
)
|
||||
|
||||
assert np.array_equal(reader.read(), expected[:4])
|
||||
assert np.array_equal(reader.read_all(), expected[4:7])
|
||||
|
||||
|
||||
def test_iteration_yields_one_chunk_per_thread(frame_file):
|
||||
path, expected = frame_file
|
||||
reader = MultiThreadedFileReader(path, n_threads=2, chunk_size=2)
|
||||
|
||||
batches = list(reader)
|
||||
|
||||
assert [len(batch) for batch in batches] == [4, 4, 2]
|
||||
assert np.array_equal(np.concatenate(batches), expected)
|
||||
|
||||
|
||||
def test_seek_resets_iteration_position(frame_file):
|
||||
path, expected = frame_file
|
||||
reader = MultiThreadedFileReader(path, n_threads=2, chunk_size=2)
|
||||
|
||||
reader.read()
|
||||
assert reader.tell() == 4
|
||||
|
||||
reader.seek(1)
|
||||
assert reader.tell() == 1
|
||||
assert np.array_equal(reader.read(), expected[1:5])
|
||||
|
||||
with pytest.raises(IndexError):
|
||||
reader.seek(11)
|
||||
|
||||
|
||||
def test_context_manager_closes_worker_files(frame_file):
|
||||
path, expected = frame_file
|
||||
|
||||
with MultiThreadedFileReader(path, n_threads=2, chunk_size=2) as reader:
|
||||
assert not reader.closed
|
||||
assert np.array_equal(reader.read(), expected[:4])
|
||||
|
||||
assert reader.closed
|
||||
reader.close()
|
||||
with pytest.raises(RuntimeError):
|
||||
reader.read()
|
||||
with pytest.raises(RuntimeError):
|
||||
reader.seek(0)
|
||||
with pytest.raises(RuntimeError):
|
||||
with reader:
|
||||
pass
|
||||
|
||||
|
||||
def test_explicit_zero_frame_limit(frame_file):
|
||||
path, expected = frame_file
|
||||
reader = MultiThreadedFileReader(
|
||||
path, n_threads=8, chunk_size=3, total_frames=0
|
||||
)
|
||||
|
||||
actual = reader.read()
|
||||
assert actual.shape == (0, *expected.shape[1:])
|
||||
assert actual.dtype == expected.dtype
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"dtype", [np.int8, np.int32, np.uint64, np.float32, np.float64]
|
||||
)
|
||||
def test_preserves_numpy_dtype(tmp_path, dtype):
|
||||
expected = np.arange(4 * 2 * 3, dtype=dtype).reshape(4, 2, 3)
|
||||
path = tmp_path / "typed-frames.npy"
|
||||
np.save(path, expected)
|
||||
|
||||
reader = MultiThreadedFileReader(path, n_threads=2, chunk_size=3)
|
||||
actual = reader.read()
|
||||
|
||||
assert actual.dtype == expected.dtype
|
||||
assert reader.dtype == expected.dtype
|
||||
assert np.array_equal(actual, expected)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("n_threads", "chunk_size", "total_frames"),
|
||||
[(0, 1, None), (1, 0, None), (1, 1, 11)],
|
||||
)
|
||||
def test_invalid_configuration(
|
||||
frame_file, n_threads, chunk_size, total_frames
|
||||
):
|
||||
path, _ = frame_file
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
MultiThreadedFileReader(
|
||||
path,
|
||||
n_threads=n_threads,
|
||||
chunk_size=chunk_size,
|
||||
total_frames=total_frames,
|
||||
)
|
||||
@@ -0,0 +1,101 @@
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from aare import PixelHistogram
|
||||
|
||||
|
||||
def _random_frames(rows, cols, n, xmin, xmax, seed=0):
|
||||
rng = np.random.default_rng(seed)
|
||||
return [rng.uniform(xmin - 0.25, xmax + 0.25, size=(rows, cols)).astype(np.float64)
|
||||
for _ in range(n)]
|
||||
|
||||
|
||||
def _reference_hdata(frames, rows, cols, n_bins, xmin, xmax):
|
||||
expected = np.zeros((rows, cols, n_bins), dtype=np.uint16)
|
||||
inv_range = n_bins / (xmax - xmin)
|
||||
for img in frames:
|
||||
for r in range(rows):
|
||||
for c in range(cols):
|
||||
v = float(img[r, c])
|
||||
if not (xmin <= v < xmax):
|
||||
continue
|
||||
b = int((v - xmin) * inv_range)
|
||||
if b >= n_bins:
|
||||
b = n_bins - 1
|
||||
expected[r, c, b] += 1
|
||||
return expected
|
||||
|
||||
|
||||
def test_async_fill_matches_reference():
|
||||
rows, cols, n_bins = 5, 7, 8
|
||||
xmin, xmax = 0.0, 2.0
|
||||
frames = _random_frames(rows, cols, n=3, xmin=xmin, xmax=xmax, seed=1)
|
||||
|
||||
hist = PixelHistogram(rows=rows, cols=cols, n_bins=n_bins, xmin=xmin, xmax=xmax)
|
||||
for img in frames:
|
||||
hist.fill_async(img)
|
||||
|
||||
np.testing.assert_array_equal(
|
||||
hist.values(),
|
||||
_reference_hdata(frames, rows, cols, n_bins, xmin, xmax),
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
def test_fill_async_copies_buffer():
|
||||
# After fill_async returns, the caller should be free to mutate the
|
||||
# numpy array without affecting the pending fill.
|
||||
rows, cols, n_bins = 4, 4, 4
|
||||
xmin, xmax = 0.0, 1.0
|
||||
hist = PixelHistogram(rows=rows, cols=cols, n_bins=n_bins, xmin=xmin, xmax=xmax, n_threads=1, max_pending=8)
|
||||
|
||||
img = np.full((rows, cols), 0.1, dtype=np.float64) # falls in bin 0
|
||||
hist.fill_async(img)
|
||||
# Mutate the original array immediately; this must not affect the
|
||||
# value that was already enqueued.
|
||||
img[:] = 0.9 # would be bin 3
|
||||
hist.flush()
|
||||
|
||||
h = hist.values()
|
||||
assert h.shape == (rows, cols, n_bins)
|
||||
# Every pixel saw one value in bin 0, none elsewhere.
|
||||
assert (h[:, :, 0] == 1).all()
|
||||
assert (h[:, :, 1:] == 0).all()
|
||||
|
||||
|
||||
def test_fill_async_rejects_wrong_shape():
|
||||
hist = PixelHistogram(8, 8, 4, 0.0, 1.0)
|
||||
bad = np.zeros((4, 4), dtype=np.float32)
|
||||
with pytest.raises(ValueError):
|
||||
hist.fill_async(bad)
|
||||
|
||||
|
||||
def test_hdata_flushes_pending():
|
||||
# Submit several frames with a tiny queue and read hdata() without an
|
||||
# explicit flush(); hdata() must drain everything first.
|
||||
rows, cols, n_bins = 3, 3, 4
|
||||
xmin, xmax = 0.0, 1.0
|
||||
hist = PixelHistogram(rows=rows, cols=cols, n_bins=n_bins, xmin=xmin, xmax=xmax,
|
||||
n_threads=1, max_pending=1)
|
||||
frames = _random_frames(rows, cols, n=8, xmin=xmin, xmax=xmax, seed=3)
|
||||
for img in frames:
|
||||
hist.fill_async(img)
|
||||
|
||||
h = hist.values() # no explicit flush()
|
||||
np.testing.assert_array_equal(
|
||||
h, _reference_hdata(frames, rows, cols, n_bins, xmin, xmax)
|
||||
)
|
||||
|
||||
|
||||
def test_bin_centers_and_edges():
|
||||
n_bins = 5
|
||||
xmin, xmax = 0.0, 1.0
|
||||
hist = PixelHistogram(rows=2, cols=2, n_bins=n_bins, xmin=xmin, xmax=xmax)
|
||||
edges = hist.bin_edges()
|
||||
centers = hist.bin_centers()
|
||||
assert edges.shape == (n_bins + 1,)
|
||||
assert centers.shape == (n_bins,)
|
||||
np.testing.assert_allclose(edges, np.linspace(xmin, xmax, n_bins + 1), atol=1e-6)
|
||||
np.testing.assert_allclose(centers, 0.5 * (edges[:-1] + edges[1:]), atol=1e-6)
|
||||
Reference in new issue
Block a user