API Reference
Complete API reference for NeuNorm 2.0, generated from the source docstrings.
Data models
Core data models for NeuNorm 2.0.
Pydantic model for event-mode neutron data.
- class neunorm.data_models.core.EventData(*, tof, x, y, chip_id=None, pulse_id=None, file_path, total_events, tof_clock=25.0)[source]
Bases:
BaseModelEvent-mode neutron data container.
Represents raw list-mode events from TPX3/TPX4 detectors with pixel coordinates and time-of-flight values.
- Parameters:
tof (np.ndarray) – Time-of-flight values in nanoseconds (1D array)
x (np.ndarray) – X pixel coordinates (1D array, same length as tof)
y (np.ndarray) – Y pixel coordinates (1D array, same length as tof)
chip_id (np.ndarray or None, optional) – Chip ID for each event (0-3 for quad detector); 1D integer array, same length as tof if present (default: None)
pulse_id (np.ndarray or None, optional) – Reconstructed pulse ID for each event; 1D integer array, same length as tof if present (default: None)
file_path (Path) – Source file path
total_events (int) – Total number of events
tof_clock (float) – TOF clock period in nanoseconds (default: 25.0 for TPX3)
Examples
>>> import numpy as np >>> from pathlib import Path >>> events = EventData( ... tof=np.array([1000, 2000, 3000], dtype=np.int64), ... x=np.array([100, 200, 150], dtype=np.int32), ... y=np.array([250, 300, 275], dtype=np.int32), ... file_path=Path('data.h5'), ... total_events=3, ... tof_clock=25.0 ... )
- model_config: ClassVar[ConfigDict] = {'arbitrary_types_allowed': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
Region-of-interest data model for NeuNorm 2.0.
- class neunorm.data_models.roi.ROI(*, x0, y0, x1=None, y1=None, width=None, height=None, inclusive=False)[source]
Bases:
BaseModelRectangular region of interest with named, self-documenting bounds.
Define it either by explicit stop indices or by size — the two forms are equivalent:
ROI(x0=10, y0=20, x1=30, y1=40) # exclusive stops ROI(x0=10, y0=20, width=20, height=20) # the same 20x20 region
Stop indices are exclusive (Python slice semantics), matching
apply_roi,apply_air_region_correctionand thebackground_roiflux proxy. AnROImay be passed anywhere those APIs accept an(x0, y0, x1, y1)tuple; the bare-tuple form keeps working unchanged for backward compatibility.- Parameters:
x0 (int) – Lower (inclusive) pixel bounds in x and y.
y0 (int) – Lower (inclusive) pixel bounds in x and y.
x1 (int, optional) – Upper (exclusive) pixel bounds. Provide these or
width/height.y1 (int, optional) – Upper (exclusive) pixel bounds. Provide these or
width/height.width (int, optional) – Extent in x and y;
x1 = x0 + widthandy1 = y0 + height. Provide these orx1/y1.height (int, optional) – Extent in x and y;
x1 = x0 + widthandy1 = y0 + height. Provide these orx1/y1.inclusive (bool, optional) – Interpret the upper bounds as inclusive (default
False= exclusive Python-slice semantics). WhenTrue, the resolvedx1/y1are bumped by one so the region spans(width + 1) x (height + 1)pixels (and an explicitx1/y1is included). This is the legacy NeuNorm 1.x / iBeatles convention;as_bounds()always returns exclusive stops, so the rest of the library stays exclusive. Bare tuples are always exclusive — use theROItype to opt into inclusive extents.
Examples
>>> ROI(x0=10, y0=20, x1=30, y1=40).as_bounds() (10, 20, 30, 40) >>> ROI(x0=10, y0=20, width=20, height=20).as_bounds() (10, 20, 30, 40) >>> ROI(x0=10, y0=20, width=20, height=20, inclusive=True).as_bounds() (10, 20, 31, 41)
- model_config: ClassVar[ConfigDict] = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- neunorm.data_models.roi.as_roi_bounds(roi)[source]
Coerce an
ROI(or a bare 4-element(x0, y0, x1, y1)sequence) to a bounds tuple.A bare sequence is returned as a plain tuple so downstream code sees a consistent
(x0, y0, x1, y1)form regardless of how the ROI was specified. Element-type/bounds validation is left to the consumer (and toROIfor the named form); only the 4-element length is enforced here so a malformed bare sequence fails fast at one place.
TOF-specific data models for NeuNorm 2.0.
Includes binning configurations for TOF/Energy/Wavelength spaces.
- class neunorm.data_models.tof.BinningConfig(*, bins=5000, bin_space='energy', use_log_bin=True, energy_range=None, wavelength_range=None, tof_range=None)[source]
Bases:
BaseModelConfiguration for TOF/Energy/Wavelength binning in neutron imaging.
Supports three binning domains: 1. Energy (eV) - for resonance imaging (nuclear cross-sections) 2. Wavelength (Å) - for Bragg edge imaging (crystallography) 3. TOF (ns) - for raw time-of-flight binning
Each domain can use linear or logarithmic spacing.
- Parameters:
bins (int) – Number of bins (default: 5000)
bin_space (Literal['tof', 'energy', 'wavelength']) – Binning domain (default: ‘energy’)
use_log_bin (bool) – Use logarithmic spacing (default: True)
energy_range (tuple[float, float] or None) – Energy range (E_min, E_max) in eV. Required for bin_space=’energy’
wavelength_range (tuple[float, float] or None) – Wavelength range (λ_min, λ_max) in Angstrom. Required for bin_space=’wavelength’
tof_range (tuple[float, float] or None) – TOF range (t_min, t_max) in nanoseconds. Optional for bin_space=’tof’
Examples
>>> # Resonance imaging (energy space, logarithmic) >>> config = BinningConfig(bins=5000, bin_space='energy', energy_range=(1.0, 100.0))
>>> # Bragg edge imaging (wavelength space, linear) >>> config = BinningConfig( ... bins=3000, ... bin_space='wavelength', ... wavelength_range=(0.5, 3.0), ... use_log_bin=False ... )
>>> # Raw TOF binning (full range) >>> config = BinningConfig(bins=10000, bin_space='tof')
- classmethod validate_energy_range_values(v)[source]
Validate energy range has correct format and values
- classmethod validate_wavelength_range_values(v)[source]
Validate wavelength range has correct format and values
- model_config: ClassVar[ConfigDict] = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
Loaders
Utility module for loading stacks of images in various formats.
- neunorm.loaders.stack_loader.load_stack(paths)[source]
Load a stack of images from the given file paths, supporting both TIFF and FITS formats.
Check the extension of the first file in the list and call the appropriate loader function load_tiff_stack or load_fits_stack.
Verify all files have the same extension and raise an error if not.
TIFF loader for NeuNorm.
Loads TIFF stacks as scipp DataArrays.
- neunorm.loaders.tiff_loader.load_tiff_stack(paths, tof_edges=None)[source]
Load TIFF stack as scipp DataArray with variance tracking.
Uses Pillow (PIL) to read TIFF images and constructs a scipp DataArray.
- Parameters:
paths (Sequence[str | Path]) – List of paths to TIFF files
tof_edges (Optional[np.ndarray]) – Time-of-flight values for the first dimension. Accepts either bin edges (N+1) or bin centers (N), where N is the number of images in the loaded stack.
- Returns:
DataArray with dimensions (TOF/image, y, x)
dims: [‘TOF’, ‘y’, ‘x’] if tof_edges provided, else [‘N_image’, ‘y’, ‘x’]
coords: y, x pixel indices, and optionally TOF. Additionally, TIFF metadata is added as coordinates. Each metadata coordinate may be scalar (when its value is constant across the stack and not float-convertible) or stack-dimensioned (when values are float-convertible or differ across files).
- Return type:
sc.DataArray
FITS loader for NeuNorm based on astropy.
Loads FITS files into scipp DataArrays.
- neunorm.loaders.fits_loader.load_fits_stack(paths, tof_edges=None)[source]
Load FITS stack as scipp DataArray with metadata and optional TOF coordinates.
Handles:
List of FITS files (stacked along the first dimension)
Metadata extraction from FITS headers
- Parameters:
paths (Sequence[str | Path]) – List of paths to FITS files
tof_edges (Optional[np.ndarray]) – Time-of-flight values for the first dimension. Accepts either bin edges (N+1) or bin centers (N), where N is the number of images in the loaded stack.
- Returns:
DataArray with dimensions (TOF/image, y, x)
dims: [‘TOF’, ‘y’, ‘x’] if tof_edges provided, else [‘N_image’, ‘y’, ‘x’]
coords: y, x pixel indices, and optionally TOF. Additionally, FITS header keys are added as (unaligned) coordinates. The
COMMENTandHISTORYkeys are skipped. A key whose value is constant across the stack is stored as a scalar coordinate; a key whose value differs across files is stored as an array coordinate along the stack dimension.
- Return type:
sc.DataArray
Event data loader for TPX3/TPX4 HDF5 files.
Loads raw event-mode data from HDF5 files containing tof, x, y arrays.
- neunorm.loaders.event_loader.load_event_data(file_path, tof_clock=25.0, max_events=None)[source]
Load event-mode data from HDF5 file.
Expected HDF5 structure: - ‘tof’: Time-of-flight values (int32/int64 array) - ‘x’: X pixel coordinates (int16/int32 array) - ‘y’: Y pixel coordinates (int16/int32 array)
- Parameters:
file_path (str or Path) – Path to HDF5 file containing event data
tof_clock (float, optional) – TOF clock period in nanoseconds (default: 25.0 for TPX3) Raw TOF ticks are multiplied by this value
max_events (int, optional) – Maximum number of events to load (for testing/memory limits) If None, loads all events
- Returns:
Pydantic model containing event arrays and metadata
- Return type:
- Raises:
FileNotFoundError – If file doesn’t exist
KeyError – If HDF5 file missing required fields (tof, x, y)
Examples
>>> events = load_event_data('run_12557.h5', tof_clock=25.0) >>> print(f"Loaded {events.total_events:,} events") >>> print(f"TOF range: {events.tof.min()} - {events.tof.max()} ns")
- neunorm.loaders.event_loader.load_event_nexus(file_path, detector_bank='bank1', detector_shape=(512, 512), event_id_offset=0, max_events=None)[source]
Load event-mode data from SNS NeXus HDF5 file.
Reads from the SNS NeXus event bank structure: /entry/<bank>_events/ with datasets: - event_id: Linearized pixel detector IDs - event_time_offset: Time-of-flight values (in microseconds)
- Parameters:
file_path (str or Path) – Path to NeXus HDF5 file containing event data
detector_bank (str) – Specific detector bank to load (e.g., ‘bank100’).
detector_shape (tuple[int, int], optional) – Detector dimensions (x_bins, y_bins) for unrolling event_id to x, y. Default: (512, 512) for SNS VENUS detectors
event_id_offset (int, optional) – Base offset subtracted from each event_id before unrolling to x, y pixel coordinates (default: 0)
max_events (int, optional) – Maximum number of events to load (for testing/memory limits) If None, loads all events
- Returns:
Pydantic model containing event arrays (tof in ns, x, y) and metadata
- Return type:
- Raises:
FileNotFoundError – If file doesn’t exist
KeyError – If the NeXus structure, required fields, or the requested detector_bank are not found
Examples
>>> # Use the default detector bank ('bank1') >>> events = load_event_nexus('VENUS_15159.nxs.h5')
>>> # Specify detector bank >>> events = load_event_nexus('VENUS_15159.nxs.h5', detector_bank='bank100')
>>> # Custom detector shape >>> events = load_event_nexus('file.nxs.h5', detector_shape=(256, 256))
Metadata loaders for NeuNorm.
Reads acquisition metadata (proton charge, duration, detector, TOF binning) from VENUS NeXus files, plus optional shutter-count and spectra-TOF sidecar text files.
- neunorm.loaders.metadata_loader.load_metadata(file_path, read_shutter_counts=False, read_spectra_tof=False)[source]
Load metadata from NeXus file.
- Parameters:
file_path (str or Path) – Path to NeXus HDF5 file containing metadata
read_shutter_counts (bool) – Whether to read shutter counts from the image directory specified in the metadata (default: False)
read_spectra_tof (bool) – Whether to read spectra TOF from the image directory specified in the metadata (default: False)
- Returns:
Metadata values for proton charge, duration, image file path, and optionally shutter counts. All values are returned as scipp Variables.
- Return type:
- neunorm.loaders.metadata_loader.load_shutter_counts(image_path)[source]
Load shutter counts from a text file.
- Parameters:
image_path (str or Path) – Path to the directory containing the image files, where we expect to find a shutter count file named
*_ShutterCount.txt- Returns:
Variable containing shutter counts loaded from the file, up until the first count of 0 is encountered.
- Return type:
sc.Variable
- neunorm.loaders.metadata_loader.load_spectra_tof(image_path)[source]
Load TOF values from spectra text file.
- Parameters:
image_path (str or Path) – Path to the directory containing the image files, where we expect to find a spectra file named
*_Spectra.txt- Returns:
Variable containing TOF values loaded from the file, same number as images in the stack.
- Return type:
sc.Variable
Processing
Dark current correction.
- neunorm.processing.dark_corrector.subtract_dark(data, dark, clip_negative=True)[source]
Subtract dark current with variance propagation.
data_corr = data - dark
Requirements: - Subtract dark current from sample and OB images - Propagate variance correctly through subtraction using scipp - Handle negative values (clip to zero or flag as invalid) - Support both 2D dark (averaged) and 3D dark (per-frame) inputs
- Parameters:
data (sc.DataArray) – Sample or OB histogram with variance
dark (sc.DataArray) – Dark current histogram with variance
clip_negative (bool) – If True, clip negative values to zero after subtraction (default: True) If False, value will be masked
- Returns:
Dark-corrected data with propagated variance
- Return type:
sc.DataArray
Air region correction.
- neunorm.processing.air_region_corrector.apply_air_region_correction(transmission, air_roi)[source]
Scale transmission so air region has mean = 1.0.
Requirements
Calculate mean transmission in user-specified air region
Scale entire image so air region = 1.0
Support per-image correction (radiography) and per-TOF-bin correction (hyperspectral)
Propagate uncertainty from air region mean
Formula
T_final = T / mean(T[air_ROI])
Uncertainty: σ_T_final = T_final × √[(σ_T/T)² + (σ_air/<T_air>)²]
- param transmission:
Normalized transmission (after OB correction)
- type transmission:
sc.DataArray
- param air_roi:
Air region as an
ROIor a bare(x0, y0, x1, y1)tuple, where x1 and y1 are exclusive upper bounds.- type air_roi:
ROI or tuple[int, int, int, int]
Transmission normalization for neutron imaging.
Implements the core neutron imaging equation: T = Sample / OpenBeam with proper uncertainty propagation and beam corrections.
- neunorm.processing.normalizer.as_roi_bounds_list(background_roi)[source]
Normalize a
background_roiargument to a list of exclusive(x0, y0, x1, y1)bounds.Accepts a single ROI (an
ROIor a bare 4-int(x0,y0,x1,y1)sequence) — backward compatible — or a sequence of those (pooled). A bare 4-int sequence is treated as ONE ROI; a sequence whose elements are ROIs or sequences is a list of ROIs. NumPy integer bounds are coerced to built-inintso provenance JSON-encodes losslessly.
- neunorm.processing.normalizer.normalize_transmission(sample, ob, proton_charge_sample=None, proton_charge_ob=None, pc_uncertainty=0.005, background_roi=None, background_roi_strict=True)[source]
Normalize sample by open beam to compute transmission.
Formula: T = (Sample / pc_sample) / (OB / pc_ob)
Handles: - Variance propagation (automatic via scipp) - Proton charge corrections (for SNS pulsed beam) - Systematic uncertainties - Mask preservation
- Parameters:
sample (sc.DataArray) – Sample histogram with variance
ob (sc.DataArray) – Open beam histogram with variance
proton_charge_sample (float or sc.Variable, optional) – Integrated proton charge during sample acquisition (Coulombs) If provided, normalizes by beam intensity
proton_charge_ob (float or sc.Variable, optional) – Integrated proton charge during OB acquisition (Coulombs)
pc_uncertainty (float, optional) – Relative proton charge uncertainty (default: 0.005 = 0.5%) From PLEIADES measurements
background_roi (ROI/tuple or a sequence of them, optional) – Sample-free background region(s) — an
ROI(or a bare(x0, y0, x1, y1)tuple, exclusive stops), or a sequence of those which are pooled (sum(counts over all ROIs) / sum(pixels)). When given, each image is normalized by its pooled background mean — a proton-charge proxy for when proton charge is unavailable (e.g. MARS):T = (S/mean(S[B])) / (O/mean(O[B])). For legacy inclusive extents (a width-wROI spanningw+1pixels), useROI(..., inclusive=True); seeapply_background_roifor the open-beam-less form. Mutually exclusive withproton_charge_sample/proton_charge_ob. Uncertainty is propagated first-order (the in-ROI sample/ROI-mean correlation is not corrected). Unlessbackground_roi_strict=False, raisesValueErrorif the pooled mean is not strictly positive and finite in every image. Indices are resolved against the passed arrays; if a pipeline crops withroifirst, givebackground_roiin the post-crop frame.background_roi_strict (bool, optional) – With the default
True, a non-positive/non-finite pooled background mean raisesValueError.Falseskips only that guard and lets zeros propagate through the division (inf/nan output) — the legacy 1.x semantics, for downstreams reproducing 1.x outputs bit for bit. Structural errors (bad ROI bounds, missing dims) always raise.
- Returns:
Transmission with dimensions matching input Unit: dimensionless Includes propagated variance and systematic uncertainties
- Return type:
sc.DataArray
Examples
>>> # Basic normalization >>> transmission = normalize_transmission(hist_sample, hist_ob)
>>> # With proton charge correction (SNS) >>> transmission = normalize_transmission( ... hist_sample, hist_ob, ... proton_charge_sample=500.0, ... proton_charge_ob=505.0 ... )
Notes
Scipp automatically propagates variance through division
Masks are preserved from both sample and OB (OR combination)
Zero OB counts produce inf/nan (handle with masks before calling)
- neunorm.processing.normalizer.normalize_with_dark(sample, ob, dark, proton_charge_sample=None, proton_charge_ob=None, pc_uncertainty=0.005, background_roi=None, background_roi_strict=True)[source]
Dark-correct and normalize in one step, treating the shared dark frame correctly.
Computes
T = (sample - dark) / (ob - dark)(with the optional proton-charge correction) where the same averageddarkis subtracted from both sample and open beam.subtract_dark+normalize_transmissionwould treat the numerator and denominator as statistically independent and propagateVar(dark)twice; this function removes that spurious shared-dark covariance term.The transmission values are identical to
normalize_transmission(subtract_dark(sample, dark), subtract_dark(ob, dark), ...)— only the propagated variance is corrected (reduced) by2 * k**2 * (sample-dark) * Var(dark) / (ob-dark)**3, withk = pc_ob / pc_sample(1 when no proton charge). The proton-charge systematic and the sample/open-beam Poisson terms are unchanged.- Parameters:
sample (sc.DataArray) – Sample, open-beam and (averaged) dark-current frames, each carrying Poisson variance. The same
darkis subtracted from bothsampleandob.ob (sc.DataArray) – Sample, open-beam and (averaged) dark-current frames, each carrying Poisson variance. The same
darkis subtracted from bothsampleandob.dark (sc.DataArray) – Sample, open-beam and (averaged) dark-current frames, each carrying Poisson variance. The same
darkis subtracted from bothsampleandob.proton_charge_sample (float or sc.Variable, optional) – Integrated proton charge for the SNS beam correction (see
normalize_transmission).proton_charge_ob (float or sc.Variable, optional) – Integrated proton charge for the SNS beam correction (see
normalize_transmission).pc_uncertainty (float, optional) – Relative proton-charge uncertainty (default 0.005).
background_roi (tuple[int, int, int, int], optional) – Background-ROI flux normalization (see
normalize_transmission), used instead of proton charge. The shared-dark correction then usesk = co/cs(ratio of dark-corrected ROI means) in place of the proton-charge ratio, and additionally removes the ROI-mean shared-dark covariance term2*T^2*Cov(cs,co)/(cs*co)(Cov(cs,co) = Var(mean(D_roi))) — the ROI-mean analog of the pixel-level correction. (The in-ROI pixel/ROI-mean correlation remains uncorrected, as documented onnormalize_transmission.)background_roi_strict (bool, optional) – See
normalize_transmission:Falseskips the strictly-positive/finite pooled-mean guard and lets zeros propagate (legacy 1.x semantics).
- Returns:
Transmission with correctly-propagated variance (no shared-dark double-counting).
- Return type:
sc.DataArray
- neunorm.processing.normalizer.apply_background_roi(data, background_roi, strict=True)[source]
Flux-flatten a stack by its pooled background-ROI mean (no open beam).
Returns
data / pooled_mean(data over background_roi)— the sample-only form of the background-ROI flux proxy, for when there is no open beam to normalize against.background_roiis a single ROI or a sequence of ROIs (pooled assum(counts) / sum(pixels)); seenormalize_transmission(..., background_roi=)for the with-open-beam transmission form.First-order uncertainty from the pooled ROI mean is propagated (
Var += corrected**2 * Var(coeff) / coeff**2); the in-ROI pixel/ROI-mean correlation is not corrected. Reductions are mask-aware. RaisesValueErrorif the pooled mean is not strictly positive and finite in every image (unlessstrict=False).- Parameters:
data (sc.DataArray) – Image stack with
x/ydims (e.g.(spectral, x, y)), optionally carrying variance.background_roi (ROI/tuple or a sequence of them) – Sample-free background region(s), pooled.
strict (bool, optional) – With the default
True, a non-positive/non-finite pooled mean raisesValueError.Falseskips only that guard and lets zeros propagate through the division (inf/nan output) — the legacy 1.x semantics, for downstreams reproducing 1.x outputs bit for bit. Structural errors (bad ROI bounds, missing dims) always raise.
- Returns:
datascaled so its pooled background-ROI mean is 1 per image.- Return type:
sc.DataArray
Reference image preparation.
- neunorm.processing.reference_preparer.median_with_variance(data, dim)[source]
Compute the median and an approximation of the propagation of variance.
Use the approximation of
Var(median) ≈ (π / (2n)) * mean_variance
- Parameters:
data (sc.DataArray) – Input data with associated variances.
dim (str) – Dimension along which to compute the median.
- Returns:
DataArray containing the median values and their estimated variances.
- Return type:
sc.DataArray
- neunorm.processing.reference_preparer.prepare_reference(stack, method='mean', dim='frame')[source]
Reduce a 3D frame stack to a 2D reference image.
Function for cropping spatial dimensions to a region of interest (ROI).
- neunorm.processing.roi_clipper.apply_roi(data, roi)[source]
Crop spatial dimensions to ROI.
Crop to specified ROI: (x0, y0, x1, y1) Work with 2D, 3D, and 4D arrays (preserve other dimensions) Update coordinate arrays if present Validate ROI is within bounds
- Parameters:
- Returns:
Cropped data array with updated coordinates.
- Return type:
sc.DataArray
Module for combining multiple runs of neutron imaging data into a single dataset with aggregated metadata.
- neunorm.processing.run_combiner.combine_runs(runs, metadata_keys_to_sum=('acquisition_time', 'p_charge'), metadata_check_match=(), normalize_by_runs=False)[source]
Combine multiple runs by summing with metadata aggregation.
Combines a list of sc.DataArrays representing individual runs by summing their data values and variances.
Masks are combined using logical OR. If a pixel is masked in any run, it will be masked in the combined result. This ensures that all bad pixels are properly accounted for in the final dataset.
The function also aggregates metadata by summing specified keys across runs, while retaining other metadata from the first run. This allows for accurate representation of total acquisition time, proton charge, and other relevant parameters in the combined dataset.
- Parameters:
runs (list[sc.DataArray]) – List of DataArrays representing individual runs.
metadata_keys_to_sum (Sequence[str], optional) – Sequence of metadata keys to aggregate across runs, by default (“acquisition_time”, “p_charge”). These coordinates are summed across runs unless
normalize_by_runs=True, in which case they are averaged instead.metadata_check_match (Sequence[str], optional) – Sequence of metadata keys that must match across all runs for combination, by default ()
normalize_by_runs (bool, optional) – Whether to normalize the combined data by the number of runs, by default False
- Returns:
Combined DataArray with summed data and aggregated metadata.
- Return type:
sc.DataArray
Module for rebinning spatial dimensions. Provides functionality to combine adjacent spatial pixels.
- neunorm.processing.spatial_rebinner.rebin_spatial(data, factor)[source]
Bin NxN spatial pixels. Trade-off: lose spatial resolution.
Bin NxN spatial pixels by summing counts Support 2D, 3D, and 4D arrays (preserve other dimensions) Propagate variance correctly Handle edge cases (non-divisible dimensions)
Uncertainty quantification utilities for NeuNorm 2.0.
Provides functions for: - Attaching Poisson variance to count data - Adding systematic uncertainties - Extracting uncertainties from variance
Uses scipp’s automatic variance propagation through arithmetic operations.
- neunorm.processing.uncertainty_calculator.attach_poisson_variance(data)[source]
Attach Poisson variance to count data.
For Poisson counting statistics: σ²(N) = N (variance equals counts)
- Parameters:
data (sc.DataArray) – Count data with unit=’counts’
- Returns:
Copy of data with .variances = values
- Return type:
sc.DataArray
- Raises:
ValueError – If data unit is not ‘counts’
Notes
Creates a copy - does not modify original data. If data already has variance, it will be overwritten (with warning).
Examples
>>> counts = sc.array(dims=['x'], values=[100, 200, 300], unit='counts') >>> data = sc.DataArray(data=counts) >>> data_with_var = attach_poisson_variance(data) >>> print(data_with_var.variances) # [100, 200, 300]
- neunorm.processing.uncertainty_calculator.add_systematic_variance(data, relative_uncertainty)[source]
Add systematic uncertainty to data variance.
Used for beam monitor corrections (proton charge, shutter counts) and other systematic uncertainties.
- Parameters:
data (sc.DataArray) – Data with or without existing variance
relative_uncertainty (float) – Relative uncertainty (fractional). Examples: - 0.005 = 0.5% (typical proton charge uncertainty) - 0.01 = 1.0%
- Returns:
Copy with systematic variance added to existing variance
- Return type:
sc.DataArray
Notes
Systematic variance added in quadrature: var_total = var_existing + (relative_unc * value)²
If no existing variance, only systematic is added.
Examples
>>> data = sc.DataArray(data=sc.array(dims=['x'], values=[100.0, 200.0], unit='counts')) >>> data.variances = np.array([100.0, 200.0]) # Poisson (float data required for variances) >>> # Add 0.5% systematic (e.g., proton charge) >>> data_sys = add_systematic_variance(data, 0.005) >>> # var_total = 100 + (0.005*100)² = 100.25
- neunorm.processing.uncertainty_calculator.get_uncertainty(data)[source]
Get standard deviation (σ) from variance (σ²).
- Parameters:
data (sc.DataArray) – Data with .variances attached
- Returns:
Standard deviation (sqrt of variance)
- Return type:
sc.DataArray
- Raises:
ValueError – If data has no variance
Notes
Equivalent to scipp.stddevs() but with better error message.
Examples
>>> data = sc.DataArray(data=sc.array(dims=['x'], values=[100.0], unit='counts')) >>> data.variances = np.array([100.0]) # float data required for variances >>> uncertainty = get_uncertainty(data) >>> print(uncertainty.values) # [10.0]
- neunorm.processing.uncertainty_calculator.get_relative_uncertainty(data)[source]
Get relative uncertainty (σ / value).
- Parameters:
data (sc.DataArray) – Data with .variances attached
- Returns:
Relative uncertainty (σ / value)
- Return type:
sc.DataArray
- Raises:
ValueError – If data has no variance
Examples
>>> data = sc.DataArray(data=sc.array(dims=['x'], values=[100.0, 400.0], unit='counts')) >>> data.variances = np.array([100.0, 400.0]) # Poisson (float data required for variances) >>> rel_unc = get_relative_uncertainty(data) >>> # For Poisson: σ/N = √N/N = 1/√N >>> print(rel_unc.values) # [0.1, 0.05]
Time-of-flight
Binning utilities for TOF/Energy/Wavelength conversions.
Provides functions for: - Creating TOF bin edges from energy or wavelength specifications - Converting histograms between TOF/energy/wavelength spaces - Physics-based conversions using scipp.constants
All conversions preserve variance (uncertainty) information.
- neunorm.tof.binning.tof_to_energy(tof, flight_path)[source]
Convert time-of-flight to neutron energy.
Formula: E = (1/2) * m_n * (L/t)²
- Parameters:
tof (sc.Variable) – Time-of-flight with unit compatible with ‘s’ (seconds)
flight_path (sc.Variable) – Flight path length with unit compatible with ‘m’ (meters)
- Returns:
Neutron energy with unit ‘eV’
- Return type:
sc.Variable
- Raises:
ValueError – If TOF is zero or negative
Notes
Uses scipp.constants.m_n for neutron mass (no hardcoded values). Higher TOF → lower energy (inverse relationship).
Examples
>>> tof = sc.scalar(1e-3, unit='s') # 1 ms >>> L = sc.scalar(25.0, unit='m') >>> energy = tof_to_energy(tof, L) >>> print(energy) # ~3.27 eV
- neunorm.tof.binning.tof_to_wavelength(tof, flight_path)[source]
Convert time-of-flight to neutron wavelength.
Formula: λ = h * t / (m_n * L)
- Parameters:
tof (sc.Variable) – Time-of-flight with unit compatible with ‘s’
flight_path (sc.Variable) – Flight path length with unit compatible with ‘m’
- Returns:
Neutron wavelength with unit ‘angstrom’
- Return type:
sc.Variable
Notes
Uses scipp.constants.h and scipp.constants.m_n (no hardcoded values). Linear relationship: TOF ∝ wavelength.
Examples
>>> tof = sc.scalar(1e-3, unit='s') # 1 ms >>> L = sc.scalar(25.0, unit='m') >>> wavelength = tof_to_wavelength(tof, L) >>> print(wavelength) # ~0.158 Å
- neunorm.tof.binning.wavelength_to_energy(wavelength)[source]
Convert neutron wavelength to energy via de Broglie relation.
Formula: E = h² / (2 * m_n * λ²)
- Parameters:
wavelength (sc.Variable) – Wavelength with unit compatible with ‘angstrom’
- Returns:
Energy with unit ‘eV’
- Return type:
sc.Variable
Notes
Uses scipp.constants (no hardcoded values). Inverse square relationship: E ∝ 1/λ².
Examples
>>> wl = sc.scalar(1.8, unit='angstrom') # Thermal neutron >>> energy = wavelength_to_energy(wl) >>> print(energy) # ~0.025 eV
- neunorm.tof.binning.energy_to_wavelength(energy)[source]
Convert neutron energy to wavelength via de Broglie relation.
Formula: λ = h / sqrt(2 * m_n * E)
- Parameters:
energy (sc.Variable) – Energy with unit compatible with ‘eV’
- Returns:
Wavelength with unit ‘angstrom’
- Return type:
sc.Variable
- Raises:
ValueError – If energy is zero or negative
Notes
Uses scipp.constants (no hardcoded values).
Examples
>>> energy = sc.scalar(0.025, unit='eV') # Thermal >>> wl = energy_to_wavelength(energy) >>> print(wl) # ~1.8 Å
- neunorm.tof.binning.create_tof_bins(config, flight_path=None, offset=<scipp.Variable> () int64 [µs] 0)[source]
Create TOF bin edges from binning configuration.
Supports three binning strategies: 1. bin_space=’energy’: Create energy bins, convert to TOF (reversed) 2. bin_space=’wavelength’: Create wavelength bins, convert to TOF (ascending) 3. bin_space=’tof’: Create TOF bins directly
- Parameters:
config (BinningConfig) – Binning configuration specifying domain and range
flight_path (sc.Variable, optional) – Flight path in meters. Required for energy/wavelength modes.
offset (sc.Variable, optional) – Detector time offset (e.g. TIDelay) applied so energy/wavelength bin edges live in raw detector-TOF space — matching the raw event TOF that is histogrammed into them and the later coordinate labeling. Default: 0 us. Ignored for ‘tof’ mode.
- Returns:
TOF bin edges with dimension ‘tof’ and unit ‘ns’
- Return type:
sc.Variable
Examples
>>> from neunorm.data_models.tof import BinningConfig >>> config = BinningConfig(bins=1000, bin_space='energy', energy_range=(1, 100)) >>> L = sc.scalar(25.0, unit='m') >>> tof_bins = create_tof_bins(config, L)
- neunorm.tof.binning.get_energy_histogram(hist_tof, flight_path, offset=<scipp.Variable> () int64 [µs] 0)[source]
Convert TOF histogram to energy histogram.
Converts TOF bin edges to energy and reverses data order (high TOF → low energy).
- Parameters:
hist_tof (sc.DataArray) – Histogram with ‘tof’ dimension
flight_path (sc.Variable) – Flight path in meters
offset (sc.Variable) – Detector time offset (e.g. TIDelay) applied during TOF→energy labeling so it matches offset-aware bin edges. Default: 0 us. Pass the same offset used to build the histogram’s energy bins.
- Returns:
Histogram with ‘energy’ dimension (reversed)
- Return type:
sc.DataArray
Notes
Preserves variance if present. Both data and variance are reversed.
- neunorm.tof.binning.get_wavelength_histogram(hist_tof, flight_path, offset=<scipp.Variable> () int64 [µs] 0)[source]
Convert TOF histogram to wavelength histogram.
Converts TOF bin edges to wavelength. NO reversal needed (low TOF → low wavelength, both ascending).
- Parameters:
hist_tof (sc.DataArray) – Histogram with ‘tof’ dimension
flight_path (sc.Variable) – Flight path in meters
offset (sc.Variable) – Detector time offset (e.g. TIDelay) applied during TOF→wavelength labeling so it matches offset-aware bin edges. Default: 0 us. Pass the same offset used to build the histogram’s wavelength bins.
- Returns:
Histogram with ‘wavelength’ dimension
- Return type:
sc.DataArray
Notes
Preserves variance if present. No data reversal (unlike energy conversion).
Convert between TOF and wavelength or energy
- neunorm.tof.coordinate_converter.convert_tof_to_wavelength(tof, distance, offset=<scipp.Variable> () int64 [µs] 0)[source]
Convert time-of-flight (TOF) data to wavelength.
TOF → Wavelength: λ = (h × TOF) / (m_n × L)
- Parameters:
tof (sc.Variable) – Variable containing TOF values with appropriate units (e.g., microseconds).
distance (sc.Variable) – Variable representing the distance from the source to the detector, with appropriate units (e.g., meters).
offset (sc.Variable) – Variable representing the time offset, with appropriate units (e.g., microseconds).
- Returns:
Variable containing wavelength values corresponding to the input TOF data in units of Angstroms.
- Return type:
sc.Variable
- neunorm.tof.coordinate_converter.convert_wavelength_to_tof(wavelength, distance, offset=<scipp.Variable> () int64 [µs] 0)[source]
Convert wavelength data to time-of-flight (TOF).
Wavelength → TOF: TOF = (λ × m_n × L) / h
- Parameters:
wavelength (sc.Variable) – Variable containing wavelength values with appropriate units (e.g., Angstroms).
distance (sc.Variable) – Variable representing the distance from the source to the detector, with appropriate units (e.g., meters).
offset (sc.Variable) – Variable representing the time offset, with appropriate units (e.g., microseconds).
- Returns:
Variable containing TOF values corresponding to the input wavelength data in units matching the offset unit.
- Return type:
sc.Variable
- neunorm.tof.coordinate_converter.convert_tof_to_energy(tof, distance, offset=<scipp.Variable> () int64 [µs] 0)[source]
Convert time-of-flight (TOF) data to energy.
TOF → Energy: E = (1/2) × m_n × (L / TOF)²
- Parameters:
tof (sc.Variable) – Variable containing TOF values with appropriate units (e.g., microseconds).
distance (sc.Variable) – Variable representing the distance from the source to the detector, with appropriate units (e.g., meters).
offset (sc.Variable) – Variable representing the time offset, with appropriate units (e.g., microseconds).
- Returns:
Variable containing energy values corresponding to the input TOF data in units of meV.
- Return type:
sc.Variable
- neunorm.tof.coordinate_converter.convert_energy_to_tof(energy, distance, offset=<scipp.Variable> () int64 [µs] 0)[source]
Convert energy data to time-of-flight (TOF).
Energy → TOF (the exact inverse of
convert_tof_to_energy()): TOF = L / sqrt(2E / m_n) − offset- Parameters:
energy (sc.Variable) – Variable containing energy values with appropriate units (e.g., meV or eV).
distance (sc.Variable) – Variable representing the distance from the source to the detector, with appropriate units (e.g., meters).
offset (sc.Variable) – Variable representing the time offset, with appropriate units (e.g., microseconds).
- Returns:
Variable containing TOF values corresponding to the input energy data, in units matching the offset unit.
- Return type:
sc.Variable
Event-to-histogram converter for TOF event data.
Converts event-mode data to 3D histograms using chunked processing for memory efficiency. Ported from venus_tof with enhancements.
- neunorm.tof.event_converter.convert_events_to_histogram(events, binning, flight_path, x_bins=514, y_bins=514, chunk_size=500000000, compute_variance=True, detector_time_offset=<scipp.Variable> () int64 [µs] 0)[source]
Convert event-mode data to 3D TOF histogram.
Uses chunked processing for memory efficiency (can handle billions of events). Optionally attaches Poisson variance for uncertainty quantification.
- Parameters:
events (EventData) – Event data (tof, x, y arrays)
binning (BinningConfig) – TOF/energy/wavelength binning configuration
flight_path (sc.Variable) – Flight path in meters (required for energy/wavelength binning)
x_bins (int, optional) – Number of spatial bins in x (default: 514, native TPX3 resolution)
y_bins (int, optional) – Number of spatial bins in y (default: 514)
chunk_size (int, optional) – Events per chunk for processing (default: 500M) Larger = faster but more memory
compute_variance (bool, optional) – Attach Poisson variance (var = counts). Default: True
detector_time_offset (sc.Variable, optional) – Detector time offset (e.g. TIDelay) applied when building energy/wavelength bin edges so they live in raw detector-TOF space, matching the raw event TOF histogrammed into them. Default: 0 us. Has no effect for
bin_space='tof'.
- Returns:
3D histogram (tof, x, y) with optional variance
- Return type:
sc.DataArray
Notes
Chunked processing allows handling datasets larger than RAM. Based on venus_tof implementation with performance optimizations.
Examples
>>> events = load_event_data('data.h5') >>> binning = BinningConfig(bins=5000, bin_space='energy', energy_range=(1, 100)) >>> hist = convert_events_to_histogram(events, binning, flight_path=sc.scalar(25.0, unit='m')) >>> print(hist.shape) # (5000, 514, 514)
- neunorm.tof.event_converter.convert_events_to_2d_histogram(events, detector_shape, chunk_size=500000000)[source]
Convert events to 2D spatial histogram (no TOF).
- Parameters:
- Returns:
2D histogram (x, y) with counts
- Return type:
sc.DataArray
Module for rebinning TOF histograms. Provides functionality to combine adjacent TOF bins and update edges accordingly.
- neunorm.tof.histogram_rebinner.rebin_with_snapped_boundaries(old_edges, requested_tof_edges)[source]
For requested TOF edges that don’t align with original TOF edges, snap to the nearest original edge on the left. This ensures that we only combine adjacent bins and don’t create arbitrary bin schemes, which is a requirement for histogram-mode data.
- Parameters:
old_edges (sc.Variable) – Original TOF bin edges.
requested_tof_edges (sc.Variable) – Desired TOF bin edges that may not align with original edges.
- Returns:
New TOF edges snapped to the nearest original edges on the left.
- Return type:
sc.Variable
- neunorm.tof.histogram_rebinner.rebin_tof(data, width, unit='bins', logarithmic=False, tof_dim='tof', l_source_to_detector=25.0, detector_time_offset=5000.0)[source]
Combine N adjacent TOF bins. Returns rebinned data.
Requirements - Combine N adjacent TOF bins by summing counts - Update TOF bin edges accordingly - Propagate variance correctly through summation
Constraints
For histogram-mode data (TPX1, TPX3 histogram mode): - Can ONLY combine adjacent bins - Cannot create arbitrary bin schemes (would require raw events) - Cannot split bins
- Parameters:
data (sc.DataArray) – Input data with TOF dimension.
width (Union[int, float, sc.Variable]) – Width of the new TOF bins in terms of the specified unit. Must be positive. If a sc.Variable is provided, it is interpreted as the desired edges of the new TOF bins, can be in units of time, wavelength or dimensionless (interpreted as bin indices), and will be convertible to the unit of the TOF coordinates.
unit (str) – Unit by which the new bin width is specified. Must be one of time, wavelength, bins, or manual. Default is bins. If bins, width is interpreted as the number of adjacent bins to combine. If time, width is interpreted as the desired width of the new TOF bins in the same unit as the coordinates. If wavelength, width is interpreted as the desired width of the new TOF bins in Angstrom units, and converted to time using the provided source-to-detector distance and detector time offset. If manual, width is required to be a 1-D sc.Variable (at least two values) representing the desired edges of the new TOF bins. Its interpretation depends on the variable’s unit: if dimensionless, the values are treated as integer bin indices into the existing TOF coordinate (must be of integer dtype and within bounds) and the corresponding original edges are selected; otherwise the values are interpreted as explicit TOF edges in, or convertible to, the unit of the TOF coordinates (a wavelength unit is also accepted and converted using l_source_to_detector and detector_time_offset), and are then snapped to the nearest original edges on the left.
logarithmic (bool) – Whether to use logarithmic binning. Default is False.
tof_dim (str) – Name of the TOF dimension in the DataArray. Default is “tof”.
l_source_to_detector (float) – Distance from the source to the detector in meters. Required for wavelength binning. Default is 25.0.
detector_time_offset (float) – Time offset of the detector in same unit as TOF. Required for wavelength binning. Default is 5000.0.
- Returns:
Rebinned DataArray with updated TOF bins and propagated variance.
- Return type:
sc.DataArray
Bad pixel detection for TOF neutron imaging.
Provides tools for detecting dead and hot pixels in event-mode and histogram neutron imaging data. Uses MAD (Median Absolute Deviation) for robust outlier detection.
Ported from venus_tof.masking with generalizations for tof/energy/wavelength dimensions.
- neunorm.tof.pixel_detector.detect_dead_pixels(hist)[source]
Detect dead pixels (zero counts across all spectral bins).
Dead pixels can be: - Permanently damaged (physical failure) - Temporarily disabled (radiation damage, recoverable via power cycle)
- Parameters:
hist (sc.DataArray) – 3D histogram with dimensions (tof/energy/wavelength, x, y) Spectral dimension can be any of: ‘tof’, ‘energy’, ‘wavelength’
- Returns:
Boolean mask with dimensions (x, y) where True = dead pixel
- Return type:
sc.Variable
Examples
>>> dead_mask = detect_dead_pixels(hist_ob) >>> print(f"Found {dead_mask.values.sum()} dead pixels")
- neunorm.tof.pixel_detector.detect_hot_pixels(hist, sigma=5.0)[source]
Detect hot pixels using MAD (Median Absolute Deviation) threshold.
Hot pixels are caused by radiation damage and generate fake events uniformly across all spectral bins, resulting in abnormally high spatial sum values.
MAD is more robust than standard deviation for outlier detection because it is not affected by the outliers themselves.
- Parameters:
hist (sc.DataArray) – 3D histogram with dimensions (tof/energy/wavelength, x, y)
sigma (float, optional) – Threshold in units of MAD (default: 5.0) Common values: - 3.0: Aggressive (catches more pixels, may have false positives) - 5.0: Balanced (recommended for most cases) - 10.0: Conservative (only catches extreme outliers)
- Returns:
Boolean mask with dimensions (x, y) where True = hot pixel
- Return type:
sc.Variable
Notes
The MAD threshold is converted to approximate standard deviations using the scale factor 1.4826, which makes MAD equivalent to sigma for normally distributed data.
Formula: threshold = median + sigma × MAD × 1.4826
Examples
>>> hot_mask = detect_hot_pixels(hist_ta, sigma=5.0) >>> print(f"Found {hot_mask.values.sum()} hot pixels")
>>> # Try different thresholds >>> hot_conservative = detect_hot_pixels(hist_ta, sigma=10.0) >>> hot_aggressive = detect_hot_pixels(hist_ta, sigma=3.0)
- neunorm.tof.pixel_detector.detect_bad_pixels_for_transmission(sample, ob, sigma=5.0)[source]
Detect bad pixels from both sample and open beam for transmission imaging.
For transmission imaging (T = Sample/OB), a pixel is invalid if it’s problematic in EITHER dataset: - Hot pixel in OB → denominator wrong → T wrong - Hot pixel in sample → numerator wrong → T wrong - Dead pixel in OB → division by zero → T undefined - Dead pixel in sample → zero numerator → T=0 (looks like perfect attenuation)
This function applies 4 separate masks to each histogram: - dead_pixels_sample - hot_pixels_sample - dead_pixels_ob - hot_pixels_ob
Scipp automatically combines all masks with OR during operations, so any pixel flagged by any mask will be excluded from calculations.
- Parameters:
sample (sc.DataArray) – Sample histogram (tof/energy/wavelength, x, y)
ob (sc.DataArray) – Open beam reference histogram (tof/energy/wavelength, x, y)
sigma (float, optional) – MAD threshold for hot pixel detection (default: 5.0)
- Returns:
Dictionary containing individual masks for diagnostics: - ‘dead_sample’: Dead pixels in sample - ‘hot_sample’: Hot pixels in sample - ‘dead_ob’: Dead pixels in open beam - ‘hot_ob’: Hot pixels in open beam
- Return type:
Examples
>>> masks = detect_bad_pixels_for_transmission(hist_ta, hist_ob, sigma=5.0) >>> >>> # Both histograms now have all 4 masks applied >>> print(list(hist_ta.masks.keys())) >>> >>> # Calculate transmission (masks automatically applied by scipp) >>> transmission = hist_ta / hist_ob
Notes
The function modifies the input histograms in-place by adding masks to their .masks dictionaries. The original data values are not changed.
Pulse ID reconstruction from TOF data with rollover correction.
Implements a three-pass algorithm to reconstruct pulse assignments from time-of-flight data that exhibits rollover behavior and short-range temporal disorder from TPX3 readout FIFOs.
The algorithm handles: - TOF rollovers when neutron pulse period resets - Clustered false positive detections from temporal disorder - Fine-grained boundary refinement near rollover regions - Multi-chip processing (4 independent TPX3 chips) - Parallel processing for multi-chip detectors (P4 optimization)
See also
examples, tests
- neunorm.tof.pulse_reconstruction.assign_chip_ids(x, y, detector_shape=(514, 514))[source]
Assign a chip id (0-3) to each event from its pixel quadrant, for a 2x2 quad detector.
The loaders do not record which physical chip an event came from, but multi-chip pulse reconstruction (
reconstruct_pulse_ids()withchip_id) needs the events partitioned by chip. For a standard 2x2 quad Timepix3 detector each chip tiles one spatial quadrant, so the chip can be recovered from the pixel(x, y)position.Note
This assumes the standard 2x2 quad layout — four equal chips splitting the detector at
x_bins // 2andy_bins // 2— with chip numberingchip = (x >= W/2) + 2*(y >= H/2)(row-major: 0=lower-left, 1=lower-right, 2=upper-left, 3=upper-right). The numbering itself is arbitrary for reconstruction (which only needs the four chips separated), but if the VENUS detector uses a different physical tiling this mapping must be adjusted. Single-chip detectors do not need this helper.- Parameters:
x (np.ndarray) – 1D integer pixel-coordinate arrays (same length), e.g.
events.x/events.y.y (np.ndarray) – 1D integer pixel-coordinate arrays (same length), e.g.
events.x/events.y.detector_shape (tuple[int, int], optional) –
(x_bins, y_bins)of the full detector; the chip boundary is at the midpoint of each axis. Default(514, 514)to match the VENUS TPX3 pipeline /event_converter.
- Returns:
1D
intarray of chip ids in{0, 1, 2, 3}, same length asx/y.- Return type:
np.ndarray
- neunorm.tof.pulse_reconstruction.reconstruct_pulse_ids(tof, chip_id=None, threshold=-10.0, window=20, late_margin=14.0, n_jobs=None)[source]
Reconstruct pulse IDs from TOF data using three-pass algorithm.
Handles TOF rollovers when neutron pulse period resets and corrects for short-range temporal disorder from TPX3 readout FIFOs. For multi-chip detectors, processes each chip independently (pulse IDs naturally sync because all chips measure the same physical pulses).
- Parameters:
tof (np.ndarray) – Time-of-flight values (1D array, milliseconds)
chip_id (np.ndarray, optional) – Chip ID for each event (0-3 for quad detector). If None, assumes single chip. If provided, processes each chip independently.
threshold (float, optional) – Negative threshold for rollover detection (default: -10.0 ms) TOF drop below this value indicates pulse boundary
window (int, optional) – Number of events to examine on each side of rollover (default: 20) for boundary refinement
late_margin (float, optional) – TOF value above which events are considered late hits from previous pulse (default: 14.0 ms, appropriate for 16.67ms pulse period)
n_jobs (int, optional) – Number of parallel workers for multi-chip processing. - None or 1: Sequential processing (default, safe) - -1: Use all available CPU cores - N > 1: Use N parallel workers Only affects multi-chip processing; single chip always runs sequentially.
- Returns:
Pulse ID array (int32) with same length as tof Values: 0, 1, 2, … for sequential pulses
- Return type:
np.ndarray
- Raises:
ValueError – If n_jobs is 0 or < -1 If chip_id length doesn’t match tof length
Notes
The three-pass algorithm: 1. Pass 1: Vectorized rollover detection using np.diff 2. Pass 2: Clean clustered rollovers (false positives from disorder) 3. Pass 2b: Coarse pulse assignment based on cleaned rollover positions 4. Pass 3: Refine boundaries by optimizing within local window
For multi-chip detectors (VENUS quad TPX3), each chip is processed independently. Pulse IDs automatically synchronize because: - All chips measure the same physical neutron pulses - TDC triggers are synchronized to pulse generation - Rollover detection finds same pulse boundaries per chip
Parallel processing (n_jobs > 1 or n_jobs=-1) uses ProcessPoolExecutor to process each chip in a separate process. This provides ~3-4x speedup for 4-chip detectors on multi-core systems.
Test accuracy: 99.67% on synthetic data with extreme temporal disorder (window size 8-11 events). Real TPX3 data likely has better performance.
Examples
Single chip detector:
>>> from neunorm.loaders.event_loader import load_event_data >>> from neunorm.tof.pulse_reconstruction import reconstruct_pulse_ids >>> events = load_event_data('run_12557.h5') >>> # load_event_data stores TOF in nanoseconds; this API expects milliseconds >>> tof_ms = events.tof / 1e6 >>> pulse_ids = reconstruct_pulse_ids(tof_ms) >>> print(f"Found {pulse_ids.max() + 1} pulses")
Multi-chip detector (VENUS quad) with parallel processing:
>>> import numpy as np >>> from neunorm.tof.pulse_reconstruction import assign_chip_ids >>> events = load_event_data('run_14749.h5') >>> tof_ms = events.tof / 1e6 # nanoseconds -> milliseconds >>> # the loaders don't record the chip; derive it from the pixel quadrant >>> chip_id = assign_chip_ids(events.x, events.y, detector_shape=(514, 514)) >>> pulse_ids = reconstruct_pulse_ids( ... tof_ms, ... chip_id=chip_id, ... threshold=-10.0, ... late_margin=14.0, ... n_jobs=4, # Process 4 chips in parallel ... ) >>> # Pulse IDs synchronized across all 4 chips >>> for chip in np.unique(chip_id): # robust: only chips actually present ... mask = chip_id == chip ... print(f"Chip {chip}: {pulse_ids[mask].max() + 1} pulses")
Filter events by pulse:
>>> # Skip first 5 pulses (warmup) >>> kept = events[pulse_ids >= 5] # EventData is indexable; filters all per-event arrays
Resonance detection for neutron transmission imaging.
Provides automatic detection of resonance dips in energy-space transmission spectra using tiered filtering (background subtraction, SNR, peak shape).
Ported from venus_tof.resonance with minimal modifications.
- class neunorm.tof.resonance.ResonanceDetectionConfig(*, background_sigma_fraction=0.05, initial_prominence=0.01, initial_width=3, min_snr=50.0, snr_window_fraction=0.15, min_peak_width=3, max_peak_width=60, min_prom_width_ratio=0.001)[source]
Bases:
BaseModelConfiguration for automatic resonance detection in transmission spectra.
This class encapsulates parameters for tiered filtering: - Background subtraction (Gaussian filter) - Initial peak detection (find_peaks) - SNR filtering (Poisson statistics) - Peak shape filtering (width and prominence/width ratio)
- Parameters:
background_sigma_fraction (float) – Gaussian filter width as fraction of spectrum length (default: 0.05 = 5%)
initial_prominence (float) – Minimum prominence for initial peak detection (default: 0.01)
initial_width (int) – Minimum peak width in bins for initial detection (default: 3)
min_snr (float) – Minimum signal-to-noise ratio using Poisson statistics (default: 50.0)
snr_window_fraction (float) – Relative energy window for SNR calculation (default: 0.15 = ±15% of E)
min_peak_width (int) – Minimum allowed peak width in bins (default: 3)
max_peak_width (int) – Maximum allowed peak width in bins (default: 60)
min_prom_width_ratio (float) – Minimum prominence/width ratio (default: 0.001)
Examples
>>> config = ResonanceDetectionConfig(min_snr=100.0, max_peak_width=40) >>> result = detect_resonances(hist_ta, hist_ob, config=config)
- classmethod validate_max_greater_than_min(v, info)[source]
Validate that
max_peak_widthexceedsmin_peak_width.
- model_config: ClassVar[ConfigDict] = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- neunorm.tof.resonance.detect_resonances(hist_ta, hist_ob, config=None, known_resonances=None, validation_tolerance=0.05)[source]
Auto-detect resonance dips in neutron transmission data.
Uses tiered filtering approach: 1. Background subtraction (Gaussian filter) 2. Initial peak detection (scipy.signal.find_peaks) 3. SNR filtering (Poisson statistics with relative energy windows) 4. Peak shape filtering (width and prominence/width ratio)
- Parameters:
hist_ta (sc.DataArray) – Sample histogram with dimensions (energy, x, y)
hist_ob (sc.DataArray) – Open beam histogram with dimensions (energy, x, y)
config (ResonanceDetectionConfig, optional) – Detection parameters. If None, uses defaults.
known_resonances (np.ndarray, optional) – Known resonance energies (eV) for validation
validation_tolerance (float) – Relative tolerance for matching known resonances (default: 0.05 = ±5%)
- Returns:
Detection results containing: - ‘resonance_energies’: np.ndarray of detected energies (eV) - ‘resonance_indices’: np.ndarray of bin indices - ‘snr_values’: np.ndarray of SNR for each resonance - ‘n_initial’: int, peaks after initial detection - ‘n_snr_filtered’: int, peaks after SNR filter - ‘n_shape_filtered’: int, peaks after shape filter - ‘validation’: dict, only when
known_resonancesis given and at least one peak passes the SNR filter- Return type:
Examples
>>> result = detect_resonances(hist_ta, hist_ob) >>> print(f"Detected {len(result['resonance_energies'])} resonances")
- neunorm.tof.resonance.aggregate_resonance_image(hist_ta, hist_ob, resonance_indices)[source]
Create aggregated 2D transmission image from resonance bins.
Sums raw counts over detected resonance bins, THEN computes transmission. This is mathematically correct: (Σa)/(Σb) ≠ Σ(a/b)
- Parameters:
hist_ta (sc.DataArray) – Sample histogram with dimensions (energy, x, y)
hist_ob (sc.DataArray) – Open beam histogram with dimensions (energy, x, y)
resonance_indices (np.ndarray) – Energy bin indices corresponding to detected resonances
- Returns:
Aggregated transmission image with dimensions (x, y)
- Return type:
sc.DataArray
Examples
>>> result = detect_resonances(hist_ta, hist_ob) >>> trans_image = aggregate_resonance_image(hist_ta, hist_ob, result['resonance_indices'])
- neunorm.tof.resonance.print_detection_summary(result)[source]
Print human-readable summary of detection results.
- Parameters:
result (dict) – Output from detect_resonances()
- Return type:
None
Per-TOF-bin counting-statistics analysis and rebinning recommendations.
- class neunorm.tof.statistics_analyzer.StatisticsReport(counts_per_bin, snr_per_bin, low_statistics_bins, recommended_rebinning, preserve_regions)[source]
Bases:
objectSummary of per-TOF-bin counting statistics.
- Parameters:
- counts_per_bin
Total counts in each TOF bin.
- Type:
np.ndarray
- snr_per_bin
Signal-to-noise ratio per bin (
sqrt(N)for Poisson statistics).- Type:
np.ndarray
- low_statistics_bins
Indices of bins whose SNR is below the requested minimum.
- Type:
np.ndarray
- neunorm.tof.statistics_analyzer.analyze_statistics(data, min_snr=3.0, tof_dim='tof')[source]
Analyze per-TOF-bin statistics and recommend rebinning.
Requirements - Calculate total counts per TOF bin - Calculate SNR per TOF bin: SNR = √(N) - Identify bins with inadequate statistics (below threshold) - Generate rebinning recommendation - Flag features to preserve (Bragg edges, resonances) # TODO
- Parameters:
- Return type:
Pipelines
VENUS CCD/CMOS normalization pipeline.
- neunorm.pipelines.venus_ccd.run_venus_ccd_pipeline(sample_paths, ob_paths, dark_paths=None, output_path=None, roi=None, gamma_filter=True, air_roi=None, background_roi=None)[source]
Execute VENUS CCD/CMOS normalization pipeline.
Pipeline Steps (12 total) - Load TIFF/FITS (sample, OB, dark [optional]) - Load p_charge metadata - Run combine (critical for VENUS) - ROI clip (optional) - Average dark (optional) / OB - Dead pixel detection - Gamma filtering (optional, less critical than MARS) - Dark correction (optional) - p_charge beam correction - Normalization - Air region correction (optional) - Error propagation (includes p_charge uncertainty) - Output
- Parameters:
sample_paths (Sequence[Sequence[str | Path]]) – List of lists of paths to sample TIFF or FITS files. Each inner list represents a run that should be combined before processing.
ob_paths (Sequence[Sequence[str | Path]]) – List of lists of paths to open beam TIFF or FITS files. Each inner list represents a run that should be combined before processing.
dark_paths (Optional[Sequence[Sequence[str | Path]]]) – List of lists of paths to dark current TIFF or FITS files. Each inner list represents a run that should be combined before processing. Optional (default: None). If omitted (None or an empty list), dark correction is skipped and the dark-frame variance does not contribute to the propagated uncertainty.
output_path (Optional[Path]) – Path to save the output file (HDF5 or TIFF). Required; a value of None raises
ValueError(the default exists only sodark_pathscan keep its positional slot).roi (Optional[tuple]) – Region of interest to crop to — an
ROIor a bare(x0, y0, x1, y1)tuple.gamma_filter (bool) – Whether to apply gamma filtering to the sample data (default: True)
air_roi (Optional[tuple]) – Region of interest for air correction — an
ROIor a bare(x0, y0, x1, y1)tuple. If None, air correction is not applied.background_roi (ROI/tuple or a sequence of them) – Sample-free background region(s) — one ROI or a pooled sequence of ROIs (see
normalize_transmission) — for flux-proxy normalization when proton charge is unavailable. Mutually exclusive with proton-charge correction. Ifroiis also given the detector is cropped first, sobackground_roiindices are resolved in the post-crop frame.
- Return type:
Notes
This function writes the normalized transmission data to disk in either HDF5 or TIFF format, depending on the file extension of output_path. Metadata and detector masks are included in the output.
- Returns:
Final normalized transmission DataArray with metadata and masks
- Return type:
sc.DataArray
- Parameters:
VENUS TPX1 pipeline.
- neunorm.pipelines.venus_tpx1.run_venus_tpx1_pipeline(sample_hdf5_paths, ob_hdf5_paths, sample_tiff_paths, ob_tiff_paths, output_path, roi=None, air_roi=None, rebin_by_tof=False, rebin_by_spatial=None, flight_path=<scipp.Variable> () float64 [m] 25)[source]
Execute VENUS TPX1 normalization pipeline.
Pipeline Steps (11 total) - Load TIFF stack (pre-binned histograms from auto-reduction) - Load TOF bin edges - Load metadata (including proton charge and detector time offset) - Run combine - ROI clip (optional) - Dead pixel detection - Statistics analysis + rebinning recommendation (only when
rebin_by_tof=True) - Rebinning (TOF and/or spatial, optional) - Beam correction (proton charge) - Normalization (TOF-resolved) - Air region correction (optional) - Error propagation - Output- Parameters:
sample_hdf5_paths (Sequence[str | Path]) – List of paths to sample HDF5 files containing metadata.
ob_hdf5_paths (Sequence[str | Path]) – List of paths to open beam HDF5 files containing metadata.
sample_tiff_paths (Sequence[Sequence[str | Path]]) – List of lists of paths to sample TIFF files. Each inner list represents a run that should be combined before processing.
ob_tiff_paths (Sequence[Sequence[str | Path]]) – List of lists of paths to open beam TIFF files. Each inner list represents a run that should be combined before processing.
output_path (Path) – Path to save the output file (HDF5 or TIFF)
roi (Optional[tuple]) – Region of interest to crop to — an
ROIor a bare(x0, y0, x1, y1)tuple.air_roi (Optional[tuple]) – Region of interest for air correction — an
ROIor a bare(x0, y0, x1, y1)tuple. If None, air correction is not applied.rebin_by_tof (Optional[Union[bool,int]]) – Whether to apply TOF rebinning based on statistics analysis. If an integer is provided, it will be used as the rebinning factor instead of the recommended one.
rebin_by_spatial (Optional[int | tuple[int, int]]) – Whether to apply spatial rebinning. If an integer is provided, it is used as the rebinning factor for both spatial axes. A
(x, y)tuple selects per-axis rebinning factors (x and y). If None, no spatial rebinning is applied.flight_path (sc.Variable) – Source-to-detector flight path used for TOF→energy/wavelength coordinate labeling. Defaults to
VENUS_FLIGHT_PATH_M(25 m); set it per detector/sample position.
- Return type:
Notes
This function writes the normalized transmission data to disk in either HDF5 or TIFF format, depending on the file extension of output_path. Metadata and detector masks are included in the output.
VENUS TPX3 histogram pipeline.
- neunorm.pipelines.venus_tpx3_histogram.run_venus_tpx3_histogram_pipeline(sample_hdf5_paths, ob_hdf5_paths, sample_tiff_paths, ob_tiff_paths, output_path, roi=None, air_roi=None, rebin_by_tof=False, rebin_by_spatial=None, flight_path=<scipp.Variable> () float64 [m] 25)[source]
Execute VENUS TPX3 histogram normalization pipeline.
Pipeline Steps - Load TIFF stack (pre-binned by DAQ pipeline) - Load TOF bin edges - Load metadata (including proton charge and detector time offset) - Run combine - ROI clip (optional) - Dead pixel detection - Hot pixel detection (TPX3-specific, even in histogram mode) - Statistics analysis + rebinning recommendation (only when
rebin_by_tof=True) - Rebinning (TOF and/or spatial, optional) - Beam correction (proton charge) - Normalization (TOF-resolved) - Air region correction (optional) - Error propagation - Output- Parameters:
sample_hdf5_paths (Sequence[str | Path]) – List of paths to sample HDF5 files containing metadata.
ob_hdf5_paths (Sequence[str | Path]) – List of paths to open beam HDF5 files containing metadata.
sample_tiff_paths (Sequence[Sequence[str | Path]]) – List of lists of paths to sample TIFF files. Each inner list represents a run that should be combined before processing.
ob_tiff_paths (Sequence[Sequence[str | Path]]) – List of lists of paths to open beam TIFF files. Each inner list represents a run that should be combined before processing.
output_path (Path) – Path to save the output file (HDF5 or TIFF)
roi (Optional[tuple]) – Region of interest to crop to — an
ROIor a bare(x0, y0, x1, y1)tuple.air_roi (Optional[tuple]) – Region of interest for air correction — an
ROIor a bare(x0, y0, x1, y1)tuple. If None, air correction is not applied.rebin_by_tof (Optional[Union[bool,int]]) – Whether to apply TOF rebinning based on statistics analysis. If an integer is provided, it will be used as the rebinning factor instead of the recommended one.
rebin_by_spatial (Optional[int | tuple[int, int]]) – Whether to apply spatial rebinning. If a single integer is provided, it is used as the rebinning factor for both spatial axes. A
(x, y)tuple selects per-axis rebinning factors (x and y). If None, no spatial rebinning is applied.flight_path (sc.Variable) – Source-to-detector flight path used for TOF→energy/wavelength coordinate labeling. Defaults to
VENUS_FLIGHT_PATH_M(25 m); set it per detector/sample position.
- Return type:
Notes
This function writes the normalized transmission data to disk in either HDF5 or TIFF format, depending on the file extension of output_path. Metadata and detector masks are included in the output.
VENUS TPX3 event pipeline.
- neunorm.pipelines.venus_tpx3_event.run_venus_tpx3_event_pipeline(sample_paths, ob_paths, binning, output_path, roi=None, air_roi=None, rebin_by_tof=False, rebin_by_spatial=None, detector_shape=(514, 514), event_id_offset=1000000, bank_name='bank100', flight_path=<scipp.Variable> () float64 [m] 25)[source]
Execute VENUS TPX3 event normalization pipeline.
Pipeline Steps - Load event data - Run combine (optional) - ROI clip (optional) - Dead pixel detection - Hot pixel detection - Statistics analysis (only when
rebin_by_tof=True) - Coarsening strategy (spatial/TOF) - Event → histogram conversion (flexible binning) - Beam correction (p_charge) - Normalization (TOF-resolved) - Air region correction (optional) - Error propagation - Output- Parameters:
sample_paths (Sequence[str | Path]) – List of paths to sample HDF5 files.
ob_paths (Sequence[str | Path]) – List of paths to open beam HDF5 files
binning (BinningConfig) – Configuration for TOF/energy/wavelength binning. Required for event → histogram conversion.
output_path (Path) – Path to save the output file (HDF5 or TIFF)
roi (Optional[tuple]) – Region of interest to crop to — an
ROIor a bare(x0, y0, x1, y1)tuple.air_roi (Optional[tuple]) – Region of interest for air correction — an
ROIor a bare(x0, y0, x1, y1)tuple.rebin_by_tof (Optional[bool,int]) – Whether to apply TOF rebinning based on statistics analysis. If an integer is provided, it will be used as the rebinning factor instead of the recommended one.
rebin_by_spatial (Optional[int | tuple[int, int]]) – Whether to apply spatial rebinning. If an integer is provided, it is used as the rebinning factor for both spatial axes. A
(x, y)tuple selects per-axis rebinning factors. If None, no spatial rebinning is applied.detector_shape (tuple[int, int]) – Shape of the TPX3 detector (default: (514, 514))
event_id_offset (int) – Offset to apply when unrolling event_id to x, y coordinates. This accounts for any non-zero starting point in the event_ids.
bank_name (str) – Name of the detector bank in the NeXus file to load (default: “bank100”)
flight_path (sc.Variable) – Source-to-detector flight path used for both energy/wavelength binning and the TOF→energy/wavelength coordinate labeling. Defaults to
VENUS_FLIGHT_PATH_M(25 m); set it per detector/sample position (the VENUS L2 varies ~24.5–25.5 m).
- Return type:
Notes
This function writes the normalized transmission data to disk in either HDF5 or TIFF format, depending on the file extension of output_path. Metadata and detector masks are included in the output.
MARS CCD/CMOS normalization pipeline.
- neunorm.pipelines.mars_ccd.run_mars_ccd_pipeline(sample_paths, ob_paths, dark_paths=None, output_path=None, roi=None, gamma_filter=True, background_roi=None)[source]
Execute MARS CCD/CMOS normalization pipeline.
Pipeline Steps (10 total) - Load TIFF/FITS (sample, OB, dark [optional]) - Run combine (optional) - ROI clip (optional) - Average dark (optional) / OB - Dead pixel detection (existing tof/pixel_detector.py) - Gamma filtering (filters/gamma_filter.py) - Dark correction (optional, processing/dark_corrector.py) - Normalization (existing processing/normalizer.py) - Output (exporters/hdf5_writer.py, exporters/tiff_writer.py)
- Parameters:
sample_paths (Sequence[Sequence[str | Path]]) – List of lists of paths to sample TIFF or FITS files. Each inner list represents a run that should be combined before processing.
ob_paths (Sequence[Sequence[str | Path]]) – List of lists of paths to open beam TIFF or FITS files. Each inner list represents a run that should be combined before processing.
dark_paths (Optional[Sequence[Sequence[str | Path]]]) – List of lists of paths to dark current TIFF or FITS files. Each inner list represents a run that should be combined before processing. Optional (default: None). If omitted (None or an empty list), dark correction is skipped and the dark-frame variance does not contribute to the propagated uncertainty.
output_path (Optional[Path]) – Path to save the output file (HDF5 or TIFF). Required; a value of None raises
ValueError(the default exists only sodark_pathscan keep its positional slot).roi (Optional[tuple]) – Region of interest to crop to — an
ROIor a bare(x0, y0, x1, y1)tuple.gamma_filter (bool) – Whether to apply gamma filtering to the sample data (default: True)
background_roi (ROI/tuple or a sequence of them) – Sample-free background region(s) — one ROI or a pooled sequence of ROIs (see
normalize_transmission) — for flux-proxy normalization when proton charge is unavailable. Mutually exclusive with proton-charge correction. Ifroiis also given the detector is cropped first, sobackground_roiindices are resolved in the post-crop frame.
- Return type:
Notes
This function writes the normalized transmission data to disk in either HDF5 or TIFF format, depending on the file extension of output_path. Metadata and detector masks are included in the output.
MARS TPX3 normalization pipeline.
- neunorm.pipelines.mars_tpx3.run_mars_tpx3_pipeline(sample_paths, ob_paths, output_path, roi=None, gamma_filter=True, detector_shape=(514, 514), background_roi=None)[source]
Execute MARS TPX3 normalization pipeline.
Pipeline Step 1. Load event data 2. Convert events to 2D histogram 3. Run combine (optional) 4. ROI clip (optional) 5. Dead pixel detection 6. Hot pixel detection 7. Gamma filtering 8. Normalization 9. Output
- Parameters:
sample_paths (Sequence[Sequence[str | Path]]) – List of lists of paths to sample HDF5 files. Each inner list corresponds to one run and will be combined before processing.
ob_paths (Sequence[Sequence[str | Path]]) – List of lists of paths to open beam HDF5 files Each inner list corresponds to one run and will be combined before processing.
output_path (Path) – Path to save the output file (HDF5 or TIFF)
roi (Optional[tuple]) – Region of interest to crop to — an
ROIor a bare(x0, y0, x1, y1)tuple.gamma_filter (bool) – Whether to apply gamma filtering to the sample data (default: True)
detector_shape (tuple[int, int]) – Shape of the TPX3 detector (default: (514, 514))
background_roi (ROI/tuple or a sequence of them) – Sample-free background region(s) — one ROI or a pooled sequence of ROIs (see
normalize_transmission) — for flux-proxy normalization when proton charge is unavailable. Mutually exclusive with proton-charge correction. Ifroiis also given the detector is cropped first, sobackground_roiindices are resolved in the post-crop frame.
- Return type:
Notes
This function writes the normalized transmission data to disk in either HDF5 or TIFF format, depending on the file extension of output_path. Metadata and detector masks are included in the output.
Exporters
HDF5 writer for Neunorm outputs, including provenance metadata.
- neunorm.exporters.hdf5_writer.write_hdf5(output_path, transmission, dead_pixel_mask='dead', hot_pixel_mask='hot', metadata=None)[source]
Write processed data to HDF5 with full provenance.
Write transmission array (3D or 4D depending on workflow) Write uncertainty array (same shape as transmission) Write pixel masks (dead, hot) Write TOF bin edges (for hyperspectral data) Store full processing metadata/provenance
Output Structure:
/transmission # (θ, [TOF,] y, x) float32 /uncertainty # (θ, [TOF,] y, x) float32 (only if variances are present) /masks/dead # (y, x) bool (only if the named dead-pixel mask exists) /masks/hot # (y, x) bool (only if the named hot-pixel mask exists) /tof # (N+1,) float64 (only if the data carries a "tof" coordinate) /metadata/ # processing provenance (only when metadata is supplied)
Metadata values are stored as native datasets (strings, scalars, flat arrays). Nested values such as per-run file-path lists (
sample_paths=[[...], [...]]) are stored as a JSON string with aencoding="json"dataset attribute; read them back withjson.loads(dataset.asstr()[()]). The round-trip is lossless for string-valued provenance (what the pipelines emit); only non-JSON-serializable leaves are coerced viastr()(json.dumps(..., default=str)), so JSON-native numbers/bools/null keep their types.Metadata contents:
Input file paths
Processing timestamp
Gamma filter parameters used
ROI applied (if any)
Number of runs combined (if any)
Software version
- Parameters:
output_path (Union[Path, str]) – Path to output HDF5 file
transmission (sc.DataArray) – Processed transmission data to write. Variances, coordinates, and masks should be included in this DataArray.
dead_pixel_mask (str) – Name of the dead pixel mask in transmission.masks (default: “dead”)
hot_pixel_mask (str) – Name of the hot pixel mask in transmission.masks (default: “hot”)
metadata (Optional[dict]) – Dictionary of metadata to store in the file (default: None)
- Return type:
None
TIFF writing utilities using scitiff to preserve scipp metadata and coordinates.
- neunorm.exporters.tiff_writer.convert_metadata_to_scitiff_coords(metadata)[source]
Convert metadata dictionary to scitiff-compatible DataGroup.
This function takes a metadata dictionary and converts it into a scitiff DataGroup format, which can be embedded in TIFF tags when saving with scitiff. It handles various data types and ensures that the resulting DataGroup is compatible with scitiff’s requirements.
Sequence values (e.g. lists of source paths, an ROI tuple) are stored as JSON strings rather than object-dtype scipp scalars: scitiff’s metadata schema only accepts scalar and typed 1-D/2-D variables, so an object-dtype (
PyObject) scalar fails serialization. JSON encoding mirrors the HDF5 writer’s provenance convention; read a value back withjson.loads(extra[key]).- Parameters:
metadata (dict) – Dictionary containing metadata key-value pairs to convert.
- Returns:
A DataGroup containing the converted metadata, ready for embedding in scitiff.
- Return type:
sc.DataGroup
- neunorm.exporters.tiff_writer.write_tiff_stack(output_path, transmission, metadata=None, daqmetadata=None)[source]
Write transmission stack as TIFF files using scitiff.
Uses scitiff to preserve scipp DataArray metadata and coordinates.
Requirements
Write transmission as TIFF stack (one file per image or multi-page TIFF) Write uncertainty (stdevs) and a mask packed into the same stack via the channel dimension (
concat_stdevs_and_mask=True), not as a separate file Support 32-bit float output Embed metadata in scitiff format (JSON in TIFF tags) Preserve scipp coordinate information through scitiffScitiff Metadata Schema https://scipp.github.io/scitiff/index.html#scitiff-metadata-schema
- Parameters:
output_path (Union[Path, str]) – Path to save TIFF files
transmission (sc.DataArray) – DataArray containing transmission data, variances, coordinates
metadata (Optional[dict]) – Additional metadata to embed in the TIFF files (default: None). Can include any key-value pairs.
daqmetadata (Optional[dict]) – Additional DAQ metadata to embed in the TIFF files (default: None). Created as scitiff.DAQMetadata with keys: ‘facility’, ‘instrument’, ‘detector_type’, ‘source_type’, ‘source’, ‘simulated’
- Return type:
None
Filters
Gamma filter to remove outliers from neutron imaging data.
- neunorm.filters.gamma_filter.apply_gamma_filter(data, threshold_sigma=5.0, kernel_size=3, preserve_variance=True)[source]
Remove gamma contamination by replacing outliers with local median.
Formula:
FOR each pixel (i, j): IF value > median(neighborhood) + k * σ(neighborhood): Replace with median(neighborhood) Update variance estimate
The filter will:
Detect gamma spikes (statistical outliers above threshold)
Replace detected spikes with local median (3x3 or 5x5 neighborhood)
Support both single images and stacks
Configurable threshold (default: median + k×σ)
- Parameters:
data (sc.DataArray) – Input neutron imaging data.
threshold_sigma (float) – Number of standard deviations to use as the threshold for identifying outliers.
kernel_size (int) – Size of the local neighborhood for computing the median.
preserve_variance (bool) – If True, keep the original variance for all pixels. If False, update the variance of outliers to the local median variance.
- Returns:
Gamma-filtered data with propagated variance if requested.
- Return type:
sc.DataArray
Utilities
Physical constants for neutron TOF calculations.
All fundamental physical constants sourced from scipy.constants (CODATA 2018). NO hardcoded values for standard physics quantities - always use scipy.
Detector-specific and facility-specific constants are documented with sources.
- neunorm.utils.constants.M_N = 1.67492750056e-27
scipy.constants.m_n (CODATA 2018)
- Type:
Neutron mass in kg. Source
- neunorm.utils.constants.H = 6.62607015e-34
scipy.constants.h (exact, 2019 SI definition)
- Type:
Planck constant in J·s. Source
- neunorm.utils.constants.E_CHARGE = 1.602176634e-19
scipy.constants.e (exact, 2019 SI definition)
- Type:
Elementary charge in Coulombs. Source
- neunorm.utils.constants.H_OVER_MN = 3.956034006119441e-07
h / m_n ratio for wavelength conversion.
Used in: λ = (h/m_n) * (t/L) Value: ~3.956034e-7 m·s Computed from scipy.constants for full precision (no rounding).
- neunorm.utils.constants.DE_BROGLIE_EV_ANGSQ = 0.08180421023520228
de Broglie constant for wavelength-energy conversion.
Used in: E(eV) = DE_BROGLIE_EV_ANGSQ / λ²(Å) Value: ~0.0818 eV·Å² Computed from scipy.constants with unit conversion (m² → Ų).
Derivation: E = h² / (2·m_n·λ²) [SI units: J = (J·s)² / (kg·m²)] E(eV) = E(J) / e [Convert J to eV] With λ in Å: multiply by 1e20 (since 1 Å = 1e-10 m, so 1 Ų = 1e-20 m²)
- neunorm.utils.constants.TPX3_CLOCK_NS = 25.0
Timepix3 detector time resolution.
Source: - Timepix3 Manual, Section 3.2, Medipix Collaboration (2014) - URL: https://medipix.web.cern.ch/ - Technical specification: 1.5625 ns × 16 (clock divider) = 25 ns
Last verified: 2025-11-17 Precision: Exact (clock specification from manufacturer)
- neunorm.utils.constants.TPX4_CLOCK_NS = 0.195
Timepix4 detector time resolution (improved from TPX3).
Source: - Timepix4 Specifications, Medipix Collaboration (2023) - URL: https://medipix.web.cern.ch/
Last verified: 2025-11-17 Note: TPX4 not yet deployed at VENUS as of 2025-11-17
- neunorm.utils.constants.VENUS_FLIGHT_PATH_M = 25.0
Default VENUS beamline L2 distance (moderator to detector).
Source: - VENUS beamline design documents, Spallation Neutron Source, ORNL - URL: https://neutrons.ornl.gov/venus - Contact: Instrument scientists for precise measurements
Last verified: 2025-11-17
Important Notes: - Actual flight path varies by detector position and sample position - Always verify with beamline staff for high-precision measurements - Typical range: 24.5 - 25.5 m depending on detector mount
- neunorm.utils.constants.MARS_FLIGHT_PATH_M = None
MARS beamline uses continuous neutron beam (HFIR reactor).
No pulsed source → no time-of-flight capability → flight path concept not applicable. For MARS normalization, use traditional flat-field correction only.
- neunorm.utils.constants.ev_to_joule(energy_ev)[source]
Convert electron volts to Joules.
Uses scipy.constants.e (elementary charge).
- neunorm.utils.constants.joule_to_ev(energy_j)[source]
Convert Joules to electron volts.
Uses scipy.constants.e (elementary charge).