Migrating from NeuNorm 1.x to 2.0
NeuNorm 2.0 is a complete, scipp-based rewrite. It is a
breaking change: code written against the 1.x
NeuNorm.normalization.Normalization API will not run unchanged, and there is no
drop-in compatibility shim. The flat-field normalization physics is preserved,
but the API is new.
Note
If you are not ready to migrate, pin the legacy line: pip install "NeuNorm<2".
The 1.x source is archived under
archive/neunorm-1.x/.
What changed at a glance
1.x |
2.0 |
|
|---|---|---|
Import |
|
|
Distribution name |
|
|
Minimum Python |
2.7 / 3.x |
3.11+ |
Core data type |
NumPy arrays |
|
Uncertainty |
not propagated |
propagated automatically |
API style |
one stateful |
functions + per-detector pipelines |
Primary output |
TIFF |
HDF5 (TIFF secondary) |
TOF / event mode |
none |
first-class (VENUS, Timepix) |
Dev environment |
conda |
pixi ( |
The fastest path: use a pipeline
In 1.x you drove a stateful object through load → normalization → export.
In 2.0 the equivalent is a single pipeline call for your detector/facility,
which loads, corrects, normalizes, and writes the result (with uncertainty, masks,
and provenance) to HDF5.
1.x
from NeuNorm.normalization import Normalization
o_norm = Normalization()
o_norm.load(file="sample.tif", data_type="sample")
o_norm.load(folder="/data/ob/", data_type="ob")
o_norm.load(folder="/data/df/", data_type="df") # dark / "df" frames
o_norm.normalization()
o_norm.export(folder="/data/normalized/", data_type="normalized")
normalized = o_norm.get_normalized_data() # list of NumPy arrays
2.0 (MARS CCD/CMOS — continuous source, the closest analogue to the 1.x CCD case)
from pathlib import Path
from neunorm.pipelines.mars_ccd import run_mars_ccd_pipeline
# Each inner list is one acquisition "run" to combine before processing.
transmission = run_mars_ccd_pipeline(
sample_paths=[["sample_0001.tif", "sample_0002.tif"]],
ob_paths=[["ob_0001.tif", "ob_0002.tif"]],
dark_paths=[["dark_0001.tif"]],
output_path=Path("/data/normalized.hdf5"),
roi=(x0, y0, x1, y1), # optional
gamma_filter=True, # on by default
)
# `transmission` is a scipp.DataArray; the NumPy values are transmission.values
Pick the pipeline that matches your detector and facility:
Pipeline |
Detector |
Facility |
TOF |
|---|---|---|---|
|
CCD/CMOS |
MARS (HFIR) |
no |
|
Timepix3 |
MARS (HFIR) |
no |
|
CCD/CMOS |
VENUS (SNS) |
no |
|
Timepix1 |
VENUS (SNS) |
yes |
|
Timepix3 |
VENUS (SNS) |
yes |
|
Timepix3 (event) |
VENUS (SNS) |
yes |
These share the same flow but not the same signature — TPX detectors omit
dark_paths, the TOF pipelines add rebin_by_tof/rebin_by_spatial, and
run_venus_tpx3_event_pipeline takes a BinningConfig and flat (per-run) path
lists. Check each function’s signature in the API Reference reference.
The composable path: build your own workflow
If you need finer control than a pipeline offers, 2.0 exposes the individual steps
as functions (the pipelines themselves are just compositions of these — read a
pipeline’s source for the exact ordering). The core operation is
normalize_transmission:
from neunorm.loaders.stack_loader import load_stack
from neunorm.processing.normalizer import normalize_transmission
sample = load_stack(["sample_0001.tif", "sample_0002.tif"]) # scipp.DataArray
ob = load_stack(["ob_0001.tif", "ob_0002.tif"])
transmission = normalize_transmission(sample, ob) # T = sample / ob, variances tracked
Method / concept mapping
1.x |
2.0 |
|---|---|
|
a |
|
pipeline |
|
pipeline |
|
Flux normalization by a background ROI: |
|
|
|
|
auto/manual gamma filtering on |
|
|
|
|
the pipeline returns a |
|
|
|
not needed — inputs are explicit function arguments |
ROI
1.x had an ROI object; 2.0 has one too (neunorm.data_models.roi.ROI) and also accepts a bare
4-integer tuple in the same order. 2.x stops are exclusive (1.x x1/y1 were inclusive):
# 1.x
from NeuNorm.roi import ROI
roi = ROI(x0=10, y0=10, x1=110, y1=110)
# 2.0 — named ROI (exclusive stops) or a bare tuple
from neunorm.data_models.roi import ROI
roi = ROI(x0=10, y0=10, x1=111, y1=111) # explicit stops
roi = ROI(x0=10, y0=10, width=101, height=101) # or by size
roi = (10, 10, 111, 111) # or a bare (x0, y0, x1, y1) tuple
Working with the result
A 2.0 pipeline returns a scipp.DataArray instead of a NumPy array:
transmission.values # NumPy array of transmission values
transmission.variances # propagated variances (None in 1.x — not tracked)
transmission.coords # axis coordinates (e.g. wavelength for TOF data)
transmission.masks # detector masks (e.g. dead pixels)
The normalized result is also written to output_path as HDF5 (and optionally
TIFF), so downstream tools can read it directly without re-running the pipeline.
Next steps
API Reference — full API reference for every loader, processing function, and pipeline.
The per-workflow guides under NeuNorm 2.0 Data Reduction Workflows walk through each detector/facility combination end to end.