Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Data I/O and NeXus/TOF

This page documents the Python-facing TIFF, NeXus, normalization, and TOF energy-grid behavior outside the MCP-specific workflow page.

Axis Convention

NEREIDS uses the spectral axis first:

(n_energy_or_tof, height, width)

This applies to TIFF stacks, NeXus counts, normalized transmission cubes, uncertainty cubes, and the arrays passed to from_counts(...) and from_transmission(...).

For single spectra, use 1D arrays with shape (n_energy,).

TIFF Stacks

Use load_tiff_stack(...) for a multi-frame TIFF:

import nereids

transmission = nereids.load_tiff_stack("transmission_stack.tif")
# transmission.shape == (n_frames, height, width)

Use load_tiff_folder(...) for a directory of single-frame TIFF files:

transmission = nereids.load_tiff_folder("frames", pattern="frame_*.tif")

Folder frames are sorted lexicographically by filename. Use zero-padded names such as frame_0001.tif, frame_0002.tif, and so on. The optional pattern matches filenames, not full paths, and supports * and ?.

Normalization

Raw sample and open-beam arrays can be normalized to transmission:

transmission, uncertainty = nereids.normalize(
    sample_counts,
    open_beam_counts,
    pc_sample=sample_proton_charge,
    pc_ob=open_beam_proton_charge,
)

The formula is:

T = (C_sample / C_open_beam) * (PC_open_beam / PC_sample)

sample_counts and open_beam_counts must have identical shape. Optional dark_current is a 2D (height, width) array.

For fitting raw counts directly, prefer from_counts(...) or fit_counts_spectrum_typed(...) so the counts-KL dispatch can use the counts-domain likelihood.

NeXus Histogram Loading

For agent-orchestrated NeXus workflows driven by a manifest, see MCP Server. This section covers the raw-Python loader.

Use probe_nexus(...) to inspect a file without loading full data:

meta = nereids.probe_nexus("sample.nxs")
print(meta.has_histogram, meta.has_events, meta.flight_path_m)

Use load_nexus_histogram(...) for pre-histogrammed data:

sample = nereids.load_nexus_histogram("sample.nxs")
open_beam = nereids.load_nexus_histogram("open_beam.nxs")

assert sample.counts.shape[0] == sample.tof_edges_us.shape[0] - 1

The loader reads VENUS/rustpix-style histogram data from /entry/histogram/counts and returns:

  • counts: float64 array with shape (n_tof, height, width).
  • tof_edges_us: ascending TOF bin edges in microseconds.
  • flight_path_m: optional file metadata.
  • dead_pixels: optional (height, width) mask.

Histogram files may contain multiple rotation angles. The default multi_angle_mode="error" rejects those files because silently summing projection angles loses information. Choose explicitly:

summed = nereids.load_nexus_histogram("scan.nxs", multi_angle_mode="sum")
angle0 = nereids.load_nexus_histogram(
    "scan.nxs",
    multi_angle_mode="select",
    angle_index=0,
)

NeXus Event Loading

Use load_nexus_events(...) when event data must be histogrammed at load time:

events = nereids.load_nexus_events(
    "events.nxs",
    n_bins=2000,
    tof_min_us=10.0,
    tof_max_us=50000.0,
    height=512,
    width=512,
)

The event loader reads /entry/neutrons/event_time_offset, /x, and /y, bins events into a linear TOF grid, and returns the same NexusData shape contract as the histogram loader.

TOF Edges to Energy Centers

NeXus loaders return counts in ascending TOF order. Neutron energy decreases as TOF increases, so direct TOF-bin conversion would be descending in energy. tof_to_energy_centers(...) returns ascending energy centers suitable for NEREIDS fitting:

flight_path_m = sample.flight_path_m or 25.0
energies = nereids.tof_to_energy_centers(
    sample.tof_edges_us,
    flight_path_m,
    delay_us=0.0,
)

When pairing these energies with NeXus counts, keep arrays aligned. The MCP workflow reverses counts to the same ascending-energy order before fitting. For direct Python workflows, use this pattern:

energies = nereids.tof_to_energy_centers(sample.tof_edges_us, flight_path_m)

# load_nexus_histogram returns ascending TOF. Reverse axis 0 to align with
# ascending energy centers.
sample_counts = sample.counts[::-1, :, :]
open_beam_counts = open_beam.counts[::-1, :, :]

data = nereids.from_counts(sample_counts, open_beam_counts)
result = nereids.spatial_map_typed(data, energies, [u238], c=charge_ratio)

If you construct an energy grid yourself, make sure the grid and every spectral array use the same order and that energies.shape[0] matches the first array dimension.

Counts vs Transmission Fitting

Use counts APIs when you have raw sample and open-beam counts:

fit = nereids.fit_counts_spectrum_typed(
    sample_counts_1d,
    open_beam_counts_1d,
    energies,
    [(u238, 0.0005)],
    c=charge_ratio,
)

Use transmission APIs when your data is already normalized:

fit = nereids.fit_spectrum_typed(
    transmission_1d,
    uncertainty_1d,
    energies,
    [(u238, 0.0005)],
)

For spatial maps, the same distinction is encoded by the input constructor:

counts_data = nereids.from_counts(sample_counts_3d, open_beam_counts_3d)
trans_data = nereids.from_transmission(transmission_3d, uncertainty_3d)