From 87cd8c650b185eccbf8f46728a3ff2ae0305e731 Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Wed, 25 Feb 2026 18:52:56 +0100 Subject: [PATCH 01/24] rename submodule to kinematics --- .../src/ess/reduce/{time_of_flight => kinematics}/__init__.py | 0 .../src/ess/reduce/{time_of_flight => kinematics}/eto_to_tof.py | 0 .../src/ess/reduce/{time_of_flight => kinematics}/fakes.py | 0 .../reduce/{time_of_flight => kinematics}/interpolator_numba.py | 0 .../reduce/{time_of_flight => kinematics}/interpolator_scipy.py | 0 .../src/ess/reduce/{time_of_flight => kinematics}/lut.py | 0 .../src/ess/reduce/{time_of_flight => kinematics}/resample.py | 0 .../src/ess/reduce/{time_of_flight => kinematics}/types.py | 0 .../src/ess/reduce/{time_of_flight => kinematics}/workflow.py | 0 9 files changed, 0 insertions(+), 0 deletions(-) rename packages/essreduce/src/ess/reduce/{time_of_flight => kinematics}/__init__.py (100%) rename packages/essreduce/src/ess/reduce/{time_of_flight => kinematics}/eto_to_tof.py (100%) rename packages/essreduce/src/ess/reduce/{time_of_flight => kinematics}/fakes.py (100%) rename packages/essreduce/src/ess/reduce/{time_of_flight => kinematics}/interpolator_numba.py (100%) rename packages/essreduce/src/ess/reduce/{time_of_flight => kinematics}/interpolator_scipy.py (100%) rename packages/essreduce/src/ess/reduce/{time_of_flight => kinematics}/lut.py (100%) rename packages/essreduce/src/ess/reduce/{time_of_flight => kinematics}/resample.py (100%) rename packages/essreduce/src/ess/reduce/{time_of_flight => kinematics}/types.py (100%) rename packages/essreduce/src/ess/reduce/{time_of_flight => kinematics}/workflow.py (100%) diff --git a/packages/essreduce/src/ess/reduce/time_of_flight/__init__.py b/packages/essreduce/src/ess/reduce/kinematics/__init__.py similarity index 100% rename from packages/essreduce/src/ess/reduce/time_of_flight/__init__.py rename to packages/essreduce/src/ess/reduce/kinematics/__init__.py diff --git a/packages/essreduce/src/ess/reduce/time_of_flight/eto_to_tof.py b/packages/essreduce/src/ess/reduce/kinematics/eto_to_tof.py similarity index 100% rename from packages/essreduce/src/ess/reduce/time_of_flight/eto_to_tof.py rename to packages/essreduce/src/ess/reduce/kinematics/eto_to_tof.py diff --git a/packages/essreduce/src/ess/reduce/time_of_flight/fakes.py b/packages/essreduce/src/ess/reduce/kinematics/fakes.py similarity index 100% rename from packages/essreduce/src/ess/reduce/time_of_flight/fakes.py rename to packages/essreduce/src/ess/reduce/kinematics/fakes.py diff --git a/packages/essreduce/src/ess/reduce/time_of_flight/interpolator_numba.py b/packages/essreduce/src/ess/reduce/kinematics/interpolator_numba.py similarity index 100% rename from packages/essreduce/src/ess/reduce/time_of_flight/interpolator_numba.py rename to packages/essreduce/src/ess/reduce/kinematics/interpolator_numba.py diff --git a/packages/essreduce/src/ess/reduce/time_of_flight/interpolator_scipy.py b/packages/essreduce/src/ess/reduce/kinematics/interpolator_scipy.py similarity index 100% rename from packages/essreduce/src/ess/reduce/time_of_flight/interpolator_scipy.py rename to packages/essreduce/src/ess/reduce/kinematics/interpolator_scipy.py diff --git a/packages/essreduce/src/ess/reduce/time_of_flight/lut.py b/packages/essreduce/src/ess/reduce/kinematics/lut.py similarity index 100% rename from packages/essreduce/src/ess/reduce/time_of_flight/lut.py rename to packages/essreduce/src/ess/reduce/kinematics/lut.py diff --git a/packages/essreduce/src/ess/reduce/time_of_flight/resample.py b/packages/essreduce/src/ess/reduce/kinematics/resample.py similarity index 100% rename from packages/essreduce/src/ess/reduce/time_of_flight/resample.py rename to packages/essreduce/src/ess/reduce/kinematics/resample.py diff --git a/packages/essreduce/src/ess/reduce/time_of_flight/types.py b/packages/essreduce/src/ess/reduce/kinematics/types.py similarity index 100% rename from packages/essreduce/src/ess/reduce/time_of_flight/types.py rename to packages/essreduce/src/ess/reduce/kinematics/types.py diff --git a/packages/essreduce/src/ess/reduce/time_of_flight/workflow.py b/packages/essreduce/src/ess/reduce/kinematics/workflow.py similarity index 100% rename from packages/essreduce/src/ess/reduce/time_of_flight/workflow.py rename to packages/essreduce/src/ess/reduce/kinematics/workflow.py From 9a4e75a75384974543c9f46206ba6ba93672d57c Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Wed, 25 Feb 2026 20:38:24 +0100 Subject: [PATCH 02/24] can now compute wavelengths from on-the-fly built lut --- packages/essreduce/src/ess/reduce/__init__.py | 4 +- .../src/ess/reduce/kinematics/__init__.py | 39 ++- .../src/ess/reduce/kinematics/lut.py | 38 +-- .../{eto_to_tof.py => to_wavelength.py} | 250 ++++++------------ .../src/ess/reduce/kinematics/types.py | 48 +--- .../src/ess/reduce/kinematics/workflow.py | 20 +- 6 files changed, 137 insertions(+), 262 deletions(-) rename packages/essreduce/src/ess/reduce/kinematics/{eto_to_tof.py => to_wavelength.py} (70%) diff --git a/packages/essreduce/src/ess/reduce/__init__.py b/packages/essreduce/src/ess/reduce/__init__.py index d65bb3aa..18600f3e 100644 --- a/packages/essreduce/src/ess/reduce/__init__.py +++ b/packages/essreduce/src/ess/reduce/__init__.py @@ -4,7 +4,7 @@ import importlib.metadata -from . import nexus, normalization, time_of_flight, uncertainty +from . import kinematics, nexus, normalization, uncertainty try: __version__ = importlib.metadata.version("essreduce") @@ -13,4 +13,4 @@ del importlib -__all__ = ["nexus", "normalization", "time_of_flight", "uncertainty"] +__all__ = ["kinematics", "nexus", "normalization", "uncertainty"] diff --git a/packages/essreduce/src/ess/reduce/kinematics/__init__.py b/packages/essreduce/src/ess/reduce/kinematics/__init__.py index dbb9fc49..5af58dcb 100644 --- a/packages/essreduce/src/ess/reduce/kinematics/__init__.py +++ b/packages/essreduce/src/ess/reduce/kinematics/__init__.py @@ -2,15 +2,15 @@ # Copyright (c) 2025 Scipp contributors (https://github.com/scipp) """ -Utilities for computing real neutron time-of-flight from chopper settings and +Utilities for computing neutron wavelength from chopper settings and neutron time-of-arrival at the detectors. """ from ..nexus.types import DiskChoppers -from .eto_to_tof import providers from .lut import ( BeamlineComponentReading, DistanceResolution, + LookupTableWorkflow, LtotalRange, NumberOfSimulatedNeutrons, PulsePeriod, @@ -19,35 +19,38 @@ SimulationSeed, SourcePosition, TimeResolution, - TofLookupTableWorkflow, simulate_chopper_cascade_using_tof, ) +from .to_wavelength import providers from .types import ( DetectorLtotal, - ErrorLimitedTofLookupTable, + ErrorLimitedLookupTable, + LookupTable, + LookupTableFilename, LookupTableRelativeErrorThreshold, MonitorLtotal, PulseStrideOffset, - TimeOfFlightLookupTable, - TimeOfFlightLookupTableFilename, - ToaDetector, - TofDetector, - TofLookupTable, - TofLookupTableFilename, - TofMonitor, + # ToaDetector, + # TofDetector, + # TofLookupTable, + # TofLookupTableFilename, + # TofMonitor, WavelengthDetector, WavelengthMonitor, ) -from .workflow import GenericTofWorkflow +from .workflow import GenericWavelengthWorkflow __all__ = [ "BeamlineComponentReading", "DetectorLtotal", "DiskChoppers", "DistanceResolution", - "ErrorLimitedTofLookupTable", - "GenericTofWorkflow", + "ErrorLimitedLookupTable", + "GenericWavelengthWorkflow", + "LookupTable", + "LookupTableFilename", "LookupTableRelativeErrorThreshold", + "LookupTableWorkflow", "LtotalRange", "MonitorLtotal", "NumberOfSimulatedNeutrons", @@ -57,15 +60,7 @@ "SimulationResults", "SimulationSeed", "SourcePosition", - "TimeOfFlightLookupTable", - "TimeOfFlightLookupTableFilename", "TimeResolution", - "ToaDetector", - "TofDetector", - "TofLookupTable", - "TofLookupTableFilename", - "TofLookupTableWorkflow", - "TofMonitor", "WavelengthDetector", "WavelengthMonitor", "providers", diff --git a/packages/essreduce/src/ess/reduce/kinematics/lut.py b/packages/essreduce/src/ess/reduce/kinematics/lut.py index 92078feb..abe1cc33 100644 --- a/packages/essreduce/src/ess/reduce/kinematics/lut.py +++ b/packages/essreduce/src/ess/reduce/kinematics/lut.py @@ -11,7 +11,7 @@ import scipp as sc from ..nexus.types import AnyRun, DiskChoppers -from .types import TofLookupTable +from .types import LookupTable @dataclass @@ -136,7 +136,7 @@ class SimulationResults: """ -def _compute_mean_tof( +def _compute_mean_wavelength( simulation: BeamlineComponentReading, distance: sc.Variable, time_bins: sc.Variable, @@ -168,11 +168,11 @@ def _compute_mean_tof( toas = simulation.time_of_arrival + (travel_length / simulation.speed).to( unit=time_unit, copy=False ) - tofs = distance / simulation.speed + # tofs = distance / simulation.speed data = sc.DataArray( data=simulation.weight, - coords={"toa": toas, "tof": tofs.to(unit=time_unit, copy=False)}, + coords={"toa": toas, "wavelength": simulation.wavelength}, ) # Add the event_time_offset coordinate, wrapped to the frame_period @@ -189,27 +189,29 @@ def _compute_mean_tof( binned = data.bin(event_time_offset=time_bins) binned_sum = binned.bins.sum() - # Weighted mean of tof inside each bin - mean_tof = (binned.bins.data * binned.bins.coords["tof"]).bins.sum() / binned_sum - # Compute the variance of the tofs to track regions with large uncertainty + # Weighted mean of wavelength inside each bin + mean_wavelength = ( + binned.bins.data * binned.bins.coords["wavelength"] + ).bins.sum() / binned_sum + # Compute the variance of the wavelengths to track regions with large uncertainty variance = ( - binned.bins.data * (binned.bins.coords["tof"] - mean_tof) ** 2 + binned.bins.data * (binned.bins.coords["wavelength"] - mean_wavelength) ** 2 ).bins.sum() / binned_sum - mean_tof.variances = variance.values - return mean_tof + mean_wavelength.variances = variance.values + return mean_wavelength -def make_tof_lookup_table( +def make_wavelength_lookup_table( simulation: SimulationResults, ltotal_range: LtotalRange, distance_resolution: DistanceResolution, time_resolution: TimeResolution, pulse_period: PulsePeriod, pulse_stride: PulseStride, -) -> TofLookupTable: +) -> LookupTable: """ - Compute a lookup table for time-of-flight as a function of distance and + Compute a lookup table for wavelength as a function of distance and time-of-arrival. Parameters @@ -328,7 +330,7 @@ def make_tof_lookup_table( ) pieces.append( - _compute_mean_tof( + _compute_mean_wavelength( simulation=simulation_reading, distance=dist, time_bins=time_bins, @@ -355,7 +357,7 @@ def make_tof_lookup_table( }, ) - return TofLookupTable( + return LookupTable( array=table, pulse_period=pulse_period, pulse_stride=pulse_stride, @@ -442,13 +444,13 @@ def simulate_chopper_cascade_using_tof( return SimulationResults(readings=sim_readings, choppers=choppers) -def TofLookupTableWorkflow(): +def LookupTableWorkflow(): """ - Create a workflow for computing a time-of-flight lookup table from a + Create a workflow for computing a wavelength lookup table from a simulation of neutrons propagating through a chopper cascade. """ wf = sl.Pipeline( - (make_tof_lookup_table, simulate_chopper_cascade_using_tof), + (make_wavelength_lookup_table, simulate_chopper_cascade_using_tof), params={ PulsePeriod: 1.0 / sc.scalar(14.0, unit="Hz"), PulseStride: 1, diff --git a/packages/essreduce/src/ess/reduce/kinematics/eto_to_tof.py b/packages/essreduce/src/ess/reduce/kinematics/to_wavelength.py similarity index 70% rename from packages/essreduce/src/ess/reduce/kinematics/eto_to_tof.py rename to packages/essreduce/src/ess/reduce/kinematics/to_wavelength.py index 7982680b..5b244ac7 100644 --- a/packages/essreduce/src/ess/reduce/kinematics/eto_to_tof.py +++ b/packages/essreduce/src/ess/reduce/kinematics/to_wavelength.py @@ -38,23 +38,27 @@ from .resample import rebin_strictly_increasing from .types import ( DetectorLtotal, - ErrorLimitedTofLookupTable, + ErrorLimitedLookupTable, + LookupTable, LookupTableRelativeErrorThreshold, MonitorLtotal, PulseStrideOffset, - ToaDetector, - TofDetector, - TofLookupTable, - TofMonitor, WavelengthDetector, WavelengthMonitor, ) -class TofInterpolator: - def __init__(self, lookup: sc.DataArray, distance_unit: str, time_unit: str): +class WavelengthInterpolator: + def __init__( + self, + lookup: sc.DataArray, + distance_unit: str, + time_unit: str, + wavelength_unit: str = 'angstrom', + ): self._distance_unit = distance_unit self._time_unit = time_unit + self._wavelength_unit = wavelength_unit self._time_edges = ( lookup.coords["event_time_offset"] @@ -68,7 +72,7 @@ def __init__(self, lookup: sc.DataArray, distance_unit: str, time_unit: str): self._interpolator = InterpolatorImpl( time_edges=self._time_edges, distance_edges=self._distance_edges, - values=lookup.data.to(unit=self._time_unit, copy=False).values, + values=lookup.data.to(unit=self._wavelength_unit, copy=False).values, ) def __call__( @@ -100,12 +104,12 @@ def __call__( pulse_index=pulse_index.values if pulse_index is not None else None, pulse_period=pulse_period.value, ), - unit=self._time_unit, + unit=self._wavelength_unit, ) -def _time_of_flight_data_histogram( - da: sc.DataArray, lookup: ErrorLimitedTofLookupTable, ltotal: sc.Variable +def _compute_wavelength_histogram( + da: sc.DataArray, lookup: ErrorLimitedLookupTable, ltotal: sc.Variable ) -> sc.DataArray: # In NeXus, 'time_of_flight' is the canonical name in NXmonitor, but in some files, # it may be called 'tof' or 'frame_time'. @@ -126,18 +130,18 @@ def _time_of_flight_data_histogram( etos = rebinned.coords[key] # Create linear interpolator - interp = TofInterpolator( + interp = WavelengthInterpolator( lookup.array, distance_unit=ltotal.unit, time_unit=eto_unit ) - # Compute time-of-flight of the bin edges using the interpolator - tofs = interp( + # Compute wavelengths of the bin edges using the interpolator + wavs = interp( ltotal=ltotal.broadcast(sizes=etos.sizes), event_time_offset=etos, pulse_period=pulse_period, ) - return rebinned.assign_coords(tof=tofs).drop_coords( + return rebinned.assign_coords(wavelength=wavs).drop_coords( list({key} & {"time_of_flight", "frame_time"}) ) @@ -148,11 +152,11 @@ def _guess_pulse_stride_offset( event_time_offset: sc.Variable, pulse_period: sc.Variable, pulse_stride: int, - interp: TofInterpolator, + interp: WavelengthInterpolator, ) -> int: """ Using the minimum ``event_time_zero`` to calculate a reference time when computing - the time-of-flight for the neutron events makes the workflow depend on when the + the wavelength for the neutron events makes the workflow depend on when the first event was recorded. There is no straightforward way to know if we started recording at the beginning of a frame, or half-way through a frame, without looking at the chopper logs. This can be manually corrected using the pulse_stride_offset @@ -161,9 +165,9 @@ def _guess_pulse_stride_offset( Here, we perform a simple guess for the ``pulse_stride_offset`` if it is not provided. - We choose a few random events, compute the time-of-flight for every possible value + We choose a few random events, compute the wavelength for every possible value of pulse_stride_offset, and return the value that yields the least number of NaNs - in the computed time-of-flight. + in the computed wavelength. Parameters ---------- @@ -180,8 +184,8 @@ def _guess_pulse_stride_offset( interp: Interpolator for the lookup table. """ - tofs = {} - # Choose a few random events to compute the time-of-flight + wavs = {} + # Choose a few random events for which to compute the wavelength inds = np.random.choice( len(event_time_offset), min(5000, len(event_time_offset)), replace=False ) @@ -198,25 +202,25 @@ def _guess_pulse_stride_offset( ) for i in range(pulse_stride): pulse_inds = (pulse_index + i) % pulse_stride - tofs[i] = interp( + wavs[i] = interp( ltotal=ltotal, event_time_offset=etos, pulse_index=pulse_inds, pulse_period=pulse_period, ) # Find the entry in the list with the least number of nan values - return sorted(tofs, key=lambda x: sc.isnan(tofs[x]).sum())[0] + return sorted(wavs, key=lambda x: sc.isnan(wavs[x]).sum())[0] -def _prepare_tof_interpolation_inputs( +def _prepare_wavelength_interpolation_inputs( da: sc.DataArray, - lookup: ErrorLimitedTofLookupTable, + lookup: ErrorLimitedLookupTable, ltotal: sc.Variable, pulse_stride_offset: int | None, ) -> dict: """ - Prepare the inputs required for the time-of-flight interpolation. - This function is used when computing the time-of-flight for event data, and for + Prepare the inputs required for the wavelength interpolation. + This function is used when computing the wavelength for event data, and for computing the time-of-arrival for event data (as they both require guessing the pulse_stride_offset if not provided). @@ -225,8 +229,7 @@ def _prepare_tof_interpolation_inputs( da: Data array with event data. lookup: - Lookup table giving time-of-flight as a function of distance and time of - arrival. + Lookup table giving wavelength as a function of distance and time of arrival. ltotal: Total length of the flight path from the source to the detector. pulse_stride_offset: @@ -238,7 +241,7 @@ def _prepare_tof_interpolation_inputs( eto_unit = elem_unit(etos) # Create linear interpolator - interp = TofInterpolator( + interp = WavelengthInterpolator( lookup.array, distance_unit=ltotal.unit, time_unit=eto_unit ) @@ -302,21 +305,21 @@ def _prepare_tof_interpolation_inputs( } -def _time_of_flight_data_events( +def _compute_wavelength_events( da: sc.DataArray, - lookup: ErrorLimitedTofLookupTable, + lookup: ErrorLimitedLookupTable, ltotal: sc.Variable, pulse_stride_offset: int | None, ) -> sc.DataArray: - inputs = _prepare_tof_interpolation_inputs( + inputs = _prepare_wavelength_interpolation_inputs( da=da, lookup=lookup, ltotal=ltotal, pulse_stride_offset=pulse_stride_offset, ) - # Compute time-of-flight for all neutrons using the interpolator - tofs = inputs["interp"]( + # Compute wavelength for all neutrons using the interpolator + wavs = inputs["interp"]( ltotal=inputs["ltotal"], event_time_offset=inputs["eto"], pulse_index=inputs["pulse_index"], @@ -324,8 +327,8 @@ def _time_of_flight_data_events( ) parts = da.bins.constituents - parts["data"] = tofs - result = da.bins.assign_coords(tof=sc.bins(**parts, validate_indices=False)) + parts["data"] = wavs + result = da.bins.assign_coords(wavelength=sc.bins(**parts, validate_indices=False)) out = result.bins.drop_coords("event_time_offset") # The result may still have an 'event_time_zero' dimension (in the case of an @@ -363,6 +366,7 @@ def detector_ltotal_from_straight_line_approximation( gravity: Gravity vector. """ + # TODO: scatter=True should not be hard-coded here graph = { **scn.conversion.graph.beamline.beamline(scatter=True), 'source_position': lambda: source_position, @@ -403,10 +407,10 @@ def monitor_ltotal_from_straight_line_approximation( def _mask_large_uncertainty_in_lut( - table: TofLookupTable, error_threshold: float -) -> TofLookupTable: + table: LookupTable, error_threshold: float +) -> LookupTable: """ - Mask regions in the time-of-flight lookup table with large uncertainty using NaNs. + Mask regions in the lookup table with large uncertainty using NaNs. Parameters ---------- @@ -416,12 +420,10 @@ def _mask_large_uncertainty_in_lut( Threshold for the relative standard deviation (coefficient of variation) of the projected time-of-flight above which values are masked. """ - # TODO: The error threshold could be made dependent on the time-of-flight or - # distance, instead of being a single value for the whole table. da = table.array relative_error = sc.stddevs(da.data) / sc.values(da.data) mask = relative_error > sc.scalar(error_threshold) - return TofLookupTable( + return LookupTable( **{ **asdict(table), "array": sc.where(mask, sc.scalar(np.nan, unit=da.unit), da), @@ -430,25 +432,25 @@ def _mask_large_uncertainty_in_lut( def mask_large_uncertainty_in_lut_detector( - table: TofLookupTable, + table: LookupTable, error_threshold: LookupTableRelativeErrorThreshold, detector_name: NeXusDetectorName, -) -> ErrorLimitedTofLookupTable[snx.NXdetector]: +) -> ErrorLimitedLookupTable[snx.NXdetector]: """ - Mask regions in the time-of-flight lookup table with large uncertainty using NaNs. + Mask regions in the wavelength lookup table with large uncertainty using NaNs. Parameters ---------- table: - Lookup table with time-of-flight as a function of distance and time-of-arrival. + Lookup table with wavelength as a function of distance and time-of-arrival. error_threshold: Threshold for the relative standard deviation (coefficient of variation) of the - projected time-of-flight above which values are masked. + projected wavelength above which values are masked. detector_name: Name of the detector for which to apply the error threshold. This is used to get the correct error threshold from the dictionary of error thresholds. """ - return ErrorLimitedTofLookupTable[snx.NXdetector]( + return ErrorLimitedLookupTable[snx.NXdetector]( _mask_large_uncertainty_in_lut( table=table, error_threshold=error_threshold[detector_name] ) @@ -456,42 +458,42 @@ def mask_large_uncertainty_in_lut_detector( def mask_large_uncertainty_in_lut_monitor( - table: TofLookupTable, + table: LookupTable, error_threshold: LookupTableRelativeErrorThreshold, monitor_name: NeXusName[MonitorType], -) -> ErrorLimitedTofLookupTable[MonitorType]: +) -> ErrorLimitedLookupTable[MonitorType]: """ - Mask regions in the time-of-flight lookup table with large uncertainty using NaNs. + Mask regions in the wavelength lookup table with large uncertainty using NaNs. Parameters ---------- table: - Lookup table with time-of-flight as a function of distance and time-of-arrival. + Lookup table with wavelength as a function of distance and time-of-arrival. error_threshold: Threshold for the relative standard deviation (coefficient of variation) of the - projected time-of-flight above which values are masked. + projected wavelength above which values are masked. monitor_name: Name of the monitor for which to apply the error threshold. This is used to get the correct error threshold from the dictionary of error thresholds. """ - return ErrorLimitedTofLookupTable[MonitorType]( + return ErrorLimitedLookupTable[MonitorType]( _mask_large_uncertainty_in_lut( table=table, error_threshold=error_threshold[monitor_name] ) ) -def _compute_tof_data( +def _compute_wavelength_data( da: sc.DataArray, - lookup: ErrorLimitedTofLookupTable[Component], + lookup: ErrorLimitedLookupTable[Component], ltotal: sc.Variable, pulse_stride_offset: int, ) -> sc.DataArray: if da.bins is None: - data = _time_of_flight_data_histogram(da=da, lookup=lookup, ltotal=ltotal) - out = rebin_strictly_increasing(data, dim='tof') + data = _compute_wavelength_histogram(da=da, lookup=lookup, ltotal=ltotal) + out = rebin_strictly_increasing(data, dim='wavelength') else: - out = _time_of_flight_data_events( + out = _compute_wavelength_events( da=da, lookup=lookup, ltotal=ltotal, @@ -500,16 +502,16 @@ def _compute_tof_data( return out.assign_coords(Ltotal=ltotal) -def detector_time_of_flight_data( +def detector_wavelength_data( detector_data: RawDetector[RunType], - lookup: ErrorLimitedTofLookupTable[snx.NXdetector], + lookup: ErrorLimitedLookupTable[snx.NXdetector], ltotal: DetectorLtotal[RunType], pulse_stride_offset: PulseStrideOffset, -) -> TofDetector[RunType]: +) -> WavelengthDetector[RunType]: """ - Convert the time-of-arrival (event_time_offset) data to time-of-flight data using a + Convert the time-of-arrival (event_time_offset) data to wavelength data using a lookup table. - The output data will have two new coordinates: time-of-flight and Ltotal. + The output data will have two new coordinates: wavelength and Ltotal. Parameters ---------- @@ -517,7 +519,7 @@ def detector_time_of_flight_data( Raw detector data loaded from a NeXus file, e.g., NXdetector containing NXevent_data. lookup: - Lookup table giving time-of-flight as a function of distance and time of + Lookup table giving wavelength as a function of distance and time of arrival. ltotal: Total length of the flight path from the source to the detector. @@ -525,8 +527,8 @@ def detector_time_of_flight_data( When pulse-skipping, the offset of the first pulse in the stride. This is typically zero but can be a small integer < pulse_stride. """ - return TofDetector[RunType]( - _compute_tof_data( + return WavelengthDetector[RunType]( + _compute_wavelength_data( da=detector_data, lookup=lookup, ltotal=ltotal, @@ -535,16 +537,16 @@ def detector_time_of_flight_data( ) -def monitor_time_of_flight_data( +def monitor_wavelength_data( monitor_data: RawMonitor[RunType, MonitorType], - lookup: ErrorLimitedTofLookupTable[MonitorType], + lookup: ErrorLimitedLookupTable[MonitorType], ltotal: MonitorLtotal[RunType, MonitorType], pulse_stride_offset: PulseStrideOffset, -) -> TofMonitor[RunType, MonitorType]: +) -> WavelengthMonitor[RunType, MonitorType]: """ - Convert the time-of-arrival (event_time_offset) data to time-of-flight data using a + Convert the time-of-arrival (event_time_offset) data to wavelength data using a lookup table. - The output data will have two new coordinates: time-of-flight and Ltotal. + The output data will have two new coordinates: wavelength and Ltotal. Parameters ---------- @@ -552,7 +554,7 @@ def monitor_time_of_flight_data( Raw monitor data loaded from a NeXus file, e.g., NXmonitor containing NXevent_data. lookup: - Lookup table giving time-of-flight as a function of distance and time of + Lookup table giving wavelength as a function of distance and time of arrival. ltotal: Total length of the flight path from the source to the monitor. @@ -560,8 +562,8 @@ def monitor_time_of_flight_data( When pulse-skipping, the offset of the first pulse in the stride. This is typically zero but can be a small integer < pulse_stride. """ - return TofMonitor[RunType, MonitorType]( - _compute_tof_data( + return WavelengthMonitor[RunType, MonitorType]( + _compute_wavelength_data( da=monitor_data, lookup=lookup, ltotal=ltotal, @@ -570,109 +572,13 @@ def monitor_time_of_flight_data( ) -def detector_time_of_arrival_data( - detector_data: RawDetector[RunType], - lookup: ErrorLimitedTofLookupTable[snx.NXdetector], - ltotal: DetectorLtotal[RunType], - pulse_stride_offset: PulseStrideOffset, -) -> ToaDetector[RunType]: - """ - Convert the time-of-flight data to time-of-arrival data using a lookup table. - The output data will have a time-of-arrival coordinate. - The time-of-arrival is the time since the neutron was emitted from the source. - It is basically equal to event_time_offset + pulse_index * pulse_period. - - TODO: This is not actually the 'time-of-arrival' in the strict sense, as it is - still wrapped over the frame period. We should consider unwrapping it in the future - to get the true time-of-arrival. - Or give it a different name to avoid confusion. - - Parameters - ---------- - da: - Raw detector data loaded from a NeXus file, e.g., NXdetector containing - NXevent_data. - lookup: - Lookup table giving time-of-flight as a function of distance and time of - arrival. - ltotal: - Total length of the flight path from the source to the detector. - pulse_stride_offset: - When pulse-skipping, the offset of the first pulse in the stride. This is - typically zero but can be a small integer < pulse_stride. - """ - if detector_data.bins is None: - raise NotImplementedError( - "Computing time-of-arrival in histogram mode is not implemented yet." - ) - inputs = _prepare_tof_interpolation_inputs( - da=detector_data, - lookup=lookup, - ltotal=ltotal, - pulse_stride_offset=pulse_stride_offset, - ) - parts = detector_data.bins.constituents - parts["data"] = inputs["eto"] - # The pulse index is None if pulse_stride == 1 (i.e., no pulse skipping) - if inputs["pulse_index"] is not None: - parts["data"] = parts["data"] + inputs["pulse_index"] * inputs["pulse_period"] - result = detector_data.bins.assign_coords( - toa=sc.bins(**parts, validate_indices=False) - ) - return ToaDetector[RunType](result) - - -def _tof_to_wavelength(da: sc.DataArray) -> sc.DataArray: - """ - Convert time-of-flight data to wavelength data. - - Here we assume that the input data contains a Ltotal coordinate, which is required - for the conversion. - This coordinate is assigned in the ``_compute_tof_data`` function. - """ - return da.transform_coords( - 'wavelength', graph={"wavelength": wavelength_from_tof}, keep_intermediate=False - ) - - -def detector_wavelength_data( - detector_data: TofDetector[RunType], -) -> WavelengthDetector[RunType]: - """ - Convert time-of-flight coordinate of the detector data to wavelength. - - Parameters - ---------- - da: - Detector data with time-of-flight coordinate. - """ - return WavelengthDetector[RunType](_tof_to_wavelength(detector_data)) - - -def monitor_wavelength_data( - monitor_data: TofMonitor[RunType, MonitorType], -) -> WavelengthMonitor[RunType, MonitorType]: - """ - Convert time-of-flight coordinate of the monitor data to wavelength. - - Parameters - ---------- - da: - Monitor data with time-of-flight coordinate. - """ - return WavelengthMonitor[RunType, MonitorType](_tof_to_wavelength(monitor_data)) - - def providers() -> tuple[Callable]: """ Providers of the time-of-flight workflow. """ return ( - detector_time_of_flight_data, - monitor_time_of_flight_data, detector_ltotal_from_straight_line_approximation, monitor_ltotal_from_straight_line_approximation, - detector_time_of_arrival_data, detector_wavelength_data, monitor_wavelength_data, mask_large_uncertainty_in_lut_detector, diff --git a/packages/essreduce/src/ess/reduce/kinematics/types.py b/packages/essreduce/src/ess/reduce/kinematics/types.py index 1972fa1d..86dd086c 100644 --- a/packages/essreduce/src/ess/reduce/kinematics/types.py +++ b/packages/essreduce/src/ess/reduce/kinematics/types.py @@ -10,22 +10,19 @@ from ..nexus.types import Component, MonitorType, RunType -TofLookupTableFilename = NewType("TofLookupTableFilename", str) -"""Filename of the time-of-flight lookup table.""" - -TimeOfFlightLookupTableFilename = TofLookupTableFilename -"""Filename of the time-of-flight lookup table (alias).""" +LookupTableFilename = NewType("LookupTableFilename", str) +"""Filename of the wavelength lookup table.""" @dataclass -class TofLookupTable: +class LookupTable: """ - Lookup table giving time-of-flight as a function of distance and time of arrival. + Lookup table giving wavelength as a function of distance and ``event_time_offset``. """ array: sc.DataArray - """The lookup table data array that maps (distance, time_of_arrival) to - time_of_flight.""" + """The lookup table data array that maps (distance, event_time_offset) to + wavelength.""" pulse_period: sc.Variable """Pulse period of the neutron source.""" pulse_stride: int @@ -33,7 +30,7 @@ class TofLookupTable: distance_resolution: sc.Variable """Resolution of the distance coordinate in the lookup table.""" time_resolution: sc.Variable - """Resolution of the time_of_arrival coordinate in the lookup table.""" + """Resolution of the event_time_offset coordinate in the lookup table.""" choppers: sc.DataGroup | None = None """Chopper parameters used when generating the lookup table, if any. This is made optional so we can still support old lookup tables without chopper info.""" @@ -47,14 +44,9 @@ def plot(self, *args, **kwargs) -> Any: return self.array.plot(*args, **kwargs) -TimeOfFlightLookupTable = TofLookupTable -"""Lookup table giving time-of-flight as a function of distance and time of arrival -(alias).""" - - -class ErrorLimitedTofLookupTable(sl.Scope[Component, TofLookupTable], TofLookupTable): +class ErrorLimitedLookupTable(sl.Scope[Component, LookupTable], LookupTable): """Lookup table that is masked with NaNs in regions where the standard deviation of - the time-of-flight is above a certain threshold.""" + the wavelength is above a certain threshold.""" PulseStrideOffset = NewType("PulseStrideOffset", int | None) @@ -66,7 +58,7 @@ class ErrorLimitedTofLookupTable(sl.Scope[Component, TofLookupTable], TofLookupT LookupTableRelativeErrorThreshold = NewType("LookupTableRelativeErrorThreshold", dict) """ Threshold for the relative standard deviation (coefficient of variation) of the -projected time-of-flight above which values are masked. +projected wavelength above which values are masked. The threshold can be different for different beamline components (monitors, detector banks, etc.). The dictionary should have the component names as keys and the corresponding thresholds as values. @@ -91,26 +83,6 @@ class MonitorLtotal(sl.Scope[RunType, MonitorType, sc.Variable], sc.Variable): """Total path length of neutrons from source to monitor.""" -class TofDetector(sl.Scope[RunType, sc.DataArray], sc.DataArray): - """Detector data with time-of-flight coordinate.""" - - -class ToaDetector(sl.Scope[RunType, sc.DataArray], sc.DataArray): - """Detector data with time-of-arrival coordinate. - - When the pulse stride is 1 (i.e., no pulse skipping), the time-of-arrival is the - same as the event_time_offset. When pulse skipping is used, the time-of-arrival is - the event_time_offset + pulse_offset * pulse_period. - This means that the time-of-arrival is basically the event_time_offset wrapped - over the frame period instead of the pulse period - (where frame_period = pulse_stride * pulse_period). - """ - - -class TofMonitor(sl.Scope[RunType, MonitorType, sc.DataArray], sc.DataArray): - """Monitor data with time-of-flight coordinate.""" - - class WavelengthDetector(sl.Scope[RunType, sc.DataArray], sc.DataArray): """Detector data with wavelength coordinate.""" diff --git a/packages/essreduce/src/ess/reduce/kinematics/workflow.py b/packages/essreduce/src/ess/reduce/kinematics/workflow.py index 1cbd9fba..662779b9 100644 --- a/packages/essreduce/src/ess/reduce/kinematics/workflow.py +++ b/packages/essreduce/src/ess/reduce/kinematics/workflow.py @@ -6,12 +6,12 @@ import scipp as sc from ..nexus import GenericNeXusWorkflow -from . import eto_to_tof -from .types import PulseStrideOffset, TofLookupTable, TofLookupTableFilename +from . import to_wavelength +from .types import LookupTable, LookupTableFilename, PulseStrideOffset -def load_tof_lookup_table(filename: TofLookupTableFilename) -> TofLookupTable: - """Load a time-of-flight lookup table from an HDF5 file.""" +def load_lookup_table(filename: LookupTableFilename) -> LookupTable: + """Load a wavelength lookup table from an HDF5 file.""" table = sc.io.load_hdf5(filename) # Support old format where the metadata were stored as coordinates of the DataArray. @@ -38,19 +38,19 @@ def load_tof_lookup_table(filename: TofLookupTableFilename) -> TofLookupTable: if "error_threshold" in table: del table["error_threshold"] - return TofLookupTable(**table) + return LookupTable(**table) -def GenericTofWorkflow( +def GenericWavelengthWorkflow( *, run_types: Iterable[sciline.typing.Key], monitor_types: Iterable[sciline.typing.Key], ) -> sciline.Pipeline: """ - Generic workflow for computing the neutron time-of-flight for detector and monitor + Generic workflow for computing the neutron wavelength for detector and monitor data. - This workflow builds on the ``GenericNeXusWorkflow`` and computes time-of-flight + This workflow builds on the ``GenericNeXusWorkflow`` and computes wavelength from a lookup table that is created from the chopper settings, detector Ltotal and the neutron time-of-arrival. @@ -82,10 +82,10 @@ def GenericTofWorkflow( """ wf = GenericNeXusWorkflow(run_types=run_types, monitor_types=monitor_types) - for provider in eto_to_tof.providers(): + for provider in to_wavelength.providers(): wf.insert(provider) - wf.insert(load_tof_lookup_table) + wf.insert(load_lookup_table) # Default parameters wf[PulseStrideOffset] = None From 7834b5747ce7989f3fab7af51be0ea66018f41c6 Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Wed, 25 Feb 2026 20:42:25 +0100 Subject: [PATCH 03/24] quick fix to dream notebook --- .../essreduce/docs/user-guide/tof/dream.ipynb | 85 ++++++++++--------- 1 file changed, 46 insertions(+), 39 deletions(-) diff --git a/packages/essreduce/docs/user-guide/tof/dream.ipynb b/packages/essreduce/docs/user-guide/tof/dream.ipynb index 2703772c..898fd079 100644 --- a/packages/essreduce/docs/user-guide/tof/dream.ipynb +++ b/packages/essreduce/docs/user-guide/tof/dream.ipynb @@ -26,7 +26,7 @@ "import scippnexus as snx\n", "from scippneutron.chopper import DiskChopper\n", "from ess.reduce.nexus.types import AnyRun, RawDetector, SampleRun, NeXusDetectorName\n", - "from ess.reduce.time_of_flight import *" + "from ess.reduce.kinematics import *" ] }, { @@ -201,7 +201,7 @@ "metadata": {}, "outputs": [], "source": [ - "from ess.reduce.time_of_flight.fakes import FakeBeamline\n", + "from ess.reduce.kinematics.fakes import FakeBeamline\n", "\n", "ess_beamline = FakeBeamline(\n", " choppers=disk_choppers,\n", @@ -299,14 +299,14 @@ "metadata": {}, "outputs": [], "source": [ - "wf = GenericTofWorkflow(run_types=[SampleRun], monitor_types=[])\n", + "wf = GenericWavelengthWorkflow(run_types=[SampleRun], monitor_types=[])\n", "\n", "wf[RawDetector[SampleRun]] = raw_data\n", "wf[DetectorLtotal[SampleRun]] = Ltotal\n", "wf[NeXusDetectorName] = 'dream_detector'\n", "wf[LookupTableRelativeErrorThreshold] = {'dream_detector': float(\"inf\")}\n", "\n", - "wf.visualize(TofDetector[SampleRun])" + "wf.visualize(WavelengthDetector[SampleRun])" ] }, { @@ -342,14 +342,14 @@ "metadata": {}, "outputs": [], "source": [ - "lut_wf = TofLookupTableWorkflow()\n", + "lut_wf = LookupTableWorkflow()\n", "lut_wf[DiskChoppers[AnyRun]] = disk_choppers\n", "lut_wf[SourcePosition] = source_position\n", "lut_wf[LtotalRange] = (\n", " sc.scalar(5.0, unit=\"m\"),\n", " sc.scalar(80.0, unit=\"m\"),\n", ")\n", - "lut_wf.visualize(TofLookupTable)" + "lut_wf.visualize(LookupTable)" ] }, { @@ -381,20 +381,19 @@ "def to_event_time_offset(sim):\n", " # Compute event_time_offset at the detector\n", " eto = (\n", - " sim.time_of_arrival + ((Ltotal - sim.distance) / sim.speed).to(unit=\"us\")\n", + " sim.time_of_arrival + ((lut_wf.compute(LtotalRange)[1] - sim.distance) / sim.speed).to(unit=\"us\")\n", " ) % sc.scalar(1e6 / 14.0, unit=\"us\")\n", - " # Compute time-of-flight at the detector\n", - " tof = (Ltotal / sim.speed).to(unit=\"us\")\n", + " # # Compute time-of-flight at the detector\n", + " # tof = (Ltotal / sim.speed).to(unit=\"us\")\n", " return sc.DataArray(\n", " data=sim.weight,\n", - " coords={\"wavelength\": sim.wavelength, \"event_time_offset\": eto, \"tof\": tof},\n", + " coords={\"wavelength\": sim.wavelength, \"event_time_offset\": eto},\n", " )\n", "\n", "\n", "events = to_event_time_offset(sim.readings[\"t0\"])\n", - "fig1 = events.hist(wavelength=300, event_time_offset=300).plot(norm=\"log\")\n", - "fig2 = events.hist(tof=300, event_time_offset=300).plot(norm=\"log\")\n", - "fig1 + fig2" + "fig = events.hist(wavelength=300, event_time_offset=300).plot(norm=\"log\")\n", + "fig" ] }, { @@ -414,10 +413,10 @@ "metadata": {}, "outputs": [], "source": [ - "table = lut_wf.compute(TofLookupTable)\n", + "table = lut_wf.compute(LookupTable)\n", "\n", "# Overlay mean on the figure above\n", - "table.array[\"distance\", -1].plot(ax=fig2.ax, color=\"C1\", ls=\"-\", marker=None)" + "table.array[\"distance\", -1].plot(ax=fig.ax, color=\"C1\", ls=\"-\", marker=None)" ] }, { @@ -456,11 +455,11 @@ "outputs": [], "source": [ "# Set the computed lookup table onto the original workflow\n", - "wf[TofLookupTable] = table\n", + "wf[LookupTable] = table\n", "\n", "# Compute time-of-flight of neutron events\n", - "tofs = wf.compute(TofDetector[SampleRun])\n", - "tofs" + "wavs = wf.compute(WavelengthDetector[SampleRun])\n", + "wavs" ] }, { @@ -478,7 +477,7 @@ "metadata": {}, "outputs": [], "source": [ - "tofs.bins.concat().hist(tof=300).plot()" + "# tofs.bins.concat().hist(tof=300).plot()" ] }, { @@ -498,17 +497,17 @@ "metadata": {}, "outputs": [], "source": [ - "from scippneutron.conversion.graph.beamline import beamline\n", - "from scippneutron.conversion.graph.tof import elastic\n", + "# from scippneutron.conversion.graph.beamline import beamline\n", + "# from scippneutron.conversion.graph.tof import elastic\n", "\n", - "# Perform coordinate transformation\n", - "graph = {**beamline(scatter=False), **elastic(\"tof\")}\n", - "wav_wfm = tofs.transform_coords(\"wavelength\", graph=graph)\n", + "# # Perform coordinate transformation\n", + "# graph = {**beamline(scatter=False), **elastic(\"tof\")}\n", + "# wav_wfm = tofs.transform_coords(\"wavelength\", graph=graph)\n", "\n", "# Define wavelength bin edges\n", - "wavs = sc.linspace(\"wavelength\", 0.8, 4.6, 201, unit=\"angstrom\")\n", + "edges = sc.linspace(\"wavelength\", 0.8, 4.6, 201, unit=\"angstrom\")\n", "\n", - "histogrammed = wav_wfm.hist(wavelength=wavs).squeeze()\n", + "histogrammed = wavs.hist(wavelength=edges).squeeze()\n", "histogrammed.plot()" ] }, @@ -536,7 +535,7 @@ "pp.plot(\n", " {\n", " \"wfm\": histogrammed,\n", - " \"ground_truth\": ground_truth.hist(wavelength=wavs),\n", + " \"ground_truth\": ground_truth.hist(wavelength=edges),\n", " }\n", ")" ] @@ -622,8 +621,8 @@ "wf[DetectorLtotal[SampleRun]] = Ltotal\n", "\n", "# Compute tofs and wavelengths\n", - "tofs = wf.compute(TofDetector[SampleRun])\n", - "wav_wfm = tofs.transform_coords(\"wavelength\", graph=graph)\n", + "wav_wfm = wf.compute(WavelengthDetector[SampleRun])\n", + "# wav_wfm = tofs.transform_coords(\"wavelength\", graph=graph)\n", "\n", "# Compare in plot\n", "ground_truth = []\n", @@ -634,8 +633,8 @@ "figs = [\n", " pp.plot(\n", " {\n", - " \"wfm\": wav_wfm[\"detector_number\", i].bins.concat().hist(wavelength=wavs),\n", - " \"ground_truth\": ground_truth[i].hist(wavelength=wavs),\n", + " \"wfm\": wav_wfm[\"detector_number\", i].bins.concat().hist(wavelength=edges),\n", + " \"ground_truth\": ground_truth[i].hist(wavelength=edges),\n", " },\n", " title=f\"Pixel {i+1}\",\n", " )\n", @@ -747,7 +746,7 @@ "metadata": {}, "outputs": [], "source": [ - "table = lut_wf.compute(TofLookupTable)\n", + "table = lut_wf.compute(LookupTable)\n", "table.plot(ymin=65) / (sc.stddevs(table.array) / sc.values(table.array)).plot(norm=\"linear\", ymin=55, vmax=0.05)" ] }, @@ -771,11 +770,11 @@ "metadata": {}, "outputs": [], "source": [ - "wf[TofLookupTable] = table\n", + "wf[LookupTable] = table\n", "\n", "wf[LookupTableRelativeErrorThreshold] = {'dream_detector': 0.01}\n", "\n", - "masked_table = wf.compute(ErrorLimitedTofLookupTable[snx.NXdetector])\n", + "masked_table = wf.compute(ErrorLimitedLookupTable[snx.NXdetector])\n", "masked_table.plot(ymin=65)" ] }, @@ -804,9 +803,9 @@ "wf[DetectorLtotal[SampleRun]] = Ltotal\n", "\n", "# Compute time-of-flight\n", - "tofs = wf.compute(TofDetector[SampleRun])\n", + "wav_wfm = wf.compute(WavelengthDetector[SampleRun])\n", "# Compute wavelength\n", - "wav_wfm = tofs.transform_coords(\"wavelength\", graph=graph)\n", + "# wav_wfm = tofs.transform_coords(\"wavelength\", graph=graph)\n", "\n", "# Compare to the true wavelengths\n", "ground_truth = ess_beamline.model_result[\"detector\"].data.flatten(to=\"event\")\n", @@ -814,11 +813,19 @@ "\n", "pp.plot(\n", " {\n", - " \"wfm\": wav_wfm.hist(wavelength=wavs).squeeze(),\n", - " \"ground_truth\": ground_truth.hist(wavelength=wavs),\n", + " \"wfm\": wav_wfm.hist(wavelength=edges).squeeze(),\n", + " \"ground_truth\": ground_truth.hist(wavelength=edges),\n", " }\n", ")" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "08994779-76b4-4f05-8ccd-c8076f85e04a", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { @@ -837,7 +844,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.7" + "version": "3.12.12" } }, "nbformat": 4, From 2754a7ed3a51f432227c224117d7a95be6ceefe6 Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Wed, 25 Feb 2026 23:29:39 +0100 Subject: [PATCH 04/24] remove unused import --- packages/essreduce/src/ess/reduce/kinematics/to_wavelength.py | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/essreduce/src/ess/reduce/kinematics/to_wavelength.py b/packages/essreduce/src/ess/reduce/kinematics/to_wavelength.py index 5b244ac7..f759fa05 100644 --- a/packages/essreduce/src/ess/reduce/kinematics/to_wavelength.py +++ b/packages/essreduce/src/ess/reduce/kinematics/to_wavelength.py @@ -15,7 +15,6 @@ import scippneutron as scn import scippnexus as snx from scippneutron._utils import elem_unit -from scippneutron.conversion.tof import wavelength_from_tof try: from .interpolator_numba import Interpolator as InterpolatorImpl From c54df5ba7e8fbc47f09b5f9d9098bac14bc81f88 Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Wed, 25 Feb 2026 23:39:45 +0100 Subject: [PATCH 05/24] some formatting --- packages/essreduce/docs/user-guide/tof/dream.ipynb | 8 -------- packages/essreduce/tests/nexus/workflow_test.py | 6 +++--- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/packages/essreduce/docs/user-guide/tof/dream.ipynb b/packages/essreduce/docs/user-guide/tof/dream.ipynb index 898fd079..bea0db22 100644 --- a/packages/essreduce/docs/user-guide/tof/dream.ipynb +++ b/packages/essreduce/docs/user-guide/tof/dream.ipynb @@ -818,14 +818,6 @@ " }\n", ")" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "08994779-76b4-4f05-8ccd-c8076f85e04a", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/packages/essreduce/tests/nexus/workflow_test.py b/packages/essreduce/tests/nexus/workflow_test.py index cf72b06f..804d6f28 100644 --- a/packages/essreduce/tests/nexus/workflow_test.py +++ b/packages/essreduce/tests/nexus/workflow_test.py @@ -889,9 +889,9 @@ def test_generic_nexus_workflow_includes_only_given_monitor_types() -> None: def assert_not_contains_type_arg(node: object, excluded: set[type]) -> None: - assert not any( - arg in excluded for arg in getattr(node, "__args__", ()) - ), f"Node {node} contains one of {excluded!r}" + assert not any(arg in excluded for arg in getattr(node, "__args__", ())), ( + f"Node {node} contains one of {excluded!r}" + ) def test_generic_nexus_workflow_load_custom_field_user_affiliation( From 65a8b8cbe5a1c947bcf174c986b7ab9e6a537c1d Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Wed, 4 Mar 2026 23:06:19 +0100 Subject: [PATCH 06/24] start renaming variables in tests --- .../interpolator_test.py | 4 +- .../lut_test.py | 128 +++++++++--------- .../resample_tests.py | 2 +- .../unwrap_test.py | 114 ++++++++-------- .../wfm_test.py | 62 ++++----- .../workflow_test.py | 120 ++++++++-------- 6 files changed, 215 insertions(+), 215 deletions(-) rename packages/essreduce/tests/{time_of_flight => kinematics}/interpolator_test.py (96%) rename packages/essreduce/tests/{time_of_flight => kinematics}/lut_test.py (69%) rename packages/essreduce/tests/{time_of_flight => kinematics}/resample_tests.py (99%) rename packages/essreduce/tests/{time_of_flight => kinematics}/unwrap_test.py (82%) rename packages/essreduce/tests/{time_of_flight => kinematics}/wfm_test.py (89%) rename packages/essreduce/tests/{time_of_flight => kinematics}/workflow_test.py (64%) diff --git a/packages/essreduce/tests/time_of_flight/interpolator_test.py b/packages/essreduce/tests/kinematics/interpolator_test.py similarity index 96% rename from packages/essreduce/tests/time_of_flight/interpolator_test.py rename to packages/essreduce/tests/kinematics/interpolator_test.py index 5e1c013d..e0b1fbb9 100644 --- a/packages/essreduce/tests/time_of_flight/interpolator_test.py +++ b/packages/essreduce/tests/kinematics/interpolator_test.py @@ -3,10 +3,10 @@ import numpy as np -from ess.reduce.time_of_flight.interpolator_numba import ( +from ess.reduce.kinematics.interpolator_numba import ( Interpolator as InterpolatorNumba, ) -from ess.reduce.time_of_flight.interpolator_scipy import ( +from ess.reduce.kinematics.interpolator_scipy import ( Interpolator as InterpolatorScipy, ) diff --git a/packages/essreduce/tests/time_of_flight/lut_test.py b/packages/essreduce/tests/kinematics/lut_test.py similarity index 69% rename from packages/essreduce/tests/time_of_flight/lut_test.py rename to packages/essreduce/tests/kinematics/lut_test.py index ee118dcb..66356966 100644 --- a/packages/essreduce/tests/time_of_flight/lut_test.py +++ b/packages/essreduce/tests/kinematics/lut_test.py @@ -4,30 +4,30 @@ import scipp as sc from scippneutron.chopper import DiskChopper -from ess.reduce import time_of_flight +from ess.reduce import kinematics +from ess.reduce.kinematics import LookupTableWorkflow from ess.reduce.nexus.types import AnyRun -from ess.reduce.time_of_flight import TofLookupTableWorkflow sl = pytest.importorskip("sciline") def test_lut_workflow_computes_table(): - wf = TofLookupTableWorkflow() - wf[time_of_flight.DiskChoppers[AnyRun]] = {} - wf[time_of_flight.SourcePosition] = sc.vector([0, 0, 0], unit='m') - wf[time_of_flight.NumberOfSimulatedNeutrons] = 100_000 - wf[time_of_flight.SimulationSeed] = 60 - wf[time_of_flight.PulseStride] = 1 + wf = LookupTableWorkflow() + wf[kinematics.DiskChoppers[AnyRun]] = {} + wf[kinematics.SourcePosition] = sc.vector([0, 0, 0], unit='m') + wf[kinematics.NumberOfSimulatedNeutrons] = 100_000 + wf[kinematics.SimulationSeed] = 60 + wf[kinematics.PulseStride] = 1 lmin, lmax = sc.scalar(25.0, unit='m'), sc.scalar(35.0, unit='m') dres = sc.scalar(0.1, unit='m') tres = sc.scalar(333.0, unit='us') - wf[time_of_flight.LtotalRange] = lmin, lmax - wf[time_of_flight.DistanceResolution] = dres - wf[time_of_flight.TimeResolution] = tres + wf[kinematics.LtotalRange] = lmin, lmax + wf[kinematics.DistanceResolution] = dres + wf[kinematics.TimeResolution] = tres - table = wf.compute(time_of_flight.TofLookupTable) + table = wf.compute(kinematics.TofLookupTable) assert table.array.coords['distance'].min() < lmin assert table.array.coords['distance'].max() > lmax @@ -41,22 +41,22 @@ def test_lut_workflow_computes_table(): def test_lut_workflow_pulse_skipping(): - wf = TofLookupTableWorkflow() - wf[time_of_flight.DiskChoppers[AnyRun]] = {} - wf[time_of_flight.SourcePosition] = sc.vector([0, 0, 0], unit='m') - wf[time_of_flight.NumberOfSimulatedNeutrons] = 100_000 - wf[time_of_flight.SimulationSeed] = 62 - wf[time_of_flight.PulseStride] = 2 + wf = LookupTableWorkflow() + wf[kinematics.DiskChoppers[AnyRun]] = {} + wf[kinematics.SourcePosition] = sc.vector([0, 0, 0], unit='m') + wf[kinematics.NumberOfSimulatedNeutrons] = 100_000 + wf[kinematics.SimulationSeed] = 62 + wf[kinematics.PulseStride] = 2 lmin, lmax = sc.scalar(55.0, unit='m'), sc.scalar(65.0, unit='m') dres = sc.scalar(0.1, unit='m') tres = sc.scalar(250.0, unit='us') - wf[time_of_flight.LtotalRange] = lmin, lmax - wf[time_of_flight.DistanceResolution] = dres - wf[time_of_flight.TimeResolution] = tres + wf[kinematics.LtotalRange] = lmin, lmax + wf[kinematics.DistanceResolution] = dres + wf[kinematics.TimeResolution] = tres - table = wf.compute(time_of_flight.TofLookupTable) + table = wf.compute(kinematics.TofLookupTable) assert table.array.coords['event_time_offset'].max() == 2 * sc.scalar( 1 / 14, unit='s' @@ -64,22 +64,22 @@ def test_lut_workflow_pulse_skipping(): def test_lut_workflow_non_exact_distance_range(): - wf = TofLookupTableWorkflow() - wf[time_of_flight.DiskChoppers[AnyRun]] = {} - wf[time_of_flight.SourcePosition] = sc.vector([0, 0, 0], unit='m') - wf[time_of_flight.NumberOfSimulatedNeutrons] = 100_000 - wf[time_of_flight.SimulationSeed] = 63 - wf[time_of_flight.PulseStride] = 1 + wf = LookupTableWorkflow() + wf[kinematics.DiskChoppers[AnyRun]] = {} + wf[kinematics.SourcePosition] = sc.vector([0, 0, 0], unit='m') + wf[kinematics.NumberOfSimulatedNeutrons] = 100_000 + wf[kinematics.SimulationSeed] = 63 + wf[kinematics.PulseStride] = 1 lmin, lmax = sc.scalar(25.0, unit='m'), sc.scalar(35.0, unit='m') dres = sc.scalar(0.33, unit='m') tres = sc.scalar(250.0, unit='us') - wf[time_of_flight.LtotalRange] = lmin, lmax - wf[time_of_flight.DistanceResolution] = dres - wf[time_of_flight.TimeResolution] = tres + wf[kinematics.LtotalRange] = lmin, lmax + wf[kinematics.DistanceResolution] = dres + wf[kinematics.TimeResolution] = tres - table = wf.compute(time_of_flight.TofLookupTable) + table = wf.compute(kinematics.TofLookupTable) assert table.array.coords['distance'].min() < lmin assert table.array.coords['distance'].max() > lmax @@ -146,21 +146,21 @@ def _make_choppers(): def test_lut_workflow_computes_table_with_choppers(): - wf = TofLookupTableWorkflow() - wf[time_of_flight.DiskChoppers[AnyRun]] = _make_choppers() - wf[time_of_flight.SourcePosition] = sc.vector([0, 0, 0], unit='m') - wf[time_of_flight.NumberOfSimulatedNeutrons] = 100_000 - wf[time_of_flight.SimulationSeed] = 64 - wf[time_of_flight.PulseStride] = 1 - - wf[time_of_flight.LtotalRange] = ( + wf = LookupTableWorkflow() + wf[kinematics.DiskChoppers[AnyRun]] = _make_choppers() + wf[kinematics.SourcePosition] = sc.vector([0, 0, 0], unit='m') + wf[kinematics.NumberOfSimulatedNeutrons] = 100_000 + wf[kinematics.SimulationSeed] = 64 + wf[kinematics.PulseStride] = 1 + + wf[kinematics.LtotalRange] = ( sc.scalar(35.0, unit='m'), sc.scalar(65.0, unit='m'), ) - wf[time_of_flight.DistanceResolution] = sc.scalar(0.1, unit='m') - wf[time_of_flight.TimeResolution] = sc.scalar(250.0, unit='us') + wf[kinematics.DistanceResolution] = sc.scalar(0.1, unit='m') + wf[kinematics.TimeResolution] = sc.scalar(250.0, unit='us') - table = wf.compute(time_of_flight.TofLookupTable) + table = wf.compute(kinematics.TofLookupTable) # At low distance, the rays are more focussed low_dist = table.array['distance', 2] @@ -180,21 +180,21 @@ def test_lut_workflow_computes_table_with_choppers(): def test_lut_workflow_computes_table_with_choppers_full_beamline_range(): - wf = TofLookupTableWorkflow() - wf[time_of_flight.DiskChoppers[AnyRun]] = _make_choppers() - wf[time_of_flight.SourcePosition] = sc.vector([0, 0, 0], unit='m') - wf[time_of_flight.NumberOfSimulatedNeutrons] = 100_000 - wf[time_of_flight.SimulationSeed] = 64 - wf[time_of_flight.PulseStride] = 1 - - wf[time_of_flight.LtotalRange] = ( + wf = LookupTableWorkflow() + wf[kinematics.DiskChoppers[AnyRun]] = _make_choppers() + wf[kinematics.SourcePosition] = sc.vector([0, 0, 0], unit='m') + wf[kinematics.NumberOfSimulatedNeutrons] = 100_000 + wf[kinematics.SimulationSeed] = 64 + wf[kinematics.PulseStride] = 1 + + wf[kinematics.LtotalRange] = ( sc.scalar(5.0, unit='m'), sc.scalar(65.0, unit='m'), ) - wf[time_of_flight.DistanceResolution] = sc.scalar(0.1, unit='m') - wf[time_of_flight.TimeResolution] = sc.scalar(250.0, unit='us') + wf[kinematics.DistanceResolution] = sc.scalar(0.1, unit='m') + wf[kinematics.TimeResolution] = sc.scalar(250.0, unit='us') - table = wf.compute(time_of_flight.TofLookupTable) + table = wf.compute(kinematics.TofLookupTable) # Close to source: early times and large spread da = table.array['distance', 2] @@ -230,21 +230,21 @@ def test_lut_workflow_computes_table_with_choppers_full_beamline_range(): def test_lut_workflow_raises_for_distance_before_source(): - wf = TofLookupTableWorkflow() - wf[time_of_flight.DiskChoppers[AnyRun]] = {} - wf[time_of_flight.SourcePosition] = sc.vector([0, 0, 10], unit='m') - wf[time_of_flight.NumberOfSimulatedNeutrons] = 100_000 - wf[time_of_flight.SimulationSeed] = 65 - wf[time_of_flight.PulseStride] = 1 + wf = LookupTableWorkflow() + wf[kinematics.DiskChoppers[AnyRun]] = {} + wf[kinematics.SourcePosition] = sc.vector([0, 0, 10], unit='m') + wf[kinematics.NumberOfSimulatedNeutrons] = 100_000 + wf[kinematics.SimulationSeed] = 65 + wf[kinematics.PulseStride] = 1 # Setting the starting point at zero will make a table that would cover a range # from -0.2m to 65.0m - wf[time_of_flight.LtotalRange] = ( + wf[kinematics.LtotalRange] = ( sc.scalar(0.0, unit='m'), sc.scalar(65.0, unit='m'), ) - wf[time_of_flight.DistanceResolution] = sc.scalar(0.1, unit='m') - wf[time_of_flight.TimeResolution] = sc.scalar(250.0, unit='us') + wf[kinematics.DistanceResolution] = sc.scalar(0.1, unit='m') + wf[kinematics.TimeResolution] = sc.scalar(250.0, unit='us') with pytest.raises(ValueError, match="Building the Tof lookup table failed"): - _ = wf.compute(time_of_flight.TofLookupTable) + _ = wf.compute(kinematics.TofLookupTable) diff --git a/packages/essreduce/tests/time_of_flight/resample_tests.py b/packages/essreduce/tests/kinematics/resample_tests.py similarity index 99% rename from packages/essreduce/tests/time_of_flight/resample_tests.py rename to packages/essreduce/tests/kinematics/resample_tests.py index 1afd1c93..241af8eb 100644 --- a/packages/essreduce/tests/time_of_flight/resample_tests.py +++ b/packages/essreduce/tests/kinematics/resample_tests.py @@ -6,7 +6,7 @@ import scipp as sc from scipp.testing import assert_identical -from ess.reduce.time_of_flight import resample +from ess.reduce.kinematics import resample class TestFindStrictlyIncreasingSections: diff --git a/packages/essreduce/tests/time_of_flight/unwrap_test.py b/packages/essreduce/tests/kinematics/unwrap_test.py similarity index 82% rename from packages/essreduce/tests/time_of_flight/unwrap_test.py rename to packages/essreduce/tests/kinematics/unwrap_test.py index 3d486ed9..21ceda9b 100644 --- a/packages/essreduce/tests/time_of_flight/unwrap_test.py +++ b/packages/essreduce/tests/kinematics/unwrap_test.py @@ -7,7 +7,7 @@ from scippneutron.conversion.graph.beamline import beamline as beamline_graph from scippneutron.conversion.graph.tof import elastic as elastic_graph -from ess.reduce import time_of_flight +from ess.reduce import kinematics from ess.reduce.nexus.types import ( AnyRun, FrameMonitor0, @@ -17,8 +17,8 @@ RawMonitor, SampleRun, ) -from ess.reduce.time_of_flight import ( - GenericTofWorkflow, +from ess.reduce.kinematics import ( + GenericWavelengthWorkflow, PulsePeriod, TofLookupTableWorkflow, fakes, @@ -29,13 +29,13 @@ def make_lut_workflow(choppers, neutrons, seed, pulse_stride): lut_wf = TofLookupTableWorkflow() - lut_wf[time_of_flight.DiskChoppers[AnyRun]] = choppers - lut_wf[time_of_flight.SourcePosition] = fakes.source_position() - lut_wf[time_of_flight.NumberOfSimulatedNeutrons] = neutrons - lut_wf[time_of_flight.SimulationSeed] = seed - lut_wf[time_of_flight.PulseStride] = pulse_stride - lut_wf[time_of_flight.SimulationResults] = lut_wf.compute( - time_of_flight.SimulationResults + lut_wf[kinematics.DiskChoppers[AnyRun]] = choppers + lut_wf[kinematics.SourcePosition] = fakes.source_position() + lut_wf[kinematics.NumberOfSimulatedNeutrons] = neutrons + lut_wf[kinematics.SimulationSeed] = seed + lut_wf[kinematics.PulseStride] = pulse_stride + lut_wf[kinematics.SimulationResults] = lut_wf.compute( + kinematics.SimulationResults ) return lut_wf @@ -75,26 +75,26 @@ def _make_workflow_event_mode( ) mon, ref = beamline.get_monitor("detector") - pl = GenericTofWorkflow(run_types=[SampleRun], monitor_types=[FrameMonitor0]) + pl = GenericWavelengthWorkflow(run_types=[SampleRun], monitor_types=[FrameMonitor0]) if detector_or_monitor == "detector": pl[NeXusDetectorName] = "detector" pl[RawDetector[SampleRun]] = mon - pl[time_of_flight.DetectorLtotal[SampleRun]] = distance + pl[kinematics.DetectorLtotal[SampleRun]] = distance else: pl[NeXusName[FrameMonitor0]] = "monitor" pl[RawMonitor[SampleRun, FrameMonitor0]] = mon - pl[time_of_flight.MonitorLtotal[SampleRun, FrameMonitor0]] = distance + pl[kinematics.MonitorLtotal[SampleRun, FrameMonitor0]] = distance - pl[time_of_flight.LookupTableRelativeErrorThreshold] = { + pl[kinematics.LookupTableRelativeErrorThreshold] = { 'detector': error_threshold, 'monitor': error_threshold, } - pl[time_of_flight.PulseStrideOffset] = pulse_stride_offset + pl[kinematics.PulseStrideOffset] = pulse_stride_offset lut_wf = lut_workflow.copy() - lut_wf[time_of_flight.LtotalRange] = distance, distance + lut_wf[kinematics.LtotalRange] = distance, distance - pl[time_of_flight.TofLookupTable] = lut_wf.compute(time_of_flight.TofLookupTable) + pl[kinematics.TofLookupTable] = lut_wf.compute(kinematics.TofLookupTable) return pl, ref @@ -116,25 +116,25 @@ def _make_workflow_histogram_mode( ).to(unit=mon.bins.coords["event_time_offset"].bins.unit) ).rename(event_time_offset=dim) - pl = GenericTofWorkflow(run_types=[SampleRun], monitor_types=[FrameMonitor0]) + pl = GenericWavelengthWorkflow(run_types=[SampleRun], monitor_types=[FrameMonitor0]) if detector_or_monitor == "detector": pl[NeXusDetectorName] = "detector" pl[RawDetector[SampleRun]] = mon - pl[time_of_flight.DetectorLtotal[SampleRun]] = distance + pl[kinematics.DetectorLtotal[SampleRun]] = distance else: pl[NeXusName[FrameMonitor0]] = "monitor" pl[RawMonitor[SampleRun, FrameMonitor0]] = mon - pl[time_of_flight.MonitorLtotal[SampleRun, FrameMonitor0]] = distance + pl[kinematics.MonitorLtotal[SampleRun, FrameMonitor0]] = distance - pl[time_of_flight.LookupTableRelativeErrorThreshold] = { + pl[kinematics.LookupTableRelativeErrorThreshold] = { 'detector': error_threshold, 'monitor': error_threshold, } lut_wf = lut_workflow.copy() - lut_wf[time_of_flight.LtotalRange] = distance, distance + lut_wf[kinematics.LtotalRange] = distance, distance - pl[time_of_flight.TofLookupTable] = lut_wf.compute(time_of_flight.TofLookupTable) + pl[kinematics.TofLookupTable] = lut_wf.compute(kinematics.TofLookupTable) return pl, ref @@ -160,7 +160,7 @@ def _validate_result_events(tofs, ref, percentile, diff_threshold, rtol): def _validate_result_histogram_mode(tofs, ref, percentile, diff_threshold, rtol): - assert "time_of_flight" not in tofs.coords + assert "kinematics" not in tofs.coords assert "frame_time" not in tofs.coords graph = {**beamline_graph(scatter=False), **elastic_graph("tof")} @@ -197,9 +197,9 @@ def test_unwrap_with_no_choppers(detector_or_monitor) -> None: ) if detector_or_monitor == "detector": - tofs = pl.compute(time_of_flight.TofDetector[SampleRun]) + tofs = pl.compute(kinematics.TofDetector[SampleRun]) else: - tofs = pl.compute(time_of_flight.TofMonitor[SampleRun, FrameMonitor0]) + tofs = pl.compute(kinematics.TofMonitor[SampleRun, FrameMonitor0]) _validate_result_events( tofs=tofs, ref=ref, percentile=96, diff_threshold=1.0, rtol=0.02 @@ -225,9 +225,9 @@ def test_standard_unwrap(dist, detector_or_monitor, lut_workflow_psc_choppers) - ) if detector_or_monitor == "detector": - tofs = pl.compute(time_of_flight.TofDetector[SampleRun]) + tofs = pl.compute(kinematics.TofDetector[SampleRun]) else: - tofs = pl.compute(time_of_flight.TofMonitor[SampleRun, FrameMonitor0]) + tofs = pl.compute(kinematics.TofMonitor[SampleRun, FrameMonitor0]) _validate_result_events( tofs=tofs, ref=ref, percentile=100, diff_threshold=0.02, rtol=0.05 @@ -239,7 +239,7 @@ def test_standard_unwrap(dist, detector_or_monitor, lut_workflow_psc_choppers) - # At 80m, events are split between the second and third pulse. # At 108m, events are split between the third and fourth pulse. @pytest.mark.parametrize("dist", [30.0, 60.0, 80.0, 108.0]) -@pytest.mark.parametrize("dim", ["time_of_flight", "tof", "frame_time"]) +@pytest.mark.parametrize("dim", ["kinematics", "tof", "frame_time"]) @pytest.mark.parametrize("detector_or_monitor", ["detector", "monitor"]) def test_standard_unwrap_histogram_mode( dist, dim, detector_or_monitor, lut_workflow_psc_choppers @@ -255,9 +255,9 @@ def test_standard_unwrap_histogram_mode( ) if detector_or_monitor == "detector": - tofs = pl.compute(time_of_flight.TofDetector[SampleRun]) + tofs = pl.compute(kinematics.TofDetector[SampleRun]) else: - tofs = pl.compute(time_of_flight.TofMonitor[SampleRun, FrameMonitor0]) + tofs = pl.compute(kinematics.TofMonitor[SampleRun, FrameMonitor0]) _validate_result_histogram_mode( tofs=tofs, ref=ref, percentile=96, diff_threshold=0.4, rtol=0.05 @@ -281,9 +281,9 @@ def test_pulse_skipping_unwrap( ) if detector_or_monitor == "detector": - tofs = pl.compute(time_of_flight.TofDetector[SampleRun]) + tofs = pl.compute(kinematics.TofDetector[SampleRun]) else: - tofs = pl.compute(time_of_flight.TofMonitor[SampleRun, FrameMonitor0]) + tofs = pl.compute(kinematics.TofMonitor[SampleRun, FrameMonitor0]) _validate_result_events( tofs=tofs, ref=ref, percentile=100, diff_threshold=0.1, rtol=0.05 @@ -310,9 +310,9 @@ def test_pulse_skipping_unwrap_180_phase_shift(detector_or_monitor) -> None: ) if detector_or_monitor == "detector": - tofs = pl.compute(time_of_flight.TofDetector[SampleRun]) + tofs = pl.compute(kinematics.TofDetector[SampleRun]) else: - tofs = pl.compute(time_of_flight.TofMonitor[SampleRun, FrameMonitor0]) + tofs = pl.compute(kinematics.TofMonitor[SampleRun, FrameMonitor0]) _validate_result_events( tofs=tofs, ref=ref, percentile=100, diff_threshold=0.1, rtol=0.05 @@ -335,9 +335,9 @@ def test_pulse_skipping_stride_offset_guess_gives_expected_result( ) if detector_or_monitor == "detector": - tofs = pl.compute(time_of_flight.TofDetector[SampleRun]) + tofs = pl.compute(kinematics.TofDetector[SampleRun]) else: - tofs = pl.compute(time_of_flight.TofMonitor[SampleRun, FrameMonitor0]) + tofs = pl.compute(kinematics.TofMonitor[SampleRun, FrameMonitor0]) _validate_result_events( tofs=tofs, ref=ref, percentile=100, diff_threshold=0.1, rtol=0.05 @@ -375,9 +375,9 @@ def test_pulse_skipping_unwrap_when_all_neutrons_arrive_after_second_pulse( ) if detector_or_monitor == "detector": - tofs = pl.compute(time_of_flight.TofDetector[SampleRun]) + tofs = pl.compute(kinematics.TofDetector[SampleRun]) else: - tofs = pl.compute(time_of_flight.TofMonitor[SampleRun, FrameMonitor0]) + tofs = pl.compute(kinematics.TofMonitor[SampleRun, FrameMonitor0]) _validate_result_events( tofs=tofs, ref=ref, percentile=100, diff_threshold=0.1, rtol=0.05 @@ -403,18 +403,18 @@ def test_pulse_skipping_unwrap_when_first_half_of_first_pulse_is_missing( lut_wf = make_lut_workflow( choppers=choppers, neutrons=300_000, seed=1234, pulse_stride=2 ) - lut_wf[time_of_flight.LtotalRange] = distance, distance + lut_wf[kinematics.LtotalRange] = distance, distance - pl = GenericTofWorkflow(run_types=[SampleRun], monitor_types=[FrameMonitor0]) + pl = GenericWavelengthWorkflow(run_types=[SampleRun], monitor_types=[FrameMonitor0]) # Skip first pulse = half of the first frame a = mon.group('event_time_zero')['event_time_zero', 1:] a.bins.coords['event_time_zero'] = sc.bins_like(a, a.coords['event_time_zero']) concatenated = a.bins.concat('event_time_zero') - pl[time_of_flight.TofLookupTable] = lut_wf.compute(time_of_flight.TofLookupTable) - pl[time_of_flight.PulseStrideOffset] = 1 # Start the stride at the second pulse - pl[time_of_flight.LookupTableRelativeErrorThreshold] = { + pl[kinematics.TofLookupTable] = lut_wf.compute(kinematics.TofLookupTable) + pl[kinematics.PulseStrideOffset] = 1 # Start the stride at the second pulse + pl[kinematics.LookupTableRelativeErrorThreshold] = { 'detector': np.inf, 'monitor': np.inf, } @@ -422,13 +422,13 @@ def test_pulse_skipping_unwrap_when_first_half_of_first_pulse_is_missing( if detector_or_monitor == "detector": pl[NeXusDetectorName] = "detector" pl[RawDetector[SampleRun]] = concatenated - pl[time_of_flight.DetectorLtotal[SampleRun]] = distance - tofs = pl.compute(time_of_flight.TofDetector[SampleRun]) + pl[kinematics.DetectorLtotal[SampleRun]] = distance + tofs = pl.compute(kinematics.TofDetector[SampleRun]) else: pl[NeXusName[FrameMonitor0]] = "monitor" pl[RawMonitor[SampleRun, FrameMonitor0]] = concatenated - pl[time_of_flight.MonitorLtotal[SampleRun, FrameMonitor0]] = distance - tofs = pl.compute(time_of_flight.TofMonitor[SampleRun, FrameMonitor0]) + pl[kinematics.MonitorLtotal[SampleRun, FrameMonitor0]] = distance + tofs = pl.compute(kinematics.TofMonitor[SampleRun, FrameMonitor0]) # Convert to wavelength graph = {**beamline_graph(scatter=False), **elastic_graph("tof")} @@ -491,9 +491,9 @@ def test_pulse_skipping_stride_3(detector_or_monitor) -> None: ) if detector_or_monitor == "detector": - tofs = pl.compute(time_of_flight.TofDetector[SampleRun]) + tofs = pl.compute(kinematics.TofDetector[SampleRun]) else: - tofs = pl.compute(time_of_flight.TofMonitor[SampleRun, FrameMonitor0]) + tofs = pl.compute(kinematics.TofMonitor[SampleRun, FrameMonitor0]) _validate_result_events( tofs=tofs, ref=ref, percentile=100, diff_threshold=0.1, rtol=0.05 @@ -505,7 +505,7 @@ def test_pulse_skipping_unwrap_histogram_mode( detector_or_monitor, lut_workflow_pulse_skipping ) -> None: pl, ref = _make_workflow_histogram_mode( - dim='time_of_flight', + dim='kinematics', distance=sc.scalar(50.0, unit="m"), choppers=fakes.pulse_skipping_choppers(), lut_workflow=lut_workflow_pulse_skipping, @@ -515,9 +515,9 @@ def test_pulse_skipping_unwrap_histogram_mode( ) if detector_or_monitor == "detector": - tofs = pl.compute(time_of_flight.TofDetector[SampleRun]) + tofs = pl.compute(kinematics.TofDetector[SampleRun]) else: - tofs = pl.compute(time_of_flight.TofMonitor[SampleRun, FrameMonitor0]) + tofs = pl.compute(kinematics.TofMonitor[SampleRun, FrameMonitor0]) _validate_result_histogram_mode( tofs=tofs, ref=ref, percentile=96, diff_threshold=0.4, rtol=0.05 @@ -548,9 +548,9 @@ def test_unwrap_int(dtype, detector_or_monitor, lut_workflow_psc_choppers) -> No pl[target] = mon if detector_or_monitor == "detector": - tofs = pl.compute(time_of_flight.TofDetector[SampleRun]) + tofs = pl.compute(kinematics.TofDetector[SampleRun]) else: - tofs = pl.compute(time_of_flight.TofMonitor[SampleRun, FrameMonitor0]) + tofs = pl.compute(kinematics.TofMonitor[SampleRun, FrameMonitor0]) _validate_result_events( tofs=tofs, ref=ref, percentile=100, diff_threshold=0.02, rtol=0.05 @@ -575,7 +575,7 @@ def test_compute_toa(): detector_or_monitor="detector", ) - toas = pl.compute(time_of_flight.ToaDetector[SampleRun]) + toas = pl.compute(kinematics.ToaDetector[SampleRun]) assert "toa" in toas.bins.coords raw = pl.compute(RawDetector[SampleRun]) @@ -602,7 +602,7 @@ def test_compute_toa_pulse_skipping(): raw = pl.compute(RawDetector[SampleRun]) - toas = pl.compute(time_of_flight.ToaDetector[SampleRun]) + toas = pl.compute(kinematics.ToaDetector[SampleRun]) assert "toa" in toas.bins.coords pulse_period = lut_wf.compute(PulsePeriod) diff --git a/packages/essreduce/tests/time_of_flight/wfm_test.py b/packages/essreduce/tests/kinematics/wfm_test.py similarity index 89% rename from packages/essreduce/tests/time_of_flight/wfm_test.py rename to packages/essreduce/tests/kinematics/wfm_test.py index 67432966..0246227a 100644 --- a/packages/essreduce/tests/time_of_flight/wfm_test.py +++ b/packages/essreduce/tests/kinematics/wfm_test.py @@ -8,9 +8,9 @@ from scippneutron.conversion.graph.beamline import beamline as beamline_graph from scippneutron.conversion.graph.tof import elastic as elastic_graph -from ess.reduce import time_of_flight +from ess.reduce import kinematics from ess.reduce.nexus.types import AnyRun, NeXusDetectorName, RawDetector, SampleRun -from ess.reduce.time_of_flight import GenericTofWorkflow, TofLookupTableWorkflow, fakes +from ess.reduce.kinematics import GenericWavelengthWorkflow, TofLookupTableWorkflow, fakes sl = pytest.importorskip("sciline") @@ -112,13 +112,13 @@ def dream_source_position() -> sc.Variable: @pytest.fixture(scope="module") def lut_workflow_dream_choppers() -> sl.Pipeline: lut_wf = TofLookupTableWorkflow() - lut_wf[time_of_flight.DiskChoppers[AnyRun]] = dream_choppers() - lut_wf[time_of_flight.SourcePosition] = dream_source_position() - lut_wf[time_of_flight.NumberOfSimulatedNeutrons] = 100_000 - lut_wf[time_of_flight.SimulationSeed] = 432 - lut_wf[time_of_flight.PulseStride] = 1 - lut_wf[time_of_flight.SimulationResults] = lut_wf.compute( - time_of_flight.SimulationResults + lut_wf[kinematics.DiskChoppers[AnyRun]] = dream_choppers() + lut_wf[kinematics.SourcePosition] = dream_source_position() + lut_wf[kinematics.NumberOfSimulatedNeutrons] = 100_000 + lut_wf[kinematics.SimulationSeed] = 432 + lut_wf[kinematics.PulseStride] = 1 + lut_wf[kinematics.SimulationResults] = lut_wf.compute( + kinematics.SimulationResults ) return lut_wf @@ -129,16 +129,16 @@ def setup_workflow( lut_workflow: sl.Pipeline, error_threshold: float = 0.1, ) -> sl.Pipeline: - pl = GenericTofWorkflow(run_types=[SampleRun], monitor_types=[]) + pl = GenericWavelengthWorkflow(run_types=[SampleRun], monitor_types=[]) pl[RawDetector[SampleRun]] = raw_data - pl[time_of_flight.DetectorLtotal[SampleRun]] = ltotal + pl[kinematics.DetectorLtotal[SampleRun]] = ltotal pl[NeXusDetectorName] = "detector" - pl[time_of_flight.LookupTableRelativeErrorThreshold] = {"detector": error_threshold} + pl[kinematics.LookupTableRelativeErrorThreshold] = {"detector": error_threshold} lut_wf = lut_workflow.copy() - lut_wf[time_of_flight.LtotalRange] = ltotal.min(), ltotal.max() + lut_wf[kinematics.LtotalRange] = ltotal.min(), ltotal.max() - pl[time_of_flight.TofLookupTable] = lut_wf.compute(time_of_flight.TofLookupTable) + pl[kinematics.TofLookupTable] = lut_wf.compute(kinematics.TofLookupTable) return pl @@ -193,7 +193,7 @@ def test_dream_wfm( raw_data=raw, ltotal=ltotal, lut_workflow=lut_workflow_dream_choppers ) - tofs = pl.compute(time_of_flight.TofDetector[SampleRun]) + tofs = pl.compute(kinematics.TofDetector[SampleRun]) # Convert to wavelength graph = {**beamline_graph(scatter=False), **elastic_graph("tof")} @@ -212,13 +212,13 @@ def test_dream_wfm( @pytest.fixture(scope="module") def lut_workflow_dream_choppers_time_overlap(): lut_wf = TofLookupTableWorkflow() - lut_wf[time_of_flight.DiskChoppers[AnyRun]] = dream_choppers_with_frame_overlap() - lut_wf[time_of_flight.SourcePosition] = dream_source_position() - lut_wf[time_of_flight.NumberOfSimulatedNeutrons] = 100_000 - lut_wf[time_of_flight.SimulationSeed] = 432 - lut_wf[time_of_flight.PulseStride] = 1 - lut_wf[time_of_flight.SimulationResults] = lut_wf.compute( - time_of_flight.SimulationResults + lut_wf[kinematics.DiskChoppers[AnyRun]] = dream_choppers_with_frame_overlap() + lut_wf[kinematics.SourcePosition] = dream_source_position() + lut_wf[kinematics.NumberOfSimulatedNeutrons] = 100_000 + lut_wf[kinematics.SimulationSeed] = 432 + lut_wf[kinematics.PulseStride] = 1 + lut_wf[kinematics.SimulationResults] = lut_wf.compute( + kinematics.SimulationResults ) return lut_wf @@ -280,7 +280,7 @@ def test_dream_wfm_with_subframe_time_overlap( error_threshold=0.01, ) - tofs = pl.compute(time_of_flight.TofDetector[SampleRun]) + tofs = pl.compute(kinematics.TofDetector[SampleRun]) # Convert to wavelength graph = {**beamline_graph(scatter=False), **elastic_graph("tof")} @@ -404,13 +404,13 @@ def v20_source_position(): @pytest.fixture(scope="module") def lut_workflow_v20_choppers(): lut_wf = TofLookupTableWorkflow() - lut_wf[time_of_flight.DiskChoppers[AnyRun]] = v20_choppers() - lut_wf[time_of_flight.SourcePosition] = v20_source_position() - lut_wf[time_of_flight.NumberOfSimulatedNeutrons] = 300_000 - lut_wf[time_of_flight.SimulationSeed] = 431 - lut_wf[time_of_flight.PulseStride] = 1 - lut_wf[time_of_flight.SimulationResults] = lut_wf.compute( - time_of_flight.SimulationResults + lut_wf[kinematics.DiskChoppers[AnyRun]] = v20_choppers() + lut_wf[kinematics.SourcePosition] = v20_source_position() + lut_wf[kinematics.NumberOfSimulatedNeutrons] = 300_000 + lut_wf[kinematics.SimulationSeed] = 431 + lut_wf[kinematics.PulseStride] = 1 + lut_wf[kinematics.SimulationResults] = lut_wf.compute( + kinematics.SimulationResults ) return lut_wf @@ -463,7 +463,7 @@ def test_v20_compute_wavelengths_from_wfm( raw_data=raw, ltotal=ltotal, lut_workflow=lut_workflow_v20_choppers ) - tofs = pl.compute(time_of_flight.TofDetector[SampleRun]) + tofs = pl.compute(kinematics.TofDetector[SampleRun]) # Convert to wavelength graph = {**beamline_graph(scatter=False), **elastic_graph("tof")} diff --git a/packages/essreduce/tests/time_of_flight/workflow_test.py b/packages/essreduce/tests/kinematics/workflow_test.py similarity index 64% rename from packages/essreduce/tests/time_of_flight/workflow_test.py rename to packages/essreduce/tests/kinematics/workflow_test.py index a9848517..b4bb4d41 100644 --- a/packages/essreduce/tests/time_of_flight/workflow_test.py +++ b/packages/essreduce/tests/kinematics/workflow_test.py @@ -7,7 +7,7 @@ import scippnexus as snx from scipp.testing import assert_identical -from ess.reduce import time_of_flight +from ess.reduce import kinematics from ess.reduce.nexus.types import ( AnyRun, DiskChoppers, @@ -18,8 +18,8 @@ RawDetector, SampleRun, ) -from ess.reduce.time_of_flight import ( - GenericTofWorkflow, +from ess.reduce.kinematics import ( + GenericWavelengthWorkflow, TofLookupTableWorkflow, fakes, ) @@ -28,7 +28,7 @@ @pytest.fixture -def workflow() -> GenericTofWorkflow: +def workflow() -> GenericWavelengthWorkflow: sizes = {'detector_number': 10} calibrated_beamline = sc.DataArray( data=sc.ones(sizes=sizes), @@ -63,9 +63,9 @@ def workflow() -> GenericTofWorkflow: ) ) - wf = GenericTofWorkflow(run_types=[SampleRun], monitor_types=[]) + wf = GenericWavelengthWorkflow(run_types=[SampleRun], monitor_types=[]) wf[NeXusDetectorName] = "detector" - wf[time_of_flight.LookupTableRelativeErrorThreshold] = {'detector': np.inf} + wf[kinematics.LookupTableRelativeErrorThreshold] = {'detector': np.inf} wf[EmptyDetector[SampleRun]] = calibrated_beamline wf[NeXusData[snx.NXdetector, SampleRun]] = nexus_data wf[Position[snx.NXsample, SampleRun]] = sc.vector([0, 0, 77], unit='m') @@ -77,13 +77,13 @@ def workflow() -> GenericTofWorkflow: def test_TofLookupTableWorkflow_can_compute_tof_lut(): wf = TofLookupTableWorkflow() wf[DiskChoppers[AnyRun]] = fakes.psc_choppers() - wf[time_of_flight.NumberOfSimulatedNeutrons] = 10_000 - wf[time_of_flight.LtotalRange] = ( + wf[kinematics.NumberOfSimulatedNeutrons] = 10_000 + wf[kinematics.LtotalRange] = ( sc.scalar(75.0, unit="m"), sc.scalar(85.0, unit="m"), ) - wf[time_of_flight.SourcePosition] = fakes.source_position() - lut = wf.compute(time_of_flight.TofLookupTable) + wf[kinematics.SourcePosition] = fakes.source_position() + lut = wf.compute(kinematics.TofLookupTable) assert lut.array is not None assert lut.distance_resolution is not None assert lut.time_resolution is not None @@ -93,54 +93,54 @@ def test_TofLookupTableWorkflow_can_compute_tof_lut(): @pytest.mark.parametrize("coord", ["tof", "wavelength"]) -def test_GenericTofWorkflow_with_tof_lut_from_tof_simulation(workflow, coord: str): +def test_GenericWavelengthWorkflow_with_tof_lut_from_tof_simulation(workflow, coord: str): # Should be able to compute DetectorData without chopper and simulation params # This contains event_time_offset (time-of-arrival). _ = workflow.compute(RawDetector[SampleRun]) # By default, the workflow tries to load the LUT from file with pytest.raises(sciline.UnsatisfiedRequirement): - _ = workflow.compute(time_of_flight.TofLookupTable) + _ = workflow.compute(kinematics.TofLookupTable) with pytest.raises(sciline.UnsatisfiedRequirement): - _ = workflow.compute(time_of_flight.TofDetector[SampleRun]) + _ = workflow.compute(kinematics.TofDetector[SampleRun]) lut_wf = TofLookupTableWorkflow() lut_wf[DiskChoppers[AnyRun]] = fakes.psc_choppers() - lut_wf[time_of_flight.NumberOfSimulatedNeutrons] = 10_000 - lut_wf[time_of_flight.LtotalRange] = ( + lut_wf[kinematics.NumberOfSimulatedNeutrons] = 10_000 + lut_wf[kinematics.LtotalRange] = ( sc.scalar(75.0, unit="m"), sc.scalar(85.0, unit="m"), ) - lut_wf[time_of_flight.SourcePosition] = fakes.source_position() - table = lut_wf.compute(time_of_flight.TofLookupTable) + lut_wf[kinematics.SourcePosition] = fakes.source_position() + table = lut_wf.compute(kinematics.TofLookupTable) - workflow[time_of_flight.TofLookupTable] = table + workflow[kinematics.TofLookupTable] = table if coord == "tof": - detector = workflow.compute(time_of_flight.TofDetector[SampleRun]) + detector = workflow.compute(kinematics.TofDetector[SampleRun]) assert 'tof' in detector.bins.coords else: - detector = workflow.compute(time_of_flight.WavelengthDetector[SampleRun]) + detector = workflow.compute(kinematics.WavelengthDetector[SampleRun]) assert 'wavelength' in detector.bins.coords @pytest.mark.parametrize("coord", ["tof", "wavelength"]) -def test_GenericTofWorkflow_with_tof_lut_from_file( +def test_GenericWavelengthWorkflow_with_tof_lut_from_file( workflow, tmp_path: pytest.TempPathFactory, coord: str ): lut_wf = TofLookupTableWorkflow() lut_wf[DiskChoppers[AnyRun]] = fakes.psc_choppers() - lut_wf[time_of_flight.NumberOfSimulatedNeutrons] = 10_000 - lut_wf[time_of_flight.LtotalRange] = ( + lut_wf[kinematics.NumberOfSimulatedNeutrons] = 10_000 + lut_wf[kinematics.LtotalRange] = ( sc.scalar(75.0, unit="m"), sc.scalar(85.0, unit="m"), ) - lut_wf[time_of_flight.SourcePosition] = fakes.source_position() - lut = lut_wf.compute(time_of_flight.TofLookupTable) + lut_wf[kinematics.SourcePosition] = fakes.source_position() + lut = lut_wf.compute(kinematics.TofLookupTable) lut.save_hdf5(filename=tmp_path / "lut.h5") - workflow[time_of_flight.TofLookupTableFilename] = (tmp_path / "lut.h5").as_posix() + workflow[kinematics.TofLookupTableFilename] = (tmp_path / "lut.h5").as_posix() - loaded_lut = workflow.compute(time_of_flight.TofLookupTable) + loaded_lut = workflow.compute(kinematics.TofLookupTable) assert_identical(lut.array, loaded_lut.array) assert_identical(lut.pulse_period, loaded_lut.pulse_period) assert lut.pulse_stride == loaded_lut.pulse_stride @@ -149,25 +149,25 @@ def test_GenericTofWorkflow_with_tof_lut_from_file( assert_identical(lut.choppers, loaded_lut.choppers) if coord == "tof": - detector = workflow.compute(time_of_flight.TofDetector[SampleRun]) + detector = workflow.compute(kinematics.TofDetector[SampleRun]) assert 'tof' in detector.bins.coords else: - detector = workflow.compute(time_of_flight.WavelengthDetector[SampleRun]) + detector = workflow.compute(kinematics.WavelengthDetector[SampleRun]) assert 'wavelength' in detector.bins.coords -def test_GenericTofWorkflow_with_tof_lut_from_file_old_format( +def test_GenericWavelengthWorkflow_with_tof_lut_from_file_old_format( workflow, tmp_path: pytest.TempPathFactory ): lut_wf = TofLookupTableWorkflow() lut_wf[DiskChoppers[AnyRun]] = fakes.psc_choppers() - lut_wf[time_of_flight.NumberOfSimulatedNeutrons] = 10_000 - lut_wf[time_of_flight.LtotalRange] = ( + lut_wf[kinematics.NumberOfSimulatedNeutrons] = 10_000 + lut_wf[kinematics.LtotalRange] = ( sc.scalar(75.0, unit="m"), sc.scalar(85.0, unit="m"), ) - lut_wf[time_of_flight.SourcePosition] = fakes.source_position() - lut = lut_wf.compute(time_of_flight.TofLookupTable) + lut_wf[kinematics.SourcePosition] = fakes.source_position() + lut = lut_wf.compute(kinematics.TofLookupTable) old_lut = sc.DataArray( data=lut.array.data, coords={ @@ -181,8 +181,8 @@ def test_GenericTofWorkflow_with_tof_lut_from_file_old_format( ) old_lut.save_hdf5(filename=tmp_path / "lut.h5") - workflow[time_of_flight.TofLookupTableFilename] = (tmp_path / "lut.h5").as_posix() - loaded_lut = workflow.compute(time_of_flight.TofLookupTable) + workflow[kinematics.TofLookupTableFilename] = (tmp_path / "lut.h5").as_posix() + loaded_lut = workflow.compute(kinematics.TofLookupTable) assert_identical(lut.array, loaded_lut.array) assert_identical(lut.pulse_period, loaded_lut.pulse_period) assert lut.pulse_stride == loaded_lut.pulse_stride @@ -190,49 +190,49 @@ def test_GenericTofWorkflow_with_tof_lut_from_file_old_format( assert_identical(lut.time_resolution, loaded_lut.time_resolution) assert loaded_lut.choppers is None # No chopper info in old format - detector = workflow.compute(time_of_flight.TofDetector[SampleRun]) + detector = workflow.compute(kinematics.TofDetector[SampleRun]) assert 'tof' in detector.bins.coords -def test_GenericTofWorkflow_with_tof_lut_from_tof_simulation_using_alias(workflow): +def test_GenericWavelengthWorkflow_with_tof_lut_from_tof_simulation_using_alias(workflow): # Should be able to compute DetectorData without chopper and simulation params # This contains event_time_offset (time-of-arrival). _ = workflow.compute(RawDetector[SampleRun]) lut_wf = TofLookupTableWorkflow() lut_wf[DiskChoppers[AnyRun]] = fakes.psc_choppers() - lut_wf[time_of_flight.NumberOfSimulatedNeutrons] = 10_000 - lut_wf[time_of_flight.LtotalRange] = ( + lut_wf[kinematics.NumberOfSimulatedNeutrons] = 10_000 + lut_wf[kinematics.LtotalRange] = ( sc.scalar(75.0, unit="m"), sc.scalar(85.0, unit="m"), ) - lut_wf[time_of_flight.SourcePosition] = fakes.source_position() - table = lut_wf.compute(time_of_flight.TimeOfFlightLookupTable) + lut_wf[kinematics.SourcePosition] = fakes.source_position() + table = lut_wf.compute(kinematics.TimeOfFlightLookupTable) - workflow[time_of_flight.TimeOfFlightLookupTable] = table + workflow[kinematics.TimeOfFlightLookupTable] = table # Should now be able to compute DetectorData with chopper and simulation params - detector = workflow.compute(time_of_flight.TofDetector[SampleRun]) + detector = workflow.compute(kinematics.TofDetector[SampleRun]) assert 'tof' in detector.bins.coords -def test_GenericTofWorkflow_with_tof_lut_from_file_using_alias( +def test_GenericWavelengthWorkflow_with_tof_lut_from_file_using_alias( workflow, tmp_path: pytest.TempPathFactory ): lut_wf = TofLookupTableWorkflow() lut_wf[DiskChoppers[AnyRun]] = fakes.psc_choppers() - lut_wf[time_of_flight.NumberOfSimulatedNeutrons] = 10_000 - lut_wf[time_of_flight.LtotalRange] = ( + lut_wf[kinematics.NumberOfSimulatedNeutrons] = 10_000 + lut_wf[kinematics.LtotalRange] = ( sc.scalar(75.0, unit="m"), sc.scalar(85.0, unit="m"), ) - lut_wf[time_of_flight.SourcePosition] = fakes.source_position() - lut = lut_wf.compute(time_of_flight.TimeOfFlightLookupTable) + lut_wf[kinematics.SourcePosition] = fakes.source_position() + lut = lut_wf.compute(kinematics.TimeOfFlightLookupTable) lut.save_hdf5(filename=tmp_path / "lut.h5") - workflow[time_of_flight.TimeOfFlightLookupTableFilename] = ( + workflow[kinematics.TimeOfFlightLookupTableFilename] = ( tmp_path / "lut.h5" ).as_posix() - loaded_lut = workflow.compute(time_of_flight.TimeOfFlightLookupTable) + loaded_lut = workflow.compute(kinematics.TimeOfFlightLookupTable) assert_identical(lut.array, loaded_lut.array) assert_identical(lut.pulse_period, loaded_lut.pulse_period) assert lut.pulse_stride == loaded_lut.pulse_stride @@ -240,30 +240,30 @@ def test_GenericTofWorkflow_with_tof_lut_from_file_using_alias( assert_identical(lut.time_resolution, loaded_lut.time_resolution) assert_identical(lut.choppers, loaded_lut.choppers) - detector = workflow.compute(time_of_flight.TofDetector[SampleRun]) + detector = workflow.compute(kinematics.TofDetector[SampleRun]) assert 'tof' in detector.bins.coords @pytest.mark.parametrize("coord", ["tof", "wavelength"]) -def test_GenericTofWorkflow_assigns_Ltotal_coordinate(workflow, coord): +def test_GenericWavelengthWorkflow_assigns_Ltotal_coordinate(workflow, coord): raw = workflow.compute(RawDetector[SampleRun]) assert "Ltotal" not in raw.coords lut_wf = TofLookupTableWorkflow() lut_wf[DiskChoppers[AnyRun]] = fakes.psc_choppers() - lut_wf[time_of_flight.NumberOfSimulatedNeutrons] = 10_000 - lut_wf[time_of_flight.LtotalRange] = ( + lut_wf[kinematics.NumberOfSimulatedNeutrons] = 10_000 + lut_wf[kinematics.LtotalRange] = ( sc.scalar(20.0, unit="m"), sc.scalar(100.0, unit="m"), ) - lut_wf[time_of_flight.SourcePosition] = fakes.source_position() - table = lut_wf.compute(time_of_flight.TofLookupTable) - workflow[time_of_flight.TofLookupTable] = table + lut_wf[kinematics.SourcePosition] = fakes.source_position() + table = lut_wf.compute(kinematics.TofLookupTable) + workflow[kinematics.TofLookupTable] = table if coord == "tof": - result = workflow.compute(time_of_flight.TofDetector[SampleRun]) + result = workflow.compute(kinematics.TofDetector[SampleRun]) else: - result = workflow.compute(time_of_flight.WavelengthDetector[SampleRun]) + result = workflow.compute(kinematics.WavelengthDetector[SampleRun]) assert "Ltotal" in result.coords From a87b8eeefb1a3b4872b39ba26574c8682756384b Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Wed, 4 Mar 2026 23:25:01 +0100 Subject: [PATCH 07/24] more name fixing --- .../src/ess/reduce/kinematics/lut.py | 2 +- .../ess/reduce/kinematics/to_wavelength.py | 5 +- .../essreduce/tests/kinematics/lut_test.py | 14 +- .../essreduce/tests/kinematics/unwrap_test.py | 124 +++++++-------- .../essreduce/tests/kinematics/wfm_test.py | 8 +- .../tests/kinematics/workflow_test.py | 146 ++++-------------- 6 files changed, 105 insertions(+), 194 deletions(-) diff --git a/packages/essreduce/src/ess/reduce/kinematics/lut.py b/packages/essreduce/src/ess/reduce/kinematics/lut.py index abe1cc33..dab74663 100644 --- a/packages/essreduce/src/ess/reduce/kinematics/lut.py +++ b/packages/essreduce/src/ess/reduce/kinematics/lut.py @@ -323,7 +323,7 @@ def make_wavelength_lookup_table( if simulation_reading is None: closest = sorted_simulation_results[-1] raise ValueError( - "Building the Tof lookup table failed: the requested position " + "Building the lookup table failed: the requested position " f"{dist.value} {dist.unit} is before the component with the lowest " "distance in the simulation. The first component in the beamline " f"has distance {closest.distance.value} {closest.distance.unit}." diff --git a/packages/essreduce/src/ess/reduce/kinematics/to_wavelength.py b/packages/essreduce/src/ess/reduce/kinematics/to_wavelength.py index f759fa05..6b228e5d 100644 --- a/packages/essreduce/src/ess/reduce/kinematics/to_wavelength.py +++ b/packages/essreduce/src/ess/reduce/kinematics/to_wavelength.py @@ -112,7 +112,8 @@ def _compute_wavelength_histogram( ) -> sc.DataArray: # In NeXus, 'time_of_flight' is the canonical name in NXmonitor, but in some files, # it may be called 'tof' or 'frame_time'. - key = next(iter(set(da.coords.keys()) & {"time_of_flight", "tof", "frame_time"})) + possible_names = {"time_of_flight", "tof", "frame_time"} + key = next(iter(set(da.coords.keys()) & possible_names)) raw_eto = da.coords[key].to(dtype=float, copy=False) eto_unit = raw_eto.unit pulse_period = lookup.pulse_period.to(unit=eto_unit) @@ -141,7 +142,7 @@ def _compute_wavelength_histogram( ) return rebinned.assign_coords(wavelength=wavs).drop_coords( - list({key} & {"time_of_flight", "frame_time"}) + list({key} & possible_names) ) diff --git a/packages/essreduce/tests/kinematics/lut_test.py b/packages/essreduce/tests/kinematics/lut_test.py index 66356966..25cc5bbf 100644 --- a/packages/essreduce/tests/kinematics/lut_test.py +++ b/packages/essreduce/tests/kinematics/lut_test.py @@ -27,7 +27,7 @@ def test_lut_workflow_computes_table(): wf[kinematics.DistanceResolution] = dres wf[kinematics.TimeResolution] = tres - table = wf.compute(kinematics.TofLookupTable) + table = wf.compute(kinematics.LookupTable) assert table.array.coords['distance'].min() < lmin assert table.array.coords['distance'].max() > lmax @@ -56,7 +56,7 @@ def test_lut_workflow_pulse_skipping(): wf[kinematics.DistanceResolution] = dres wf[kinematics.TimeResolution] = tres - table = wf.compute(kinematics.TofLookupTable) + table = wf.compute(kinematics.LookupTable) assert table.array.coords['event_time_offset'].max() == 2 * sc.scalar( 1 / 14, unit='s' @@ -79,7 +79,7 @@ def test_lut_workflow_non_exact_distance_range(): wf[kinematics.DistanceResolution] = dres wf[kinematics.TimeResolution] = tres - table = wf.compute(kinematics.TofLookupTable) + table = wf.compute(kinematics.LookupTable) assert table.array.coords['distance'].min() < lmin assert table.array.coords['distance'].max() > lmax @@ -160,7 +160,7 @@ def test_lut_workflow_computes_table_with_choppers(): wf[kinematics.DistanceResolution] = sc.scalar(0.1, unit='m') wf[kinematics.TimeResolution] = sc.scalar(250.0, unit='us') - table = wf.compute(kinematics.TofLookupTable) + table = wf.compute(kinematics.LookupTable) # At low distance, the rays are more focussed low_dist = table.array['distance', 2] @@ -194,7 +194,7 @@ def test_lut_workflow_computes_table_with_choppers_full_beamline_range(): wf[kinematics.DistanceResolution] = sc.scalar(0.1, unit='m') wf[kinematics.TimeResolution] = sc.scalar(250.0, unit='us') - table = wf.compute(kinematics.TofLookupTable) + table = wf.compute(kinematics.LookupTable) # Close to source: early times and large spread da = table.array['distance', 2] @@ -246,5 +246,5 @@ def test_lut_workflow_raises_for_distance_before_source(): wf[kinematics.DistanceResolution] = sc.scalar(0.1, unit='m') wf[kinematics.TimeResolution] = sc.scalar(250.0, unit='us') - with pytest.raises(ValueError, match="Building the Tof lookup table failed"): - _ = wf.compute(kinematics.TofLookupTable) + with pytest.raises(ValueError, match="Building the lookup table failed"): + _ = wf.compute(kinematics.LookupTable) diff --git a/packages/essreduce/tests/kinematics/unwrap_test.py b/packages/essreduce/tests/kinematics/unwrap_test.py index 21ceda9b..826ebec4 100644 --- a/packages/essreduce/tests/kinematics/unwrap_test.py +++ b/packages/essreduce/tests/kinematics/unwrap_test.py @@ -4,10 +4,14 @@ import pytest import scipp as sc from scippneutron.chopper import DiskChopper -from scippneutron.conversion.graph.beamline import beamline as beamline_graph -from scippneutron.conversion.graph.tof import elastic as elastic_graph from ess.reduce import kinematics +from ess.reduce.kinematics import ( + GenericWavelengthWorkflow, + LookupTableWorkflow, + PulsePeriod, + fakes, +) from ess.reduce.nexus.types import ( AnyRun, FrameMonitor0, @@ -17,26 +21,18 @@ RawMonitor, SampleRun, ) -from ess.reduce.kinematics import ( - GenericWavelengthWorkflow, - PulsePeriod, - TofLookupTableWorkflow, - fakes, -) sl = pytest.importorskip("sciline") def make_lut_workflow(choppers, neutrons, seed, pulse_stride): - lut_wf = TofLookupTableWorkflow() + lut_wf = LookupTableWorkflow() lut_wf[kinematics.DiskChoppers[AnyRun]] = choppers lut_wf[kinematics.SourcePosition] = fakes.source_position() lut_wf[kinematics.NumberOfSimulatedNeutrons] = neutrons lut_wf[kinematics.SimulationSeed] = seed lut_wf[kinematics.PulseStride] = pulse_stride - lut_wf[kinematics.SimulationResults] = lut_wf.compute( - kinematics.SimulationResults - ) + lut_wf[kinematics.SimulationResults] = lut_wf.compute(kinematics.SimulationResults) return lut_wf @@ -94,7 +90,7 @@ def _make_workflow_event_mode( lut_wf = lut_workflow.copy() lut_wf[kinematics.LtotalRange] = distance, distance - pl[kinematics.TofLookupTable] = lut_wf.compute(kinematics.TofLookupTable) + pl[kinematics.LookupTable] = lut_wf.compute(kinematics.LookupTable) return pl, ref @@ -134,17 +130,16 @@ def _make_workflow_histogram_mode( lut_wf = lut_workflow.copy() lut_wf[kinematics.LtotalRange] = distance, distance - pl[kinematics.TofLookupTable] = lut_wf.compute(kinematics.TofLookupTable) + pl[kinematics.LookupTable] = lut_wf.compute(kinematics.LookupTable) return pl, ref -def _validate_result_events(tofs, ref, percentile, diff_threshold, rtol): - assert "event_time_offset" not in tofs.coords +def _validate_result_events(wavs, ref, percentile, diff_threshold, rtol): + assert "event_time_offset" not in wavs.coords + assert "tof" not in wavs.coords - # Convert to wavelength - graph = {**beamline_graph(scatter=False), **elastic_graph("tof")} - wavs = tofs.transform_coords("wavelength", graph=graph).bins.concat().value + wavs = wavs.bins.concat().value diff = abs( (wavs.coords["wavelength"] - ref.coords["wavelength"]) @@ -159,12 +154,13 @@ def _validate_result_events(tofs, ref, percentile, diff_threshold, rtol): assert sc.isclose(ref.data.sum(), nevents, rtol=sc.scalar(rtol)) -def _validate_result_histogram_mode(tofs, ref, percentile, diff_threshold, rtol): - assert "kinematics" not in tofs.coords - assert "frame_time" not in tofs.coords +def _validate_result_histogram_mode(wavs, ref, percentile, diff_threshold, rtol): + assert "tof" not in wavs.coords + assert "time_of_flight" not in wavs.coords + assert "frame_time" not in wavs.coords - graph = {**beamline_graph(scatter=False), **elastic_graph("tof")} - wavs = tofs.transform_coords("wavelength", graph=graph) + # graph = {**beamline_graph(scatter=False), **elastic_graph("tof")} + # wavs = tofs.transform_coords("wavelength", graph=graph) ref = ref.hist(wavelength=wavs.coords["wavelength"]) # We divide by the maximum to avoid large relative differences at the edges of the # frames where the counts are low. @@ -172,7 +168,7 @@ def _validate_result_histogram_mode(tofs, ref, percentile, diff_threshold, rtol) assert np.nanpercentile(diff.values, percentile) < diff_threshold # Make sure that we have not lost too many events (we lose some because they may be # given a NaN tof from the lookup). - assert sc.isclose(ref.data.nansum(), tofs.data.nansum(), rtol=sc.scalar(rtol)) + assert sc.isclose(ref.data.nansum(), wavs.data.nansum(), rtol=sc.scalar(rtol)) @pytest.mark.parametrize("detector_or_monitor", ["detector", "monitor"]) @@ -197,12 +193,12 @@ def test_unwrap_with_no_choppers(detector_or_monitor) -> None: ) if detector_or_monitor == "detector": - tofs = pl.compute(kinematics.TofDetector[SampleRun]) + wavs = pl.compute(kinematics.WavelengthDetector[SampleRun]) else: - tofs = pl.compute(kinematics.TofMonitor[SampleRun, FrameMonitor0]) + wavs = pl.compute(kinematics.WavelengthMonitor[SampleRun, FrameMonitor0]) _validate_result_events( - tofs=tofs, ref=ref, percentile=96, diff_threshold=1.0, rtol=0.02 + wavs=wavs, ref=ref, percentile=96, diff_threshold=1.0, rtol=0.02 ) @@ -225,12 +221,12 @@ def test_standard_unwrap(dist, detector_or_monitor, lut_workflow_psc_choppers) - ) if detector_or_monitor == "detector": - tofs = pl.compute(kinematics.TofDetector[SampleRun]) + wavs = pl.compute(kinematics.WavelengthDetector[SampleRun]) else: - tofs = pl.compute(kinematics.TofMonitor[SampleRun, FrameMonitor0]) + wavs = pl.compute(kinematics.WavelengthMonitor[SampleRun, FrameMonitor0]) _validate_result_events( - tofs=tofs, ref=ref, percentile=100, diff_threshold=0.02, rtol=0.05 + wavs=wavs, ref=ref, percentile=100, diff_threshold=0.02, rtol=0.05 ) @@ -239,7 +235,7 @@ def test_standard_unwrap(dist, detector_or_monitor, lut_workflow_psc_choppers) - # At 80m, events are split between the second and third pulse. # At 108m, events are split between the third and fourth pulse. @pytest.mark.parametrize("dist", [30.0, 60.0, 80.0, 108.0]) -@pytest.mark.parametrize("dim", ["kinematics", "tof", "frame_time"]) +@pytest.mark.parametrize("dim", ["time_of_flight", "tof", "frame_time"]) @pytest.mark.parametrize("detector_or_monitor", ["detector", "monitor"]) def test_standard_unwrap_histogram_mode( dist, dim, detector_or_monitor, lut_workflow_psc_choppers @@ -255,12 +251,12 @@ def test_standard_unwrap_histogram_mode( ) if detector_or_monitor == "detector": - tofs = pl.compute(kinematics.TofDetector[SampleRun]) + wavs = pl.compute(kinematics.WavelengthDetector[SampleRun]) else: - tofs = pl.compute(kinematics.TofMonitor[SampleRun, FrameMonitor0]) + wavs = pl.compute(kinematics.WavelengthMonitor[SampleRun, FrameMonitor0]) _validate_result_histogram_mode( - tofs=tofs, ref=ref, percentile=96, diff_threshold=0.4, rtol=0.05 + wavs=wavs, ref=ref, percentile=96, diff_threshold=0.4, rtol=0.05 ) @@ -281,12 +277,12 @@ def test_pulse_skipping_unwrap( ) if detector_or_monitor == "detector": - tofs = pl.compute(kinematics.TofDetector[SampleRun]) + wavs = pl.compute(kinematics.WavelengthDetector[SampleRun]) else: - tofs = pl.compute(kinematics.TofMonitor[SampleRun, FrameMonitor0]) + wavs = pl.compute(kinematics.WavelengthMonitor[SampleRun, FrameMonitor0]) _validate_result_events( - tofs=tofs, ref=ref, percentile=100, diff_threshold=0.1, rtol=0.05 + wavs=wavs, ref=ref, percentile=100, diff_threshold=0.1, rtol=0.05 ) @@ -310,12 +306,12 @@ def test_pulse_skipping_unwrap_180_phase_shift(detector_or_monitor) -> None: ) if detector_or_monitor == "detector": - tofs = pl.compute(kinematics.TofDetector[SampleRun]) + wavs = pl.compute(kinematics.WavelengthDetector[SampleRun]) else: - tofs = pl.compute(kinematics.TofMonitor[SampleRun, FrameMonitor0]) + wavs = pl.compute(kinematics.WavelengthMonitor[SampleRun, FrameMonitor0]) _validate_result_events( - tofs=tofs, ref=ref, percentile=100, diff_threshold=0.1, rtol=0.05 + wavs=wavs, ref=ref, percentile=100, diff_threshold=0.1, rtol=0.05 ) @@ -335,12 +331,12 @@ def test_pulse_skipping_stride_offset_guess_gives_expected_result( ) if detector_or_monitor == "detector": - tofs = pl.compute(kinematics.TofDetector[SampleRun]) + wavs = pl.compute(kinematics.WavelengthDetector[SampleRun]) else: - tofs = pl.compute(kinematics.TofMonitor[SampleRun, FrameMonitor0]) + wavs = pl.compute(kinematics.WavelengthMonitor[SampleRun, FrameMonitor0]) _validate_result_events( - tofs=tofs, ref=ref, percentile=100, diff_threshold=0.1, rtol=0.05 + wavs=wavs, ref=ref, percentile=100, diff_threshold=0.1, rtol=0.05 ) @@ -375,12 +371,12 @@ def test_pulse_skipping_unwrap_when_all_neutrons_arrive_after_second_pulse( ) if detector_or_monitor == "detector": - tofs = pl.compute(kinematics.TofDetector[SampleRun]) + wavs = pl.compute(kinematics.WavelengthDetector[SampleRun]) else: - tofs = pl.compute(kinematics.TofMonitor[SampleRun, FrameMonitor0]) + wavs = pl.compute(kinematics.WavelengthMonitor[SampleRun, FrameMonitor0]) _validate_result_events( - tofs=tofs, ref=ref, percentile=100, diff_threshold=0.1, rtol=0.05 + wavs=wavs, ref=ref, percentile=100, diff_threshold=0.1, rtol=0.05 ) @@ -412,7 +408,7 @@ def test_pulse_skipping_unwrap_when_first_half_of_first_pulse_is_missing( a.bins.coords['event_time_zero'] = sc.bins_like(a, a.coords['event_time_zero']) concatenated = a.bins.concat('event_time_zero') - pl[kinematics.TofLookupTable] = lut_wf.compute(kinematics.TofLookupTable) + pl[kinematics.LookupTable] = lut_wf.compute(kinematics.LookupTable) pl[kinematics.PulseStrideOffset] = 1 # Start the stride at the second pulse pl[kinematics.LookupTableRelativeErrorThreshold] = { 'detector': np.inf, @@ -423,16 +419,16 @@ def test_pulse_skipping_unwrap_when_first_half_of_first_pulse_is_missing( pl[NeXusDetectorName] = "detector" pl[RawDetector[SampleRun]] = concatenated pl[kinematics.DetectorLtotal[SampleRun]] = distance - tofs = pl.compute(kinematics.TofDetector[SampleRun]) + wavs = pl.compute(kinematics.WavelengthDetector[SampleRun]) else: pl[NeXusName[FrameMonitor0]] = "monitor" pl[RawMonitor[SampleRun, FrameMonitor0]] = concatenated pl[kinematics.MonitorLtotal[SampleRun, FrameMonitor0]] = distance - tofs = pl.compute(kinematics.TofMonitor[SampleRun, FrameMonitor0]) + wavs = pl.compute(kinematics.WavelengthMonitor[SampleRun, FrameMonitor0]) # Convert to wavelength - graph = {**beamline_graph(scatter=False), **elastic_graph("tof")} - wavs = tofs.transform_coords("wavelength", graph=graph).bins.concat().value + # graph = {**beamline_graph(scatter=False), **elastic_graph("tof")} + wavs = wavs.bins.concat().value # Bin the events in toa starting from the pulse period to skip the first pulse. ref = ( ref.bin( @@ -459,14 +455,14 @@ def test_pulse_skipping_unwrap_when_first_half_of_first_pulse_is_missing( # All errors should be small assert np.nanpercentile(diff.values, 100) < 0.05 # Make sure that we have not lost too many events (we lose some because they may be - # given a NaN tof from the lookup). + # given a NaN wavelength from the lookup). if detector_or_monitor == "detector": target = RawDetector[SampleRun] else: target = RawMonitor[SampleRun, FrameMonitor0] assert sc.isclose( pl.compute(target).data.nansum(), - tofs.data.nansum(), + wavs.data.nansum(), rtol=sc.scalar(1.0e-3), ) @@ -491,12 +487,12 @@ def test_pulse_skipping_stride_3(detector_or_monitor) -> None: ) if detector_or_monitor == "detector": - tofs = pl.compute(kinematics.TofDetector[SampleRun]) + wavs = pl.compute(kinematics.WavelengthDetector[SampleRun]) else: - tofs = pl.compute(kinematics.TofMonitor[SampleRun, FrameMonitor0]) + wavs = pl.compute(kinematics.WavelengthMonitor[SampleRun, FrameMonitor0]) _validate_result_events( - tofs=tofs, ref=ref, percentile=100, diff_threshold=0.1, rtol=0.05 + wavs=wavs, ref=ref, percentile=100, diff_threshold=0.1, rtol=0.05 ) @@ -505,7 +501,7 @@ def test_pulse_skipping_unwrap_histogram_mode( detector_or_monitor, lut_workflow_pulse_skipping ) -> None: pl, ref = _make_workflow_histogram_mode( - dim='kinematics', + dim='time_of_flight', distance=sc.scalar(50.0, unit="m"), choppers=fakes.pulse_skipping_choppers(), lut_workflow=lut_workflow_pulse_skipping, @@ -515,12 +511,12 @@ def test_pulse_skipping_unwrap_histogram_mode( ) if detector_or_monitor == "detector": - tofs = pl.compute(kinematics.TofDetector[SampleRun]) + wavs = pl.compute(kinematics.WavelengthDetector[SampleRun]) else: - tofs = pl.compute(kinematics.TofMonitor[SampleRun, FrameMonitor0]) + wavs = pl.compute(kinematics.WavelengthMonitor[SampleRun, FrameMonitor0]) _validate_result_histogram_mode( - tofs=tofs, ref=ref, percentile=96, diff_threshold=0.4, rtol=0.05 + wavs=wavs, ref=ref, percentile=96, diff_threshold=0.4, rtol=0.05 ) @@ -548,12 +544,12 @@ def test_unwrap_int(dtype, detector_or_monitor, lut_workflow_psc_choppers) -> No pl[target] = mon if detector_or_monitor == "detector": - tofs = pl.compute(kinematics.TofDetector[SampleRun]) + wavs = pl.compute(kinematics.WavelengthDetector[SampleRun]) else: - tofs = pl.compute(kinematics.TofMonitor[SampleRun, FrameMonitor0]) + wavs = pl.compute(kinematics.WavelengthMonitor[SampleRun, FrameMonitor0]) _validate_result_events( - tofs=tofs, ref=ref, percentile=100, diff_threshold=0.02, rtol=0.05 + wavs=wavs, ref=ref, percentile=100, diff_threshold=0.02, rtol=0.05 ) diff --git a/packages/essreduce/tests/kinematics/wfm_test.py b/packages/essreduce/tests/kinematics/wfm_test.py index 0246227a..6b856a10 100644 --- a/packages/essreduce/tests/kinematics/wfm_test.py +++ b/packages/essreduce/tests/kinematics/wfm_test.py @@ -10,7 +10,7 @@ from ess.reduce import kinematics from ess.reduce.nexus.types import AnyRun, NeXusDetectorName, RawDetector, SampleRun -from ess.reduce.kinematics import GenericWavelengthWorkflow, TofLookupTableWorkflow, fakes +from ess.reduce.kinematics import GenericWavelengthWorkflow, LookupTableWorkflow, fakes sl = pytest.importorskip("sciline") @@ -111,7 +111,7 @@ def dream_source_position() -> sc.Variable: @pytest.fixture(scope="module") def lut_workflow_dream_choppers() -> sl.Pipeline: - lut_wf = TofLookupTableWorkflow() + lut_wf = LookupTableWorkflow() lut_wf[kinematics.DiskChoppers[AnyRun]] = dream_choppers() lut_wf[kinematics.SourcePosition] = dream_source_position() lut_wf[kinematics.NumberOfSimulatedNeutrons] = 100_000 @@ -211,7 +211,7 @@ def test_dream_wfm( @pytest.fixture(scope="module") def lut_workflow_dream_choppers_time_overlap(): - lut_wf = TofLookupTableWorkflow() + lut_wf = LookupTableWorkflow() lut_wf[kinematics.DiskChoppers[AnyRun]] = dream_choppers_with_frame_overlap() lut_wf[kinematics.SourcePosition] = dream_source_position() lut_wf[kinematics.NumberOfSimulatedNeutrons] = 100_000 @@ -403,7 +403,7 @@ def v20_source_position(): @pytest.fixture(scope="module") def lut_workflow_v20_choppers(): - lut_wf = TofLookupTableWorkflow() + lut_wf = LookupTableWorkflow() lut_wf[kinematics.DiskChoppers[AnyRun]] = v20_choppers() lut_wf[kinematics.SourcePosition] = v20_source_position() lut_wf[kinematics.NumberOfSimulatedNeutrons] = 300_000 diff --git a/packages/essreduce/tests/kinematics/workflow_test.py b/packages/essreduce/tests/kinematics/workflow_test.py index b4bb4d41..1afc84c7 100644 --- a/packages/essreduce/tests/kinematics/workflow_test.py +++ b/packages/essreduce/tests/kinematics/workflow_test.py @@ -8,6 +8,11 @@ from scipp.testing import assert_identical from ess.reduce import kinematics +from ess.reduce.kinematics import ( + GenericWavelengthWorkflow, + LookupTableWorkflow, + fakes, +) from ess.reduce.nexus.types import ( AnyRun, DiskChoppers, @@ -18,11 +23,6 @@ RawDetector, SampleRun, ) -from ess.reduce.kinematics import ( - GenericWavelengthWorkflow, - TofLookupTableWorkflow, - fakes, -) sl = pytest.importorskip("sciline") @@ -74,8 +74,8 @@ def workflow() -> GenericWavelengthWorkflow: return wf -def test_TofLookupTableWorkflow_can_compute_tof_lut(): - wf = TofLookupTableWorkflow() +def test_LookupTableWorkflow_can_compute_lut(): + wf = LookupTableWorkflow() wf[DiskChoppers[AnyRun]] = fakes.psc_choppers() wf[kinematics.NumberOfSimulatedNeutrons] = 10_000 wf[kinematics.LtotalRange] = ( @@ -83,7 +83,7 @@ def test_TofLookupTableWorkflow_can_compute_tof_lut(): sc.scalar(85.0, unit="m"), ) wf[kinematics.SourcePosition] = fakes.source_position() - lut = wf.compute(kinematics.TofLookupTable) + lut = wf.compute(kinematics.LookupTable) assert lut.array is not None assert lut.distance_resolution is not None assert lut.time_resolution is not None @@ -92,18 +92,17 @@ def test_TofLookupTableWorkflow_can_compute_tof_lut(): assert lut.choppers is not None -@pytest.mark.parametrize("coord", ["tof", "wavelength"]) -def test_GenericWavelengthWorkflow_with_tof_lut_from_tof_simulation(workflow, coord: str): +def test_GenericWavelengthWorkflow_with_lut_from_tof_simulation(workflow): # Should be able to compute DetectorData without chopper and simulation params # This contains event_time_offset (time-of-arrival). _ = workflow.compute(RawDetector[SampleRun]) # By default, the workflow tries to load the LUT from file with pytest.raises(sciline.UnsatisfiedRequirement): - _ = workflow.compute(kinematics.TofLookupTable) + _ = workflow.compute(kinematics.LookupTable) with pytest.raises(sciline.UnsatisfiedRequirement): _ = workflow.compute(kinematics.TofDetector[SampleRun]) - lut_wf = TofLookupTableWorkflow() + lut_wf = LookupTableWorkflow() lut_wf[DiskChoppers[AnyRun]] = fakes.psc_choppers() lut_wf[kinematics.NumberOfSimulatedNeutrons] = 10_000 lut_wf[kinematics.LtotalRange] = ( @@ -111,23 +110,17 @@ def test_GenericWavelengthWorkflow_with_tof_lut_from_tof_simulation(workflow, co sc.scalar(85.0, unit="m"), ) lut_wf[kinematics.SourcePosition] = fakes.source_position() - table = lut_wf.compute(kinematics.TofLookupTable) - - workflow[kinematics.TofLookupTable] = table + table = lut_wf.compute(kinematics.LookupTable) - if coord == "tof": - detector = workflow.compute(kinematics.TofDetector[SampleRun]) - assert 'tof' in detector.bins.coords - else: - detector = workflow.compute(kinematics.WavelengthDetector[SampleRun]) - assert 'wavelength' in detector.bins.coords + workflow[kinematics.LookupTable] = table + detector = workflow.compute(kinematics.WavelengthDetector[SampleRun]) + assert 'wavelength' in detector.bins.coords -@pytest.mark.parametrize("coord", ["tof", "wavelength"]) -def test_GenericWavelengthWorkflow_with_tof_lut_from_file( - workflow, tmp_path: pytest.TempPathFactory, coord: str +def test_GenericWavelengthWorkflow_with_lut_from_file( + workflow, tmp_path: pytest.TempPathFactory ): - lut_wf = TofLookupTableWorkflow() + lut_wf = LookupTableWorkflow() lut_wf[DiskChoppers[AnyRun]] = fakes.psc_choppers() lut_wf[kinematics.NumberOfSimulatedNeutrons] = 10_000 lut_wf[kinematics.LtotalRange] = ( @@ -135,12 +128,12 @@ def test_GenericWavelengthWorkflow_with_tof_lut_from_file( sc.scalar(85.0, unit="m"), ) lut_wf[kinematics.SourcePosition] = fakes.source_position() - lut = lut_wf.compute(kinematics.TofLookupTable) + lut = lut_wf.compute(kinematics.LookupTable) lut.save_hdf5(filename=tmp_path / "lut.h5") - workflow[kinematics.TofLookupTableFilename] = (tmp_path / "lut.h5").as_posix() + workflow[kinematics.LookupTableFilename] = (tmp_path / "lut.h5").as_posix() - loaded_lut = workflow.compute(kinematics.TofLookupTable) + loaded_lut = workflow.compute(kinematics.LookupTable) assert_identical(lut.array, loaded_lut.array) assert_identical(lut.pulse_period, loaded_lut.pulse_period) assert lut.pulse_stride == loaded_lut.pulse_stride @@ -148,18 +141,14 @@ def test_GenericWavelengthWorkflow_with_tof_lut_from_file( assert_identical(lut.time_resolution, loaded_lut.time_resolution) assert_identical(lut.choppers, loaded_lut.choppers) - if coord == "tof": - detector = workflow.compute(kinematics.TofDetector[SampleRun]) - assert 'tof' in detector.bins.coords - else: - detector = workflow.compute(kinematics.WavelengthDetector[SampleRun]) - assert 'wavelength' in detector.bins.coords + detector = workflow.compute(kinematics.WavelengthDetector[SampleRun]) + assert 'wavelength' in detector.bins.coords -def test_GenericWavelengthWorkflow_with_tof_lut_from_file_old_format( +def test_GenericWavelengthWorkflow_with_lut_from_file_old_format( workflow, tmp_path: pytest.TempPathFactory ): - lut_wf = TofLookupTableWorkflow() + lut_wf = LookupTableWorkflow() lut_wf[DiskChoppers[AnyRun]] = fakes.psc_choppers() lut_wf[kinematics.NumberOfSimulatedNeutrons] = 10_000 lut_wf[kinematics.LtotalRange] = ( @@ -167,7 +156,7 @@ def test_GenericWavelengthWorkflow_with_tof_lut_from_file_old_format( sc.scalar(85.0, unit="m"), ) lut_wf[kinematics.SourcePosition] = fakes.source_position() - lut = lut_wf.compute(kinematics.TofLookupTable) + lut = lut_wf.compute(kinematics.LookupTable) old_lut = sc.DataArray( data=lut.array.data, coords={ @@ -181,8 +170,8 @@ def test_GenericWavelengthWorkflow_with_tof_lut_from_file_old_format( ) old_lut.save_hdf5(filename=tmp_path / "lut.h5") - workflow[kinematics.TofLookupTableFilename] = (tmp_path / "lut.h5").as_posix() - loaded_lut = workflow.compute(kinematics.TofLookupTable) + workflow[kinematics.LookupTableFilename] = (tmp_path / "lut.h5").as_posix() + loaded_lut = workflow.compute(kinematics.LookupTable) assert_identical(lut.array, loaded_lut.array) assert_identical(lut.pulse_period, loaded_lut.pulse_period) assert lut.pulse_stride == loaded_lut.pulse_stride @@ -190,80 +179,5 @@ def test_GenericWavelengthWorkflow_with_tof_lut_from_file_old_format( assert_identical(lut.time_resolution, loaded_lut.time_resolution) assert loaded_lut.choppers is None # No chopper info in old format - detector = workflow.compute(kinematics.TofDetector[SampleRun]) - assert 'tof' in detector.bins.coords - - -def test_GenericWavelengthWorkflow_with_tof_lut_from_tof_simulation_using_alias(workflow): - # Should be able to compute DetectorData without chopper and simulation params - # This contains event_time_offset (time-of-arrival). - _ = workflow.compute(RawDetector[SampleRun]) - - lut_wf = TofLookupTableWorkflow() - lut_wf[DiskChoppers[AnyRun]] = fakes.psc_choppers() - lut_wf[kinematics.NumberOfSimulatedNeutrons] = 10_000 - lut_wf[kinematics.LtotalRange] = ( - sc.scalar(75.0, unit="m"), - sc.scalar(85.0, unit="m"), - ) - lut_wf[kinematics.SourcePosition] = fakes.source_position() - table = lut_wf.compute(kinematics.TimeOfFlightLookupTable) - - workflow[kinematics.TimeOfFlightLookupTable] = table - # Should now be able to compute DetectorData with chopper and simulation params - detector = workflow.compute(kinematics.TofDetector[SampleRun]) - assert 'tof' in detector.bins.coords - - -def test_GenericWavelengthWorkflow_with_tof_lut_from_file_using_alias( - workflow, tmp_path: pytest.TempPathFactory -): - lut_wf = TofLookupTableWorkflow() - lut_wf[DiskChoppers[AnyRun]] = fakes.psc_choppers() - lut_wf[kinematics.NumberOfSimulatedNeutrons] = 10_000 - lut_wf[kinematics.LtotalRange] = ( - sc.scalar(75.0, unit="m"), - sc.scalar(85.0, unit="m"), - ) - lut_wf[kinematics.SourcePosition] = fakes.source_position() - lut = lut_wf.compute(kinematics.TimeOfFlightLookupTable) - lut.save_hdf5(filename=tmp_path / "lut.h5") - - workflow[kinematics.TimeOfFlightLookupTableFilename] = ( - tmp_path / "lut.h5" - ).as_posix() - loaded_lut = workflow.compute(kinematics.TimeOfFlightLookupTable) - assert_identical(lut.array, loaded_lut.array) - assert_identical(lut.pulse_period, loaded_lut.pulse_period) - assert lut.pulse_stride == loaded_lut.pulse_stride - assert_identical(lut.distance_resolution, loaded_lut.distance_resolution) - assert_identical(lut.time_resolution, loaded_lut.time_resolution) - assert_identical(lut.choppers, loaded_lut.choppers) - - detector = workflow.compute(kinematics.TofDetector[SampleRun]) - assert 'tof' in detector.bins.coords - - -@pytest.mark.parametrize("coord", ["tof", "wavelength"]) -def test_GenericWavelengthWorkflow_assigns_Ltotal_coordinate(workflow, coord): - raw = workflow.compute(RawDetector[SampleRun]) - - assert "Ltotal" not in raw.coords - - lut_wf = TofLookupTableWorkflow() - lut_wf[DiskChoppers[AnyRun]] = fakes.psc_choppers() - lut_wf[kinematics.NumberOfSimulatedNeutrons] = 10_000 - lut_wf[kinematics.LtotalRange] = ( - sc.scalar(20.0, unit="m"), - sc.scalar(100.0, unit="m"), - ) - lut_wf[kinematics.SourcePosition] = fakes.source_position() - table = lut_wf.compute(kinematics.TofLookupTable) - workflow[kinematics.TofLookupTable] = table - - if coord == "tof": - result = workflow.compute(kinematics.TofDetector[SampleRun]) - else: - result = workflow.compute(kinematics.WavelengthDetector[SampleRun]) - - assert "Ltotal" in result.coords + detector = workflow.compute(kinematics.WavelengthDetector[SampleRun]) + assert 'wavelength' in detector.bins.coords From a108988b7369f480ade569fbd3b890e0418a9d94 Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Wed, 4 Mar 2026 23:28:42 +0100 Subject: [PATCH 08/24] update wfm tests --- .../essreduce/tests/kinematics/wfm_test.py | 34 +++++-------------- 1 file changed, 8 insertions(+), 26 deletions(-) diff --git a/packages/essreduce/tests/kinematics/wfm_test.py b/packages/essreduce/tests/kinematics/wfm_test.py index 6b856a10..9181b8be 100644 --- a/packages/essreduce/tests/kinematics/wfm_test.py +++ b/packages/essreduce/tests/kinematics/wfm_test.py @@ -9,8 +9,8 @@ from scippneutron.conversion.graph.tof import elastic as elastic_graph from ess.reduce import kinematics -from ess.reduce.nexus.types import AnyRun, NeXusDetectorName, RawDetector, SampleRun from ess.reduce.kinematics import GenericWavelengthWorkflow, LookupTableWorkflow, fakes +from ess.reduce.nexus.types import AnyRun, NeXusDetectorName, RawDetector, SampleRun sl = pytest.importorskip("sciline") @@ -117,9 +117,7 @@ def lut_workflow_dream_choppers() -> sl.Pipeline: lut_wf[kinematics.NumberOfSimulatedNeutrons] = 100_000 lut_wf[kinematics.SimulationSeed] = 432 lut_wf[kinematics.PulseStride] = 1 - lut_wf[kinematics.SimulationResults] = lut_wf.compute( - kinematics.SimulationResults - ) + lut_wf[kinematics.SimulationResults] = lut_wf.compute(kinematics.SimulationResults) return lut_wf @@ -138,7 +136,7 @@ def setup_workflow( lut_wf = lut_workflow.copy() lut_wf[kinematics.LtotalRange] = ltotal.min(), ltotal.max() - pl[kinematics.TofLookupTable] = lut_wf.compute(kinematics.TofLookupTable) + pl[kinematics.LookupTable] = lut_wf.compute(kinematics.LookupTable) return pl @@ -193,11 +191,7 @@ def test_dream_wfm( raw_data=raw, ltotal=ltotal, lut_workflow=lut_workflow_dream_choppers ) - tofs = pl.compute(kinematics.TofDetector[SampleRun]) - - # Convert to wavelength - graph = {**beamline_graph(scatter=False), **elastic_graph("tof")} - wavs = tofs.transform_coords("wavelength", graph=graph) + wavs = pl.compute(kinematics.WavelengthDetector[SampleRun]) for da in wavs.flatten(to='pixel'): x = sc.sort(da.value, key='id') @@ -217,9 +211,7 @@ def lut_workflow_dream_choppers_time_overlap(): lut_wf[kinematics.NumberOfSimulatedNeutrons] = 100_000 lut_wf[kinematics.SimulationSeed] = 432 lut_wf[kinematics.PulseStride] = 1 - lut_wf[kinematics.SimulationResults] = lut_wf.compute( - kinematics.SimulationResults - ) + lut_wf[kinematics.SimulationResults] = lut_wf.compute(kinematics.SimulationResults) return lut_wf @@ -280,11 +272,7 @@ def test_dream_wfm_with_subframe_time_overlap( error_threshold=0.01, ) - tofs = pl.compute(kinematics.TofDetector[SampleRun]) - - # Convert to wavelength - graph = {**beamline_graph(scatter=False), **elastic_graph("tof")} - wavs = tofs.transform_coords("wavelength", graph=graph) + wavs = pl.compute(kinematics.WavelengthDetector[SampleRun]) for da in wavs.flatten(to='pixel'): x = sc.sort(da.value, key='id') @@ -409,9 +397,7 @@ def lut_workflow_v20_choppers(): lut_wf[kinematics.NumberOfSimulatedNeutrons] = 300_000 lut_wf[kinematics.SimulationSeed] = 431 lut_wf[kinematics.PulseStride] = 1 - lut_wf[kinematics.SimulationResults] = lut_wf.compute( - kinematics.SimulationResults - ) + lut_wf[kinematics.SimulationResults] = lut_wf.compute(kinematics.SimulationResults) return lut_wf @@ -463,11 +449,7 @@ def test_v20_compute_wavelengths_from_wfm( raw_data=raw, ltotal=ltotal, lut_workflow=lut_workflow_v20_choppers ) - tofs = pl.compute(kinematics.TofDetector[SampleRun]) - - # Convert to wavelength - graph = {**beamline_graph(scatter=False), **elastic_graph("tof")} - wavs = tofs.transform_coords("wavelength", graph=graph) + wavs = pl.compute(kinematics.WavelengthDetector[SampleRun]) for da in wavs.flatten(to='pixel'): x = sc.sort(da.value, key='id') From 7aaf07ffbf57a3ac12108fa7d93c82840b7f498f Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Fri, 6 Mar 2026 16:23:19 +0100 Subject: [PATCH 09/24] kinematics -> unwrap --- packages/essreduce/src/ess/reduce/__init__.py | 4 +- .../reduce/{kinematics => unwrap}/__init__.py | 0 .../reduce/{kinematics => unwrap}/fakes.py | 0 .../interpolator_numba.py | 0 .../interpolator_scipy.py | 0 .../ess/reduce/{kinematics => unwrap}/lut.py | 0 .../reduce/{kinematics => unwrap}/resample.py | 0 .../{kinematics => unwrap}/to_wavelength.py | 0 .../reduce/{kinematics => unwrap}/types.py | 0 .../reduce/{kinematics => unwrap}/workflow.py | 0 .../interpolator_test.py | 4 +- .../tests/{kinematics => unwrap}/lut_test.py | 112 +++++++++--------- .../{kinematics => unwrap}/resample_tests.py | 2 +- .../{kinematics => unwrap}/unwrap_test.py | 98 +++++++-------- .../tests/{kinematics => unwrap}/wfm_test.py | 54 ++++----- .../{kinematics => unwrap}/workflow_test.py | 58 ++++----- 16 files changed, 166 insertions(+), 166 deletions(-) rename packages/essreduce/src/ess/reduce/{kinematics => unwrap}/__init__.py (100%) rename packages/essreduce/src/ess/reduce/{kinematics => unwrap}/fakes.py (100%) rename packages/essreduce/src/ess/reduce/{kinematics => unwrap}/interpolator_numba.py (100%) rename packages/essreduce/src/ess/reduce/{kinematics => unwrap}/interpolator_scipy.py (100%) rename packages/essreduce/src/ess/reduce/{kinematics => unwrap}/lut.py (100%) rename packages/essreduce/src/ess/reduce/{kinematics => unwrap}/resample.py (100%) rename packages/essreduce/src/ess/reduce/{kinematics => unwrap}/to_wavelength.py (100%) rename packages/essreduce/src/ess/reduce/{kinematics => unwrap}/types.py (100%) rename packages/essreduce/src/ess/reduce/{kinematics => unwrap}/workflow.py (100%) rename packages/essreduce/tests/{kinematics => unwrap}/interpolator_test.py (96%) rename packages/essreduce/tests/{kinematics => unwrap}/lut_test.py (73%) rename packages/essreduce/tests/{kinematics => unwrap}/resample_tests.py (99%) rename packages/essreduce/tests/{kinematics => unwrap}/unwrap_test.py (84%) rename packages/essreduce/tests/{kinematics => unwrap}/wfm_test.py (89%) rename packages/essreduce/tests/{kinematics => unwrap}/workflow_test.py (75%) diff --git a/packages/essreduce/src/ess/reduce/__init__.py b/packages/essreduce/src/ess/reduce/__init__.py index 16ddb2be..1ce5144f 100644 --- a/packages/essreduce/src/ess/reduce/__init__.py +++ b/packages/essreduce/src/ess/reduce/__init__.py @@ -3,7 +3,7 @@ import importlib.metadata -from . import kinematics, nexus, normalization, uncertainty +from . import nexus, normalization, uncertainty, unwrap try: __version__ = importlib.metadata.version("essreduce") @@ -12,4 +12,4 @@ del importlib -__all__ = ["kinematics", "nexus", "normalization", "uncertainty"] +__all__ = ["nexus", "normalization", "uncertainty", "unwrap"] diff --git a/packages/essreduce/src/ess/reduce/kinematics/__init__.py b/packages/essreduce/src/ess/reduce/unwrap/__init__.py similarity index 100% rename from packages/essreduce/src/ess/reduce/kinematics/__init__.py rename to packages/essreduce/src/ess/reduce/unwrap/__init__.py diff --git a/packages/essreduce/src/ess/reduce/kinematics/fakes.py b/packages/essreduce/src/ess/reduce/unwrap/fakes.py similarity index 100% rename from packages/essreduce/src/ess/reduce/kinematics/fakes.py rename to packages/essreduce/src/ess/reduce/unwrap/fakes.py diff --git a/packages/essreduce/src/ess/reduce/kinematics/interpolator_numba.py b/packages/essreduce/src/ess/reduce/unwrap/interpolator_numba.py similarity index 100% rename from packages/essreduce/src/ess/reduce/kinematics/interpolator_numba.py rename to packages/essreduce/src/ess/reduce/unwrap/interpolator_numba.py diff --git a/packages/essreduce/src/ess/reduce/kinematics/interpolator_scipy.py b/packages/essreduce/src/ess/reduce/unwrap/interpolator_scipy.py similarity index 100% rename from packages/essreduce/src/ess/reduce/kinematics/interpolator_scipy.py rename to packages/essreduce/src/ess/reduce/unwrap/interpolator_scipy.py diff --git a/packages/essreduce/src/ess/reduce/kinematics/lut.py b/packages/essreduce/src/ess/reduce/unwrap/lut.py similarity index 100% rename from packages/essreduce/src/ess/reduce/kinematics/lut.py rename to packages/essreduce/src/ess/reduce/unwrap/lut.py diff --git a/packages/essreduce/src/ess/reduce/kinematics/resample.py b/packages/essreduce/src/ess/reduce/unwrap/resample.py similarity index 100% rename from packages/essreduce/src/ess/reduce/kinematics/resample.py rename to packages/essreduce/src/ess/reduce/unwrap/resample.py diff --git a/packages/essreduce/src/ess/reduce/kinematics/to_wavelength.py b/packages/essreduce/src/ess/reduce/unwrap/to_wavelength.py similarity index 100% rename from packages/essreduce/src/ess/reduce/kinematics/to_wavelength.py rename to packages/essreduce/src/ess/reduce/unwrap/to_wavelength.py diff --git a/packages/essreduce/src/ess/reduce/kinematics/types.py b/packages/essreduce/src/ess/reduce/unwrap/types.py similarity index 100% rename from packages/essreduce/src/ess/reduce/kinematics/types.py rename to packages/essreduce/src/ess/reduce/unwrap/types.py diff --git a/packages/essreduce/src/ess/reduce/kinematics/workflow.py b/packages/essreduce/src/ess/reduce/unwrap/workflow.py similarity index 100% rename from packages/essreduce/src/ess/reduce/kinematics/workflow.py rename to packages/essreduce/src/ess/reduce/unwrap/workflow.py diff --git a/packages/essreduce/tests/kinematics/interpolator_test.py b/packages/essreduce/tests/unwrap/interpolator_test.py similarity index 96% rename from packages/essreduce/tests/kinematics/interpolator_test.py rename to packages/essreduce/tests/unwrap/interpolator_test.py index e0b1fbb9..b7f14426 100644 --- a/packages/essreduce/tests/kinematics/interpolator_test.py +++ b/packages/essreduce/tests/unwrap/interpolator_test.py @@ -3,10 +3,10 @@ import numpy as np -from ess.reduce.kinematics.interpolator_numba import ( +from ess.reduce.unwrap.interpolator_numba import ( Interpolator as InterpolatorNumba, ) -from ess.reduce.kinematics.interpolator_scipy import ( +from ess.reduce.unwrap.interpolator_scipy import ( Interpolator as InterpolatorScipy, ) diff --git a/packages/essreduce/tests/kinematics/lut_test.py b/packages/essreduce/tests/unwrap/lut_test.py similarity index 73% rename from packages/essreduce/tests/kinematics/lut_test.py rename to packages/essreduce/tests/unwrap/lut_test.py index 25cc5bbf..621ea372 100644 --- a/packages/essreduce/tests/kinematics/lut_test.py +++ b/packages/essreduce/tests/unwrap/lut_test.py @@ -4,8 +4,8 @@ import scipp as sc from scippneutron.chopper import DiskChopper -from ess.reduce import kinematics -from ess.reduce.kinematics import LookupTableWorkflow +from ess.reduce import unwrap +from ess.reduce.unwrap import LookupTableWorkflow from ess.reduce.nexus.types import AnyRun sl = pytest.importorskip("sciline") @@ -13,21 +13,21 @@ def test_lut_workflow_computes_table(): wf = LookupTableWorkflow() - wf[kinematics.DiskChoppers[AnyRun]] = {} - wf[kinematics.SourcePosition] = sc.vector([0, 0, 0], unit='m') - wf[kinematics.NumberOfSimulatedNeutrons] = 100_000 - wf[kinematics.SimulationSeed] = 60 - wf[kinematics.PulseStride] = 1 + wf[unwrap.DiskChoppers[AnyRun]] = {} + wf[unwrap.SourcePosition] = sc.vector([0, 0, 0], unit='m') + wf[unwrap.NumberOfSimulatedNeutrons] = 100_000 + wf[unwrap.SimulationSeed] = 60 + wf[unwrap.PulseStride] = 1 lmin, lmax = sc.scalar(25.0, unit='m'), sc.scalar(35.0, unit='m') dres = sc.scalar(0.1, unit='m') tres = sc.scalar(333.0, unit='us') - wf[kinematics.LtotalRange] = lmin, lmax - wf[kinematics.DistanceResolution] = dres - wf[kinematics.TimeResolution] = tres + wf[unwrap.LtotalRange] = lmin, lmax + wf[unwrap.DistanceResolution] = dres + wf[unwrap.TimeResolution] = tres - table = wf.compute(kinematics.LookupTable) + table = wf.compute(unwrap.LookupTable) assert table.array.coords['distance'].min() < lmin assert table.array.coords['distance'].max() > lmax @@ -42,21 +42,21 @@ def test_lut_workflow_computes_table(): def test_lut_workflow_pulse_skipping(): wf = LookupTableWorkflow() - wf[kinematics.DiskChoppers[AnyRun]] = {} - wf[kinematics.SourcePosition] = sc.vector([0, 0, 0], unit='m') - wf[kinematics.NumberOfSimulatedNeutrons] = 100_000 - wf[kinematics.SimulationSeed] = 62 - wf[kinematics.PulseStride] = 2 + wf[unwrap.DiskChoppers[AnyRun]] = {} + wf[unwrap.SourcePosition] = sc.vector([0, 0, 0], unit='m') + wf[unwrap.NumberOfSimulatedNeutrons] = 100_000 + wf[unwrap.SimulationSeed] = 62 + wf[unwrap.PulseStride] = 2 lmin, lmax = sc.scalar(55.0, unit='m'), sc.scalar(65.0, unit='m') dres = sc.scalar(0.1, unit='m') tres = sc.scalar(250.0, unit='us') - wf[kinematics.LtotalRange] = lmin, lmax - wf[kinematics.DistanceResolution] = dres - wf[kinematics.TimeResolution] = tres + wf[unwrap.LtotalRange] = lmin, lmax + wf[unwrap.DistanceResolution] = dres + wf[unwrap.TimeResolution] = tres - table = wf.compute(kinematics.LookupTable) + table = wf.compute(unwrap.LookupTable) assert table.array.coords['event_time_offset'].max() == 2 * sc.scalar( 1 / 14, unit='s' @@ -65,21 +65,21 @@ def test_lut_workflow_pulse_skipping(): def test_lut_workflow_non_exact_distance_range(): wf = LookupTableWorkflow() - wf[kinematics.DiskChoppers[AnyRun]] = {} - wf[kinematics.SourcePosition] = sc.vector([0, 0, 0], unit='m') - wf[kinematics.NumberOfSimulatedNeutrons] = 100_000 - wf[kinematics.SimulationSeed] = 63 - wf[kinematics.PulseStride] = 1 + wf[unwrap.DiskChoppers[AnyRun]] = {} + wf[unwrap.SourcePosition] = sc.vector([0, 0, 0], unit='m') + wf[unwrap.NumberOfSimulatedNeutrons] = 100_000 + wf[unwrap.SimulationSeed] = 63 + wf[unwrap.PulseStride] = 1 lmin, lmax = sc.scalar(25.0, unit='m'), sc.scalar(35.0, unit='m') dres = sc.scalar(0.33, unit='m') tres = sc.scalar(250.0, unit='us') - wf[kinematics.LtotalRange] = lmin, lmax - wf[kinematics.DistanceResolution] = dres - wf[kinematics.TimeResolution] = tres + wf[unwrap.LtotalRange] = lmin, lmax + wf[unwrap.DistanceResolution] = dres + wf[unwrap.TimeResolution] = tres - table = wf.compute(kinematics.LookupTable) + table = wf.compute(unwrap.LookupTable) assert table.array.coords['distance'].min() < lmin assert table.array.coords['distance'].max() > lmax @@ -147,20 +147,20 @@ def _make_choppers(): def test_lut_workflow_computes_table_with_choppers(): wf = LookupTableWorkflow() - wf[kinematics.DiskChoppers[AnyRun]] = _make_choppers() - wf[kinematics.SourcePosition] = sc.vector([0, 0, 0], unit='m') - wf[kinematics.NumberOfSimulatedNeutrons] = 100_000 - wf[kinematics.SimulationSeed] = 64 - wf[kinematics.PulseStride] = 1 + wf[unwrap.DiskChoppers[AnyRun]] = _make_choppers() + wf[unwrap.SourcePosition] = sc.vector([0, 0, 0], unit='m') + wf[unwrap.NumberOfSimulatedNeutrons] = 100_000 + wf[unwrap.SimulationSeed] = 64 + wf[unwrap.PulseStride] = 1 - wf[kinematics.LtotalRange] = ( + wf[unwrap.LtotalRange] = ( sc.scalar(35.0, unit='m'), sc.scalar(65.0, unit='m'), ) - wf[kinematics.DistanceResolution] = sc.scalar(0.1, unit='m') - wf[kinematics.TimeResolution] = sc.scalar(250.0, unit='us') + wf[unwrap.DistanceResolution] = sc.scalar(0.1, unit='m') + wf[unwrap.TimeResolution] = sc.scalar(250.0, unit='us') - table = wf.compute(kinematics.LookupTable) + table = wf.compute(unwrap.LookupTable) # At low distance, the rays are more focussed low_dist = table.array['distance', 2] @@ -181,20 +181,20 @@ def test_lut_workflow_computes_table_with_choppers(): def test_lut_workflow_computes_table_with_choppers_full_beamline_range(): wf = LookupTableWorkflow() - wf[kinematics.DiskChoppers[AnyRun]] = _make_choppers() - wf[kinematics.SourcePosition] = sc.vector([0, 0, 0], unit='m') - wf[kinematics.NumberOfSimulatedNeutrons] = 100_000 - wf[kinematics.SimulationSeed] = 64 - wf[kinematics.PulseStride] = 1 + wf[unwrap.DiskChoppers[AnyRun]] = _make_choppers() + wf[unwrap.SourcePosition] = sc.vector([0, 0, 0], unit='m') + wf[unwrap.NumberOfSimulatedNeutrons] = 100_000 + wf[unwrap.SimulationSeed] = 64 + wf[unwrap.PulseStride] = 1 - wf[kinematics.LtotalRange] = ( + wf[unwrap.LtotalRange] = ( sc.scalar(5.0, unit='m'), sc.scalar(65.0, unit='m'), ) - wf[kinematics.DistanceResolution] = sc.scalar(0.1, unit='m') - wf[kinematics.TimeResolution] = sc.scalar(250.0, unit='us') + wf[unwrap.DistanceResolution] = sc.scalar(0.1, unit='m') + wf[unwrap.TimeResolution] = sc.scalar(250.0, unit='us') - table = wf.compute(kinematics.LookupTable) + table = wf.compute(unwrap.LookupTable) # Close to source: early times and large spread da = table.array['distance', 2] @@ -231,20 +231,20 @@ def test_lut_workflow_computes_table_with_choppers_full_beamline_range(): def test_lut_workflow_raises_for_distance_before_source(): wf = LookupTableWorkflow() - wf[kinematics.DiskChoppers[AnyRun]] = {} - wf[kinematics.SourcePosition] = sc.vector([0, 0, 10], unit='m') - wf[kinematics.NumberOfSimulatedNeutrons] = 100_000 - wf[kinematics.SimulationSeed] = 65 - wf[kinematics.PulseStride] = 1 + wf[unwrap.DiskChoppers[AnyRun]] = {} + wf[unwrap.SourcePosition] = sc.vector([0, 0, 10], unit='m') + wf[unwrap.NumberOfSimulatedNeutrons] = 100_000 + wf[unwrap.SimulationSeed] = 65 + wf[unwrap.PulseStride] = 1 # Setting the starting point at zero will make a table that would cover a range # from -0.2m to 65.0m - wf[kinematics.LtotalRange] = ( + wf[unwrap.LtotalRange] = ( sc.scalar(0.0, unit='m'), sc.scalar(65.0, unit='m'), ) - wf[kinematics.DistanceResolution] = sc.scalar(0.1, unit='m') - wf[kinematics.TimeResolution] = sc.scalar(250.0, unit='us') + wf[unwrap.DistanceResolution] = sc.scalar(0.1, unit='m') + wf[unwrap.TimeResolution] = sc.scalar(250.0, unit='us') with pytest.raises(ValueError, match="Building the lookup table failed"): - _ = wf.compute(kinematics.LookupTable) + _ = wf.compute(unwrap.LookupTable) diff --git a/packages/essreduce/tests/kinematics/resample_tests.py b/packages/essreduce/tests/unwrap/resample_tests.py similarity index 99% rename from packages/essreduce/tests/kinematics/resample_tests.py rename to packages/essreduce/tests/unwrap/resample_tests.py index a5186fae..79aab8b3 100644 --- a/packages/essreduce/tests/kinematics/resample_tests.py +++ b/packages/essreduce/tests/unwrap/resample_tests.py @@ -6,7 +6,7 @@ import scipp as sc from scipp.testing import assert_identical -from ess.reduce.kinematics import resample +from ess.reduce.unwrap import resample class TestFindStrictlyIncreasingSections: diff --git a/packages/essreduce/tests/kinematics/unwrap_test.py b/packages/essreduce/tests/unwrap/unwrap_test.py similarity index 84% rename from packages/essreduce/tests/kinematics/unwrap_test.py rename to packages/essreduce/tests/unwrap/unwrap_test.py index 826ebec4..5d2dd31b 100644 --- a/packages/essreduce/tests/kinematics/unwrap_test.py +++ b/packages/essreduce/tests/unwrap/unwrap_test.py @@ -5,8 +5,8 @@ import scipp as sc from scippneutron.chopper import DiskChopper -from ess.reduce import kinematics -from ess.reduce.kinematics import ( +from ess.reduce import unwrap +from ess.reduce.unwrap import ( GenericWavelengthWorkflow, LookupTableWorkflow, PulsePeriod, @@ -27,12 +27,12 @@ def make_lut_workflow(choppers, neutrons, seed, pulse_stride): lut_wf = LookupTableWorkflow() - lut_wf[kinematics.DiskChoppers[AnyRun]] = choppers - lut_wf[kinematics.SourcePosition] = fakes.source_position() - lut_wf[kinematics.NumberOfSimulatedNeutrons] = neutrons - lut_wf[kinematics.SimulationSeed] = seed - lut_wf[kinematics.PulseStride] = pulse_stride - lut_wf[kinematics.SimulationResults] = lut_wf.compute(kinematics.SimulationResults) + lut_wf[unwrap.DiskChoppers[AnyRun]] = choppers + lut_wf[unwrap.SourcePosition] = fakes.source_position() + lut_wf[unwrap.NumberOfSimulatedNeutrons] = neutrons + lut_wf[unwrap.SimulationSeed] = seed + lut_wf[unwrap.PulseStride] = pulse_stride + lut_wf[unwrap.SimulationResults] = lut_wf.compute(unwrap.SimulationResults) return lut_wf @@ -75,22 +75,22 @@ def _make_workflow_event_mode( if detector_or_monitor == "detector": pl[NeXusDetectorName] = "detector" pl[RawDetector[SampleRun]] = mon - pl[kinematics.DetectorLtotal[SampleRun]] = distance + pl[unwrap.DetectorLtotal[SampleRun]] = distance else: pl[NeXusName[FrameMonitor0]] = "monitor" pl[RawMonitor[SampleRun, FrameMonitor0]] = mon - pl[kinematics.MonitorLtotal[SampleRun, FrameMonitor0]] = distance + pl[unwrap.MonitorLtotal[SampleRun, FrameMonitor0]] = distance - pl[kinematics.LookupTableRelativeErrorThreshold] = { + pl[unwrap.LookupTableRelativeErrorThreshold] = { 'detector': error_threshold, 'monitor': error_threshold, } - pl[kinematics.PulseStrideOffset] = pulse_stride_offset + pl[unwrap.PulseStrideOffset] = pulse_stride_offset lut_wf = lut_workflow.copy() - lut_wf[kinematics.LtotalRange] = distance, distance + lut_wf[unwrap.LtotalRange] = distance, distance - pl[kinematics.LookupTable] = lut_wf.compute(kinematics.LookupTable) + pl[unwrap.LookupTable] = lut_wf.compute(unwrap.LookupTable) return pl, ref @@ -116,21 +116,21 @@ def _make_workflow_histogram_mode( if detector_or_monitor == "detector": pl[NeXusDetectorName] = "detector" pl[RawDetector[SampleRun]] = mon - pl[kinematics.DetectorLtotal[SampleRun]] = distance + pl[unwrap.DetectorLtotal[SampleRun]] = distance else: pl[NeXusName[FrameMonitor0]] = "monitor" pl[RawMonitor[SampleRun, FrameMonitor0]] = mon - pl[kinematics.MonitorLtotal[SampleRun, FrameMonitor0]] = distance + pl[unwrap.MonitorLtotal[SampleRun, FrameMonitor0]] = distance - pl[kinematics.LookupTableRelativeErrorThreshold] = { + pl[unwrap.LookupTableRelativeErrorThreshold] = { 'detector': error_threshold, 'monitor': error_threshold, } lut_wf = lut_workflow.copy() - lut_wf[kinematics.LtotalRange] = distance, distance + lut_wf[unwrap.LtotalRange] = distance, distance - pl[kinematics.LookupTable] = lut_wf.compute(kinematics.LookupTable) + pl[unwrap.LookupTable] = lut_wf.compute(unwrap.LookupTable) return pl, ref @@ -193,9 +193,9 @@ def test_unwrap_with_no_choppers(detector_or_monitor) -> None: ) if detector_or_monitor == "detector": - wavs = pl.compute(kinematics.WavelengthDetector[SampleRun]) + wavs = pl.compute(unwrap.WavelengthDetector[SampleRun]) else: - wavs = pl.compute(kinematics.WavelengthMonitor[SampleRun, FrameMonitor0]) + wavs = pl.compute(unwrap.WavelengthMonitor[SampleRun, FrameMonitor0]) _validate_result_events( wavs=wavs, ref=ref, percentile=96, diff_threshold=1.0, rtol=0.02 @@ -221,9 +221,9 @@ def test_standard_unwrap(dist, detector_or_monitor, lut_workflow_psc_choppers) - ) if detector_or_monitor == "detector": - wavs = pl.compute(kinematics.WavelengthDetector[SampleRun]) + wavs = pl.compute(unwrap.WavelengthDetector[SampleRun]) else: - wavs = pl.compute(kinematics.WavelengthMonitor[SampleRun, FrameMonitor0]) + wavs = pl.compute(unwrap.WavelengthMonitor[SampleRun, FrameMonitor0]) _validate_result_events( wavs=wavs, ref=ref, percentile=100, diff_threshold=0.02, rtol=0.05 @@ -251,9 +251,9 @@ def test_standard_unwrap_histogram_mode( ) if detector_or_monitor == "detector": - wavs = pl.compute(kinematics.WavelengthDetector[SampleRun]) + wavs = pl.compute(unwrap.WavelengthDetector[SampleRun]) else: - wavs = pl.compute(kinematics.WavelengthMonitor[SampleRun, FrameMonitor0]) + wavs = pl.compute(unwrap.WavelengthMonitor[SampleRun, FrameMonitor0]) _validate_result_histogram_mode( wavs=wavs, ref=ref, percentile=96, diff_threshold=0.4, rtol=0.05 @@ -277,9 +277,9 @@ def test_pulse_skipping_unwrap( ) if detector_or_monitor == "detector": - wavs = pl.compute(kinematics.WavelengthDetector[SampleRun]) + wavs = pl.compute(unwrap.WavelengthDetector[SampleRun]) else: - wavs = pl.compute(kinematics.WavelengthMonitor[SampleRun, FrameMonitor0]) + wavs = pl.compute(unwrap.WavelengthMonitor[SampleRun, FrameMonitor0]) _validate_result_events( wavs=wavs, ref=ref, percentile=100, diff_threshold=0.1, rtol=0.05 @@ -306,9 +306,9 @@ def test_pulse_skipping_unwrap_180_phase_shift(detector_or_monitor) -> None: ) if detector_or_monitor == "detector": - wavs = pl.compute(kinematics.WavelengthDetector[SampleRun]) + wavs = pl.compute(unwrap.WavelengthDetector[SampleRun]) else: - wavs = pl.compute(kinematics.WavelengthMonitor[SampleRun, FrameMonitor0]) + wavs = pl.compute(unwrap.WavelengthMonitor[SampleRun, FrameMonitor0]) _validate_result_events( wavs=wavs, ref=ref, percentile=100, diff_threshold=0.1, rtol=0.05 @@ -331,9 +331,9 @@ def test_pulse_skipping_stride_offset_guess_gives_expected_result( ) if detector_or_monitor == "detector": - wavs = pl.compute(kinematics.WavelengthDetector[SampleRun]) + wavs = pl.compute(unwrap.WavelengthDetector[SampleRun]) else: - wavs = pl.compute(kinematics.WavelengthMonitor[SampleRun, FrameMonitor0]) + wavs = pl.compute(unwrap.WavelengthMonitor[SampleRun, FrameMonitor0]) _validate_result_events( wavs=wavs, ref=ref, percentile=100, diff_threshold=0.1, rtol=0.05 @@ -371,9 +371,9 @@ def test_pulse_skipping_unwrap_when_all_neutrons_arrive_after_second_pulse( ) if detector_or_monitor == "detector": - wavs = pl.compute(kinematics.WavelengthDetector[SampleRun]) + wavs = pl.compute(unwrap.WavelengthDetector[SampleRun]) else: - wavs = pl.compute(kinematics.WavelengthMonitor[SampleRun, FrameMonitor0]) + wavs = pl.compute(unwrap.WavelengthMonitor[SampleRun, FrameMonitor0]) _validate_result_events( wavs=wavs, ref=ref, percentile=100, diff_threshold=0.1, rtol=0.05 @@ -399,7 +399,7 @@ def test_pulse_skipping_unwrap_when_first_half_of_first_pulse_is_missing( lut_wf = make_lut_workflow( choppers=choppers, neutrons=300_000, seed=1234, pulse_stride=2 ) - lut_wf[kinematics.LtotalRange] = distance, distance + lut_wf[unwrap.LtotalRange] = distance, distance pl = GenericWavelengthWorkflow(run_types=[SampleRun], monitor_types=[FrameMonitor0]) @@ -408,9 +408,9 @@ def test_pulse_skipping_unwrap_when_first_half_of_first_pulse_is_missing( a.bins.coords['event_time_zero'] = sc.bins_like(a, a.coords['event_time_zero']) concatenated = a.bins.concat('event_time_zero') - pl[kinematics.LookupTable] = lut_wf.compute(kinematics.LookupTable) - pl[kinematics.PulseStrideOffset] = 1 # Start the stride at the second pulse - pl[kinematics.LookupTableRelativeErrorThreshold] = { + pl[unwrap.LookupTable] = lut_wf.compute(unwrap.LookupTable) + pl[unwrap.PulseStrideOffset] = 1 # Start the stride at the second pulse + pl[unwrap.LookupTableRelativeErrorThreshold] = { 'detector': np.inf, 'monitor': np.inf, } @@ -418,13 +418,13 @@ def test_pulse_skipping_unwrap_when_first_half_of_first_pulse_is_missing( if detector_or_monitor == "detector": pl[NeXusDetectorName] = "detector" pl[RawDetector[SampleRun]] = concatenated - pl[kinematics.DetectorLtotal[SampleRun]] = distance - wavs = pl.compute(kinematics.WavelengthDetector[SampleRun]) + pl[unwrap.DetectorLtotal[SampleRun]] = distance + wavs = pl.compute(unwrap.WavelengthDetector[SampleRun]) else: pl[NeXusName[FrameMonitor0]] = "monitor" pl[RawMonitor[SampleRun, FrameMonitor0]] = concatenated - pl[kinematics.MonitorLtotal[SampleRun, FrameMonitor0]] = distance - wavs = pl.compute(kinematics.WavelengthMonitor[SampleRun, FrameMonitor0]) + pl[unwrap.MonitorLtotal[SampleRun, FrameMonitor0]] = distance + wavs = pl.compute(unwrap.WavelengthMonitor[SampleRun, FrameMonitor0]) # Convert to wavelength # graph = {**beamline_graph(scatter=False), **elastic_graph("tof")} @@ -487,9 +487,9 @@ def test_pulse_skipping_stride_3(detector_or_monitor) -> None: ) if detector_or_monitor == "detector": - wavs = pl.compute(kinematics.WavelengthDetector[SampleRun]) + wavs = pl.compute(unwrap.WavelengthDetector[SampleRun]) else: - wavs = pl.compute(kinematics.WavelengthMonitor[SampleRun, FrameMonitor0]) + wavs = pl.compute(unwrap.WavelengthMonitor[SampleRun, FrameMonitor0]) _validate_result_events( wavs=wavs, ref=ref, percentile=100, diff_threshold=0.1, rtol=0.05 @@ -511,9 +511,9 @@ def test_pulse_skipping_unwrap_histogram_mode( ) if detector_or_monitor == "detector": - wavs = pl.compute(kinematics.WavelengthDetector[SampleRun]) + wavs = pl.compute(unwrap.WavelengthDetector[SampleRun]) else: - wavs = pl.compute(kinematics.WavelengthMonitor[SampleRun, FrameMonitor0]) + wavs = pl.compute(unwrap.WavelengthMonitor[SampleRun, FrameMonitor0]) _validate_result_histogram_mode( wavs=wavs, ref=ref, percentile=96, diff_threshold=0.4, rtol=0.05 @@ -544,9 +544,9 @@ def test_unwrap_int(dtype, detector_or_monitor, lut_workflow_psc_choppers) -> No pl[target] = mon if detector_or_monitor == "detector": - wavs = pl.compute(kinematics.WavelengthDetector[SampleRun]) + wavs = pl.compute(unwrap.WavelengthDetector[SampleRun]) else: - wavs = pl.compute(kinematics.WavelengthMonitor[SampleRun, FrameMonitor0]) + wavs = pl.compute(unwrap.WavelengthMonitor[SampleRun, FrameMonitor0]) _validate_result_events( wavs=wavs, ref=ref, percentile=100, diff_threshold=0.02, rtol=0.05 @@ -571,7 +571,7 @@ def test_compute_toa(): detector_or_monitor="detector", ) - toas = pl.compute(kinematics.ToaDetector[SampleRun]) + toas = pl.compute(unwrap.ToaDetector[SampleRun]) assert "toa" in toas.bins.coords raw = pl.compute(RawDetector[SampleRun]) @@ -598,7 +598,7 @@ def test_compute_toa_pulse_skipping(): raw = pl.compute(RawDetector[SampleRun]) - toas = pl.compute(kinematics.ToaDetector[SampleRun]) + toas = pl.compute(unwrap.ToaDetector[SampleRun]) assert "toa" in toas.bins.coords pulse_period = lut_wf.compute(PulsePeriod) diff --git a/packages/essreduce/tests/kinematics/wfm_test.py b/packages/essreduce/tests/unwrap/wfm_test.py similarity index 89% rename from packages/essreduce/tests/kinematics/wfm_test.py rename to packages/essreduce/tests/unwrap/wfm_test.py index 9181b8be..840a4a2a 100644 --- a/packages/essreduce/tests/kinematics/wfm_test.py +++ b/packages/essreduce/tests/unwrap/wfm_test.py @@ -8,8 +8,8 @@ from scippneutron.conversion.graph.beamline import beamline as beamline_graph from scippneutron.conversion.graph.tof import elastic as elastic_graph -from ess.reduce import kinematics -from ess.reduce.kinematics import GenericWavelengthWorkflow, LookupTableWorkflow, fakes +from ess.reduce import unwrap +from ess.reduce.unwrap import GenericWavelengthWorkflow, LookupTableWorkflow, fakes from ess.reduce.nexus.types import AnyRun, NeXusDetectorName, RawDetector, SampleRun sl = pytest.importorskip("sciline") @@ -112,12 +112,12 @@ def dream_source_position() -> sc.Variable: @pytest.fixture(scope="module") def lut_workflow_dream_choppers() -> sl.Pipeline: lut_wf = LookupTableWorkflow() - lut_wf[kinematics.DiskChoppers[AnyRun]] = dream_choppers() - lut_wf[kinematics.SourcePosition] = dream_source_position() - lut_wf[kinematics.NumberOfSimulatedNeutrons] = 100_000 - lut_wf[kinematics.SimulationSeed] = 432 - lut_wf[kinematics.PulseStride] = 1 - lut_wf[kinematics.SimulationResults] = lut_wf.compute(kinematics.SimulationResults) + lut_wf[unwrap.DiskChoppers[AnyRun]] = dream_choppers() + lut_wf[unwrap.SourcePosition] = dream_source_position() + lut_wf[unwrap.NumberOfSimulatedNeutrons] = 100_000 + lut_wf[unwrap.SimulationSeed] = 432 + lut_wf[unwrap.PulseStride] = 1 + lut_wf[unwrap.SimulationResults] = lut_wf.compute(unwrap.SimulationResults) return lut_wf @@ -129,14 +129,14 @@ def setup_workflow( ) -> sl.Pipeline: pl = GenericWavelengthWorkflow(run_types=[SampleRun], monitor_types=[]) pl[RawDetector[SampleRun]] = raw_data - pl[kinematics.DetectorLtotal[SampleRun]] = ltotal + pl[unwrap.DetectorLtotal[SampleRun]] = ltotal pl[NeXusDetectorName] = "detector" - pl[kinematics.LookupTableRelativeErrorThreshold] = {"detector": error_threshold} + pl[unwrap.LookupTableRelativeErrorThreshold] = {"detector": error_threshold} lut_wf = lut_workflow.copy() - lut_wf[kinematics.LtotalRange] = ltotal.min(), ltotal.max() + lut_wf[unwrap.LtotalRange] = ltotal.min(), ltotal.max() - pl[kinematics.LookupTable] = lut_wf.compute(kinematics.LookupTable) + pl[unwrap.LookupTable] = lut_wf.compute(unwrap.LookupTable) return pl @@ -191,7 +191,7 @@ def test_dream_wfm( raw_data=raw, ltotal=ltotal, lut_workflow=lut_workflow_dream_choppers ) - wavs = pl.compute(kinematics.WavelengthDetector[SampleRun]) + wavs = pl.compute(unwrap.WavelengthDetector[SampleRun]) for da in wavs.flatten(to='pixel'): x = sc.sort(da.value, key='id') @@ -206,12 +206,12 @@ def test_dream_wfm( @pytest.fixture(scope="module") def lut_workflow_dream_choppers_time_overlap(): lut_wf = LookupTableWorkflow() - lut_wf[kinematics.DiskChoppers[AnyRun]] = dream_choppers_with_frame_overlap() - lut_wf[kinematics.SourcePosition] = dream_source_position() - lut_wf[kinematics.NumberOfSimulatedNeutrons] = 100_000 - lut_wf[kinematics.SimulationSeed] = 432 - lut_wf[kinematics.PulseStride] = 1 - lut_wf[kinematics.SimulationResults] = lut_wf.compute(kinematics.SimulationResults) + lut_wf[unwrap.DiskChoppers[AnyRun]] = dream_choppers_with_frame_overlap() + lut_wf[unwrap.SourcePosition] = dream_source_position() + lut_wf[unwrap.NumberOfSimulatedNeutrons] = 100_000 + lut_wf[unwrap.SimulationSeed] = 432 + lut_wf[unwrap.PulseStride] = 1 + lut_wf[unwrap.SimulationResults] = lut_wf.compute(unwrap.SimulationResults) return lut_wf @@ -272,7 +272,7 @@ def test_dream_wfm_with_subframe_time_overlap( error_threshold=0.01, ) - wavs = pl.compute(kinematics.WavelengthDetector[SampleRun]) + wavs = pl.compute(unwrap.WavelengthDetector[SampleRun]) for da in wavs.flatten(to='pixel'): x = sc.sort(da.value, key='id') @@ -392,12 +392,12 @@ def v20_source_position(): @pytest.fixture(scope="module") def lut_workflow_v20_choppers(): lut_wf = LookupTableWorkflow() - lut_wf[kinematics.DiskChoppers[AnyRun]] = v20_choppers() - lut_wf[kinematics.SourcePosition] = v20_source_position() - lut_wf[kinematics.NumberOfSimulatedNeutrons] = 300_000 - lut_wf[kinematics.SimulationSeed] = 431 - lut_wf[kinematics.PulseStride] = 1 - lut_wf[kinematics.SimulationResults] = lut_wf.compute(kinematics.SimulationResults) + lut_wf[unwrap.DiskChoppers[AnyRun]] = v20_choppers() + lut_wf[unwrap.SourcePosition] = v20_source_position() + lut_wf[unwrap.NumberOfSimulatedNeutrons] = 300_000 + lut_wf[unwrap.SimulationSeed] = 431 + lut_wf[unwrap.PulseStride] = 1 + lut_wf[unwrap.SimulationResults] = lut_wf.compute(unwrap.SimulationResults) return lut_wf @@ -449,7 +449,7 @@ def test_v20_compute_wavelengths_from_wfm( raw_data=raw, ltotal=ltotal, lut_workflow=lut_workflow_v20_choppers ) - wavs = pl.compute(kinematics.WavelengthDetector[SampleRun]) + wavs = pl.compute(unwrap.WavelengthDetector[SampleRun]) for da in wavs.flatten(to='pixel'): x = sc.sort(da.value, key='id') diff --git a/packages/essreduce/tests/kinematics/workflow_test.py b/packages/essreduce/tests/unwrap/workflow_test.py similarity index 75% rename from packages/essreduce/tests/kinematics/workflow_test.py rename to packages/essreduce/tests/unwrap/workflow_test.py index 1afc84c7..14e3ad1e 100644 --- a/packages/essreduce/tests/kinematics/workflow_test.py +++ b/packages/essreduce/tests/unwrap/workflow_test.py @@ -7,8 +7,8 @@ import scippnexus as snx from scipp.testing import assert_identical -from ess.reduce import kinematics -from ess.reduce.kinematics import ( +from ess.reduce import unwrap +from ess.reduce.unwrap import ( GenericWavelengthWorkflow, LookupTableWorkflow, fakes, @@ -65,7 +65,7 @@ def workflow() -> GenericWavelengthWorkflow: wf = GenericWavelengthWorkflow(run_types=[SampleRun], monitor_types=[]) wf[NeXusDetectorName] = "detector" - wf[kinematics.LookupTableRelativeErrorThreshold] = {'detector': np.inf} + wf[unwrap.LookupTableRelativeErrorThreshold] = {'detector': np.inf} wf[EmptyDetector[SampleRun]] = calibrated_beamline wf[NeXusData[snx.NXdetector, SampleRun]] = nexus_data wf[Position[snx.NXsample, SampleRun]] = sc.vector([0, 0, 77], unit='m') @@ -77,13 +77,13 @@ def workflow() -> GenericWavelengthWorkflow: def test_LookupTableWorkflow_can_compute_lut(): wf = LookupTableWorkflow() wf[DiskChoppers[AnyRun]] = fakes.psc_choppers() - wf[kinematics.NumberOfSimulatedNeutrons] = 10_000 - wf[kinematics.LtotalRange] = ( + wf[unwrap.NumberOfSimulatedNeutrons] = 10_000 + wf[unwrap.LtotalRange] = ( sc.scalar(75.0, unit="m"), sc.scalar(85.0, unit="m"), ) - wf[kinematics.SourcePosition] = fakes.source_position() - lut = wf.compute(kinematics.LookupTable) + wf[unwrap.SourcePosition] = fakes.source_position() + lut = wf.compute(unwrap.LookupTable) assert lut.array is not None assert lut.distance_resolution is not None assert lut.time_resolution is not None @@ -98,22 +98,22 @@ def test_GenericWavelengthWorkflow_with_lut_from_tof_simulation(workflow): _ = workflow.compute(RawDetector[SampleRun]) # By default, the workflow tries to load the LUT from file with pytest.raises(sciline.UnsatisfiedRequirement): - _ = workflow.compute(kinematics.LookupTable) + _ = workflow.compute(unwrap.LookupTable) with pytest.raises(sciline.UnsatisfiedRequirement): - _ = workflow.compute(kinematics.TofDetector[SampleRun]) + _ = workflow.compute(unwrap.TofDetector[SampleRun]) lut_wf = LookupTableWorkflow() lut_wf[DiskChoppers[AnyRun]] = fakes.psc_choppers() - lut_wf[kinematics.NumberOfSimulatedNeutrons] = 10_000 - lut_wf[kinematics.LtotalRange] = ( + lut_wf[unwrap.NumberOfSimulatedNeutrons] = 10_000 + lut_wf[unwrap.LtotalRange] = ( sc.scalar(75.0, unit="m"), sc.scalar(85.0, unit="m"), ) - lut_wf[kinematics.SourcePosition] = fakes.source_position() - table = lut_wf.compute(kinematics.LookupTable) + lut_wf[unwrap.SourcePosition] = fakes.source_position() + table = lut_wf.compute(unwrap.LookupTable) - workflow[kinematics.LookupTable] = table - detector = workflow.compute(kinematics.WavelengthDetector[SampleRun]) + workflow[unwrap.LookupTable] = table + detector = workflow.compute(unwrap.WavelengthDetector[SampleRun]) assert 'wavelength' in detector.bins.coords @@ -122,18 +122,18 @@ def test_GenericWavelengthWorkflow_with_lut_from_file( ): lut_wf = LookupTableWorkflow() lut_wf[DiskChoppers[AnyRun]] = fakes.psc_choppers() - lut_wf[kinematics.NumberOfSimulatedNeutrons] = 10_000 - lut_wf[kinematics.LtotalRange] = ( + lut_wf[unwrap.NumberOfSimulatedNeutrons] = 10_000 + lut_wf[unwrap.LtotalRange] = ( sc.scalar(75.0, unit="m"), sc.scalar(85.0, unit="m"), ) - lut_wf[kinematics.SourcePosition] = fakes.source_position() - lut = lut_wf.compute(kinematics.LookupTable) + lut_wf[unwrap.SourcePosition] = fakes.source_position() + lut = lut_wf.compute(unwrap.LookupTable) lut.save_hdf5(filename=tmp_path / "lut.h5") - workflow[kinematics.LookupTableFilename] = (tmp_path / "lut.h5").as_posix() + workflow[unwrap.LookupTableFilename] = (tmp_path / "lut.h5").as_posix() - loaded_lut = workflow.compute(kinematics.LookupTable) + loaded_lut = workflow.compute(unwrap.LookupTable) assert_identical(lut.array, loaded_lut.array) assert_identical(lut.pulse_period, loaded_lut.pulse_period) assert lut.pulse_stride == loaded_lut.pulse_stride @@ -141,7 +141,7 @@ def test_GenericWavelengthWorkflow_with_lut_from_file( assert_identical(lut.time_resolution, loaded_lut.time_resolution) assert_identical(lut.choppers, loaded_lut.choppers) - detector = workflow.compute(kinematics.WavelengthDetector[SampleRun]) + detector = workflow.compute(unwrap.WavelengthDetector[SampleRun]) assert 'wavelength' in detector.bins.coords @@ -150,13 +150,13 @@ def test_GenericWavelengthWorkflow_with_lut_from_file_old_format( ): lut_wf = LookupTableWorkflow() lut_wf[DiskChoppers[AnyRun]] = fakes.psc_choppers() - lut_wf[kinematics.NumberOfSimulatedNeutrons] = 10_000 - lut_wf[kinematics.LtotalRange] = ( + lut_wf[unwrap.NumberOfSimulatedNeutrons] = 10_000 + lut_wf[unwrap.LtotalRange] = ( sc.scalar(75.0, unit="m"), sc.scalar(85.0, unit="m"), ) - lut_wf[kinematics.SourcePosition] = fakes.source_position() - lut = lut_wf.compute(kinematics.LookupTable) + lut_wf[unwrap.SourcePosition] = fakes.source_position() + lut = lut_wf.compute(unwrap.LookupTable) old_lut = sc.DataArray( data=lut.array.data, coords={ @@ -170,8 +170,8 @@ def test_GenericWavelengthWorkflow_with_lut_from_file_old_format( ) old_lut.save_hdf5(filename=tmp_path / "lut.h5") - workflow[kinematics.LookupTableFilename] = (tmp_path / "lut.h5").as_posix() - loaded_lut = workflow.compute(kinematics.LookupTable) + workflow[unwrap.LookupTableFilename] = (tmp_path / "lut.h5").as_posix() + loaded_lut = workflow.compute(unwrap.LookupTable) assert_identical(lut.array, loaded_lut.array) assert_identical(lut.pulse_period, loaded_lut.pulse_period) assert lut.pulse_stride == loaded_lut.pulse_stride @@ -179,5 +179,5 @@ def test_GenericWavelengthWorkflow_with_lut_from_file_old_format( assert_identical(lut.time_resolution, loaded_lut.time_resolution) assert loaded_lut.choppers is None # No chopper info in old format - detector = workflow.compute(kinematics.WavelengthDetector[SampleRun]) + detector = workflow.compute(unwrap.WavelengthDetector[SampleRun]) assert 'wavelength' in detector.bins.coords From fbf182bdb5428636fad8472f2e71cbe4d3f4c0ae Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Fri, 6 Mar 2026 16:33:05 +0100 Subject: [PATCH 10/24] rename workflow --- packages/essreduce/src/ess/reduce/unwrap/__init__.py | 4 ++-- packages/essreduce/src/ess/reduce/unwrap/workflow.py | 2 +- packages/essreduce/tests/unwrap/unwrap_test.py | 8 ++++---- packages/essreduce/tests/unwrap/wfm_test.py | 4 ++-- packages/essreduce/tests/unwrap/workflow_test.py | 12 ++++++------ 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/essreduce/src/ess/reduce/unwrap/__init__.py b/packages/essreduce/src/ess/reduce/unwrap/__init__.py index 5af58dcb..ad6b78d3 100644 --- a/packages/essreduce/src/ess/reduce/unwrap/__init__.py +++ b/packages/essreduce/src/ess/reduce/unwrap/__init__.py @@ -38,7 +38,7 @@ WavelengthDetector, WavelengthMonitor, ) -from .workflow import GenericWavelengthWorkflow +from .workflow import GenericUnwrapWorkflow __all__ = [ "BeamlineComponentReading", @@ -46,7 +46,7 @@ "DiskChoppers", "DistanceResolution", "ErrorLimitedLookupTable", - "GenericWavelengthWorkflow", + "GenericUnwrapWorkflow", "LookupTable", "LookupTableFilename", "LookupTableRelativeErrorThreshold", diff --git a/packages/essreduce/src/ess/reduce/unwrap/workflow.py b/packages/essreduce/src/ess/reduce/unwrap/workflow.py index 662779b9..b493162f 100644 --- a/packages/essreduce/src/ess/reduce/unwrap/workflow.py +++ b/packages/essreduce/src/ess/reduce/unwrap/workflow.py @@ -41,7 +41,7 @@ def load_lookup_table(filename: LookupTableFilename) -> LookupTable: return LookupTable(**table) -def GenericWavelengthWorkflow( +def GenericUnwrapWorkflow( *, run_types: Iterable[sciline.typing.Key], monitor_types: Iterable[sciline.typing.Key], diff --git a/packages/essreduce/tests/unwrap/unwrap_test.py b/packages/essreduce/tests/unwrap/unwrap_test.py index 5d2dd31b..1fa4f885 100644 --- a/packages/essreduce/tests/unwrap/unwrap_test.py +++ b/packages/essreduce/tests/unwrap/unwrap_test.py @@ -7,7 +7,7 @@ from ess.reduce import unwrap from ess.reduce.unwrap import ( - GenericWavelengthWorkflow, + GenericUnwrapWorkflow, LookupTableWorkflow, PulsePeriod, fakes, @@ -71,7 +71,7 @@ def _make_workflow_event_mode( ) mon, ref = beamline.get_monitor("detector") - pl = GenericWavelengthWorkflow(run_types=[SampleRun], monitor_types=[FrameMonitor0]) + pl = GenericUnwrapWorkflow(run_types=[SampleRun], monitor_types=[FrameMonitor0]) if detector_or_monitor == "detector": pl[NeXusDetectorName] = "detector" pl[RawDetector[SampleRun]] = mon @@ -112,7 +112,7 @@ def _make_workflow_histogram_mode( ).to(unit=mon.bins.coords["event_time_offset"].bins.unit) ).rename(event_time_offset=dim) - pl = GenericWavelengthWorkflow(run_types=[SampleRun], monitor_types=[FrameMonitor0]) + pl = GenericUnwrapWorkflow(run_types=[SampleRun], monitor_types=[FrameMonitor0]) if detector_or_monitor == "detector": pl[NeXusDetectorName] = "detector" pl[RawDetector[SampleRun]] = mon @@ -401,7 +401,7 @@ def test_pulse_skipping_unwrap_when_first_half_of_first_pulse_is_missing( ) lut_wf[unwrap.LtotalRange] = distance, distance - pl = GenericWavelengthWorkflow(run_types=[SampleRun], monitor_types=[FrameMonitor0]) + pl = GenericUnwrapWorkflow(run_types=[SampleRun], monitor_types=[FrameMonitor0]) # Skip first pulse = half of the first frame a = mon.group('event_time_zero')['event_time_zero', 1:] diff --git a/packages/essreduce/tests/unwrap/wfm_test.py b/packages/essreduce/tests/unwrap/wfm_test.py index 840a4a2a..74ad1683 100644 --- a/packages/essreduce/tests/unwrap/wfm_test.py +++ b/packages/essreduce/tests/unwrap/wfm_test.py @@ -9,7 +9,7 @@ from scippneutron.conversion.graph.tof import elastic as elastic_graph from ess.reduce import unwrap -from ess.reduce.unwrap import GenericWavelengthWorkflow, LookupTableWorkflow, fakes +from ess.reduce.unwrap import GenericUnwrapWorkflow, LookupTableWorkflow, fakes from ess.reduce.nexus.types import AnyRun, NeXusDetectorName, RawDetector, SampleRun sl = pytest.importorskip("sciline") @@ -127,7 +127,7 @@ def setup_workflow( lut_workflow: sl.Pipeline, error_threshold: float = 0.1, ) -> sl.Pipeline: - pl = GenericWavelengthWorkflow(run_types=[SampleRun], monitor_types=[]) + pl = GenericUnwrapWorkflow(run_types=[SampleRun], monitor_types=[]) pl[RawDetector[SampleRun]] = raw_data pl[unwrap.DetectorLtotal[SampleRun]] = ltotal pl[NeXusDetectorName] = "detector" diff --git a/packages/essreduce/tests/unwrap/workflow_test.py b/packages/essreduce/tests/unwrap/workflow_test.py index 14e3ad1e..a723e466 100644 --- a/packages/essreduce/tests/unwrap/workflow_test.py +++ b/packages/essreduce/tests/unwrap/workflow_test.py @@ -9,7 +9,7 @@ from ess.reduce import unwrap from ess.reduce.unwrap import ( - GenericWavelengthWorkflow, + GenericUnwrapWorkflow, LookupTableWorkflow, fakes, ) @@ -28,7 +28,7 @@ @pytest.fixture -def workflow() -> GenericWavelengthWorkflow: +def workflow() -> GenericUnwrapWorkflow: sizes = {'detector_number': 10} calibrated_beamline = sc.DataArray( data=sc.ones(sizes=sizes), @@ -63,7 +63,7 @@ def workflow() -> GenericWavelengthWorkflow: ) ) - wf = GenericWavelengthWorkflow(run_types=[SampleRun], monitor_types=[]) + wf = GenericUnwrapWorkflow(run_types=[SampleRun], monitor_types=[]) wf[NeXusDetectorName] = "detector" wf[unwrap.LookupTableRelativeErrorThreshold] = {'detector': np.inf} wf[EmptyDetector[SampleRun]] = calibrated_beamline @@ -92,7 +92,7 @@ def test_LookupTableWorkflow_can_compute_lut(): assert lut.choppers is not None -def test_GenericWavelengthWorkflow_with_lut_from_tof_simulation(workflow): +def test_GenericUnwrapWorkflow_with_lut_from_tof_simulation(workflow): # Should be able to compute DetectorData without chopper and simulation params # This contains event_time_offset (time-of-arrival). _ = workflow.compute(RawDetector[SampleRun]) @@ -117,7 +117,7 @@ def test_GenericWavelengthWorkflow_with_lut_from_tof_simulation(workflow): assert 'wavelength' in detector.bins.coords -def test_GenericWavelengthWorkflow_with_lut_from_file( +def test_GenericUnwrapWorkflow_with_lut_from_file( workflow, tmp_path: pytest.TempPathFactory ): lut_wf = LookupTableWorkflow() @@ -145,7 +145,7 @@ def test_GenericWavelengthWorkflow_with_lut_from_file( assert 'wavelength' in detector.bins.coords -def test_GenericWavelengthWorkflow_with_lut_from_file_old_format( +def test_GenericUnwrapWorkflow_with_lut_from_file_old_format( workflow, tmp_path: pytest.TempPathFactory ): lut_wf = LookupTableWorkflow() From 9915597521c17c513838417cb524b0b0b7204e5c Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Fri, 6 Mar 2026 16:46:29 +0100 Subject: [PATCH 11/24] fix last tests --- .../essreduce/tests/unwrap/unwrap_test.py | 74 ++----------------- .../essreduce/tests/unwrap/workflow_test.py | 12 +-- 2 files changed, 12 insertions(+), 74 deletions(-) diff --git a/packages/essreduce/tests/unwrap/unwrap_test.py b/packages/essreduce/tests/unwrap/unwrap_test.py index 1fa4f885..62b788a6 100644 --- a/packages/essreduce/tests/unwrap/unwrap_test.py +++ b/packages/essreduce/tests/unwrap/unwrap_test.py @@ -6,12 +6,6 @@ from scippneutron.chopper import DiskChopper from ess.reduce import unwrap -from ess.reduce.unwrap import ( - GenericUnwrapWorkflow, - LookupTableWorkflow, - PulsePeriod, - fakes, -) from ess.reduce.nexus.types import ( AnyRun, FrameMonitor0, @@ -21,6 +15,12 @@ RawMonitor, SampleRun, ) +from ess.reduce.unwrap import ( + GenericUnwrapWorkflow, + LookupTableWorkflow, + PulsePeriod, + fakes, +) sl = pytest.importorskip("sciline") @@ -551,65 +551,3 @@ def test_unwrap_int(dtype, detector_or_monitor, lut_workflow_psc_choppers) -> No _validate_result_events( wavs=wavs, ref=ref, percentile=100, diff_threshold=0.02, rtol=0.05 ) - - -def test_compute_toa(): - distance = sc.scalar(80.0, unit="m") - choppers = fakes.psc_choppers() - - lut_wf = make_lut_workflow( - choppers=choppers, neutrons=500_000, seed=1234, pulse_stride=1 - ) - - pl, _ = _make_workflow_event_mode( - distance=distance, - choppers=choppers, - lut_workflow=lut_wf, - seed=2, - pulse_stride_offset=0, - error_threshold=0.1, - detector_or_monitor="detector", - ) - - toas = pl.compute(unwrap.ToaDetector[SampleRun]) - - assert "toa" in toas.bins.coords - raw = pl.compute(RawDetector[SampleRun]) - assert sc.allclose(toas.bins.coords["toa"], raw.bins.coords["event_time_offset"]) - - -def test_compute_toa_pulse_skipping(): - distance = sc.scalar(100.0, unit="m") - choppers = fakes.pulse_skipping_choppers() - - lut_wf = make_lut_workflow( - choppers=choppers, neutrons=500_000, seed=1234, pulse_stride=2 - ) - - pl, _ = _make_workflow_event_mode( - distance=distance, - choppers=choppers, - lut_workflow=lut_wf, - seed=2, - pulse_stride_offset=1, - error_threshold=0.1, - detector_or_monitor="detector", - ) - - raw = pl.compute(RawDetector[SampleRun]) - - toas = pl.compute(unwrap.ToaDetector[SampleRun]) - - assert "toa" in toas.bins.coords - pulse_period = lut_wf.compute(PulsePeriod) - hist = toas.bins.concat().hist( - toa=sc.array( - dims=["toa"], - values=[0, pulse_period.value, pulse_period.value * 2], - unit=pulse_period.unit, - ).to(unit=toas.bins.coords["toa"].unit) - ) - # There should be counts in both bins - n = raw.sum().value - assert hist.data[0].value > n / 5 - assert hist.data[1].value > n / 5 diff --git a/packages/essreduce/tests/unwrap/workflow_test.py b/packages/essreduce/tests/unwrap/workflow_test.py index a723e466..890166a2 100644 --- a/packages/essreduce/tests/unwrap/workflow_test.py +++ b/packages/essreduce/tests/unwrap/workflow_test.py @@ -8,11 +8,6 @@ from scipp.testing import assert_identical from ess.reduce import unwrap -from ess.reduce.unwrap import ( - GenericUnwrapWorkflow, - LookupTableWorkflow, - fakes, -) from ess.reduce.nexus.types import ( AnyRun, DiskChoppers, @@ -23,6 +18,11 @@ RawDetector, SampleRun, ) +from ess.reduce.unwrap import ( + GenericUnwrapWorkflow, + LookupTableWorkflow, + fakes, +) sl = pytest.importorskip("sciline") @@ -100,7 +100,7 @@ def test_GenericUnwrapWorkflow_with_lut_from_tof_simulation(workflow): with pytest.raises(sciline.UnsatisfiedRequirement): _ = workflow.compute(unwrap.LookupTable) with pytest.raises(sciline.UnsatisfiedRequirement): - _ = workflow.compute(unwrap.TofDetector[SampleRun]) + _ = workflow.compute(unwrap.WavelengthDetector[SampleRun]) lut_wf = LookupTableWorkflow() lut_wf[DiskChoppers[AnyRun]] = fakes.psc_choppers() From b011129f6c789e8eb627f0ede57d8949c251c970 Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Fri, 6 Mar 2026 16:50:48 +0100 Subject: [PATCH 12/24] fix linting --- packages/essreduce/tests/unwrap/lut_test.py | 2 +- .../essreduce/tests/unwrap/unwrap_test.py | 7 +----- packages/essreduce/tests/unwrap/wfm_test.py | 4 +--- pixi.lock | 22 ++++--------------- 4 files changed, 7 insertions(+), 28 deletions(-) diff --git a/packages/essreduce/tests/unwrap/lut_test.py b/packages/essreduce/tests/unwrap/lut_test.py index 621ea372..495e57dd 100644 --- a/packages/essreduce/tests/unwrap/lut_test.py +++ b/packages/essreduce/tests/unwrap/lut_test.py @@ -5,8 +5,8 @@ from scippneutron.chopper import DiskChopper from ess.reduce import unwrap -from ess.reduce.unwrap import LookupTableWorkflow from ess.reduce.nexus.types import AnyRun +from ess.reduce.unwrap import LookupTableWorkflow sl = pytest.importorskip("sciline") diff --git a/packages/essreduce/tests/unwrap/unwrap_test.py b/packages/essreduce/tests/unwrap/unwrap_test.py index 62b788a6..708d3dc3 100644 --- a/packages/essreduce/tests/unwrap/unwrap_test.py +++ b/packages/essreduce/tests/unwrap/unwrap_test.py @@ -15,12 +15,7 @@ RawMonitor, SampleRun, ) -from ess.reduce.unwrap import ( - GenericUnwrapWorkflow, - LookupTableWorkflow, - PulsePeriod, - fakes, -) +from ess.reduce.unwrap import GenericUnwrapWorkflow, LookupTableWorkflow, fakes sl = pytest.importorskip("sciline") diff --git a/packages/essreduce/tests/unwrap/wfm_test.py b/packages/essreduce/tests/unwrap/wfm_test.py index 74ad1683..d150640f 100644 --- a/packages/essreduce/tests/unwrap/wfm_test.py +++ b/packages/essreduce/tests/unwrap/wfm_test.py @@ -5,12 +5,10 @@ import pytest import scipp as sc from scippneutron.chopper import DiskChopper -from scippneutron.conversion.graph.beamline import beamline as beamline_graph -from scippneutron.conversion.graph.tof import elastic as elastic_graph from ess.reduce import unwrap -from ess.reduce.unwrap import GenericUnwrapWorkflow, LookupTableWorkflow, fakes from ess.reduce.nexus.types import AnyRun, NeXusDetectorName, RawDetector, SampleRun +from ess.reduce.unwrap import GenericUnwrapWorkflow, LookupTableWorkflow, fakes sl = pytest.importorskip("sciline") diff --git a/pixi.lock b/pixi.lock index 6fdfc730..17a3d60b 100644 --- a/pixi.lock +++ b/pixi.lock @@ -5,8 +5,6 @@ environments: - url: https://conda.anaconda.org/conda-forge/ indexes: - https://pypi.org/simple - options: - pypi-prerelease-mode: if-necessary-or-explicit packages: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda @@ -426,8 +424,6 @@ environments: - url: https://conda.anaconda.org/conda-forge/ indexes: - https://pypi.org/simple - options: - pypi-prerelease-mode: if-necessary-or-explicit packages: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda @@ -1053,8 +1049,6 @@ environments: - url: https://conda.anaconda.org/conda-forge/ indexes: - https://pypi.org/simple - options: - pypi-prerelease-mode: if-necessary-or-explicit packages: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda @@ -1612,8 +1606,6 @@ environments: - url: https://conda.anaconda.org/conda-forge/ indexes: - https://pypi.org/simple - options: - pypi-prerelease-mode: if-necessary-or-explicit packages: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda @@ -2033,8 +2025,6 @@ environments: - url: https://conda.anaconda.org/conda-forge/ indexes: - https://pypi.org/simple - options: - pypi-prerelease-mode: if-necessary-or-explicit packages: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda @@ -2366,8 +2356,6 @@ environments: - url: https://conda.anaconda.org/conda-forge/ indexes: - https://pypi.org/simple - options: - pypi-prerelease-mode: if-necessary-or-explicit packages: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda @@ -2787,8 +2775,6 @@ environments: - url: https://conda.anaconda.org/conda-forge/ indexes: - https://pypi.org/simple - options: - pypi-prerelease-mode: if-necessary-or-explicit packages: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda @@ -3120,8 +3106,6 @@ environments: - url: https://conda.anaconda.org/conda-forge/ indexes: - https://pypi.org/simple - options: - pypi-prerelease-mode: if-necessary-or-explicit packages: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda @@ -3685,7 +3669,7 @@ packages: requires_python: '>=3.8' - pypi: ./packages/essimaging name: essimaging - version: 26.1.1.dev1198+gbcc8a6f.d20260226 + version: 26.1.1.dev1215+g99155975 sha256: a8e5ca76e8f0394bfe2d09155889730f5889b48ccf696c48ecd568d0cb79bbc4 requires_dist: - dask>=2022.1.0 @@ -3716,9 +3700,10 @@ packages: - tof>=25.8.0 ; extra == 'docs' - tqdm ; extra == 'docs' requires_python: '>=3.11' + editable: true - pypi: ./packages/essreduce name: essreduce - version: 26.2.3.dev457+gbcc8a6f.d20260226 + version: 26.2.3.dev474+g99155975 sha256: 2ace4ae640f740ccef8e9661e37e4ffb2322eca20341684a49a89499dd378cb2 requires_dist: - sciline>=25.11.0 @@ -3749,6 +3734,7 @@ packages: - sphinx-design ; extra == 'docs' - tof>=25.12.0 ; extra == 'docs' requires_python: '>=3.11' + editable: true - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl name: executing version: 2.2.1 From 7dddbcadf9d9a52aa08e8b0c70ff06a91084cf21 Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Mon, 9 Mar 2026 14:36:56 +0100 Subject: [PATCH 13/24] time_of_flight -> unwrap --- .../docs/odin/odin-data-reduction.ipynb | 6 ++--- .../odin/odin-make-tof-lookup-table.ipynb | 24 +++++++++---------- .../docs/tbl/tbl-data-reduction.ipynb | 4 ++-- .../docs/tbl/tbl-make-tof-lookup-table.ipynb | 24 +++++++++---------- packages/essimaging/src/ess/imaging/types.py | 2 +- packages/essimaging/src/ess/odin/workflows.py | 2 +- packages/essimaging/src/ess/tbl/workflow.py | 2 +- .../tests/odin/data_reduction_test.py | 4 ++-- .../tests/tbl/data_reduction_test.py | 22 ++++++++--------- 9 files changed, 45 insertions(+), 45 deletions(-) diff --git a/packages/essimaging/docs/odin/odin-data-reduction.ipynb b/packages/essimaging/docs/odin/odin-data-reduction.ipynb index d4a94083..4852d171 100644 --- a/packages/essimaging/docs/odin/odin-data-reduction.ipynb +++ b/packages/essimaging/docs/odin/odin-data-reduction.ipynb @@ -20,7 +20,7 @@ "outputs": [], "source": [ "import scipp as sc\n", - "from ess.reduce import time_of_flight\n", + "from ess.reduce import unwrap\n", "from ess import odin\n", "import ess.odin.data # noqa: F401\n", "from ess.imaging.types import *" @@ -45,7 +45,7 @@ "\n", "wf[Filename[SampleRun]] = odin.data.iron_simulation_sample_small()\n", "wf[NeXusDetectorName] = \"event_mode_detectors/timepix3\"\n", - "wf[time_of_flight.TimeOfFlightLookupTableFilename] = odin.data.odin_tof_lookup_table()" + "wf[unwrap.TimeOfFlightLookupTableFilename] = odin.data.odin_tof_lookup_table()" ] }, { @@ -116,7 +116,7 @@ "metadata": {}, "outputs": [], "source": [ - "table = wf.compute(time_of_flight.TimeOfFlightLookupTable)\n", + "table = wf.compute(unwrap.TimeOfFlightLookupTable)\n", "table.plot(figsize=(9, 4))" ] }, diff --git a/packages/essimaging/docs/odin/odin-make-tof-lookup-table.ipynb b/packages/essimaging/docs/odin/odin-make-tof-lookup-table.ipynb index 6478cfa9..e000c82f 100644 --- a/packages/essimaging/docs/odin/odin-make-tof-lookup-table.ipynb +++ b/packages/essimaging/docs/odin/odin-make-tof-lookup-table.ipynb @@ -16,7 +16,7 @@ "outputs": [], "source": [ "import scipp as sc\n", - "from ess.reduce import time_of_flight\n", + "from ess.reduce import unwrap\n", "from ess.reduce.nexus.types import AnyRun\n", "from ess.odin.beamline import choppers" ] @@ -39,16 +39,16 @@ "source_position = sc.vector([0, 0, 0], unit='m')\n", "disk_choppers = choppers(source_position)\n", "\n", - "wf = time_of_flight.TofLookupTableWorkflow()\n", - "wf[time_of_flight.DiskChoppers[AnyRun]] = disk_choppers\n", - "wf[time_of_flight.SourcePosition] = source_position\n", - "wf[time_of_flight.NumberOfSimulatedNeutrons] = 200_000 # Increase this number for more reliable results\n", - "wf[time_of_flight.SimulationSeed] = 1234\n", - "wf[time_of_flight.PulseStride] = 2\n", - "wf[time_of_flight.LtotalRange] = sc.scalar(55.0, unit=\"m\"), sc.scalar(65.0, unit=\"m\")\n", - "wf[time_of_flight.DistanceResolution] = sc.scalar(0.1, unit=\"m\")\n", - "wf[time_of_flight.TimeResolution] = sc.scalar(250.0, unit='us')\n", - "wf[time_of_flight.LookupTableRelativeErrorThreshold] = 0.02" + "wf = unwrap.TofLookupTableWorkflow()\n", + "wf[unwrap.DiskChoppers[AnyRun]] = disk_choppers\n", + "wf[unwrap.SourcePosition] = source_position\n", + "wf[unwrap.NumberOfSimulatedNeutrons] = 200_000 # Increase this number for more reliable results\n", + "wf[unwrap.SimulationSeed] = 1234\n", + "wf[unwrap.PulseStride] = 2\n", + "wf[unwrap.LtotalRange] = sc.scalar(55.0, unit=\"m\"), sc.scalar(65.0, unit=\"m\")\n", + "wf[unwrap.DistanceResolution] = sc.scalar(0.1, unit=\"m\")\n", + "wf[unwrap.TimeResolution] = sc.scalar(250.0, unit='us')\n", + "wf[unwrap.LookupTableRelativeErrorThreshold] = 0.02" ] }, { @@ -66,7 +66,7 @@ "metadata": {}, "outputs": [], "source": [ - "table = wf.compute(time_of_flight.TimeOfFlightLookupTable)\n", + "table = wf.compute(unwrap.TimeOfFlightLookupTable)\n", "table" ] }, diff --git a/packages/essimaging/docs/tbl/tbl-data-reduction.ipynb b/packages/essimaging/docs/tbl/tbl-data-reduction.ipynb index 2dfa79bc..a96186da 100644 --- a/packages/essimaging/docs/tbl/tbl-data-reduction.ipynb +++ b/packages/essimaging/docs/tbl/tbl-data-reduction.ipynb @@ -16,7 +16,7 @@ "outputs": [], "source": [ "import plopp as pp\n", - "from ess.reduce import time_of_flight\n", + "from ess.reduce import unwrap\n", "from ess import tbl\n", "import ess.tbl.data # noqa: F401\n", "from ess.imaging.types import *" @@ -117,7 +117,7 @@ "metadata": {}, "outputs": [], "source": [ - "table = wf.compute(time_of_flight.TimeOfFlightLookupTable)\n", + "table = wf.compute(unwrap.TimeOfFlightLookupTable)\n", "table.plot()" ] }, diff --git a/packages/essimaging/docs/tbl/tbl-make-tof-lookup-table.ipynb b/packages/essimaging/docs/tbl/tbl-make-tof-lookup-table.ipynb index cbb8e744..99690405 100644 --- a/packages/essimaging/docs/tbl/tbl-make-tof-lookup-table.ipynb +++ b/packages/essimaging/docs/tbl/tbl-make-tof-lookup-table.ipynb @@ -16,7 +16,7 @@ "outputs": [], "source": [ "import scipp as sc\n", - "from ess.reduce import time_of_flight\n", + "from ess.reduce import unwrap\n", "from ess.reduce.nexus.types import AnyRun" ] }, @@ -37,16 +37,16 @@ "source": [ "source_position = sc.vector([0, 0, 0], unit='m')\n", "\n", - "wf = time_of_flight.TofLookupTableWorkflow()\n", - "wf[time_of_flight.DiskChoppers[AnyRun]] = {}\n", - "wf[time_of_flight.SourcePosition] = source_position\n", - "wf[time_of_flight.NumberOfSimulatedNeutrons] = 200_000 # Increase this number for more reliable results\n", - "wf[time_of_flight.SimulationSeed] = 1234\n", - "wf[time_of_flight.PulseStride] = 1\n", - "wf[time_of_flight.LtotalRange] = sc.scalar(25.0, unit=\"m\"), sc.scalar(35.0, unit=\"m\")\n", - "wf[time_of_flight.DistanceResolution] = sc.scalar(0.1, unit=\"m\")\n", - "wf[time_of_flight.TimeResolution] = sc.scalar(250.0, unit='us')\n", - "wf[time_of_flight.LookupTableRelativeErrorThreshold] = 1.0" + "wf = unwrap.TofLookupTableWorkflow()\n", + "wf[unwrap.DiskChoppers[AnyRun]] = {}\n", + "wf[unwrap.SourcePosition] = source_position\n", + "wf[unwrap.NumberOfSimulatedNeutrons] = 200_000 # Increase this number for more reliable results\n", + "wf[unwrap.SimulationSeed] = 1234\n", + "wf[unwrap.PulseStride] = 1\n", + "wf[unwrap.LtotalRange] = sc.scalar(25.0, unit=\"m\"), sc.scalar(35.0, unit=\"m\")\n", + "wf[unwrap.DistanceResolution] = sc.scalar(0.1, unit=\"m\")\n", + "wf[unwrap.TimeResolution] = sc.scalar(250.0, unit='us')\n", + "wf[unwrap.LookupTableRelativeErrorThreshold] = 1.0" ] }, { @@ -64,7 +64,7 @@ "metadata": {}, "outputs": [], "source": [ - "table = wf.compute(time_of_flight.TimeOfFlightLookupTable)\n", + "table = wf.compute(unwrap.TimeOfFlightLookupTable)\n", "table" ] }, diff --git a/packages/essimaging/src/ess/imaging/types.py b/packages/essimaging/src/ess/imaging/types.py index 7667f920..ab021b29 100644 --- a/packages/essimaging/src/ess/imaging/types.py +++ b/packages/essimaging/src/ess/imaging/types.py @@ -7,7 +7,7 @@ import sciline import scipp as sc from ess.reduce.nexus import types as reduce_t -from ess.reduce.time_of_flight import types as tof_t +from ess.reduce.unwrap import types as tof_t from ess.reduce.uncertainty import UncertaintyBroadcastMode as _UncertaintyBroadcastMode # 1 TypeVars used to parametrize the generic parts of the workflow diff --git a/packages/essimaging/src/ess/odin/workflows.py b/packages/essimaging/src/ess/odin/workflows.py index 8e2d804b..b9666cc7 100644 --- a/packages/essimaging/src/ess/odin/workflows.py +++ b/packages/essimaging/src/ess/odin/workflows.py @@ -5,7 +5,7 @@ """ import sciline -from ess.reduce.time_of_flight.workflow import GenericTofWorkflow +from ess.reduce.unwrap.workflow import GenericTofWorkflow from ..imaging.conversion import providers as conversion_providers from ..imaging.types import ( diff --git a/packages/essimaging/src/ess/tbl/workflow.py b/packages/essimaging/src/ess/tbl/workflow.py index 7046a2b4..b0addc76 100644 --- a/packages/essimaging/src/ess/tbl/workflow.py +++ b/packages/essimaging/src/ess/tbl/workflow.py @@ -5,7 +5,7 @@ """ import sciline -from ess.reduce.time_of_flight.workflow import GenericTofWorkflow +from ess.reduce.unwrap.workflow import GenericTofWorkflow from ..imaging.conversion import providers as conversion_providers from ..imaging.types import ( diff --git a/packages/essimaging/tests/odin/data_reduction_test.py b/packages/essimaging/tests/odin/data_reduction_test.py index 67edc528..e14fd49e 100644 --- a/packages/essimaging/tests/odin/data_reduction_test.py +++ b/packages/essimaging/tests/odin/data_reduction_test.py @@ -3,7 +3,7 @@ import pytest import sciline as sl -from ess.reduce.time_of_flight import LookupTableRelativeErrorThreshold +from ess.reduce.unwrap import LookupTableRelativeErrorThreshold import ess.odin.data # noqa: F401 from ess import odin @@ -53,7 +53,7 @@ def test_can_load_detector_data(workflow, run_type): @pytest.mark.parametrize("run_type", [SampleRun, OpenBeamRun]) -def test_can_compute_time_of_flight(workflow, run_type): +def test_can_compute_unwrap(workflow, run_type): da = workflow.compute(TofDetector[run_type]) assert "tof" in da.bins.coords diff --git a/packages/essimaging/tests/tbl/data_reduction_test.py b/packages/essimaging/tests/tbl/data_reduction_test.py index 7e8dcd3c..3d33f44e 100644 --- a/packages/essimaging/tests/tbl/data_reduction_test.py +++ b/packages/essimaging/tests/tbl/data_reduction_test.py @@ -4,7 +4,7 @@ import pytest import sciline as sl import scipp as sc -from ess.reduce import time_of_flight +from ess.reduce import unwrap from ess.reduce.nexus.types import AnyRun import ess.tbl.data # noqa: F401 @@ -27,13 +27,13 @@ def tof_lookup_table() -> sl.Pipeline: Compute tof lookup table on-the-fly. """ - lut_wf = time_of_flight.TofLookupTableWorkflow() - lut_wf[time_of_flight.DiskChoppers[AnyRun]] = {} - lut_wf[time_of_flight.SourcePosition] = sc.vector([0, 0, 0], unit="m") - lut_wf[time_of_flight.NumberOfSimulatedNeutrons] = 200_000 - lut_wf[time_of_flight.SimulationSeed] = 333 - lut_wf[time_of_flight.PulseStride] = 1 - lut_wf[time_of_flight.LtotalRange] = ( + lut_wf = unwrap.TofLookupTableWorkflow() + lut_wf[unwrap.DiskChoppers[AnyRun]] = {} + lut_wf[unwrap.SourcePosition] = sc.vector([0, 0, 0], unit="m") + lut_wf[unwrap.NumberOfSimulatedNeutrons] = 200_000 + lut_wf[unwrap.SimulationSeed] = 333 + lut_wf[unwrap.PulseStride] = 1 + lut_wf[unwrap.LtotalRange] = ( sc.scalar(25.0, unit="m"), sc.scalar(35.0, unit="m"), ) @@ -48,7 +48,7 @@ def workflow() -> sl.Pipeline: wf = tbl.TblWorkflow() wf[Filename[SampleRun]] = tbl.data.tutorial_sample_data() wf[TimeOfFlightLookupTableFilename] = tbl.data.tbl_tof_lookup_table_no_choppers() - wf[time_of_flight.LookupTableRelativeErrorThreshold] = { + wf[unwrap.LookupTableRelativeErrorThreshold] = { "ngem_detector": float('inf'), "he3_detector_bank0": float('inf'), "he3_detector_bank1": float('inf'), @@ -77,7 +77,7 @@ def test_can_load_detector_data(workflow, bank_name): @pytest.mark.parametrize( "bank_name", ["ngem_detector", "he3_detector_bank0", "he3_detector_bank1"] ) -def test_can_compute_time_of_flight(workflow, bank_name): +def test_can_compute_unwrap(workflow, bank_name): workflow[NeXusDetectorName] = bank_name da = workflow.compute(TofDetector[SampleRun]) @@ -87,7 +87,7 @@ def test_can_compute_time_of_flight(workflow, bank_name): @pytest.mark.parametrize( "bank_name", ["ngem_detector", "he3_detector_bank0", "he3_detector_bank1"] ) -def test_can_compute_time_of_flight_from_custom_lut( +def test_can_compute_unwrap_from_custom_lut( workflow, tof_lookup_table, bank_name ): workflow[NeXusDetectorName] = bank_name From ff81bbca0700acf8a7220ac58ca60761cb438ae5 Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Mon, 9 Mar 2026 14:42:00 +0100 Subject: [PATCH 14/24] TimeOfFlightLookupTable -> LookupTable --- .../essimaging/docs/odin/odin-data-reduction.ipynb | 4 ++-- .../docs/odin/odin-make-tof-lookup-table.ipynb | 2 +- .../essimaging/docs/tbl/tbl-data-reduction.ipynb | 4 ++-- .../docs/tbl/tbl-make-tof-lookup-table.ipynb | 2 +- packages/essimaging/src/ess/imaging/types.py | 12 ++++++------ .../essimaging/tests/odin/data_reduction_test.py | 8 ++++---- packages/essimaging/tests/tbl/data_reduction_test.py | 10 +++++----- 7 files changed, 21 insertions(+), 21 deletions(-) diff --git a/packages/essimaging/docs/odin/odin-data-reduction.ipynb b/packages/essimaging/docs/odin/odin-data-reduction.ipynb index 4852d171..5b22180c 100644 --- a/packages/essimaging/docs/odin/odin-data-reduction.ipynb +++ b/packages/essimaging/docs/odin/odin-data-reduction.ipynb @@ -45,7 +45,7 @@ "\n", "wf[Filename[SampleRun]] = odin.data.iron_simulation_sample_small()\n", "wf[NeXusDetectorName] = \"event_mode_detectors/timepix3\"\n", - "wf[unwrap.TimeOfFlightLookupTableFilename] = odin.data.odin_tof_lookup_table()" + "wf[unwrap.LookupTableFilename] = odin.data.odin_tof_lookup_table()" ] }, { @@ -116,7 +116,7 @@ "metadata": {}, "outputs": [], "source": [ - "table = wf.compute(unwrap.TimeOfFlightLookupTable)\n", + "table = wf.compute(unwrap.LookupTable)\n", "table.plot(figsize=(9, 4))" ] }, diff --git a/packages/essimaging/docs/odin/odin-make-tof-lookup-table.ipynb b/packages/essimaging/docs/odin/odin-make-tof-lookup-table.ipynb index e000c82f..502753c0 100644 --- a/packages/essimaging/docs/odin/odin-make-tof-lookup-table.ipynb +++ b/packages/essimaging/docs/odin/odin-make-tof-lookup-table.ipynb @@ -66,7 +66,7 @@ "metadata": {}, "outputs": [], "source": [ - "table = wf.compute(unwrap.TimeOfFlightLookupTable)\n", + "table = wf.compute(unwrap.LookupTable)\n", "table" ] }, diff --git a/packages/essimaging/docs/tbl/tbl-data-reduction.ipynb b/packages/essimaging/docs/tbl/tbl-data-reduction.ipynb index a96186da..48a132aa 100644 --- a/packages/essimaging/docs/tbl/tbl-data-reduction.ipynb +++ b/packages/essimaging/docs/tbl/tbl-data-reduction.ipynb @@ -40,7 +40,7 @@ "wf = tbl.TblWorkflow()\n", "\n", "wf[Filename[SampleRun]] = tbl.data.tutorial_sample_data()\n", - "wf[TimeOfFlightLookupTableFilename] = tbl.data.tbl_tof_lookup_table_no_choppers()" + "wf[LookupTableFilename] = tbl.data.tbl_tof_lookup_table_no_choppers()" ] }, { @@ -117,7 +117,7 @@ "metadata": {}, "outputs": [], "source": [ - "table = wf.compute(unwrap.TimeOfFlightLookupTable)\n", + "table = wf.compute(unwrap.LookupTable)\n", "table.plot()" ] }, diff --git a/packages/essimaging/docs/tbl/tbl-make-tof-lookup-table.ipynb b/packages/essimaging/docs/tbl/tbl-make-tof-lookup-table.ipynb index 99690405..53581e93 100644 --- a/packages/essimaging/docs/tbl/tbl-make-tof-lookup-table.ipynb +++ b/packages/essimaging/docs/tbl/tbl-make-tof-lookup-table.ipynb @@ -64,7 +64,7 @@ "metadata": {}, "outputs": [], "source": [ - "table = wf.compute(unwrap.TimeOfFlightLookupTable)\n", + "table = wf.compute(unwrap.LookupTable)\n", "table" ] }, diff --git a/packages/essimaging/src/ess/imaging/types.py b/packages/essimaging/src/ess/imaging/types.py index ab021b29..2adb4a4a 100644 --- a/packages/essimaging/src/ess/imaging/types.py +++ b/packages/essimaging/src/ess/imaging/types.py @@ -7,7 +7,7 @@ import sciline import scipp as sc from ess.reduce.nexus import types as reduce_t -from ess.reduce.unwrap import types as tof_t +from ess.reduce.unwrap import types as unwrap_t from ess.reduce.uncertainty import UncertaintyBroadcastMode as _UncertaintyBroadcastMode # 1 TypeVars used to parametrize the generic parts of the workflow @@ -21,11 +21,11 @@ RawDetector = reduce_t.RawDetector RawMonitor = reduce_t.RawMonitor -DetectorLtotal = tof_t.DetectorLtotal -TofDetector = tof_t.TofDetector -PulseStrideOffset = tof_t.PulseStrideOffset -TimeOfFlightLookupTable = tof_t.TimeOfFlightLookupTable -TimeOfFlightLookupTableFilename = tof_t.TimeOfFlightLookupTableFilename +DetectorLtotal = unwrap_t.DetectorLtotal +TofDetector = unwrap_t.TofDetector +PulseStrideOffset = unwrap_t.PulseStrideOffset +LookupTable = unwrap_t.LookupTable +LookupTableFilename = unwrap_t.LookupTableFilename UncertaintyBroadcastMode = _UncertaintyBroadcastMode diff --git a/packages/essimaging/tests/odin/data_reduction_test.py b/packages/essimaging/tests/odin/data_reduction_test.py index e14fd49e..efbc3aa0 100644 --- a/packages/essimaging/tests/odin/data_reduction_test.py +++ b/packages/essimaging/tests/odin/data_reduction_test.py @@ -13,8 +13,8 @@ OpenBeamRun, RawDetector, SampleRun, - TimeOfFlightLookupTable, - TimeOfFlightLookupTableFilename, + LookupTable, + LookupTableFilename, TofDetector, WavelengthDetector, ) @@ -29,12 +29,12 @@ def workflow() -> sl.Pipeline: wf[Filename[SampleRun]] = odin.data.iron_simulation_sample_small() wf[Filename[OpenBeamRun]] = odin.data.iron_simulation_ob_small() wf[NeXusDetectorName] = "event_mode_detectors/timepix3" - wf[TimeOfFlightLookupTableFilename] = odin.data.odin_tof_lookup_table() + wf[LookupTableFilename] = odin.data.odin_tof_lookup_table() wf[LookupTableRelativeErrorThreshold] = { "event_mode_detectors/timepix3": float('inf') } # Cache the lookup table - wf[TimeOfFlightLookupTable] = wf.compute(TimeOfFlightLookupTable) + wf[LookupTable] = wf.compute(LookupTable) return wf diff --git a/packages/essimaging/tests/tbl/data_reduction_test.py b/packages/essimaging/tests/tbl/data_reduction_test.py index 3d33f44e..65f24a61 100644 --- a/packages/essimaging/tests/tbl/data_reduction_test.py +++ b/packages/essimaging/tests/tbl/data_reduction_test.py @@ -14,8 +14,8 @@ NeXusDetectorName, RawDetector, SampleRun, - TimeOfFlightLookupTable, - TimeOfFlightLookupTableFilename, + LookupTable, + LookupTableFilename, TofDetector, WavelengthDetector, ) @@ -37,7 +37,7 @@ def tof_lookup_table() -> sl.Pipeline: sc.scalar(25.0, unit="m"), sc.scalar(35.0, unit="m"), ) - return lut_wf.compute(TimeOfFlightLookupTable) + return lut_wf.compute(LookupTable) @pytest.fixture @@ -47,7 +47,7 @@ def workflow() -> sl.Pipeline: """ wf = tbl.TblWorkflow() wf[Filename[SampleRun]] = tbl.data.tutorial_sample_data() - wf[TimeOfFlightLookupTableFilename] = tbl.data.tbl_tof_lookup_table_no_choppers() + wf[LookupTableFilename] = tbl.data.tbl_tof_lookup_table_no_choppers() wf[unwrap.LookupTableRelativeErrorThreshold] = { "ngem_detector": float('inf'), "he3_detector_bank0": float('inf'), @@ -91,7 +91,7 @@ def test_can_compute_unwrap_from_custom_lut( workflow, tof_lookup_table, bank_name ): workflow[NeXusDetectorName] = bank_name - workflow[TimeOfFlightLookupTable] = tof_lookup_table + workflow[LookupTable] = tof_lookup_table da = workflow.compute(TofDetector[SampleRun]) assert "tof" in da.bins.coords From 08675c12071162bd0002ee8c5aee6a1f319cf2e4 Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Mon, 9 Mar 2026 14:50:15 +0100 Subject: [PATCH 15/24] fix more names, remove conversion to wavelength --- .../essimaging/docs/api-reference/index.md | 1 - .../essimaging/src/ess/imaging/conversion.py | 62 ------------------- packages/essimaging/src/ess/imaging/types.py | 20 +++--- packages/essimaging/src/ess/odin/workflows.py | 7 +-- packages/essimaging/src/ess/tbl/workflow.py | 10 +-- 5 files changed, 15 insertions(+), 85 deletions(-) delete mode 100644 packages/essimaging/src/ess/imaging/conversion.py diff --git a/packages/essimaging/docs/api-reference/index.md b/packages/essimaging/docs/api-reference/index.md index c84f2d87..4dd8bbab 100644 --- a/packages/essimaging/docs/api-reference/index.md +++ b/packages/essimaging/docs/api-reference/index.md @@ -10,7 +10,6 @@ :template: module-template.rst :recursive: - conversion data tools types diff --git a/packages/essimaging/src/ess/imaging/conversion.py b/packages/essimaging/src/ess/imaging/conversion.py deleted file mode 100644 index e26f5611..00000000 --- a/packages/essimaging/src/ess/imaging/conversion.py +++ /dev/null @@ -1,62 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright (c) 2025 Scipp contributors (https://github.com/scipp) -""" -Contains the providers to compute neutron time-of-flight and wavelength. -""" - -import scippneutron as scn -import scippnexus as snx - -from .types import ( - CoordTransformGraph, - GravityVector, - Position, - RunType, - TofDetector, - WavelengthDetector, -) - - -def make_coordinate_transform_graph( - sample_position: Position[snx.NXsample, RunType], - source_position: Position[snx.NXsource, RunType], - gravity: GravityVector, -) -> CoordTransformGraph[RunType]: - """ - Create a graph of coordinate transformations to compute the wavelength from the - time-of-flight. - """ - graph = { - **scn.conversion.graph.beamline.beamline(scatter=False), - **scn.conversion.graph.tof.elastic("tof"), - 'sample_position': lambda: sample_position, - 'source_position': lambda: source_position, - 'gravity': lambda: gravity, - } - return CoordTransformGraph(graph) - - -def compute_detector_wavelength( - tof_data: TofDetector[RunType], - graph: CoordTransformGraph[RunType], -) -> WavelengthDetector[RunType]: - """ - Compute the wavelength of neutrons detected by the detector. - - Parameters - ---------- - tof_data: - Data with a time-of-flight coordinate. - graph: - Graph of coordinate transformations. - """ - return WavelengthDetector[RunType]( - tof_data.transform_coords("wavelength", graph=graph) - ) - - -providers = ( - make_coordinate_transform_graph, - compute_detector_wavelength, -) -"""Providers to compute neutron time-of-flight and wavelength.""" diff --git a/packages/essimaging/src/ess/imaging/types.py b/packages/essimaging/src/ess/imaging/types.py index 2adb4a4a..d7ecfd39 100644 --- a/packages/essimaging/src/ess/imaging/types.py +++ b/packages/essimaging/src/ess/imaging/types.py @@ -22,7 +22,7 @@ RawMonitor = reduce_t.RawMonitor DetectorLtotal = unwrap_t.DetectorLtotal -TofDetector = unwrap_t.TofDetector +WavelengthDetector = unwrap_t.WavelengthDetector PulseStrideOffset = unwrap_t.PulseStrideOffset LookupTable = unwrap_t.LookupTable LookupTableFilename = unwrap_t.LookupTableFilename @@ -30,27 +30,27 @@ UncertaintyBroadcastMode = _UncertaintyBroadcastMode -SampleRun = NewType('SampleRun', int) +SampleRun = NewType("SampleRun", int) """Sample run; a run with a sample in the beam.""" -DarkBackgroundRun = NewType('DarkBackgroundRun', int) +DarkBackgroundRun = NewType("DarkBackgroundRun", int) """Dark background run; a run with no sample in the beam, and the shutter closed, to measure the dark current of the detector.""" -OpenBeamRun = NewType('OpenBeamRun', int) +OpenBeamRun = NewType("OpenBeamRun", int) """Open beam run; a run with no sample in the beam, and the shutter open, to measure the beam profile.""" -BeamMonitor1 = NewType('BeamMonitor1', int) +BeamMonitor1 = NewType("BeamMonitor1", int) """Beam monitor number 1""" -BeamMonitor2 = NewType('BeamMonitor2', int) +BeamMonitor2 = NewType("BeamMonitor2", int) """Beam monitor number 2""" -BeamMonitor3 = NewType('BeamMonitor3', int) +BeamMonitor3 = NewType("BeamMonitor3", int) """Beam monitor number 3""" -BeamMonitor4 = NewType('BeamMonitor4', int) +BeamMonitor4 = NewType("BeamMonitor4", int) """Beam monitor number 4""" RunType = reduce_t.RunType @@ -68,7 +68,7 @@ class WavelengthDetector(sciline.Scope[RunType, sc.DataArray], sc.DataArray): """Detector counts with wavelength information.""" -MaskingRules = NewType('MaskingRules', MappingProxyType[str, Callable]) +MaskingRules = NewType("MaskingRules", MappingProxyType[str, Callable]) """Functions to mask different dimensions of Odin data.""" @@ -84,7 +84,7 @@ class BackgroundSubtractedDetector(sciline.Scope[RunType, sc.DataArray], sc.Data """Detector counts with dark background subtracted.""" -NormalizedImage = NewType('NormalizedImage', sc.DataArray) +NormalizedImage = NewType("NormalizedImage", sc.DataArray) """Final image: background-subtracted sample run divided by background-subtracted open beam run.""" diff --git a/packages/essimaging/src/ess/odin/workflows.py b/packages/essimaging/src/ess/odin/workflows.py index b9666cc7..ee2a2456 100644 --- a/packages/essimaging/src/ess/odin/workflows.py +++ b/packages/essimaging/src/ess/odin/workflows.py @@ -5,9 +5,8 @@ """ import sciline -from ess.reduce.unwrap.workflow import GenericTofWorkflow +from ess.reduce.unwrap.workflow import GenericUnwrapWorkflow -from ..imaging.conversion import providers as conversion_providers from ..imaging.types import ( BeamMonitor1, BeamMonitor2, @@ -36,7 +35,7 @@ def OdinWorkflow(**kwargs) -> sciline.Pipeline: """ Workflow with default parameters for Odin. """ - workflow = GenericTofWorkflow( + workflow = GenericUnwrapWorkflow( run_types=[SampleRun, OpenBeamRun, DarkBackgroundRun], monitor_types=[BeamMonitor1, BeamMonitor2, BeamMonitor3, BeamMonitor4], **kwargs, @@ -51,7 +50,7 @@ def OdinBraggEdgeWorkflow(**kwargs) -> sciline.Pipeline: Workflow with default parameters for Odin. """ workflow = OdinWorkflow(**kwargs) - for provider in (*conversion_providers, *masking_providers): + for provider in (*masking_providers,): workflow.insert(provider) return workflow diff --git a/packages/essimaging/src/ess/tbl/workflow.py b/packages/essimaging/src/ess/tbl/workflow.py index b0addc76..fc81d4be 100644 --- a/packages/essimaging/src/ess/tbl/workflow.py +++ b/packages/essimaging/src/ess/tbl/workflow.py @@ -5,9 +5,8 @@ """ import sciline -from ess.reduce.unwrap.workflow import GenericTofWorkflow +from ess.reduce.unwrap.workflow import GenericUnwrapWorkflow -from ..imaging.conversion import providers as conversion_providers from ..imaging.types import ( BeamMonitor1, NeXusMonitorName, @@ -23,18 +22,13 @@ def default_parameters() -> dict: } -providers = (*conversion_providers,) - - def TblWorkflow(**kwargs) -> sciline.Pipeline: """ Workflow with default parameters for TBL. """ - workflow = GenericTofWorkflow( + workflow = GenericUnwrapWorkflow( run_types=[SampleRun], monitor_types=[BeamMonitor1], **kwargs ) - for provider in providers: - workflow.insert(provider) for key, param in default_parameters().items(): workflow[key] = param return workflow From 495dc18dc509d447ccc581d9ce404490940b4caf Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Mon, 9 Mar 2026 15:11:37 +0100 Subject: [PATCH 16/24] use type from essreduce --- packages/essimaging/src/ess/imaging/types.py | 4 --- .../tests/odin/data_reduction_test.py | 10 +----- .../tests/tbl/data_reduction_test.py | 35 +++++++------------ 3 files changed, 13 insertions(+), 36 deletions(-) diff --git a/packages/essimaging/src/ess/imaging/types.py b/packages/essimaging/src/ess/imaging/types.py index d7ecfd39..afc74f20 100644 --- a/packages/essimaging/src/ess/imaging/types.py +++ b/packages/essimaging/src/ess/imaging/types.py @@ -64,10 +64,6 @@ class CoordTransformGraph(sciline.Scope[RunType, dict], dict): """ -class WavelengthDetector(sciline.Scope[RunType, sc.DataArray], sc.DataArray): - """Detector counts with wavelength information.""" - - MaskingRules = NewType("MaskingRules", MappingProxyType[str, Callable]) """Functions to mask different dimensions of Odin data.""" diff --git a/packages/essimaging/tests/odin/data_reduction_test.py b/packages/essimaging/tests/odin/data_reduction_test.py index efbc3aa0..3824e559 100644 --- a/packages/essimaging/tests/odin/data_reduction_test.py +++ b/packages/essimaging/tests/odin/data_reduction_test.py @@ -15,7 +15,6 @@ SampleRun, LookupTable, LookupTableFilename, - TofDetector, WavelengthDetector, ) @@ -31,7 +30,7 @@ def workflow() -> sl.Pipeline: wf[NeXusDetectorName] = "event_mode_detectors/timepix3" wf[LookupTableFilename] = odin.data.odin_tof_lookup_table() wf[LookupTableRelativeErrorThreshold] = { - "event_mode_detectors/timepix3": float('inf') + "event_mode_detectors/timepix3": float("inf") } # Cache the lookup table wf[LookupTable] = wf.compute(LookupTable) @@ -52,13 +51,6 @@ def test_can_load_detector_data(workflow, run_type): assert "event_time_zero" in da.bins.coords -@pytest.mark.parametrize("run_type", [SampleRun, OpenBeamRun]) -def test_can_compute_unwrap(workflow, run_type): - da = workflow.compute(TofDetector[run_type]) - - assert "tof" in da.bins.coords - - @pytest.mark.parametrize("run_type", [SampleRun, OpenBeamRun]) def test_can_compute_wavelength(workflow, run_type): da = workflow.compute(WavelengthDetector[run_type]) diff --git a/packages/essimaging/tests/tbl/data_reduction_test.py b/packages/essimaging/tests/tbl/data_reduction_test.py index 65f24a61..a160e037 100644 --- a/packages/essimaging/tests/tbl/data_reduction_test.py +++ b/packages/essimaging/tests/tbl/data_reduction_test.py @@ -16,18 +16,17 @@ SampleRun, LookupTable, LookupTableFilename, - TofDetector, WavelengthDetector, ) @pytest.fixture(scope="module") -def tof_lookup_table() -> sl.Pipeline: +def wavelength_lookup_table() -> sl.Pipeline: """ - Compute tof lookup table on-the-fly. + Compute wavelength lookup table on-the-fly. """ - lut_wf = unwrap.TofLookupTableWorkflow() + lut_wf = unwrap.LookupTableWorkflow() lut_wf[unwrap.DiskChoppers[AnyRun]] = {} lut_wf[unwrap.SourcePosition] = sc.vector([0, 0, 0], unit="m") lut_wf[unwrap.NumberOfSimulatedNeutrons] = 200_000 @@ -49,9 +48,9 @@ def workflow() -> sl.Pipeline: wf[Filename[SampleRun]] = tbl.data.tutorial_sample_data() wf[LookupTableFilename] = tbl.data.tbl_tof_lookup_table_no_choppers() wf[unwrap.LookupTableRelativeErrorThreshold] = { - "ngem_detector": float('inf'), - "he3_detector_bank0": float('inf'), - "he3_detector_bank1": float('inf'), + "ngem_detector": float("inf"), + "he3_detector_bank0": float("inf"), + "he3_detector_bank1": float("inf"), } return wf @@ -77,31 +76,21 @@ def test_can_load_detector_data(workflow, bank_name): @pytest.mark.parametrize( "bank_name", ["ngem_detector", "he3_detector_bank0", "he3_detector_bank1"] ) -def test_can_compute_unwrap(workflow, bank_name): +def test_can_compute_wavelength(workflow, bank_name): workflow[NeXusDetectorName] = bank_name - da = workflow.compute(TofDetector[SampleRun]) + da = workflow.compute(WavelengthDetector[SampleRun]) - assert "tof" in da.bins.coords + assert "wavelength" in da.bins.coords @pytest.mark.parametrize( "bank_name", ["ngem_detector", "he3_detector_bank0", "he3_detector_bank1"] ) -def test_can_compute_unwrap_from_custom_lut( - workflow, tof_lookup_table, bank_name +def test_can_compute_wavelength_from_custom_lut( + workflow, wavelength_lookup_table, bank_name ): workflow[NeXusDetectorName] = bank_name - workflow[LookupTable] = tof_lookup_table - da = workflow.compute(TofDetector[SampleRun]) - - assert "tof" in da.bins.coords - - -@pytest.mark.parametrize( - "bank_name", ["ngem_detector", "he3_detector_bank0", "he3_detector_bank1"] -) -def test_can_compute_wavelength(workflow, bank_name): - workflow[NeXusDetectorName] = bank_name + workflow[LookupTable] = wavelength_lookup_table da = workflow.compute(WavelengthDetector[SampleRun]) assert "wavelength" in da.bins.coords From 0903f1a3818375b99272f07b9665099ed0907bc6 Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Mon, 9 Mar 2026 17:09:12 +0100 Subject: [PATCH 17/24] lint --- packages/essimaging/src/ess/imaging/types.py | 2 +- packages/essimaging/src/ess/odin/__init__.py | 1 - packages/essimaging/src/ess/tbl/__init__.py | 1 - packages/essimaging/tests/odin/data_reduction_test.py | 4 ++-- packages/essimaging/tests/tbl/data_reduction_test.py | 4 ++-- 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/essimaging/src/ess/imaging/types.py b/packages/essimaging/src/ess/imaging/types.py index afc74f20..ac074786 100644 --- a/packages/essimaging/src/ess/imaging/types.py +++ b/packages/essimaging/src/ess/imaging/types.py @@ -7,8 +7,8 @@ import sciline import scipp as sc from ess.reduce.nexus import types as reduce_t -from ess.reduce.unwrap import types as unwrap_t from ess.reduce.uncertainty import UncertaintyBroadcastMode as _UncertaintyBroadcastMode +from ess.reduce.unwrap import types as unwrap_t # 1 TypeVars used to parametrize the generic parts of the workflow diff --git a/packages/essimaging/src/ess/odin/__init__.py b/packages/essimaging/src/ess/odin/__init__.py index 36bab465..5a5630d9 100644 --- a/packages/essimaging/src/ess/odin/__init__.py +++ b/packages/essimaging/src/ess/odin/__init__.py @@ -1,6 +1,5 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 2025 Scipp contributors (https://github.com/scipp) -# ruff: noqa: I import importlib.metadata diff --git a/packages/essimaging/src/ess/tbl/__init__.py b/packages/essimaging/src/ess/tbl/__init__.py index cc57c520..8476e94d 100644 --- a/packages/essimaging/src/ess/tbl/__init__.py +++ b/packages/essimaging/src/ess/tbl/__init__.py @@ -1,6 +1,5 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 2025 Scipp contributors (https://github.com/scipp) -# ruff: noqa: I import importlib.metadata diff --git a/packages/essimaging/tests/odin/data_reduction_test.py b/packages/essimaging/tests/odin/data_reduction_test.py index 3824e559..eb3a3b3c 100644 --- a/packages/essimaging/tests/odin/data_reduction_test.py +++ b/packages/essimaging/tests/odin/data_reduction_test.py @@ -9,12 +9,12 @@ from ess import odin from ess.imaging.types import ( Filename, + LookupTable, + LookupTableFilename, NeXusDetectorName, OpenBeamRun, RawDetector, SampleRun, - LookupTable, - LookupTableFilename, WavelengthDetector, ) diff --git a/packages/essimaging/tests/tbl/data_reduction_test.py b/packages/essimaging/tests/tbl/data_reduction_test.py index a160e037..2a84c754 100644 --- a/packages/essimaging/tests/tbl/data_reduction_test.py +++ b/packages/essimaging/tests/tbl/data_reduction_test.py @@ -11,11 +11,11 @@ from ess import tbl from ess.imaging.types import ( Filename, + LookupTable, + LookupTableFilename, NeXusDetectorName, RawDetector, SampleRun, - LookupTable, - LookupTableFilename, WavelengthDetector, ) From 2fa4fd0a515c15ecca1e3cddae253eb47ccb6a1b Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Mon, 9 Mar 2026 22:21:35 +0100 Subject: [PATCH 18/24] update odin notebooks and data --- .../essimaging/docs/odin/odin-data-reduction.ipynb | 14 ++++++++------ ...ynb => odin-make-wavelength-lookup-table.ipynb} | 6 +++--- packages/essimaging/src/ess/odin/data.py | 13 +++++++++++++ 3 files changed, 24 insertions(+), 9 deletions(-) rename packages/essimaging/docs/odin/{odin-make-tof-lookup-table.ipynb => odin-make-wavelength-lookup-table.ipynb} (94%) diff --git a/packages/essimaging/docs/odin/odin-data-reduction.ipynb b/packages/essimaging/docs/odin/odin-data-reduction.ipynb index 5b22180c..a1b9a551 100644 --- a/packages/essimaging/docs/odin/odin-data-reduction.ipynb +++ b/packages/essimaging/docs/odin/odin-data-reduction.ipynb @@ -45,7 +45,8 @@ "\n", "wf[Filename[SampleRun]] = odin.data.iron_simulation_sample_small()\n", "wf[NeXusDetectorName] = \"event_mode_detectors/timepix3\"\n", - "wf[unwrap.LookupTableFilename] = odin.data.odin_tof_lookup_table()" + "wf[unwrap.LookupTableFilename] = odin.data.odin_wavelength_lookup_table()\n", + "wf[unwrap.LookupTableRelativeErrorThreshold] = {\"event_mode_detectors/timepix3\": float(\"inf\")}" ] }, { @@ -84,9 +85,9 @@ "id": "7", "metadata": {}, "source": [ - "## Compute neutron time-of-flight/wavelength\n", + "## Compute neutron wavelengths\n", "\n", - "We will now use the workflow to compute the neutron time-of-flight (equivalent to wavelength) using a lookup table built from the beamline chopper information." + "We will now use the workflow to compute the neutron wavelengths using a lookup table built from the beamline chopper information." ] }, { @@ -96,7 +97,7 @@ "metadata": {}, "outputs": [], "source": [ - "wf.visualize(TofDetector[SampleRun], graph_attr={\"rankdir\": \"LR\"})" + "wf.visualize(WavelengthDetector[SampleRun], graph_attr={\"rankdir\": \"LR\"})" ] }, { @@ -106,7 +107,7 @@ "source": [ "### Inspect the lookup table\n", "\n", - "It is always a good idea to quickly plot the TOF lookup table, as a sanity check." + "It is always a good idea to quickly plot the wavelength lookup table, as a sanity check." ] }, { @@ -285,7 +286,8 @@ "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython3" + "pygments_lexer": "ipython3", + "version": "3.12.12" } }, "nbformat": 4, diff --git a/packages/essimaging/docs/odin/odin-make-tof-lookup-table.ipynb b/packages/essimaging/docs/odin/odin-make-wavelength-lookup-table.ipynb similarity index 94% rename from packages/essimaging/docs/odin/odin-make-tof-lookup-table.ipynb rename to packages/essimaging/docs/odin/odin-make-wavelength-lookup-table.ipynb index 0cb5a070..9781f50f 100644 --- a/packages/essimaging/docs/odin/odin-make-tof-lookup-table.ipynb +++ b/packages/essimaging/docs/odin/odin-make-wavelength-lookup-table.ipynb @@ -5,7 +5,7 @@ "id": "0", "metadata": {}, "source": [ - "# Create a time-of-flight lookup table for ODIN" + "# Create a wavelength lookup table for ODIN" ] }, { @@ -39,7 +39,7 @@ "source_position = sc.vector([0, 0, 0], unit='m')\n", "disk_choppers = choppers(source_position)\n", "\n", - "wf = unwrap.TofLookupTableWorkflow()\n", + "wf = unwrap.LookupTableWorkflow()\n", "wf[unwrap.DiskChoppers[AnyRun]] = disk_choppers\n", "wf[unwrap.SourcePosition] = source_position\n", "wf[unwrap.NumberOfSimulatedNeutrons] = 200_000 # Increase this number for more reliable results\n", @@ -95,7 +95,7 @@ "outputs": [], "source": [ "# Write to file\n", - "table.save_hdf5('ODIN-tof-lookup-table-5m-65m.h5')" + "table.save_hdf5('ODIN-wavelength-lookup-table-5m-65m.h5')" ] } ], diff --git a/packages/essimaging/src/ess/odin/data.py b/packages/essimaging/src/ess/odin/data.py index dcd7f953..a207f85b 100644 --- a/packages/essimaging/src/ess/odin/data.py +++ b/packages/essimaging/src/ess/odin/data.py @@ -16,6 +16,7 @@ "iron_simulation_sample_small.nxs": "md5:dda6fb30aa88780c5a3d4cef6ea05278", "ODIN-tof-lookup-table.h5": "md5:e657021f4508f167b2a2eb550853b06b", "ODIN-tof-lookup-table-5m-65m.h5": "md5:c815eed6835a98d0b8d5252ffe250964", + "ODIN-wavelength-lookup-table-5m-65m.h5": "md5:44eef2a2e826cec688aeb1b985eb9f9e", # noqa: E501 }, ) @@ -70,3 +71,15 @@ def odin_tof_lookup_table() -> pathlib.Path: with ``NumberOfSimulatedNeutrons = 5_000_000``. """ return _registry.get_path("ODIN-tof-lookup-table-5m-65m.h5") + + +def odin_wavelength_lookup_table() -> pathlib.Path: + """ + Odin wavelength lookup table. + This file is used to convert the raw ``event_time_offset`` to wavelength. + + This table was computed using `Create a wavelength lookup table for ODIN + <../../odin/odin-make-wavelength-lookup-table.rst>`_ + with ``NumberOfSimulatedNeutrons = 5_000_000``. + """ + return _registry.get_path("ODIN-wavelength-lookup-table-5m-65m.h5") From be5745da8d9491b736a9c14d13c36b96f901a16e Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Mon, 9 Mar 2026 22:39:40 +0100 Subject: [PATCH 19/24] update tables to wavelength and notebooks in imaging --- .../docs/odin/odin-data-reduction.ipynb | 3 +-- .../docs/tbl/tbl-data-reduction.ipynb | 20 +++++++++---------- ...=> tbl-make-wavelength-lookup-table.ipynb} | 6 +++--- packages/essimaging/src/ess/imaging/types.py | 1 + packages/essimaging/src/ess/odin/workflows.py | 9 +++++++++ packages/essimaging/src/ess/tbl/data.py | 13 ++++++++++++ packages/essimaging/src/ess/tbl/workflow.py | 9 +++++++++ 7 files changed, 45 insertions(+), 16 deletions(-) rename packages/essimaging/docs/tbl/{tbl-make-tof-lookup-table.ipynb => tbl-make-wavelength-lookup-table.ipynb} (93%) diff --git a/packages/essimaging/docs/odin/odin-data-reduction.ipynb b/packages/essimaging/docs/odin/odin-data-reduction.ipynb index a1b9a551..c3ead4f3 100644 --- a/packages/essimaging/docs/odin/odin-data-reduction.ipynb +++ b/packages/essimaging/docs/odin/odin-data-reduction.ipynb @@ -45,8 +45,7 @@ "\n", "wf[Filename[SampleRun]] = odin.data.iron_simulation_sample_small()\n", "wf[NeXusDetectorName] = \"event_mode_detectors/timepix3\"\n", - "wf[unwrap.LookupTableFilename] = odin.data.odin_wavelength_lookup_table()\n", - "wf[unwrap.LookupTableRelativeErrorThreshold] = {\"event_mode_detectors/timepix3\": float(\"inf\")}" + "wf[unwrap.LookupTableFilename] = odin.data.odin_wavelength_lookup_table()" ] }, { diff --git a/packages/essimaging/docs/tbl/tbl-data-reduction.ipynb b/packages/essimaging/docs/tbl/tbl-data-reduction.ipynb index 48a132aa..1fbd1e2e 100644 --- a/packages/essimaging/docs/tbl/tbl-data-reduction.ipynb +++ b/packages/essimaging/docs/tbl/tbl-data-reduction.ipynb @@ -40,7 +40,7 @@ "wf = tbl.TblWorkflow()\n", "\n", "wf[Filename[SampleRun]] = tbl.data.tutorial_sample_data()\n", - "wf[LookupTableFilename] = tbl.data.tbl_tof_lookup_table_no_choppers()" + "wf[LookupTableFilename] = tbl.data.tbl_wavelength_lookup_table_no_choppers()" ] }, { @@ -89,7 +89,7 @@ "id": "8", "metadata": {}, "source": [ - "### Time-of-flight" + "### Neutron wavelengths" ] }, { @@ -99,7 +99,7 @@ "metadata": {}, "outputs": [], "source": [ - "wf.visualize(TofDetector[SampleRun], graph_attr={\"rankdir\": \"LR\"})" + "wf.visualize(WavelengthDetector[SampleRun], graph_attr={\"rankdir\": \"LR\"})" ] }, { @@ -107,7 +107,7 @@ "id": "10", "metadata": {}, "source": [ - "#### Visualize the time-of-flight lookup table" + "#### Visualize the wavelength lookup table" ] }, { @@ -136,10 +136,9 @@ "metadata": {}, "outputs": [], "source": [ - "ngem_tofs = wf.compute(TofDetector[SampleRun])\n", "ngem_wavs = wf.compute(WavelengthDetector[SampleRun])\n", "\n", - "ngem_tofs.bins.concat().hist(tof=100).plot() + ngem_wavs.bins.concat().hist(wavelength=100).plot()" + "ngem_wavs.bins.concat().hist(wavelength=100).plot()" ] }, { @@ -182,7 +181,7 @@ "id": "17", "metadata": {}, "source": [ - "### Time-of-flight" + "### Neutron wavelengths" ] }, { @@ -192,15 +191,13 @@ "metadata": {}, "outputs": [], "source": [ - "he3_tofs = {}\n", "he3_wavs = {}\n", "\n", "for bank in ('he3_detector_bank0', 'he3_detector_bank1'):\n", " he3_wf[NeXusDetectorName] = bank\n", - " he3_tofs[bank] = he3_wf.compute(TofDetector[SampleRun]).bins.concat().hist(tof=100)\n", " he3_wavs[bank] = he3_wf.compute(WavelengthDetector[SampleRun]).bins.concat().hist(wavelength=100)\n", "\n", - "pp.plot(he3_tofs) + pp.plot(he3_wavs)" + "pp.plot(he3_wavs)" ] } ], @@ -219,7 +216,8 @@ "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython3" + "pygments_lexer": "ipython3", + "version": "3.12.12" } }, "nbformat": 4, diff --git a/packages/essimaging/docs/tbl/tbl-make-tof-lookup-table.ipynb b/packages/essimaging/docs/tbl/tbl-make-wavelength-lookup-table.ipynb similarity index 93% rename from packages/essimaging/docs/tbl/tbl-make-tof-lookup-table.ipynb rename to packages/essimaging/docs/tbl/tbl-make-wavelength-lookup-table.ipynb index 104e8b23..674ad6db 100644 --- a/packages/essimaging/docs/tbl/tbl-make-tof-lookup-table.ipynb +++ b/packages/essimaging/docs/tbl/tbl-make-wavelength-lookup-table.ipynb @@ -5,7 +5,7 @@ "id": "0", "metadata": {}, "source": [ - "# Create a time-of-flight lookup table for TBL" + "# Create a wavelength lookup table for TBL" ] }, { @@ -37,7 +37,7 @@ "source": [ "source_position = sc.vector([0, 0, 0], unit='m')\n", "\n", - "wf = unwrap.TofLookupTableWorkflow()\n", + "wf = unwrap.LookupTableWorkflow()\n", "wf[unwrap.DiskChoppers[AnyRun]] = {}\n", "wf[unwrap.SourcePosition] = source_position\n", "wf[unwrap.NumberOfSimulatedNeutrons] = 200_000 # Increase this number for more reliable results\n", @@ -94,7 +94,7 @@ "source": [ "# Save chopper metadata\n", "# Write to file\n", - "table.save_hdf5('TBL-tof-lookup-table-no-choppers-5m-35m.h5')" + "table.save_hdf5('TBL-wavelength-lookup-table-no-choppers-5m-35m.h5')" ] } ], diff --git a/packages/essimaging/src/ess/imaging/types.py b/packages/essimaging/src/ess/imaging/types.py index ac074786..b1fbf109 100644 --- a/packages/essimaging/src/ess/imaging/types.py +++ b/packages/essimaging/src/ess/imaging/types.py @@ -26,6 +26,7 @@ PulseStrideOffset = unwrap_t.PulseStrideOffset LookupTable = unwrap_t.LookupTable LookupTableFilename = unwrap_t.LookupTableFilename +LookupTableRelativeErrorThreshold = unwrap_t.LookupTableRelativeErrorThreshold UncertaintyBroadcastMode = _UncertaintyBroadcastMode diff --git a/packages/essimaging/src/ess/odin/workflows.py b/packages/essimaging/src/ess/odin/workflows.py index ee2a2456..277b39f7 100644 --- a/packages/essimaging/src/ess/odin/workflows.py +++ b/packages/essimaging/src/ess/odin/workflows.py @@ -13,6 +13,7 @@ BeamMonitor3, BeamMonitor4, DarkBackgroundRun, + LookupTableRelativeErrorThreshold, NeXusMonitorName, OpenBeamRun, PulseStrideOffset, @@ -28,6 +29,14 @@ def default_parameters() -> dict: NeXusMonitorName[BeamMonitor3]: "beam_monitor_3", NeXusMonitorName[BeamMonitor4]: "beam_monitor_4", PulseStrideOffset: None, + LookupTableRelativeErrorThreshold: { + "event_mode_detectors/timepix3": float("inf"), + "histogram_mode_detectors/orca": float("inf"), + "beam_monitor_1": float("inf"), + "beam_monitor_2": float("inf"), + "beam_monitor_3": float("inf"), + "beam_monitor_4": float("inf"), + }, } diff --git a/packages/essimaging/src/ess/tbl/data.py b/packages/essimaging/src/ess/tbl/data.py index 10200bee..a4315618 100644 --- a/packages/essimaging/src/ess/tbl/data.py +++ b/packages/essimaging/src/ess/tbl/data.py @@ -13,6 +13,7 @@ "tbl_sample_data_2025-03.hdf": "md5:12db6bc06721278b3abe47992eac3e77", "TBL-tof-lookup-table-no-choppers.h5": "md5:8bc98fac0ee64fc8f5decf509c75bafe", "TBL-tof-lookup-table-no-choppers-5m-35m.h5": "md5:be7e73f32d395abd3c28b95f75934d61", # noqa: E501 + "TBL-wavelength-lookup-table-no-choppers-5m-35m.h5": "md5:e28793b7e1c12986ee63a1df68723268", # noqa: E501 'tbl-orca-focussing.hdf.zip': Entry( alg='md5', chk='f365acd9ea45dd205c0b9398d163cfa4', unzip=True ), @@ -40,6 +41,18 @@ def tbl_tof_lookup_table_no_choppers() -> pathlib.Path: return _registry.get_path("TBL-tof-lookup-table-no-choppers-5m-35m.h5") +def tbl_wavelength_lookup_table_no_choppers() -> pathlib.Path: + """ + TBL wavelength lookup table without choppers. + This file is used to convert the neutron arrival time to wavelength. + + This table was computed using `Create a wavelength lookup table for TBL + <../../tbl/tbl-make-wavelength-lookup-table.rst>`_ + with ``NumberOfSimulatedNeutrons = 5_000_000``. + """ + return _registry.get_path("TBL-wavelength-lookup-table-no-choppers-5m-35m.h5") + + def tbl_orca_focussing_data() -> pathlib.Path: """ Return the path to the TBL ORCA HDF5 file used for camera focussing. diff --git a/packages/essimaging/src/ess/tbl/workflow.py b/packages/essimaging/src/ess/tbl/workflow.py index fc81d4be..9222af38 100644 --- a/packages/essimaging/src/ess/tbl/workflow.py +++ b/packages/essimaging/src/ess/tbl/workflow.py @@ -9,6 +9,7 @@ from ..imaging.types import ( BeamMonitor1, + LookupTableRelativeErrorThreshold, NeXusMonitorName, PulseStrideOffset, SampleRun, @@ -19,6 +20,14 @@ def default_parameters() -> dict: return { NeXusMonitorName[BeamMonitor1]: "monitor_1", PulseStrideOffset: None, + LookupTableRelativeErrorThreshold: { + "ngem_detector": float("inf"), + "he3_detector_bank0": float("inf"), + "he3_detector_bank1": float("inf"), + "multiblade_detector": float("inf"), + "timepix3_detector": float("inf"), + "monitor_1": float("inf"), + }, } From d7d41de98211c60497f260352b41c4efc514d60d Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Mon, 9 Mar 2026 22:51:08 +0100 Subject: [PATCH 20/24] fix tests --- packages/essimaging/tests/odin/data_reduction_test.py | 2 +- packages/essimaging/tests/tbl/data_reduction_test.py | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/essimaging/tests/odin/data_reduction_test.py b/packages/essimaging/tests/odin/data_reduction_test.py index eb3a3b3c..cc18fd2f 100644 --- a/packages/essimaging/tests/odin/data_reduction_test.py +++ b/packages/essimaging/tests/odin/data_reduction_test.py @@ -28,7 +28,7 @@ def workflow() -> sl.Pipeline: wf[Filename[SampleRun]] = odin.data.iron_simulation_sample_small() wf[Filename[OpenBeamRun]] = odin.data.iron_simulation_ob_small() wf[NeXusDetectorName] = "event_mode_detectors/timepix3" - wf[LookupTableFilename] = odin.data.odin_tof_lookup_table() + wf[LookupTableFilename] = odin.data.odin_wavelength_lookup_table() wf[LookupTableRelativeErrorThreshold] = { "event_mode_detectors/timepix3": float("inf") } diff --git a/packages/essimaging/tests/tbl/data_reduction_test.py b/packages/essimaging/tests/tbl/data_reduction_test.py index 2a84c754..feabd2cd 100644 --- a/packages/essimaging/tests/tbl/data_reduction_test.py +++ b/packages/essimaging/tests/tbl/data_reduction_test.py @@ -32,10 +32,7 @@ def wavelength_lookup_table() -> sl.Pipeline: lut_wf[unwrap.NumberOfSimulatedNeutrons] = 200_000 lut_wf[unwrap.SimulationSeed] = 333 lut_wf[unwrap.PulseStride] = 1 - lut_wf[unwrap.LtotalRange] = ( - sc.scalar(25.0, unit="m"), - sc.scalar(35.0, unit="m"), - ) + lut_wf[unwrap.LtotalRange] = (sc.scalar(25.0, unit="m"), sc.scalar(35.0, unit="m")) return lut_wf.compute(LookupTable) @@ -46,7 +43,7 @@ def workflow() -> sl.Pipeline: """ wf = tbl.TblWorkflow() wf[Filename[SampleRun]] = tbl.data.tutorial_sample_data() - wf[LookupTableFilename] = tbl.data.tbl_tof_lookup_table_no_choppers() + wf[LookupTableFilename] = tbl.data.tbl_wavelength_lookup_table_no_choppers() wf[unwrap.LookupTableRelativeErrorThreshold] = { "ngem_detector": float("inf"), "he3_detector_bank0": float("inf"), From 6209b2c45d75b1430016ea69d0ef0f907dd22100 Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Mon, 9 Mar 2026 23:15:09 +0100 Subject: [PATCH 21/24] rename docs folder and update indexes --- packages/essimaging/docs/odin/index.md | 2 +- packages/essimaging/docs/tbl/index.md | 2 +- packages/essreduce/docs/user-guide/index.md | 2 +- .../user-guide/{tof => unwrap}/dream.ipynb | 0 .../{tof => unwrap}/frame-unwrapping.ipynb | 146 ++++++------------ .../docs/user-guide/{tof => unwrap}/index.md | 0 .../docs/user-guide/{tof => unwrap}/wfm.ipynb | 0 7 files changed, 46 insertions(+), 106 deletions(-) rename packages/essreduce/docs/user-guide/{tof => unwrap}/dream.ipynb (100%) rename packages/essreduce/docs/user-guide/{tof => unwrap}/frame-unwrapping.ipynb (78%) rename packages/essreduce/docs/user-guide/{tof => unwrap}/index.md (100%) rename packages/essreduce/docs/user-guide/{tof => unwrap}/wfm.ipynb (100%) diff --git a/packages/essimaging/docs/odin/index.md b/packages/essimaging/docs/odin/index.md index 64f0707b..9cbf1c8e 100644 --- a/packages/essimaging/docs/odin/index.md +++ b/packages/essimaging/docs/odin/index.md @@ -6,5 +6,5 @@ maxdepth: 1 --- odin-data-reduction -odin-make-tof-lookup-table +odin-make-wavelength-lookup-table ``` diff --git a/packages/essimaging/docs/tbl/index.md b/packages/essimaging/docs/tbl/index.md index 9e5ac93e..b5b99460 100644 --- a/packages/essimaging/docs/tbl/index.md +++ b/packages/essimaging/docs/tbl/index.md @@ -7,5 +7,5 @@ maxdepth: 1 tbl-data-reduction orca-image-normalization -tbl-make-tof-lookup-table +tbl-make-wavelength-lookup-table ``` diff --git a/packages/essreduce/docs/user-guide/index.md b/packages/essreduce/docs/user-guide/index.md index b8c5f084..1c91655c 100644 --- a/packages/essreduce/docs/user-guide/index.md +++ b/packages/essreduce/docs/user-guide/index.md @@ -6,7 +6,7 @@ maxdepth: 2 --- installation -tof/index +unwrap/index widget reduction-workflow-guidelines ``` diff --git a/packages/essreduce/docs/user-guide/tof/dream.ipynb b/packages/essreduce/docs/user-guide/unwrap/dream.ipynb similarity index 100% rename from packages/essreduce/docs/user-guide/tof/dream.ipynb rename to packages/essreduce/docs/user-guide/unwrap/dream.ipynb diff --git a/packages/essreduce/docs/user-guide/tof/frame-unwrapping.ipynb b/packages/essreduce/docs/user-guide/unwrap/frame-unwrapping.ipynb similarity index 78% rename from packages/essreduce/docs/user-guide/tof/frame-unwrapping.ipynb rename to packages/essreduce/docs/user-guide/unwrap/frame-unwrapping.ipynb index dcb87020..53666dd1 100644 --- a/packages/essreduce/docs/user-guide/tof/frame-unwrapping.ipynb +++ b/packages/essreduce/docs/user-guide/unwrap/frame-unwrapping.ipynb @@ -12,12 +12,12 @@ "At time-of-flight neutron sources recording event-mode, time-stamps of detected neutrons are written to files in an `NXevent_data` group.\n", "This contains two main time components, `event_time_zero` and `event_time_offset`.\n", "The sum of the two would typically yield the absolute detection time of the neutron.\n", - "For computation of wavelengths or energies during data-reduction, a time-of-flight is required.\n", + "For computation of wavelengths or energies during data-reduction, a time-of-flight (directly convertible to a wavelength with a given flight path length) is required.\n", "In principle the time-of-flight could be equivalent to `event_time_offset`, and the emission time of the neutron to `event_time_zero`.\n", "Since an actual computation of time-of-flight would require knowledge about chopper settings, detector positions, and whether the scattering of the sample is elastic or inelastic, this may however not be the case in practice.\n", "Instead, the data acquisition system may, e.g., record the time at which the proton pulse hits the target as `event_time_zero`, with `event_time_offset` representing the offset since then.\n", "\n", - "We refer to the process of \"unwrapping\" these time stamps into an actual time-of-flight as *frame unwrapping*, since `event_time_offset` \"wraps around\" with the period of the proton pulse and neutrons created by different proton pulses may be recorded with the *same* `event_time_zero`.\n", + "We refer to the process of \"unwrapping\" these time stamps into an actual time-of-flight (or wavelength) as *frame unwrapping*, since `event_time_offset` \"wraps around\" with the period of the proton pulse and neutrons created by different proton pulses may be recorded with the *same* `event_time_zero`.\n", "The figures in the remainder of this document will clarify this." ] }, @@ -32,7 +32,7 @@ "import scipp as sc\n", "from scippneutron.chopper import DiskChopper\n", "from ess.reduce.nexus.types import AnyRun, RawDetector, SampleRun, NeXusDetectorName\n", - "from ess.reduce.time_of_flight import *\n", + "from ess.reduce.unwrap import *\n", "import tof\n", "\n", "Hz = sc.Unit(\"Hz\")\n", @@ -139,13 +139,13 @@ "id": "7", "metadata": {}, "source": [ - "### Computing time-of-flight\n", + "### Computing neutron wavelengths\n", "\n", - "We describe in this section the workflow that computes time-of-flight,\n", + "We describe in this section the workflow that computes wavelengths,\n", "given `event_time_zero` and `event_time_offset` for neutron events,\n", "as well as the properties of the source pulse and the choppers in the beamline.\n", "\n", - "In short, we use a lookup table which can predict the wavelength (or time-of-flight) of the neutrons,\n", + "In short, we use a lookup table which can predict the wavelength of the neutrons,\n", "according to their `event_time_offset`.\n", "\n", "The workflow can be visualized as follows:" @@ -158,13 +158,13 @@ "metadata": {}, "outputs": [], "source": [ - "wf = GenericTofWorkflow(run_types=[SampleRun], monitor_types=[])\n", + "wf = GenericUnwrapWorkflow(run_types=[SampleRun], monitor_types=[])\n", "\n", "wf[RawDetector[SampleRun]] = nxevent_data\n", "wf[DetectorLtotal[SampleRun]] = nxevent_data.coords[\"Ltotal\"]\n", "wf[NeXusDetectorName] = \"detector\"\n", "\n", - "wf.visualize(TofDetector[SampleRun])" + "wf.visualize(WavelengthDetector[SampleRun])" ] }, { @@ -172,14 +172,14 @@ "id": "9", "metadata": {}, "source": [ - "By default, the workflow tries to load a `TofLookupTable` from a file.\n", + "By default, the workflow tries to load a `LookupTable` from a file.\n", "\n", "In this notebook, instead of using such a pre-made file,\n", "we will build our own lookup table from the chopper information and apply it to the workflow.\n", "\n", "#### Create the lookup table\n", "\n", - "The chopper information is used to construct a lookup table that provides an estimate of the real time-of-flight as a function of time-of-arrival.\n", + "The chopper information is used to construct a lookup table that provides an estimate of the neutron wavelength as a function of time-of-arrival.\n", "\n", "The [Tof](https://scipp.github.io/tof/) package can be used to propagate a pulse of neutrons through the chopper system to the detectors,\n", "and predict the most likely neutron wavelength for a given time-of-arrival.\n", @@ -197,8 +197,7 @@ "- run a simulation where a pulse of neutrons passes through the choppers and reaches the sample (or any location after the last chopper)\n", "- propagate the neutrons from the sample to a range of distances that span the minimum and maximum pixel distance from the sample (assuming neutron wavelengths do not change)\n", "- bin the neutrons in both distance and time-of-arrival (yielding a 2D binned data array)\n", - "- compute the (weighted) mean wavelength inside each bin\n", - "- convert the wavelengths to a real time-of-flight to give our final lookup table\n", + "- compute the (weighted) mean wavelength inside each bin to give our final lookup table\n", "\n", "This is done using a dedicated workflow:" ] @@ -210,7 +209,7 @@ "metadata": {}, "outputs": [], "source": [ - "lut_wf = TofLookupTableWorkflow()\n", + "lut_wf = LookupTableWorkflow()\n", "lut_wf[LtotalRange] = detectors[0].distance, detectors[-1].distance\n", "lut_wf[DiskChoppers[AnyRun]] = {\n", " \"chopper\": DiskChopper(\n", @@ -228,7 +227,7 @@ "}\n", "lut_wf[SourcePosition] = sc.vector([0, 0, 0], unit=\"m\")\n", "\n", - "lut_wf.visualize(TofLookupTable)" + "lut_wf.visualize(LookupTable)" ] }, { @@ -246,7 +245,7 @@ "metadata": {}, "outputs": [], "source": [ - "table = lut_wf.compute(TofLookupTable)\n", + "table = lut_wf.compute(LookupTable)\n", "table.plot()" ] }, @@ -255,18 +254,20 @@ "id": "13", "metadata": {}, "source": [ - "#### Computing time-of-flight from the lookup\n", + "#### Computing wavelength from the lookup\n", "\n", - "We now use the above table to perform a bilinear interpolation and compute the time-of-flight of every neutron.\n", - "We set the newly computed lookup table as the `TofLookupTable` onto the workflow `wf`.\n", + "We now use the above table to perform a bilinear interpolation and compute the wavelength of every neutron.\n", + "We set the newly computed lookup table as the `LookupTable` onto the workflow `wf`.\n", "\n", - "Looking at the workflow visualization of the `GenericTofWorkflow` above,\n", + "Looking at the workflow visualization of the `GenericUnwrapWorkflow` above,\n", "we also need to set a value for the `LookupTableRelativeErrorThreshold` parameter.\n", "This puts a cap on wavelength uncertainties: if there are regions in the table where neutrons with different wavelengths are overlapping,\n", "the uncertainty on the predicted wavelength for a given neutron time of arrival will be large.\n", "In some cases, it is desirable to throw away these neutrons by setting a low uncertainty threshold.\n", "Here, we do not have that issue as the chopper in the beamline is ensuring that neutron rays are not overlapping,\n", - "and we thus set the threshold to infinity." + "and we thus set the threshold to infinity.\n", + "\n", + "Finally, we also compare our computed wavelengths to the true wavelengths which are known for the simulated neutrons." ] }, { @@ -277,56 +278,22 @@ "outputs": [], "source": [ "# Set the computed lookup table on the original workflow\n", - "wf[TofLookupTable] = table\n", + "wf[LookupTable] = table\n", "# Set the uncertainty threshold for the neutrons at the detector to infinity\n", "wf[LookupTableRelativeErrorThreshold] = {\"detector\": float(\"inf\")}\n", "\n", - "# Compute neutron tofs\n", - "tofs = wf.compute(TofDetector[SampleRun])\n", - "\n", - "tof_hist = tofs.hist(tof=sc.scalar(500.0, unit=\"us\"))\n", - "pp.plot({det.name: tof_hist[\"detector_number\", i] for i, det in enumerate(detectors)})" - ] - }, - { - "cell_type": "markdown", - "id": "15", - "metadata": {}, - "source": [ - "### Converting to wavelength\n", - "\n", - "The time-of-flight of a neutron is commonly used as the fundamental quantity from which one can compute the neutron energy or wavelength.\n", - "\n", - "Here, we compute the wavelengths from the time-of-flight using Scippneutron's `transform_coord` utility,\n", - "and compare our computed wavelengths to the true wavelengths which are known for the simulated neutrons." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "16", - "metadata": {}, - "outputs": [], - "source": [ - "from scippneutron.conversion.graph.beamline import beamline\n", - "from scippneutron.conversion.graph.tof import elastic\n", - "\n", - "# Perform coordinate transformation\n", - "graph = {**beamline(scatter=False), **elastic(\"tof\")}\n", - "\n", - "# Define wavelength bin edges\n", - "bins = sc.linspace(\"wavelength\", 6.0, 9.0, 101, unit=\"angstrom\")\n", - "\n", - "# Compute wavelengths\n", - "wav_hist = tofs.transform_coords(\"wavelength\", graph=graph).hist(wavelength=bins)\n", + "# Compute neutron wavelengths\n", + "dw = sc.scalar(0.03, unit=\"angstrom\")\n", + "wavs = wf.compute(WavelengthDetector[SampleRun])\n", + "wav_hist = wavs.hist(wavelength=dw)\n", + "# Split detectors into a dict for plotting\n", "wavs = {det.name: wav_hist[\"detector_number\", i] for i, det in enumerate(detectors)}\n", "\n", + "# Also compare to the ground truth\n", "ground_truth = results[\"detector\"].data.flatten(to=\"event\")\n", - "ground_truth = ground_truth[~ground_truth.masks[\"blocked_by_others\"]].hist(\n", - " wavelength=bins\n", - ")\n", - "\n", + "ground_truth = ground_truth[~ground_truth.masks[\"blocked_by_others\"]].hist(wavelength=dw)\n", "wavs[\"true\"] = ground_truth\n", + "\n", "pp.plot(wavs)" ] }, @@ -391,9 +358,9 @@ "id": "19", "metadata": {}, "source": [ - "### Computing time-of-flight\n", + "### Computing wavelengths\n", "\n", - "To compute the time-of-flight in pulse skipping mode,\n", + "To compute the neutron wavelengths in pulse skipping mode,\n", "we can use the same workflow as before.\n", "\n", "The only difference is that we set the `PulseStride` to 2 to skip every other pulse." @@ -407,7 +374,7 @@ "outputs": [], "source": [ "# Lookup table workflow\n", - "lut_wf = TofLookupTableWorkflow()\n", + "lut_wf = LookupTableWorkflow()\n", "lut_wf[PulseStride] = 2\n", "lut_wf[LtotalRange] = detectors[0].distance, detectors[-1].distance\n", "lut_wf[DiskChoppers[AnyRun]] = {\n", @@ -444,7 +411,7 @@ "metadata": {}, "outputs": [], "source": [ - "table = lut_wf.compute(TofLookupTable)\n", + "table = lut_wf.compute(LookupTable)\n", "\n", "table.plot(figsize=(9, 4))" ] @@ -454,7 +421,7 @@ "id": "23", "metadata": {}, "source": [ - "The time-of-flight profiles are then:" + "The wavelength profiles are then:" ] }, { @@ -465,50 +432,23 @@ "outputs": [], "source": [ "# Reduction workflow\n", - "wf = GenericTofWorkflow(run_types=[SampleRun], monitor_types=[])\n", + "wf = GenericUnwrapWorkflow(run_types=[SampleRun], monitor_types=[])\n", "nxevent_data = results.to_nxevent_data()\n", "wf[RawDetector[SampleRun]] = nxevent_data\n", "wf[DetectorLtotal[SampleRun]] = nxevent_data.coords[\"Ltotal\"]\n", "wf[NeXusDetectorName] = \"detector\"\n", - "wf[TofLookupTable] = table\n", + "wf[LookupTable] = table\n", "wf[LookupTableRelativeErrorThreshold] = {\"detector\": float(\"inf\")}\n", "\n", - "tofs = wf.compute(TofDetector[SampleRun])\n", - "\n", - "tof_hist = tofs.hist(tof=sc.scalar(500.0, unit=\"us\"))\n", - "pp.plot({det.name: tof_hist[\"detector_number\", i] for i, det in enumerate(detectors)})" - ] - }, - { - "cell_type": "markdown", - "id": "25", - "metadata": {}, - "source": [ - "### Conversion to wavelength\n", - "\n", - "We now use the `transform_coords` as above to convert to wavelength." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "26", - "metadata": {}, - "outputs": [], - "source": [ - "# Define wavelength bin edges\n", - "bins = sc.linspace(\"wavelength\", 1.0, 8.0, 401, unit=\"angstrom\")\n", - "\n", - "# Compute wavelengths\n", - "wav_hist = tofs.transform_coords(\"wavelength\", graph=graph).hist(wavelength=bins)\n", + "wavs = wf.compute(WavelengthDetector[SampleRun])\n", + "wav_hist = wavs.hist(wavelength=dw)\n", "wavs = {det.name: wav_hist[\"detector_number\", i] for i, det in enumerate(detectors)}\n", "\n", + "# Compare to ground truth\n", "ground_truth = results[\"detector\"].data.flatten(to=\"event\")\n", - "ground_truth = ground_truth[~ground_truth.masks[\"blocked_by_others\"]].hist(\n", - " wavelength=bins\n", - ")\n", - "\n", + "ground_truth = ground_truth[~ground_truth.masks[\"blocked_by_others\"]].hist(wavelength=dw)\n", "wavs[\"true\"] = ground_truth\n", + "\n", "pp.plot(wavs)" ] } @@ -529,7 +469,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.7" + "version": "3.12.12" } }, "nbformat": 4, diff --git a/packages/essreduce/docs/user-guide/tof/index.md b/packages/essreduce/docs/user-guide/unwrap/index.md similarity index 100% rename from packages/essreduce/docs/user-guide/tof/index.md rename to packages/essreduce/docs/user-guide/unwrap/index.md diff --git a/packages/essreduce/docs/user-guide/tof/wfm.ipynb b/packages/essreduce/docs/user-guide/unwrap/wfm.ipynb similarity index 100% rename from packages/essreduce/docs/user-guide/tof/wfm.ipynb rename to packages/essreduce/docs/user-guide/unwrap/wfm.ipynb From 086b7ad96481cf491923395d88758bdf212960fb Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Mon, 9 Mar 2026 23:39:10 +0100 Subject: [PATCH 22/24] update frame unwrapping ntoebooks --- .../docs/user-guide/unwrap/dream.ipynb | 139 ++++++------------ .../user-guide/unwrap/frame-unwrapping.ipynb | 24 +-- .../docs/user-guide/unwrap/wfm.ipynb | 128 ++++++---------- 3 files changed, 101 insertions(+), 190 deletions(-) diff --git a/packages/essreduce/docs/user-guide/unwrap/dream.ipynb b/packages/essreduce/docs/user-guide/unwrap/dream.ipynb index bea0db22..0a7bc66b 100644 --- a/packages/essreduce/docs/user-guide/unwrap/dream.ipynb +++ b/packages/essreduce/docs/user-guide/unwrap/dream.ipynb @@ -8,7 +8,7 @@ "# The DREAM chopper cascade\n", "\n", "In this notebook, we simulate the beamline of the DREAM instrument and its pulse-shaping choppers.\n", - "We then show how to use `essreduce`'s `time_of_flight` module to compute neutron wavelengths from their arrival times at the detectors.\n", + "We then show how to use `essreduce`'s `unwrap` module to compute neutron wavelengths from their arrival times at the detectors.\n", "\n", "The case of DREAM is interesting because the pulse-shaping choppers can be used in a number of different modes,\n", "and the number of cutouts the choppers have typically does not equal the number of frames observed at the detectors." @@ -26,7 +26,7 @@ "import scippnexus as snx\n", "from scippneutron.chopper import DiskChopper\n", "from ess.reduce.nexus.types import AnyRun, RawDetector, SampleRun, NeXusDetectorName\n", - "from ess.reduce.kinematics import *" + "from ess.reduce.unwrap import *" ] }, { @@ -201,7 +201,7 @@ "metadata": {}, "outputs": [], "source": [ - "from ess.reduce.kinematics.fakes import FakeBeamline\n", + "from ess.reduce.unwrap.fakes import FakeBeamline\n", "\n", "ess_beamline = FakeBeamline(\n", " choppers=disk_choppers,\n", @@ -285,9 +285,9 @@ "id": "19", "metadata": {}, "source": [ - "## Computing time-of-flight\n", + "## Computing neutron wavelengths\n", "\n", - "Next, we use a workflow that provides an estimate of the real time-of-flight as a function of neutron time-of-arrival.\n", + "Next, we use a workflow that provides an estimate of the neutron wavelength as a function of neutron time-of-arrival.\n", "\n", "### Setting up the workflow" ] @@ -299,7 +299,7 @@ "metadata": {}, "outputs": [], "source": [ - "wf = GenericWavelengthWorkflow(run_types=[SampleRun], monitor_types=[])\n", + "wf = GenericUnwrapWorkflow(run_types=[SampleRun], monitor_types=[])\n", "\n", "wf[RawDetector[SampleRun]] = raw_data\n", "wf[DetectorLtotal[SampleRun]] = Ltotal\n", @@ -314,7 +314,7 @@ "id": "21", "metadata": {}, "source": [ - "By default, the workflow tries to load a `TofLookupTable` from a file.\n", + "By default, the workflow tries to load a `LookupTable` from a file.\n", "\n", "In this notebook, instead of using such a pre-made file,\n", "we will build our own lookup table from the chopper information and apply it to the workflow." @@ -325,14 +325,13 @@ "id": "22", "metadata": {}, "source": [ - "### Building the time-of-flight lookup table\n", + "### Building the wavelength lookup table\n", "\n", "We use the [Tof](https://scipp.github.io/tof/) package to propagate a pulse of neutrons through the chopper system to the detectors,\n", "and predict the most likely neutron wavelength for a given time-of-arrival and distance from source.\n", "\n", "From this,\n", - "we build a lookup table on which bilinear interpolation is used to compute a wavelength (and its corresponding time-of-flight)\n", - "for every neutron event." + "we build a lookup table on which bilinear interpolation is used to compute a wavelength for every neutron event." ] }, { @@ -346,7 +345,7 @@ "lut_wf[DiskChoppers[AnyRun]] = disk_choppers\n", "lut_wf[SourcePosition] = source_position\n", "lut_wf[LtotalRange] = (\n", - " sc.scalar(5.0, unit=\"m\"),\n", + " sc.scalar(25.0, unit=\"m\"),\n", " sc.scalar(80.0, unit=\"m\"),\n", ")\n", "lut_wf.visualize(LookupTable)" @@ -362,7 +361,7 @@ "The workflow first runs a simulation using the chopper parameters above,\n", "and the result is stored in `SimulationResults` (see graph above).\n", "\n", - "From these simulated neutrons, we create figures displaying the neutron wavelengths and time-of-flight,\n", + "From these simulated neutrons, we create a figure displaying the neutron wavelengths,\n", "as a function of arrival time at the detector.\n", "\n", "This is the basis for creating our lookup table." @@ -381,7 +380,8 @@ "def to_event_time_offset(sim):\n", " # Compute event_time_offset at the detector\n", " eto = (\n", - " sim.time_of_arrival + ((lut_wf.compute(LtotalRange)[1] - sim.distance) / sim.speed).to(unit=\"us\")\n", + " sim.time_of_arrival\n", + " + ((lut_wf.compute(LtotalRange)[1] - sim.distance) / sim.speed).to(unit=\"us\")\n", " ) % sc.scalar(1e6 / 14.0, unit=\"us\")\n", " # # Compute time-of-flight at the detector\n", " # tof = (Ltotal / sim.speed).to(unit=\"us\")\n", @@ -401,7 +401,7 @@ "id": "26", "metadata": {}, "source": [ - "The lookup table is then obtained by computing the weighted mean of the time-of-flight inside each time-of-arrival bin.\n", + "The lookup table is then obtained by computing the weighted mean of the wavelength inside each time-of-arrival bin.\n", "\n", "This is illustrated by the orange line in the figure below:" ] @@ -442,9 +442,9 @@ "id": "30", "metadata": {}, "source": [ - "### Computing a time-of-flight coordinate\n", + "### Computing a wavelength coordinate\n", "\n", - "We will now update our workflow, and use it to obtain our event data with a time-of-flight coordinate:" + "We will now update our workflow, and use it to obtain our event data with a wavelength coordinate:" ] }, { @@ -457,54 +457,8 @@ "# Set the computed lookup table onto the original workflow\n", "wf[LookupTable] = table\n", "\n", - "# Compute time-of-flight of neutron events\n", + "# Compute wavelength of neutron events\n", "wavs = wf.compute(WavelengthDetector[SampleRun])\n", - "wavs" - ] - }, - { - "cell_type": "markdown", - "id": "32", - "metadata": {}, - "source": [ - "Histogramming the data for a plot should show a profile with 6 bumps that correspond to the frames:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "33", - "metadata": {}, - "outputs": [], - "source": [ - "# tofs.bins.concat().hist(tof=300).plot()" - ] - }, - { - "cell_type": "markdown", - "id": "34", - "metadata": {}, - "source": [ - "### Converting to wavelength\n", - "\n", - "We can now convert our new time-of-flight coordinate to a neutron wavelength, using `tranform_coords`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "35", - "metadata": {}, - "outputs": [], - "source": [ - "# from scippneutron.conversion.graph.beamline import beamline\n", - "# from scippneutron.conversion.graph.tof import elastic\n", - "\n", - "# # Perform coordinate transformation\n", - "# graph = {**beamline(scatter=False), **elastic(\"tof\")}\n", - "# wav_wfm = tofs.transform_coords(\"wavelength\", graph=graph)\n", - "\n", - "# Define wavelength bin edges\n", "edges = sc.linspace(\"wavelength\", 0.8, 4.6, 201, unit=\"angstrom\")\n", "\n", "histogrammed = wavs.hist(wavelength=edges).squeeze()\n", @@ -513,7 +467,7 @@ }, { "cell_type": "markdown", - "id": "36", + "id": "32", "metadata": {}, "source": [ "### Comparing to the ground truth\n", @@ -525,7 +479,7 @@ { "cell_type": "code", "execution_count": null, - "id": "37", + "id": "33", "metadata": {}, "outputs": [], "source": [ @@ -542,12 +496,12 @@ }, { "cell_type": "markdown", - "id": "38", + "id": "34", "metadata": {}, "source": [ "## Multiple detector pixels\n", "\n", - "It is also possible to compute the neutron time-of-flight for multiple detector pixels at once,\n", + "It is also possible to compute the neutron wavelength for multiple detector pixels at once,\n", "where every pixel has different frame bounds\n", "(because every pixel is at a different distance from the source).\n", "\n", @@ -558,7 +512,7 @@ { "cell_type": "code", "execution_count": null, - "id": "39", + "id": "35", "metadata": {}, "outputs": [], "source": [ @@ -576,7 +530,7 @@ }, { "cell_type": "markdown", - "id": "40", + "id": "36", "metadata": {}, "source": [ "Our raw data has now a `detector_number` dimension of length 2.\n", @@ -587,7 +541,7 @@ { "cell_type": "code", "execution_count": null, - "id": "41", + "id": "37", "metadata": {}, "outputs": [], "source": [ @@ -602,17 +556,17 @@ }, { "cell_type": "markdown", - "id": "42", + "id": "38", "metadata": {}, "source": [ - "Computing time-of-flight is done in the same way as above.\n", + "Computing wavelength is done in the same way as above.\n", "We need to remember to update our workflow:" ] }, { "cell_type": "code", "execution_count": null, - "id": "43", + "id": "39", "metadata": {}, "outputs": [], "source": [ @@ -622,7 +576,6 @@ "\n", "# Compute tofs and wavelengths\n", "wav_wfm = wf.compute(WavelengthDetector[SampleRun])\n", - "# wav_wfm = tofs.transform_coords(\"wavelength\", graph=graph)\n", "\n", "# Compare in plot\n", "ground_truth = []\n", @@ -636,7 +589,7 @@ " \"wfm\": wav_wfm[\"detector_number\", i].bins.concat().hist(wavelength=edges),\n", " \"ground_truth\": ground_truth[i].hist(wavelength=edges),\n", " },\n", - " title=f\"Pixel {i+1}\",\n", + " title=f\"Pixel {i + 1}\",\n", " )\n", " for i in range(len(Ltotal))\n", "]\n", @@ -646,7 +599,7 @@ }, { "cell_type": "markdown", - "id": "44", + "id": "40", "metadata": {}, "source": [ "## Handling time overlap between subframes\n", @@ -658,7 +611,7 @@ "but arrive at the same time at the detector.\n", "\n", "In this case, it is actually not possible to accurately determine the wavelength of the neutrons.\n", - "ScippNeutron handles this by masking the overlapping regions and throwing away any neutrons that lie within it.\n", + "We handle this by masking the overlapping regions and throwing away any neutrons that lie within it.\n", "\n", "To simulate this, we modify slightly the phase and the cutouts of the band-control chopper:" ] @@ -666,7 +619,7 @@ { "cell_type": "code", "execution_count": null, - "id": "45", + "id": "41", "metadata": {}, "outputs": [], "source": [ @@ -697,7 +650,7 @@ }, { "cell_type": "markdown", - "id": "46", + "id": "42", "metadata": {}, "source": [ "We can now see that there is no longer a gap between the two frames at the center of each pulse (green region).\n", @@ -709,7 +662,7 @@ { "cell_type": "code", "execution_count": null, - "id": "47", + "id": "43", "metadata": {}, "outputs": [], "source": [ @@ -724,10 +677,10 @@ }, { "cell_type": "markdown", - "id": "48", + "id": "44", "metadata": {}, "source": [ - "The data in the lookup table contains both the mean time-of-flight for each distance and time-of-arrival bin,\n", + "The data in the lookup table contains both the mean wavelength for each distance and time-of-arrival bin,\n", "but also the variance inside each bin.\n", "\n", "In the regions where there is no time overlap,\n", @@ -742,17 +695,19 @@ { "cell_type": "code", "execution_count": null, - "id": "49", + "id": "45", "metadata": {}, "outputs": [], "source": [ "table = lut_wf.compute(LookupTable)\n", - "table.plot(ymin=65) / (sc.stddevs(table.array) / sc.values(table.array)).plot(norm=\"linear\", ymin=55, vmax=0.05)" + "table.plot(ymin=65) / (sc.stddevs(table.array) / sc.values(table.array)).plot(\n", + " norm=\"linear\", ymin=55, vmax=0.05\n", + ")" ] }, { "cell_type": "markdown", - "id": "50", + "id": "46", "metadata": {}, "source": [ "The workflow has a parameter which is used to mask out regions where the standard deviation is above a certain threshold.\n", @@ -766,7 +721,7 @@ { "cell_type": "code", "execution_count": null, - "id": "51", + "id": "47", "metadata": {}, "outputs": [], "source": [ @@ -780,13 +735,13 @@ }, { "cell_type": "markdown", - "id": "52", + "id": "48", "metadata": {}, "source": [ "We can now see that the central region is masked out.\n", "\n", - "The neutrons in that region will be discarded in the time-of-flight calculation\n", - "(in practice, they are given a NaN value as a time-of-flight).\n", + "The neutrons in that region will be discarded in the wavelength calculation\n", + "(in practice, they are given a NaN value as a wavelength).\n", "\n", "This is visible when comparing to the true neutron wavelengths,\n", "where we see that some counts were lost between the two frames." @@ -795,17 +750,15 @@ { "cell_type": "code", "execution_count": null, - "id": "53", + "id": "49", "metadata": {}, "outputs": [], "source": [ "wf[RawDetector[SampleRun]] = ess_beamline.get_monitor(\"detector\")[0]\n", "wf[DetectorLtotal[SampleRun]] = Ltotal\n", "\n", - "# Compute time-of-flight\n", - "wav_wfm = wf.compute(WavelengthDetector[SampleRun])\n", "# Compute wavelength\n", - "# wav_wfm = tofs.transform_coords(\"wavelength\", graph=graph)\n", + "wav_wfm = wf.compute(WavelengthDetector[SampleRun])\n", "\n", "# Compare to the true wavelengths\n", "ground_truth = ess_beamline.model_result[\"detector\"].data.flatten(to=\"event\")\n", diff --git a/packages/essreduce/docs/user-guide/unwrap/frame-unwrapping.ipynb b/packages/essreduce/docs/user-guide/unwrap/frame-unwrapping.ipynb index 53666dd1..2cad4d85 100644 --- a/packages/essreduce/docs/user-guide/unwrap/frame-unwrapping.ipynb +++ b/packages/essreduce/docs/user-guide/unwrap/frame-unwrapping.ipynb @@ -291,7 +291,9 @@ "\n", "# Also compare to the ground truth\n", "ground_truth = results[\"detector\"].data.flatten(to=\"event\")\n", - "ground_truth = ground_truth[~ground_truth.masks[\"blocked_by_others\"]].hist(wavelength=dw)\n", + "ground_truth = ground_truth[~ground_truth.masks[\"blocked_by_others\"]].hist(\n", + " wavelength=dw\n", + ")\n", "wavs[\"true\"] = ground_truth\n", "\n", "pp.plot(wavs)" @@ -299,7 +301,7 @@ }, { "cell_type": "markdown", - "id": "17", + "id": "15", "metadata": {}, "source": [ "We see that all detectors agree on the wavelength spectrum,\n", @@ -320,7 +322,7 @@ { "cell_type": "code", "execution_count": null, - "id": "18", + "id": "16", "metadata": {}, "outputs": [], "source": [ @@ -355,7 +357,7 @@ }, { "cell_type": "markdown", - "id": "19", + "id": "17", "metadata": {}, "source": [ "### Computing wavelengths\n", @@ -369,7 +371,7 @@ { "cell_type": "code", "execution_count": null, - "id": "20", + "id": "18", "metadata": {}, "outputs": [], "source": [ @@ -398,7 +400,7 @@ }, { "cell_type": "markdown", - "id": "21", + "id": "19", "metadata": {}, "source": [ "The lookup table now spans 2 pulse periods, between 0 and ~142 ms:" @@ -407,7 +409,7 @@ { "cell_type": "code", "execution_count": null, - "id": "22", + "id": "20", "metadata": {}, "outputs": [], "source": [ @@ -418,7 +420,7 @@ }, { "cell_type": "markdown", - "id": "23", + "id": "21", "metadata": {}, "source": [ "The wavelength profiles are then:" @@ -427,7 +429,7 @@ { "cell_type": "code", "execution_count": null, - "id": "24", + "id": "22", "metadata": {}, "outputs": [], "source": [ @@ -446,7 +448,9 @@ "\n", "# Compare to ground truth\n", "ground_truth = results[\"detector\"].data.flatten(to=\"event\")\n", - "ground_truth = ground_truth[~ground_truth.masks[\"blocked_by_others\"]].hist(wavelength=dw)\n", + "ground_truth = ground_truth[~ground_truth.masks[\"blocked_by_others\"]].hist(\n", + " wavelength=dw\n", + ")\n", "wavs[\"true\"] = ground_truth\n", "\n", "pp.plot(wavs)" diff --git a/packages/essreduce/docs/user-guide/unwrap/wfm.ipynb b/packages/essreduce/docs/user-guide/unwrap/wfm.ipynb index 4447e59a..69b688aa 100644 --- a/packages/essreduce/docs/user-guide/unwrap/wfm.ipynb +++ b/packages/essreduce/docs/user-guide/unwrap/wfm.ipynb @@ -10,8 +10,7 @@ "Wavelength frame multiplication (WFM) is a technique commonly used at long-pulse facilities to improve the resolution of the results measured at the neutron detectors.\n", "See for example the article by [Schmakat et al. (2020)](https://doi.org/10.1016/j.nima.2020.164467) for a description of how WFM works.\n", "\n", - "In this notebook, we show how to use `essreduce`'s `time_of_flight` module to compute an accurate a time-of-flight coordinate,\n", - "from which a wavelength can be computed." + "In this notebook, we show how to use `essreduce`'s `unwrap` module to compute an accurate a wavelength coordinate for neutrons travelling through a WFM beamline." ] }, { @@ -27,7 +26,7 @@ "import scippnexus as snx\n", "from scippneutron.chopper import DiskChopper\n", "from ess.reduce.nexus.types import AnyRun, RawDetector, SampleRun, NeXusDetectorName\n", - "from ess.reduce.time_of_flight import *" + "from ess.reduce.unwrap import *" ] }, { @@ -232,7 +231,7 @@ "metadata": {}, "outputs": [], "source": [ - "from ess.reduce.time_of_flight.fakes import FakeBeamline\n", + "from ess.reduce.unwrap.fakes import FakeBeamline\n", "\n", "ess_beamline = FakeBeamline(\n", " choppers=disk_choppers,\n", @@ -306,9 +305,9 @@ "id": "18", "metadata": {}, "source": [ - "## Computing time-of-flight\n", + "## Computing neutron wavelengths\n", "\n", - "Next, we use a workflow that provides an estimate of the real time-of-flight as a function of neutron time-of-arrival.\n", + "Next, we use a workflow that provides an estimate of the neutron wavelength as a function of neutron time-of-arrival.\n", "\n", "### Setting up the workflow" ] @@ -320,13 +319,13 @@ "metadata": {}, "outputs": [], "source": [ - "wf = GenericTofWorkflow(run_types=[SampleRun], monitor_types=[])\n", + "wf = GenericUnwrapWorkflow(run_types=[SampleRun], monitor_types=[])\n", "\n", "wf[RawDetector[SampleRun]] = raw_data\n", "wf[DetectorLtotal[SampleRun]] = Ltotal\n", "wf[NeXusDetectorName] = \"detector\"\n", "\n", - "wf.visualize(TofDetector[SampleRun])" + "wf.visualize(WavelengthDetector[SampleRun])" ] }, { @@ -334,7 +333,7 @@ "id": "20", "metadata": {}, "source": [ - "By default, the workflow tries to load a `TofLookupTable` from a file.\n", + "By default, the workflow tries to load a `LookupTable` from a file.\n", "\n", "In this notebook, instead of using such a pre-made file,\n", "we will build our own lookup table from the chopper information and apply it to the workflow." @@ -345,14 +344,13 @@ "id": "21", "metadata": {}, "source": [ - "### Building the time-of-flight lookup table\n", + "### Building the wavelength lookup table\n", "\n", "We use the [Tof](https://scipp.github.io/tof/) package to propagate a pulse of neutrons through the chopper system to the detectors,\n", "and predict the most likely neutron wavelength for a given time-of-arrival and distance from source.\n", "\n", "From this,\n", - "we build a lookup table on which bilinear interpolation is used to compute a wavelength (and its corresponding time-of-flight)\n", - "for every neutron event." + "we build a lookup table on which bilinear interpolation is used to compute a wavelength for every neutron event." ] }, { @@ -362,11 +360,11 @@ "metadata": {}, "outputs": [], "source": [ - "lut_wf = TofLookupTableWorkflow()\n", + "lut_wf = LookupTableWorkflow()\n", "lut_wf[DiskChoppers[AnyRun]] = disk_choppers\n", "lut_wf[SourcePosition] = source_position\n", "lut_wf[LtotalRange] = sc.scalar(5, unit='m'), sc.scalar(35, unit='m')\n", - "lut_wf.visualize(TofLookupTable)" + "lut_wf.visualize(LookupTable)" ] }, { @@ -379,7 +377,7 @@ "The workflow first runs a simulation using the chopper parameters above,\n", "and the result is stored in `SimulationResults` (see graph above).\n", "\n", - "From these simulated neutrons, we create figures displaying the neutron wavelengths and time-of-flight,\n", + "From these simulated neutrons, we create a figure displaying the neutron wavelengths,\n", "as a function of arrival time at the detector.\n", "\n", "This is the basis for creating our lookup table." @@ -409,9 +407,8 @@ "\n", "\n", "events = to_event_time_offset(sim.readings[\"pol\"])\n", - "fig1 = events.hist(wavelength=300, event_time_offset=300).plot(norm=\"log\")\n", - "fig2 = events.hist(tof=300, event_time_offset=300).plot(norm=\"log\")\n", - "fig1 + fig2" + "fig = events.hist(wavelength=300, event_time_offset=300).plot(norm=\"log\")\n", + "fig" ] }, { @@ -419,7 +416,7 @@ "id": "25", "metadata": {}, "source": [ - "The lookup table is then obtained by computing the weighted mean of the time-of-flight inside each time-of-arrival bin.\n", + "The lookup table is then obtained by computing the weighted mean of wavelength inside each time-of-arrival bin.\n", "\n", "This is illustrated by the orange line in the figure below:" ] @@ -431,15 +428,15 @@ "metadata": {}, "outputs": [], "source": [ - "table = lut_wf.compute(TofLookupTable)\n", + "table = lut_wf.compute(LookupTable)\n", "\n", "# Overlay mean on the figure above\n", - "table.array[\"distance\", 212].plot(ax=fig2.ax, color=\"C1\", ls=\"-\", marker=None)\n", + "table.array[\"distance\", 212].plot(ax=fig.ax, color=\"C1\", ls=\"-\", marker=None)\n", "\n", "# Zoom in\n", - "fig2.canvas.xrange = 40000, 50000\n", - "fig2.canvas.yrange = 35000, 50000\n", - "fig2" + "fig.canvas.xrange = 40000, 50000\n", + "fig.canvas.yrange = 5.5, 7.5\n", + "fig" ] }, { @@ -453,8 +450,8 @@ "the error bars on the orange line get larger.\n", "\n", "Another way of looking at this is to plot the entire table,\n", - "showing the predicted time-of-flight as a function of `event_time_offset` and distance.\n", - "We also show alongside it the standard deviation of the predicted time-of-flight." + "showing the predicted wavelength as a function of `event_time_offset` and distance.\n", + "We also show alongside it the standard deviation of the predicted wavelength." ] }, { @@ -465,13 +462,16 @@ "outputs": [], "source": [ "def plot_lut(table):\n", - " fig = table.plot(title=\"Predicted time-of-flight\") + sc.stddevs(table.array).plot(title=\"Standard deviation\", vmax=1000)\n", + " fig = table.plot(title=\"Predicted time-of-flight\") + sc.stddevs(table.array).plot(\n", + " title=\"Standard deviation\", vmax=0.5\n", + " )\n", " for f in (fig[0, 0], fig[0, 1]):\n", " f.ax.axhline(Ltotal.value, ls='dashed', color='k')\n", " f.ax.text(1e3, Ltotal.value, \"detector\", va='bottom', color='k')\n", " f.ax.text(1e3, Ltotal.value, \"at 26m\", va='top', color='k')\n", " return fig\n", "\n", + "\n", "plot_lut(table)" ] }, @@ -481,7 +481,7 @@ "metadata": {}, "source": [ "We can see that at low distances (< 7m), before the first choppers, the uncertainties are very large.\n", - "Neutrons with different wavelengths, originating from different parts of the pulse, are mixing and make it very difficult to predict a good time-of-flight.\n", + "Neutrons with different wavelengths, originating from different parts of the pulse, are mixing and make it very difficult to predict a good wavelength.\n", "\n", "As we move to larger distances, uncertainties drop overall (colors drift to blue).\n", "However, we still see spikes of uncertainties inside the frames around 26 m where the detector is placed,\n", @@ -492,9 +492,9 @@ "It is actually possible to mask out regions of large uncertainty using the `LookupTableRelativeErrorThreshold` parameter.\n", "\n", "Because we may want to use different uncertainty criteria for different experimental runs as well as different components in the beamline (monitors, multiple detector banks),\n", - "the masking is not hard-coded in the table but a parameter that is applied on-the-fly in the original workflow which computes time-of-flight.\n", + "the masking is not hard-coded in the table but a parameter that is applied on-the-fly in the original workflow which computes wavelength.\n", "\n", - "We thus first update that workflow by setting the newly computed `table` as the `TofLookupTable` parameter.\n", + "We thus first update that workflow by setting the newly computed `table` as the `LookupTable` parameter.\n", "Next, we apply a threshold for the `detector` component and inspect the masked table:" ] }, @@ -505,10 +505,10 @@ "metadata": {}, "outputs": [], "source": [ - "wf[TofLookupTable] = table\n", + "wf[LookupTable] = table\n", "wf[LookupTableRelativeErrorThreshold] = {\"detector\": 0.01}\n", "\n", - "masked = wf.compute(ErrorLimitedTofLookupTable[snx.NXdetector])\n", + "masked = wf.compute(ErrorLimitedLookupTable[snx.NXdetector])\n", "\n", "plot_lut(masked)" ] @@ -540,9 +540,9 @@ "id": "33", "metadata": {}, "source": [ - "### Computing a time-of-flight coordinate\n", + "### Computing a wavelength coordinate\n", "\n", - "We will now compute our event data with a time-of-flight coordinate:" + "We will now compute our event data with a wavelength coordinate, and histogram the results:" ] }, { @@ -552,62 +552,16 @@ "metadata": {}, "outputs": [], "source": [ - "tofs = wf.compute(TofDetector[SampleRun])\n", - "tofs" - ] - }, - { - "cell_type": "markdown", - "id": "35", - "metadata": {}, - "source": [ - "Histogramming the data for a plot should show a profile with 6 bumps that correspond to the frames:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "36", - "metadata": {}, - "outputs": [], - "source": [ - "tofs.bins.concat().hist(tof=300).plot()" - ] - }, - { - "cell_type": "markdown", - "id": "37", - "metadata": {}, - "source": [ - "### Converting to wavelength\n", - "\n", - "We can now convert our new time-of-flight coordinate to a neutron wavelength, using `tranform_coords`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "38", - "metadata": {}, - "outputs": [], - "source": [ - "from scippneutron.conversion.graph.beamline import beamline\n", - "from scippneutron.conversion.graph.tof import elastic\n", + "wavs = wf.compute(WavelengthDetector[SampleRun])\n", "\n", - "# Perform coordinate transformation\n", - "graph = {**beamline(scatter=False), **elastic(\"tof\")}\n", - "wav_wfm = tofs.transform_coords(\"wavelength\", graph=graph)\n", - "\n", - "# Define wavelength bin edges\n", - "wavs = sc.linspace(\"wavelength\", 2, 10, 301, unit=\"angstrom\")\n", - "\n", - "histogrammed = wav_wfm.hist(wavelength=wavs).squeeze()\n", + "bins = sc.linspace(\"wavelength\", 2, 10, 301, unit=\"angstrom\")\n", + "histogrammed = wavs.hist(wavelength=bins).squeeze()\n", "histogrammed.plot()" ] }, { "cell_type": "markdown", - "id": "39", + "id": "35", "metadata": {}, "source": [ "### Comparing to the ground truth\n", @@ -619,7 +573,7 @@ { "cell_type": "code", "execution_count": null, - "id": "40", + "id": "36", "metadata": {}, "outputs": [], "source": [ @@ -629,7 +583,7 @@ "pp.plot(\n", " {\n", " \"wfm\": histogrammed,\n", - " \"ground_truth\": ground_truth.hist(wavelength=wavs),\n", + " \"ground_truth\": ground_truth.hist(wavelength=bins),\n", " }\n", ")" ] @@ -651,7 +605,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.7" + "version": "3.12.12" } }, "nbformat": 4, From 4b44b75a06a8f141028023a424bc3ed976a58609 Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Thu, 12 Mar 2026 16:25:25 +0100 Subject: [PATCH 23/24] begin changing back to operate on tof lookup tables rather than wavelength --- .../src/ess/reduce/unwrap/to_wavelength.py | 45 +++++++++---------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/packages/essreduce/src/ess/reduce/unwrap/to_wavelength.py b/packages/essreduce/src/ess/reduce/unwrap/to_wavelength.py index 6b228e5d..f77f4d31 100644 --- a/packages/essreduce/src/ess/reduce/unwrap/to_wavelength.py +++ b/packages/essreduce/src/ess/reduce/unwrap/to_wavelength.py @@ -15,6 +15,7 @@ import scippneutron as scn import scippnexus as snx from scippneutron._utils import elem_unit +from scippneutron.conversion.tof import wavelength_from_tof try: from .interpolator_numba import Interpolator as InterpolatorImpl @@ -47,17 +48,10 @@ ) -class WavelengthInterpolator: - def __init__( - self, - lookup: sc.DataArray, - distance_unit: str, - time_unit: str, - wavelength_unit: str = 'angstrom', - ): +class TofInterpolator: + def __init__(self, lookup: sc.DataArray, distance_unit: str, time_unit: str): self._distance_unit = distance_unit self._time_unit = time_unit - self._wavelength_unit = wavelength_unit self._time_edges = ( lookup.coords["event_time_offset"] @@ -71,7 +65,7 @@ def __init__( self._interpolator = InterpolatorImpl( time_edges=self._time_edges, distance_edges=self._distance_edges, - values=lookup.data.to(unit=self._wavelength_unit, copy=False).values, + values=lookup.data.to(unit=self._time_unit, copy=False).values, ) def __call__( @@ -103,17 +97,16 @@ def __call__( pulse_index=pulse_index.values if pulse_index is not None else None, pulse_period=pulse_period.value, ), - unit=self._wavelength_unit, + unit=self._time_unit, ) -def _compute_wavelength_histogram( +def _compute_tof_histogram( da: sc.DataArray, lookup: ErrorLimitedLookupTable, ltotal: sc.Variable ) -> sc.DataArray: # In NeXus, 'time_of_flight' is the canonical name in NXmonitor, but in some files, # it may be called 'tof' or 'frame_time'. - possible_names = {"time_of_flight", "tof", "frame_time"} - key = next(iter(set(da.coords.keys()) & possible_names)) + key = next(iter(set(da.coords.keys()) & {"time_of_flight", "tof", "frame_time"})) raw_eto = da.coords[key].to(dtype=float, copy=False) eto_unit = raw_eto.unit pulse_period = lookup.pulse_period.to(unit=eto_unit) @@ -130,19 +123,19 @@ def _compute_wavelength_histogram( etos = rebinned.coords[key] # Create linear interpolator - interp = WavelengthInterpolator( + interp = TofInterpolator( lookup.array, distance_unit=ltotal.unit, time_unit=eto_unit ) - # Compute wavelengths of the bin edges using the interpolator - wavs = interp( + # Compute time-of-flight of the bin edges using the interpolator + tofs = interp( ltotal=ltotal.broadcast(sizes=etos.sizes), event_time_offset=etos, pulse_period=pulse_period, ) - return rebinned.assign_coords(wavelength=wavs).drop_coords( - list({key} & possible_names) + return rebinned.assign_coords(tof=tofs).drop_coords( + list({key} & {"time_of_flight", "frame_time"}) ) @@ -152,7 +145,7 @@ def _guess_pulse_stride_offset( event_time_offset: sc.Variable, pulse_period: sc.Variable, pulse_stride: int, - interp: WavelengthInterpolator, + interp: TofInterpolator, ) -> int: """ Using the minimum ``event_time_zero`` to calculate a reference time when computing @@ -241,7 +234,7 @@ def _prepare_wavelength_interpolation_inputs( eto_unit = elem_unit(etos) # Create linear interpolator - interp = WavelengthInterpolator( + interp = TofInterpolator( lookup.array, distance_unit=ltotal.unit, time_unit=eto_unit ) @@ -490,16 +483,18 @@ def _compute_wavelength_data( pulse_stride_offset: int, ) -> sc.DataArray: if da.bins is None: - data = _compute_wavelength_histogram(da=da, lookup=lookup, ltotal=ltotal) - out = rebin_strictly_increasing(data, dim='wavelength') + tofs = _compute_tof_histogram(da=da, lookup=lookup, ltotal=ltotal) + tofs = rebin_strictly_increasing(tofs, dim='time_of_flight') else: - out = _compute_wavelength_events( + tofs = _compute_tof_events( da=da, lookup=lookup, ltotal=ltotal, pulse_stride_offset=pulse_stride_offset, ) - return out.assign_coords(Ltotal=ltotal) + return tofs.assign_coords(Ltotal=ltotal).transform_coords( + 'wavelength', graph={"wavelength": wavelength_from_tof}, keep_intermediate=False + ) def detector_wavelength_data( From 4c2c4b812240419689eaacff21fe50df2314dd79 Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Thu, 12 Mar 2026 17:28:08 +0100 Subject: [PATCH 24/24] keep luts as wavelength, convert on the fly to tof before interpolating --- .../src/ess/reduce/unwrap/to_wavelength.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/essreduce/src/ess/reduce/unwrap/to_wavelength.py b/packages/essreduce/src/ess/reduce/unwrap/to_wavelength.py index f77f4d31..bd31ac6b 100644 --- a/packages/essreduce/src/ess/reduce/unwrap/to_wavelength.py +++ b/packages/essreduce/src/ess/reduce/unwrap/to_wavelength.py @@ -7,15 +7,15 @@ event_time_offset coordinates to data with a time-of-flight coordinate. """ +import dataclasses as dtc from collections.abc import Callable -from dataclasses import asdict import numpy as np import scipp as sc import scippneutron as scn import scippnexus as snx from scippneutron._utils import elem_unit -from scippneutron.conversion.tof import wavelength_from_tof +from scippneutron.conversion.tof import tof_from_wavelength, wavelength_from_tof try: from .interpolator_numba import Interpolator as InterpolatorImpl @@ -418,7 +418,7 @@ def _mask_large_uncertainty_in_lut( mask = relative_error > sc.scalar(error_threshold) return LookupTable( **{ - **asdict(table), + **dtc.asdict(table), "array": sc.where(mask, sc.scalar(np.nan, unit=da.unit), da), } ) @@ -482,6 +482,13 @@ def _compute_wavelength_data( ltotal: sc.Variable, pulse_stride_offset: int, ) -> sc.DataArray: + # The lookup table gives wavelength as a function of (eot, distance). We operate + # on tofs to reduce interpolation errors. + table_tof = tof_from_wavelength( + wavelength=lookup.array.data, Ltotal=lookup.array.coords['distance'] + ).to(unit=lookup.array.coords['event_time_offset'].unit) + # Make copy of dataclass with replace + lookup = dtc.replace(lookup, array=lookup.array.assign(table_tof)) if da.bins is None: tofs = _compute_tof_histogram(da=da, lookup=lookup, ltotal=ltotal) tofs = rebin_strictly_increasing(tofs, dim='time_of_flight')