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/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/odin/odin-data-reduction.ipynb b/packages/essimaging/docs/odin/odin-data-reduction.ipynb index d4a94083..c3ead4f3 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.LookupTableFilename] = odin.data.odin_wavelength_lookup_table()" ] }, { @@ -84,9 +84,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 +96,7 @@ "metadata": {}, "outputs": [], "source": [ - "wf.visualize(TofDetector[SampleRun], graph_attr={\"rankdir\": \"LR\"})" + "wf.visualize(WavelengthDetector[SampleRun], graph_attr={\"rankdir\": \"LR\"})" ] }, { @@ -106,7 +106,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." ] }, { @@ -116,7 +116,7 @@ "metadata": {}, "outputs": [], "source": [ - "table = wf.compute(time_of_flight.TimeOfFlightLookupTable)\n", + "table = wf.compute(unwrap.LookupTable)\n", "table.plot(figsize=(9, 4))" ] }, @@ -285,7 +285,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 68% 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 a494ad57..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" ] }, { @@ -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,15 +39,15 @@ "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(5.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')" + "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", + "wf[unwrap.SimulationSeed] = 1234\n", + "wf[unwrap.PulseStride] = 2\n", + "wf[unwrap.LtotalRange] = sc.scalar(5.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')" ] }, { @@ -65,7 +65,7 @@ "metadata": {}, "outputs": [], "source": [ - "table = wf.compute(time_of_flight.TimeOfFlightLookupTable)\n", + "table = wf.compute(unwrap.LookupTable)\n", "table.array" ] }, @@ -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/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/essimaging/docs/tbl/tbl-data-reduction.ipynb b/packages/essimaging/docs/tbl/tbl-data-reduction.ipynb index 2dfa79bc..1fbd1e2e 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 *" @@ -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_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" ] }, { @@ -117,7 +117,7 @@ "metadata": {}, "outputs": [], "source": [ - "table = wf.compute(time_of_flight.TimeOfFlightLookupTable)\n", + "table = wf.compute(unwrap.LookupTable)\n", "table.plot()" ] }, @@ -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 68% 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 870c7579..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" ] }, { @@ -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,15 +37,15 @@ "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(5.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')" + "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", + "wf[unwrap.SimulationSeed] = 1234\n", + "wf[unwrap.PulseStride] = 1\n", + "wf[unwrap.LtotalRange] = sc.scalar(5.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')" ] }, { @@ -63,7 +63,7 @@ "metadata": {}, "outputs": [], "source": [ - "table = wf.compute(time_of_flight.TimeOfFlightLookupTable)\n", + "table = wf.compute(unwrap.LookupTable)\n", "table.array" ] }, @@ -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/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 c1620c2d..b1fbf109 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.time_of_flight import types as tof_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 @@ -21,37 +21,37 @@ 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 -LookupTableRelativeErrorThreshold = tof_t.LookupTableRelativeErrorThreshold +DetectorLtotal = unwrap_t.DetectorLtotal +WavelengthDetector = unwrap_t.WavelengthDetector +PulseStrideOffset = unwrap_t.PulseStrideOffset +LookupTable = unwrap_t.LookupTable +LookupTableFilename = unwrap_t.LookupTableFilename +LookupTableRelativeErrorThreshold = unwrap_t.LookupTableRelativeErrorThreshold 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 @@ -65,11 +65,7 @@ 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]) +MaskingRules = NewType("MaskingRules", MappingProxyType[str, Callable]) """Functions to mask different dimensions of Odin data.""" @@ -85,7 +81,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/__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/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") diff --git a/packages/essimaging/src/ess/odin/workflows.py b/packages/essimaging/src/ess/odin/workflows.py index b596bf0d..277b39f7 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.time_of_flight.workflow import GenericTofWorkflow +from ess.reduce.unwrap.workflow import GenericUnwrapWorkflow -from ..imaging.conversion import providers as conversion_providers from ..imaging.types import ( BeamMonitor1, BeamMonitor2, @@ -31,11 +30,12 @@ def default_parameters() -> dict: NeXusMonitorName[BeamMonitor4]: "beam_monitor_4", PulseStrideOffset: None, LookupTableRelativeErrorThreshold: { - "event_mode_detectors/timepix3": float('inf'), - "beam_monitor_1": float('inf'), - "beam_monitor_2": float('inf'), - "beam_monitor_3": float('inf'), - "beam_monitor_4": float('inf'), + "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"), }, } @@ -44,7 +44,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, @@ -59,7 +59,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/__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/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 656b462f..9222af38 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.time_of_flight.workflow import GenericTofWorkflow +from ess.reduce.unwrap.workflow import GenericUnwrapWorkflow -from ..imaging.conversion import providers as conversion_providers from ..imaging.types import ( BeamMonitor1, LookupTableRelativeErrorThreshold, @@ -22,27 +21,23 @@ def default_parameters() -> dict: NeXusMonitorName[BeamMonitor1]: "monitor_1", PulseStrideOffset: None, LookupTableRelativeErrorThreshold: { - "timepix3_detector": float('inf'), - "ngem_detector": float('inf'), - "he3_detector_bank0": float('inf'), - "he3_detector_bank1": float('inf'), - "monitor_1": float('inf'), + "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"), }, } -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 diff --git a/packages/essimaging/tests/odin/data_reduction_test.py b/packages/essimaging/tests/odin/data_reduction_test.py index 2b22d02b..489668d0 100644 --- a/packages/essimaging/tests/odin/data_reduction_test.py +++ b/packages/essimaging/tests/odin/data_reduction_test.py @@ -8,13 +8,12 @@ from ess import odin from ess.imaging.types import ( Filename, + LookupTable, + LookupTableFilename, NeXusDetectorName, OpenBeamRun, RawDetector, SampleRun, - TimeOfFlightLookupTable, - TimeOfFlightLookupTableFilename, - TofDetector, WavelengthDetector, ) @@ -28,9 +27,9 @@ 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_wavelength_lookup_table() # Cache the lookup table - wf[TimeOfFlightLookupTable] = wf.compute(TimeOfFlightLookupTable) + wf[LookupTable] = wf.compute(LookupTable) return wf @@ -48,13 +47,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_time_of_flight(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 b1d449d0..33067e49 100644 --- a/packages/essimaging/tests/tbl/data_reduction_test.py +++ b/packages/essimaging/tests/tbl/data_reduction_test.py @@ -4,40 +4,36 @@ 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 from ess import tbl from ess.imaging.types import ( Filename, + LookupTable, + LookupTableFilename, NeXusDetectorName, RawDetector, SampleRun, - TimeOfFlightLookupTable, - TimeOfFlightLookupTableFilename, - 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 = 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] = ( - sc.scalar(25.0, unit="m"), - sc.scalar(35.0, unit="m"), - ) - return lut_wf.compute(TimeOfFlightLookupTable) + 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 + 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")) + return lut_wf.compute(LookupTable) @pytest.fixture @@ -47,7 +43,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_wavelength_lookup_table_no_choppers() return wf @@ -72,31 +68,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_time_of_flight(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_time_of_flight_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[TimeOfFlightLookupTable] = 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 diff --git a/packages/essnmx/pyproject.toml b/packages/essnmx/pyproject.toml index f518b6ca..21a83e13 100644 --- a/packages/essnmx/pyproject.toml +++ b/packages/essnmx/pyproject.toml @@ -37,7 +37,7 @@ dependencies = [ "sciline>=24.06.0", "scipp>=25.3.0", "scippnexus>=23.12.0", - "scippneutron>=26.02.0", + "scippneutron>=26.03.0", "pooch>=1.5", "pandas>=2.1.2", "gemmi>=0.6.6", diff --git a/packages/essnmx/src/ess/nmx/configurations.py b/packages/essnmx/src/ess/nmx/configurations.py index 08e821b6..92c7a9c6 100644 --- a/packages/essnmx/src/ess/nmx/configurations.py +++ b/packages/essnmx/src/ess/nmx/configurations.py @@ -98,10 +98,10 @@ class WorkflowConfig(BaseModel): description="Unit of time bins.", default=TimeBinUnit.ms, ) - tof_lookup_table_file_path: str | None = Field( - title="TOF Lookup Table File Path", - description="Path to the TOF lookup table file. " - "If None, the lookup table will be computed on-the-fly.", + lookup_table_file_path: str | None = Field( + title="Lookup Table File Path", + description="Path to the lookup table file that allows to compute wavelength/" + "time-of-flight. If None, the lookup table will be computed on-the-fly.", default=None, ) tof_simulation_num_neutrons: int = Field( diff --git a/packages/essnmx/src/ess/nmx/executables.py b/packages/essnmx/src/ess/nmx/executables.py index 21643caf..e4492e31 100644 --- a/packages/essnmx/src/ess/nmx/executables.py +++ b/packages/essnmx/src/ess/nmx/executables.py @@ -9,7 +9,7 @@ import scipp as sc import scippnexus as snx from ess.reduce.nexus.types import Filename, NeXusName, RawDetector, SampleRun -from ess.reduce.time_of_flight.types import TimeOfFlightLookupTable, TofDetector +from ess.reduce.unwrap.types import LookupTable from ._executable_helper import ( build_logger, @@ -38,6 +38,7 @@ NMXReducedDetector, NMXSampleMetadata, NMXSourceMetadata, + TofDetector, ) from .workflows import initialize_nmx_workflow, select_detector_names @@ -222,11 +223,10 @@ def reduction( base_wf[Filename[SampleRun]] = input_file_path if config.workflow.time_bin_coordinate == TimeBinCoordinate.time_of_flight: - # We cache the time of flight look up table - # only if we need to calculate time-of-flight coordinates. - # If `event_time_offset` was requested, - # we do not have to calculate the look up table at all. - base_wf[TimeOfFlightLookupTable] = base_wf.compute(TimeOfFlightLookupTable) + # We cache the lookup table only if we need to calculate time-of-flight + # coordinates. If `event_time_offset` was requested, + # we do not have to calculate the lookup table at all. + base_wf[LookupTable] = base_wf.compute(LookupTable) metadatas = base_wf.compute((NMXSampleMetadata, NMXSourceMetadata)) @@ -286,7 +286,7 @@ def reduction( ) if config.workflow.time_bin_coordinate == TimeBinCoordinate.time_of_flight: - results.lookup_table = base_wf.compute(TimeOfFlightLookupTable) + results.lookup_table = base_wf.compute(LookupTable) if not config.output.skip_file_output: save_results(results=results, output_config=config.output) diff --git a/packages/essnmx/src/ess/nmx/types.py b/packages/essnmx/src/ess/nmx/types.py index cfc4301c..324cbfca 100644 --- a/packages/essnmx/src/ess/nmx/types.py +++ b/packages/essnmx/src/ess/nmx/types.py @@ -4,9 +4,11 @@ import h5py import numpy as np +import sciline as sl import scipp as sc import scippnexus as snx -from ess.reduce.time_of_flight.types import TofLookupTable +from ess.reduce.nexus.types import RunType +from ess.reduce.unwrap.types import LookupTable from scippneutron.metadata import RadiationProbe, SourceType from ._display_helper import to_datagroup @@ -269,9 +271,13 @@ class NMXLauetof: definitions: Literal['NXlauetof'] = 'NXlauetof' instrument: NMXInstrument sample: NMXSampleMetadata - lookup_table: TofLookupTable | None = None + lookup_table: LookupTable | None = None reducer: NMXProgram = field(default_factory=NMXProgram) "Information of the reduction software." def to_datagroup(self) -> sc.DataGroup: return to_datagroup(self) + + +class TofDetector(sl.Scope[RunType, sc.DataArray], sc.DataArray): + """Detector data with time-of-flight coordinate.""" diff --git a/packages/essnmx/src/ess/nmx/workflows.py b/packages/essnmx/src/ess/nmx/workflows.py index c1835dc0..c85d2c8e 100644 --- a/packages/essnmx/src/ess/nmx/workflows.py +++ b/packages/essnmx/src/ess/nmx/workflows.py @@ -12,26 +12,30 @@ NeXusComponent, NeXusTransformation, Position, + RunType, SampleRun, ) -from ess.reduce.time_of_flight import ( - GenericTofWorkflow, +from ess.reduce.unwrap import ( + BeamlineComponentReading, + GenericUnwrapWorkflow, + LookupTableFilename, LookupTableRelativeErrorThreshold, + LookupTableWorkflow, LtotalRange, NumberOfSimulatedNeutrons, SimulationResults, SimulationSeed, - TofLookupTableWorkflow, + WavelengthDetector, ) -from ess.reduce.time_of_flight.lut import BeamlineComponentReading -from ess.reduce.time_of_flight.types import TimeOfFlightLookupTableFilename from ess.reduce.workflow import register_workflow +from scippneutron.conversion.tof import tof_from_wavelength from .configurations import WorkflowConfig from .types import ( NMXDetectorMetadata, NMXSampleMetadata, NMXSourceMetadata, + TofDetector, TofSimulationMaxWavelength, TofSimulationMinWavelength, ) @@ -51,7 +55,7 @@ def _simulate_fixed_wavelength_tof( ) -> SimulationResults: """ Simulate a pulse of neutrons propagating through the instrument using the - ``tof`` package (https://tof.readthedocs.io). + ``tof`` package (https://scipp.github.io/tof/). This runs a simulation assuming there are no choppers in the instrument. Parameters @@ -245,14 +249,24 @@ def assemble_detector_metadata( ) +def compute_detector_tof(da: WavelengthDetector[RunType]) -> TofDetector[RunType]: + """ + Compute the time-of-flight of neutrons from their wavelength. + """ + return da.transform_coords( + "tof", graph={"tof": tof_from_wavelength}, keep_intermediate=False + ) + + @register_workflow def NMXWorkflow() -> sciline.Pipeline: - generic_wf = GenericTofWorkflow(run_types=[SampleRun], monitor_types=[]) + generic_wf = GenericUnwrapWorkflow(run_types=[SampleRun], monitor_types=[]) generic_wf.insert(_retrieve_crystal_rotation) generic_wf.insert(assemble_sample_metadata) generic_wf.insert(assemble_source_metadata) generic_wf.insert(assemble_detector_metadata) + generic_wf.insert(compute_detector_tof) for key, value in default_parameters.items(): generic_wf[key] = value @@ -282,7 +296,7 @@ def _merge_workflows( def initialize_nmx_workflow(*, config: WorkflowConfig) -> sciline.Pipeline: """Initialize NMX workflow according to the workflow configuration. - If a TOF lookup table file path is provided in the configuration, + If a lookup table file path is provided in the configuration, it is used directly. Otherwise, a TOF simulation workflow is added to the NMX workflow to compute the lookup table on-the-fly. @@ -298,10 +312,10 @@ def initialize_nmx_workflow(*, config: WorkflowConfig) -> sciline.Pipeline: """ wf = NMXWorkflow() - if config.tof_lookup_table_file_path is not None: - wf[TimeOfFlightLookupTableFilename] = config.tof_lookup_table_file_path + if config.lookup_table_file_path is not None: + wf[LookupTableFilename] = config.lookup_table_file_path else: - wf = _merge_workflows(base_wf=wf, merged_wf=TofLookupTableWorkflow()) + wf = _merge_workflows(base_wf=wf, merged_wf=LookupTableWorkflow()) wf.insert(_simulate_fixed_wavelength_tof) wmax = sc.scalar(config.tof_simulation_max_wavelength, unit='angstrom') wmin = sc.scalar(config.tof_simulation_min_wavelength, unit='angstrom') diff --git a/packages/essnmx/tests/executable_test.py b/packages/essnmx/tests/executable_test.py index 5edf8b02..e5025656 100644 --- a/packages/essnmx/tests/executable_test.py +++ b/packages/essnmx/tests/executable_test.py @@ -69,7 +69,7 @@ def _check_non_default_config(testing_config: ReductionConfig) -> None: testing_model = testing_child.model_dump(mode='python') default_model = default_child.model_dump(mode='python') for key, testing_value in testing_model.items(): - if key == 'tof_lookup_table_file_path': + if key == 'lookup_table_file_path': # This value may be None or default, so we skip the check. continue default_value = default_model[key] @@ -317,43 +317,43 @@ def test_histogram_out_of_range_max_warns( @pytest.fixture -def tof_lut_file_path(tmp_path: pathlib.Path): +def lut_file_path(tmp_path: pathlib.Path): """Fixture to provide the path to the small NMX NeXus file.""" from dataclasses import is_dataclass - from ess.reduce.time_of_flight import TimeOfFlightLookupTable + from ess.reduce.unwrap import LookupTable from ess.nmx.workflows import initialize_nmx_workflow # Simply use the default workflow for testing. workflow = initialize_nmx_workflow(config=WorkflowConfig()) - tof_lut: TimeOfFlightLookupTable = workflow.compute(TimeOfFlightLookupTable) + lut: LookupTable = workflow.compute(LookupTable) # Change the tof range a bit for testing. - if isinstance(tof_lut, sc.DataArray): - tof_lut *= 2 - elif is_dataclass(tof_lut): - tof_lut.array *= 2 + if isinstance(lut, sc.DataArray): + lut *= 2 + elif is_dataclass(lut): + lut.array *= 2 else: raise TypeError("Unexpected type for TOF lookup table.") - lut_file_path = tmp_path / "nmx_tof_lookup_table.h5" - tof_lut.save_hdf5(lut_file_path.as_posix()) + lut_file_path = tmp_path / "nmx_lookup_table.h5" + lut.save_hdf5(lut_file_path.as_posix()) yield lut_file_path if lut_file_path.exists(): lut_file_path.unlink() -def test_reduction_with_tof_lut_file( - reduction_config: ReductionConfig, tof_lut_file_path: pathlib.Path +def test_reduction_with_lut_file( + reduction_config: ReductionConfig, lut_file_path: pathlib.Path ) -> None: - # Make sure the config uses no TOF lookup table file initially. - assert reduction_config.workflow.tof_lookup_table_file_path is None + # Make sure the config uses no lookup table file initially. + assert reduction_config.workflow.lookup_table_file_path is None with known_warnings(): default_results = reduction(config=reduction_config) - # Update config to use the TOF lookup table file. - reduction_config.workflow.tof_lookup_table_file_path = tof_lut_file_path.as_posix() + # Update config to use the lookup table file. + reduction_config.workflow.lookup_table_file_path = lut_file_path.as_posix() with known_warnings(): results = reduction(config=reduction_config) 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 82% rename from packages/essreduce/docs/user-guide/tof/dream.ipynb rename to packages/essreduce/docs/user-guide/unwrap/dream.ipynb index 2703772c..0a7bc66b 100644 --- a/packages/essreduce/docs/user-guide/tof/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.time_of_flight import *" + "from ess.reduce.unwrap import *" ] }, { @@ -201,7 +201,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", @@ -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,14 +299,14 @@ "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] = 'dream_detector'\n", "wf[LookupTableRelativeErrorThreshold] = {'dream_detector': float(\"inf\")}\n", "\n", - "wf.visualize(TofDetector[SampleRun])" + "wf.visualize(WavelengthDetector[SampleRun])" ] }, { @@ -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." ] }, { @@ -342,14 +341,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(25.0, unit=\"m\"),\n", " sc.scalar(80.0, unit=\"m\"),\n", ")\n", - "lut_wf.visualize(TofLookupTable)" + "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,20 +380,20 @@ "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\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", + " # # 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" ] }, { @@ -402,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:" ] @@ -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)" ] }, { @@ -443,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:" ] }, { @@ -456,65 +455,19 @@ "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" - ] - }, - { - "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", + "# Compute wavelength of neutron events\n", + "wavs = wf.compute(WavelengthDetector[SampleRun])\n", + "edges = sc.linspace(\"wavelength\", 0.8, 4.6, 201, unit=\"angstrom\")\n", "\n", - "# Define wavelength bin edges\n", - "wavs = 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()" ] }, { "cell_type": "markdown", - "id": "36", + "id": "32", "metadata": {}, "source": [ "### Comparing to the ground truth\n", @@ -526,7 +479,7 @@ { "cell_type": "code", "execution_count": null, - "id": "37", + "id": "33", "metadata": {}, "outputs": [], "source": [ @@ -536,19 +489,19 @@ "pp.plot(\n", " {\n", " \"wfm\": histogrammed,\n", - " \"ground_truth\": ground_truth.hist(wavelength=wavs),\n", + " \"ground_truth\": ground_truth.hist(wavelength=edges),\n", " }\n", ")" ] }, { "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", @@ -559,7 +512,7 @@ { "cell_type": "code", "execution_count": null, - "id": "39", + "id": "35", "metadata": {}, "outputs": [], "source": [ @@ -577,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", @@ -588,7 +541,7 @@ { "cell_type": "code", "execution_count": null, - "id": "41", + "id": "37", "metadata": {}, "outputs": [], "source": [ @@ -603,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,8 +575,7 @@ "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", "\n", "# Compare in plot\n", "ground_truth = []\n", @@ -634,10 +586,10 @@ "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", + " title=f\"Pixel {i + 1}\",\n", " )\n", " for i in range(len(Ltotal))\n", "]\n", @@ -647,7 +599,7 @@ }, { "cell_type": "markdown", - "id": "44", + "id": "40", "metadata": {}, "source": [ "## Handling time overlap between subframes\n", @@ -659,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:" ] @@ -667,7 +619,7 @@ { "cell_type": "code", "execution_count": null, - "id": "45", + "id": "41", "metadata": {}, "outputs": [], "source": [ @@ -698,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", @@ -710,7 +662,7 @@ { "cell_type": "code", "execution_count": null, - "id": "47", + "id": "43", "metadata": {}, "outputs": [], "source": [ @@ -725,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", @@ -743,17 +695,19 @@ { "cell_type": "code", "execution_count": null, - "id": "49", + "id": "45", "metadata": {}, "outputs": [], "source": [ - "table = lut_wf.compute(TofLookupTable)\n", - "table.plot(ymin=65) / (sc.stddevs(table.array) / sc.values(table.array)).plot(norm=\"linear\", ymin=55, vmax=0.05)" + "table = lut_wf.compute(LookupTable)\n", + "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", @@ -767,27 +721,27 @@ { "cell_type": "code", "execution_count": null, - "id": "51", + "id": "47", "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)" ] }, { "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." @@ -796,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", - "tofs = wf.compute(TofDetector[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", @@ -814,8 +766,8 @@ "\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", ")" ] @@ -837,7 +789,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/frame-unwrapping.ipynb b/packages/essreduce/docs/user-guide/unwrap/frame-unwrapping.ipynb similarity index 79% rename from packages/essreduce/docs/user-guide/tof/frame-unwrapping.ipynb rename to packages/essreduce/docs/user-guide/unwrap/frame-unwrapping.ipynb index dcb87020..2cad4d85 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,62 +278,30 @@ "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", + " wavelength=dw\n", ")\n", - "\n", "wavs[\"true\"] = ground_truth\n", + "\n", "pp.plot(wavs)" ] }, { "cell_type": "markdown", - "id": "17", + "id": "15", "metadata": {}, "source": [ "We see that all detectors agree on the wavelength spectrum,\n", @@ -353,7 +322,7 @@ { "cell_type": "code", "execution_count": null, - "id": "18", + "id": "16", "metadata": {}, "outputs": [], "source": [ @@ -388,12 +357,12 @@ }, { "cell_type": "markdown", - "id": "19", + "id": "17", "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." @@ -402,12 +371,12 @@ { "cell_type": "code", "execution_count": null, - "id": "20", + "id": "18", "metadata": {}, "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", @@ -431,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:" @@ -440,75 +409,50 @@ { "cell_type": "code", "execution_count": null, - "id": "22", + "id": "20", "metadata": {}, "outputs": [], "source": [ - "table = lut_wf.compute(TofLookupTable)\n", + "table = lut_wf.compute(LookupTable)\n", "\n", "table.plot(figsize=(9, 4))" ] }, { "cell_type": "markdown", - "id": "23", + "id": "21", "metadata": {}, "source": [ - "The time-of-flight profiles are then:" + "The wavelength profiles are then:" ] }, { "cell_type": "code", "execution_count": null, - "id": "24", + "id": "22", "metadata": {}, "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", + " wavelength=dw\n", ")\n", - "\n", "wavs[\"true\"] = ground_truth\n", + "\n", "pp.plot(wavs)" ] } @@ -529,7 +473,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 83% rename from packages/essreduce/docs/user-guide/tof/wfm.ipynb rename to packages/essreduce/docs/user-guide/unwrap/wfm.ipynb index 4447e59a..69b688aa 100644 --- a/packages/essreduce/docs/user-guide/tof/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, diff --git a/packages/essreduce/src/ess/reduce/__init__.py b/packages/essreduce/src/ess/reduce/__init__.py index 44e84029..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 nexus, normalization, time_of_flight, uncertainty +from . import nexus, normalization, uncertainty, unwrap try: __version__ = importlib.metadata.version("essreduce") @@ -12,4 +12,4 @@ del importlib -__all__ = ["nexus", "normalization", "time_of_flight", "uncertainty"] +__all__ = ["nexus", "normalization", "uncertainty", "unwrap"] diff --git a/packages/essreduce/src/ess/reduce/time_of_flight/__init__.py b/packages/essreduce/src/ess/reduce/unwrap/__init__.py similarity index 63% rename from packages/essreduce/src/ess/reduce/time_of_flight/__init__.py rename to packages/essreduce/src/ess/reduce/unwrap/__init__.py index dbb9fc49..c0d21027 100644 --- a/packages/essreduce/src/ess/reduce/time_of_flight/__init__.py +++ b/packages/essreduce/src/ess/reduce/unwrap/__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,33 @@ 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, WavelengthDetector, WavelengthMonitor, ) -from .workflow import GenericTofWorkflow +from .workflow import GenericUnwrapWorkflow __all__ = [ "BeamlineComponentReading", "DetectorLtotal", "DiskChoppers", "DistanceResolution", - "ErrorLimitedTofLookupTable", - "GenericTofWorkflow", + "ErrorLimitedLookupTable", + "GenericUnwrapWorkflow", + "LookupTable", + "LookupTableFilename", "LookupTableRelativeErrorThreshold", + "LookupTableWorkflow", "LtotalRange", "MonitorLtotal", "NumberOfSimulatedNeutrons", @@ -57,15 +55,7 @@ "SimulationResults", "SimulationSeed", "SourcePosition", - "TimeOfFlightLookupTable", - "TimeOfFlightLookupTableFilename", "TimeResolution", - "ToaDetector", - "TofDetector", - "TofLookupTable", - "TofLookupTableFilename", - "TofLookupTableWorkflow", - "TofMonitor", "WavelengthDetector", "WavelengthMonitor", "providers", diff --git a/packages/essreduce/src/ess/reduce/time_of_flight/fakes.py b/packages/essreduce/src/ess/reduce/unwrap/fakes.py similarity index 100% rename from packages/essreduce/src/ess/reduce/time_of_flight/fakes.py rename to packages/essreduce/src/ess/reduce/unwrap/fakes.py diff --git a/packages/essreduce/src/ess/reduce/time_of_flight/interpolator_numba.py b/packages/essreduce/src/ess/reduce/unwrap/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/unwrap/interpolator_numba.py diff --git a/packages/essreduce/src/ess/reduce/time_of_flight/interpolator_scipy.py b/packages/essreduce/src/ess/reduce/unwrap/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/unwrap/interpolator_scipy.py diff --git a/packages/essreduce/src/ess/reduce/time_of_flight/lut.py b/packages/essreduce/src/ess/reduce/unwrap/lut.py similarity index 94% rename from packages/essreduce/src/ess/reduce/time_of_flight/lut.py rename to packages/essreduce/src/ess/reduce/unwrap/lut.py index 92078feb..c76e031f 100644 --- a/packages/essreduce/src/ess/reduce/time_of_flight/lut.py +++ b/packages/essreduce/src/ess/reduce/unwrap/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,10 @@ def _compute_mean_tof( toas = simulation.time_of_arrival + (travel_length / simulation.speed).to( unit=time_unit, copy=False ) - 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 +188,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 @@ -321,14 +322,14 @@ def make_tof_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}." ) pieces.append( - _compute_mean_tof( + _compute_mean_wavelength( simulation=simulation_reading, distance=dist, time_bins=time_bins, @@ -355,7 +356,7 @@ def make_tof_lookup_table( }, ) - return TofLookupTable( + return LookupTable( array=table, pulse_period=pulse_period, pulse_stride=pulse_stride, @@ -442,13 +443,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/time_of_flight/resample.py b/packages/essreduce/src/ess/reduce/unwrap/resample.py similarity index 100% rename from packages/essreduce/src/ess/reduce/time_of_flight/resample.py rename to packages/essreduce/src/ess/reduce/unwrap/resample.py diff --git a/packages/essreduce/src/ess/reduce/time_of_flight/eto_to_tof.py b/packages/essreduce/src/ess/reduce/unwrap/to_wavelength.py similarity index 66% rename from packages/essreduce/src/ess/reduce/time_of_flight/eto_to_tof.py rename to packages/essreduce/src/ess/reduce/unwrap/to_wavelength.py index 7982680b..e3a40e77 100644 --- a/packages/essreduce/src/ess/reduce/time_of_flight/eto_to_tof.py +++ b/packages/essreduce/src/ess/reduce/unwrap/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 @@ -38,37 +37,71 @@ 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', + ): + """ + Interpolator object that converts event_time_offset and distances to + wavelengths. + + Note that interpolating wavelength directly instead of time-of-flight can lead + to higher interpolation errors. Considering + + .. math:: + + \\lambda = \\frac{t - t_0(t, L)}{L} + + while + + .. math:: + + t_{of} = t - t_0(t, L) + + and assuming :math:`t_0(t, L)` is an affine function, or slowly varying relative + to grid density, linear interpolation in :math:`t` and :math:`L` will represent + :math:`t_{tof}` exactly. + + But linear interpolation does not represent :math:`\\lambda` exactly even if + :math:`t_0` is slowly varying or affine, so the interpolation error is probably + larger when we interpolate :math:`\\lambda` rather than when we interpolate + :math:`t_{of}`. + + For this reason, we internally convert to tof and then back to wavelength + before returning the result. + """ self._distance_unit = distance_unit self._time_unit = time_unit + self._wavelength_unit = wavelength_unit - self._time_edges = ( + time_coord = ( lookup.coords["event_time_offset"] .to(unit=self._time_unit, copy=False) .values ) - self._distance_edges = ( - lookup.coords["distance"].to(unit=distance_unit, copy=False).values - ) + + distances = lookup.coords["distance"].to(unit=distance_unit, copy=False) self._interpolator = InterpolatorImpl( - time_edges=self._time_edges, - distance_edges=self._distance_edges, - values=lookup.data.to(unit=self._time_unit, copy=False).values, + time_edges=time_coord, + distance_edges=distances.values, + values=( + lookup.data.to(unit=self._wavelength_unit, copy=False) * distances + ).values, ) def __call__( @@ -92,24 +125,24 @@ def __call__( ltotal = ltotal.values event_time_offset = event_time_offset.values - return sc.array( - dims=out_dims, - values=self._interpolator( - times=event_time_offset, - distances=ltotal, - pulse_index=pulse_index.values if pulse_index is not None else None, - pulse_period=pulse_period.value, - ), - unit=self._time_unit, + out = self._interpolator( + times=event_time_offset, + distances=ltotal, + pulse_index=pulse_index.values if pulse_index is not None else None, + pulse_period=pulse_period.value, ) + out /= ltotal + + return sc.array(dims=out_dims, values=out, 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'. - 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) @@ -126,19 +159,19 @@ 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( - list({key} & {"time_of_flight", "frame_time"}) + return rebinned.assign_coords(wavelength=wavs).drop_coords( + list({key} & possible_names) ) @@ -148,11 +181,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 +194,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 +213,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 +231,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 +258,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 +270,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 +334,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 +356,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 +395,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 +436,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 +449,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 +461,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 +487,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 +531,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 +548,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 +556,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 +566,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 +583,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 +591,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 +601,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/time_of_flight/types.py b/packages/essreduce/src/ess/reduce/unwrap/types.py similarity index 61% rename from packages/essreduce/src/ess/reduce/time_of_flight/types.py rename to packages/essreduce/src/ess/reduce/unwrap/types.py index 1972fa1d..86dd086c 100644 --- a/packages/essreduce/src/ess/reduce/time_of_flight/types.py +++ b/packages/essreduce/src/ess/reduce/unwrap/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/time_of_flight/workflow.py b/packages/essreduce/src/ess/reduce/unwrap/workflow.py similarity index 84% rename from packages/essreduce/src/ess/reduce/time_of_flight/workflow.py rename to packages/essreduce/src/ess/reduce/unwrap/workflow.py index 1cbd9fba..b493162f 100644 --- a/packages/essreduce/src/ess/reduce/time_of_flight/workflow.py +++ b/packages/essreduce/src/ess/reduce/unwrap/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 GenericUnwrapWorkflow( *, 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 diff --git a/packages/essreduce/tests/time_of_flight/workflow_test.py b/packages/essreduce/tests/time_of_flight/workflow_test.py deleted file mode 100644 index a9848517..00000000 --- a/packages/essreduce/tests/time_of_flight/workflow_test.py +++ /dev/null @@ -1,269 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright (c) 2025 Scipp contributors (https://github.com/scipp) -import numpy as np -import pytest -import sciline -import scipp as sc -import scippnexus as snx -from scipp.testing import assert_identical - -from ess.reduce import time_of_flight -from ess.reduce.nexus.types import ( - AnyRun, - DiskChoppers, - EmptyDetector, - NeXusData, - NeXusDetectorName, - Position, - RawDetector, - SampleRun, -) -from ess.reduce.time_of_flight import ( - GenericTofWorkflow, - TofLookupTableWorkflow, - fakes, -) - -sl = pytest.importorskip("sciline") - - -@pytest.fixture -def workflow() -> GenericTofWorkflow: - sizes = {'detector_number': 10} - calibrated_beamline = sc.DataArray( - data=sc.ones(sizes=sizes), - coords={ - "position": sc.spatial.as_vectors( - sc.zeros(sizes=sizes, unit='m'), - sc.zeros(sizes=sizes, unit='m'), - sc.linspace("detector_number", 79, 81, 10, unit='m'), - ), - "detector_number": sc.array( - dims=["detector_number"], values=np.arange(10), unit=None - ), - }, - ) - - events = sc.DataArray( - data=sc.ones(dims=["event"], shape=[1000]), - coords={ - "event_time_offset": sc.linspace( - "event", 0.0, 1000.0 / 14, num=1000, unit="ms" - ).to(unit="ns"), - "event_id": sc.array( - dims=["event"], values=np.arange(1000) % 10, unit=None - ), - }, - ) - nexus_data = sc.DataArray( - sc.bins( - begin=sc.array(dims=["pulse"], values=[0], unit=None), - data=events, - dim="event", - ) - ) - - wf = GenericTofWorkflow(run_types=[SampleRun], monitor_types=[]) - wf[NeXusDetectorName] = "detector" - wf[time_of_flight.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') - wf[Position[snx.NXsource, SampleRun]] = sc.vector([0, 0, 0], unit='m') - - return wf - - -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] = ( - 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) - assert lut.array is not None - assert lut.distance_resolution is not None - assert lut.time_resolution is not None - assert lut.pulse_stride is not None - assert lut.pulse_period is not None - assert lut.choppers is not None - - -@pytest.mark.parametrize("coord", ["tof", "wavelength"]) -def test_GenericTofWorkflow_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) - with pytest.raises(sciline.UnsatisfiedRequirement): - _ = workflow.compute(time_of_flight.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] = ( - 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) - - workflow[time_of_flight.TofLookupTable] = table - - if coord == "tof": - detector = workflow.compute(time_of_flight.TofDetector[SampleRun]) - assert 'tof' in detector.bins.coords - else: - detector = workflow.compute(time_of_flight.WavelengthDetector[SampleRun]) - assert 'wavelength' in detector.bins.coords - - -@pytest.mark.parametrize("coord", ["tof", "wavelength"]) -def test_GenericTofWorkflow_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] = ( - 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.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) - 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) - - if coord == "tof": - detector = workflow.compute(time_of_flight.TofDetector[SampleRun]) - assert 'tof' in detector.bins.coords - else: - detector = workflow.compute(time_of_flight.WavelengthDetector[SampleRun]) - assert 'wavelength' in detector.bins.coords - - -def test_GenericTofWorkflow_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] = ( - 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) - old_lut = sc.DataArray( - data=lut.array.data, - coords={ - "distance": lut.array.coords["distance"], - "event_time_offset": lut.array.coords["event_time_offset"], - "pulse_period": lut.pulse_period, - "pulse_stride": sc.scalar(lut.pulse_stride, unit=None), - "distance_resolution": lut.distance_resolution, - "time_resolution": lut.time_resolution, - }, - ) - 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) - 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 loaded_lut.choppers is None # No chopper info in old format - - detector = workflow.compute(time_of_flight.TofDetector[SampleRun]) - assert 'tof' in detector.bins.coords - - -def test_GenericTofWorkflow_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] = ( - 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) - - workflow[time_of_flight.TimeOfFlightLookupTable] = table - # Should now be able to compute DetectorData with chopper and simulation params - detector = workflow.compute(time_of_flight.TofDetector[SampleRun]) - assert 'tof' in detector.bins.coords - - -def test_GenericTofWorkflow_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] = ( - 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.save_hdf5(filename=tmp_path / "lut.h5") - - workflow[time_of_flight.TimeOfFlightLookupTableFilename] = ( - tmp_path / "lut.h5" - ).as_posix() - loaded_lut = workflow.compute(time_of_flight.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(time_of_flight.TofDetector[SampleRun]) - assert 'tof' in detector.bins.coords - - -@pytest.mark.parametrize("coord", ["tof", "wavelength"]) -def test_GenericTofWorkflow_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] = ( - 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 - - if coord == "tof": - result = workflow.compute(time_of_flight.TofDetector[SampleRun]) - else: - result = workflow.compute(time_of_flight.WavelengthDetector[SampleRun]) - - assert "Ltotal" in result.coords diff --git a/packages/essreduce/tests/time_of_flight/interpolator_test.py b/packages/essreduce/tests/unwrap/interpolator_test.py similarity index 96% rename from packages/essreduce/tests/time_of_flight/interpolator_test.py rename to packages/essreduce/tests/unwrap/interpolator_test.py index 5e1c013d..b7f14426 100644 --- a/packages/essreduce/tests/time_of_flight/interpolator_test.py +++ b/packages/essreduce/tests/unwrap/interpolator_test.py @@ -3,10 +3,10 @@ import numpy as np -from ess.reduce.time_of_flight.interpolator_numba import ( +from ess.reduce.unwrap.interpolator_numba import ( Interpolator as InterpolatorNumba, ) -from ess.reduce.time_of_flight.interpolator_scipy import ( +from ess.reduce.unwrap.interpolator_scipy import ( Interpolator as InterpolatorScipy, ) diff --git a/packages/essreduce/tests/time_of_flight/lut_test.py b/packages/essreduce/tests/unwrap/lut_test.py similarity index 69% rename from packages/essreduce/tests/time_of_flight/lut_test.py rename to packages/essreduce/tests/unwrap/lut_test.py index ee118dcb..495e57dd 100644 --- a/packages/essreduce/tests/time_of_flight/lut_test.py +++ b/packages/essreduce/tests/unwrap/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 unwrap from ess.reduce.nexus.types import AnyRun -from ess.reduce.time_of_flight import TofLookupTableWorkflow +from ess.reduce.unwrap import LookupTableWorkflow 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[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[time_of_flight.LtotalRange] = lmin, lmax - wf[time_of_flight.DistanceResolution] = dres - wf[time_of_flight.TimeResolution] = tres + wf[unwrap.LtotalRange] = lmin, lmax + wf[unwrap.DistanceResolution] = dres + wf[unwrap.TimeResolution] = tres - table = wf.compute(time_of_flight.TofLookupTable) + table = wf.compute(unwrap.LookupTable) 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[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[time_of_flight.LtotalRange] = lmin, lmax - wf[time_of_flight.DistanceResolution] = dres - wf[time_of_flight.TimeResolution] = tres + wf[unwrap.LtotalRange] = lmin, lmax + wf[unwrap.DistanceResolution] = dres + wf[unwrap.TimeResolution] = tres - table = wf.compute(time_of_flight.TofLookupTable) + table = wf.compute(unwrap.LookupTable) 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[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[time_of_flight.LtotalRange] = lmin, lmax - wf[time_of_flight.DistanceResolution] = dres - wf[time_of_flight.TimeResolution] = tres + wf[unwrap.LtotalRange] = lmin, lmax + wf[unwrap.DistanceResolution] = dres + wf[unwrap.TimeResolution] = tres - table = wf.compute(time_of_flight.TofLookupTable) + table = wf.compute(unwrap.LookupTable) 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[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[unwrap.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[unwrap.DistanceResolution] = sc.scalar(0.1, unit='m') + wf[unwrap.TimeResolution] = sc.scalar(250.0, unit='us') - table = wf.compute(time_of_flight.TofLookupTable) + table = wf.compute(unwrap.LookupTable) # 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[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[unwrap.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[unwrap.DistanceResolution] = sc.scalar(0.1, unit='m') + wf[unwrap.TimeResolution] = sc.scalar(250.0, unit='us') - table = wf.compute(time_of_flight.TofLookupTable) + table = wf.compute(unwrap.LookupTable) # 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[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[time_of_flight.LtotalRange] = ( + wf[unwrap.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[unwrap.DistanceResolution] = sc.scalar(0.1, unit='m') + wf[unwrap.TimeResolution] = sc.scalar(250.0, unit='us') - with pytest.raises(ValueError, match="Building the Tof lookup table failed"): - _ = wf.compute(time_of_flight.TofLookupTable) + with pytest.raises(ValueError, match="Building the lookup table failed"): + _ = wf.compute(unwrap.LookupTable) diff --git a/packages/essreduce/tests/time_of_flight/resample_test.py b/packages/essreduce/tests/unwrap/resample_test.py similarity index 99% rename from packages/essreduce/tests/time_of_flight/resample_test.py rename to packages/essreduce/tests/unwrap/resample_test.py index 6d54c5f9..4fc1d951 100644 --- a/packages/essreduce/tests/time_of_flight/resample_test.py +++ b/packages/essreduce/tests/unwrap/resample_test.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.unwrap import resample class TestFindStrictlyIncreasingSections: diff --git a/packages/essreduce/tests/time_of_flight/unwrap_test.py b/packages/essreduce/tests/unwrap/unwrap_test.py similarity index 67% rename from packages/essreduce/tests/time_of_flight/unwrap_test.py rename to packages/essreduce/tests/unwrap/unwrap_test.py index 3d486ed9..708d3dc3 100644 --- a/packages/essreduce/tests/time_of_flight/unwrap_test.py +++ b/packages/essreduce/tests/unwrap/unwrap_test.py @@ -4,10 +4,8 @@ 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 time_of_flight +from ess.reduce import unwrap from ess.reduce.nexus.types import ( AnyRun, FrameMonitor0, @@ -17,26 +15,19 @@ RawMonitor, SampleRun, ) -from ess.reduce.time_of_flight import ( - GenericTofWorkflow, - PulsePeriod, - TofLookupTableWorkflow, - fakes, -) +from ess.reduce.unwrap import GenericUnwrapWorkflow, LookupTableWorkflow, fakes sl = pytest.importorskip("sciline") 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 = LookupTableWorkflow() + 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,26 +66,26 @@ def _make_workflow_event_mode( ) mon, ref = beamline.get_monitor("detector") - pl = GenericTofWorkflow(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 - pl[time_of_flight.DetectorLtotal[SampleRun]] = distance + pl[unwrap.DetectorLtotal[SampleRun]] = distance else: pl[NeXusName[FrameMonitor0]] = "monitor" pl[RawMonitor[SampleRun, FrameMonitor0]] = mon - pl[time_of_flight.MonitorLtotal[SampleRun, FrameMonitor0]] = distance + pl[unwrap.MonitorLtotal[SampleRun, FrameMonitor0]] = distance - pl[time_of_flight.LookupTableRelativeErrorThreshold] = { + pl[unwrap.LookupTableRelativeErrorThreshold] = { 'detector': error_threshold, 'monitor': error_threshold, } - pl[time_of_flight.PulseStrideOffset] = pulse_stride_offset + pl[unwrap.PulseStrideOffset] = pulse_stride_offset lut_wf = lut_workflow.copy() - lut_wf[time_of_flight.LtotalRange] = distance, distance + lut_wf[unwrap.LtotalRange] = distance, distance - pl[time_of_flight.TofLookupTable] = lut_wf.compute(time_of_flight.TofLookupTable) + pl[unwrap.LookupTable] = lut_wf.compute(unwrap.LookupTable) return pl, ref @@ -116,35 +107,34 @@ 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 = GenericUnwrapWorkflow(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[unwrap.DetectorLtotal[SampleRun]] = distance else: pl[NeXusName[FrameMonitor0]] = "monitor" pl[RawMonitor[SampleRun, FrameMonitor0]] = mon - pl[time_of_flight.MonitorLtotal[SampleRun, FrameMonitor0]] = distance + pl[unwrap.MonitorLtotal[SampleRun, FrameMonitor0]] = distance - pl[time_of_flight.LookupTableRelativeErrorThreshold] = { + pl[unwrap.LookupTableRelativeErrorThreshold] = { 'detector': error_threshold, 'monitor': error_threshold, } lut_wf = lut_workflow.copy() - lut_wf[time_of_flight.LtotalRange] = distance, distance + lut_wf[unwrap.LtotalRange] = distance, distance - pl[time_of_flight.TofLookupTable] = lut_wf.compute(time_of_flight.TofLookupTable) + pl[unwrap.LookupTable] = lut_wf.compute(unwrap.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 +149,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 "time_of_flight" 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 +163,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 +188,12 @@ def test_unwrap_with_no_choppers(detector_or_monitor) -> None: ) if detector_or_monitor == "detector": - tofs = pl.compute(time_of_flight.TofDetector[SampleRun]) + wavs = pl.compute(unwrap.WavelengthDetector[SampleRun]) else: - tofs = pl.compute(time_of_flight.TofMonitor[SampleRun, FrameMonitor0]) + wavs = pl.compute(unwrap.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 +216,12 @@ 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]) + wavs = pl.compute(unwrap.WavelengthDetector[SampleRun]) else: - tofs = pl.compute(time_of_flight.TofMonitor[SampleRun, FrameMonitor0]) + wavs = pl.compute(unwrap.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 ) @@ -255,12 +246,12 @@ def test_standard_unwrap_histogram_mode( ) if detector_or_monitor == "detector": - tofs = pl.compute(time_of_flight.TofDetector[SampleRun]) + wavs = pl.compute(unwrap.WavelengthDetector[SampleRun]) else: - tofs = pl.compute(time_of_flight.TofMonitor[SampleRun, FrameMonitor0]) + wavs = pl.compute(unwrap.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 +272,12 @@ def test_pulse_skipping_unwrap( ) if detector_or_monitor == "detector": - tofs = pl.compute(time_of_flight.TofDetector[SampleRun]) + wavs = pl.compute(unwrap.WavelengthDetector[SampleRun]) else: - tofs = pl.compute(time_of_flight.TofMonitor[SampleRun, FrameMonitor0]) + wavs = pl.compute(unwrap.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 +301,12 @@ 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]) + wavs = pl.compute(unwrap.WavelengthDetector[SampleRun]) else: - tofs = pl.compute(time_of_flight.TofMonitor[SampleRun, FrameMonitor0]) + wavs = pl.compute(unwrap.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 +326,12 @@ def test_pulse_skipping_stride_offset_guess_gives_expected_result( ) if detector_or_monitor == "detector": - tofs = pl.compute(time_of_flight.TofDetector[SampleRun]) + wavs = pl.compute(unwrap.WavelengthDetector[SampleRun]) else: - tofs = pl.compute(time_of_flight.TofMonitor[SampleRun, FrameMonitor0]) + wavs = pl.compute(unwrap.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 +366,12 @@ 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]) + wavs = pl.compute(unwrap.WavelengthDetector[SampleRun]) else: - tofs = pl.compute(time_of_flight.TofMonitor[SampleRun, FrameMonitor0]) + wavs = pl.compute(unwrap.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 ) @@ -403,18 +394,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[unwrap.LtotalRange] = distance, distance - pl = GenericTofWorkflow(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:] 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[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, } @@ -422,17 +413,17 @@ 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[unwrap.DetectorLtotal[SampleRun]] = distance + wavs = pl.compute(unwrap.WavelengthDetector[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[unwrap.MonitorLtotal[SampleRun, FrameMonitor0]] = distance + wavs = pl.compute(unwrap.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 +450,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 +482,12 @@ def test_pulse_skipping_stride_3(detector_or_monitor) -> None: ) if detector_or_monitor == "detector": - tofs = pl.compute(time_of_flight.TofDetector[SampleRun]) + wavs = pl.compute(unwrap.WavelengthDetector[SampleRun]) else: - tofs = pl.compute(time_of_flight.TofMonitor[SampleRun, FrameMonitor0]) + wavs = pl.compute(unwrap.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 ) @@ -515,12 +506,12 @@ def test_pulse_skipping_unwrap_histogram_mode( ) if detector_or_monitor == "detector": - tofs = pl.compute(time_of_flight.TofDetector[SampleRun]) + wavs = pl.compute(unwrap.WavelengthDetector[SampleRun]) else: - tofs = pl.compute(time_of_flight.TofMonitor[SampleRun, FrameMonitor0]) + wavs = pl.compute(unwrap.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,72 +539,10 @@ 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]) + wavs = pl.compute(unwrap.WavelengthDetector[SampleRun]) else: - tofs = pl.compute(time_of_flight.TofMonitor[SampleRun, FrameMonitor0]) + wavs = pl.compute(unwrap.WavelengthMonitor[SampleRun, FrameMonitor0]) _validate_result_events( - tofs=tofs, 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(time_of_flight.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(time_of_flight.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) + wavs=wavs, ref=ref, percentile=100, diff_threshold=0.02, rtol=0.05 ) - # 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/time_of_flight/wfm_test.py b/packages/essreduce/tests/unwrap/wfm_test.py similarity index 84% rename from packages/essreduce/tests/time_of_flight/wfm_test.py rename to packages/essreduce/tests/unwrap/wfm_test.py index 67432966..d150640f 100644 --- a/packages/essreduce/tests/time_of_flight/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 time_of_flight +from ess.reduce import unwrap from ess.reduce.nexus.types import AnyRun, NeXusDetectorName, RawDetector, SampleRun -from ess.reduce.time_of_flight import GenericTofWorkflow, TofLookupTableWorkflow, fakes +from ess.reduce.unwrap import GenericUnwrapWorkflow, LookupTableWorkflow, fakes sl = pytest.importorskip("sciline") @@ -111,15 +109,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 = LookupTableWorkflow() + 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,16 +125,16 @@ def setup_workflow( lut_workflow: sl.Pipeline, error_threshold: float = 0.1, ) -> sl.Pipeline: - pl = GenericTofWorkflow(run_types=[SampleRun], monitor_types=[]) + pl = GenericUnwrapWorkflow(run_types=[SampleRun], monitor_types=[]) pl[RawDetector[SampleRun]] = raw_data - pl[time_of_flight.DetectorLtotal[SampleRun]] = ltotal + pl[unwrap.DetectorLtotal[SampleRun]] = ltotal pl[NeXusDetectorName] = "detector" - pl[time_of_flight.LookupTableRelativeErrorThreshold] = {"detector": error_threshold} + pl[unwrap.LookupTableRelativeErrorThreshold] = {"detector": error_threshold} lut_wf = lut_workflow.copy() - lut_wf[time_of_flight.LtotalRange] = ltotal.min(), ltotal.max() + lut_wf[unwrap.LtotalRange] = ltotal.min(), ltotal.max() - pl[time_of_flight.TofLookupTable] = lut_wf.compute(time_of_flight.TofLookupTable) + pl[unwrap.LookupTable] = lut_wf.compute(unwrap.LookupTable) return pl @@ -193,11 +189,7 @@ def test_dream_wfm( raw_data=raw, ltotal=ltotal, lut_workflow=lut_workflow_dream_choppers ) - tofs = pl.compute(time_of_flight.TofDetector[SampleRun]) - - # Convert to wavelength - graph = {**beamline_graph(scatter=False), **elastic_graph("tof")} - wavs = tofs.transform_coords("wavelength", graph=graph) + wavs = pl.compute(unwrap.WavelengthDetector[SampleRun]) for da in wavs.flatten(to='pixel'): x = sc.sort(da.value, key='id') @@ -211,15 +203,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 = LookupTableWorkflow() + 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 @@ -280,11 +270,7 @@ def test_dream_wfm_with_subframe_time_overlap( error_threshold=0.01, ) - tofs = pl.compute(time_of_flight.TofDetector[SampleRun]) - - # Convert to wavelength - graph = {**beamline_graph(scatter=False), **elastic_graph("tof")} - wavs = tofs.transform_coords("wavelength", graph=graph) + wavs = pl.compute(unwrap.WavelengthDetector[SampleRun]) for da in wavs.flatten(to='pixel'): x = sc.sort(da.value, key='id') @@ -403,15 +389,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 = LookupTableWorkflow() + 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 @@ -463,11 +447,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]) - - # Convert to wavelength - graph = {**beamline_graph(scatter=False), **elastic_graph("tof")} - wavs = tofs.transform_coords("wavelength", graph=graph) + 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/unwrap/workflow_test.py b/packages/essreduce/tests/unwrap/workflow_test.py new file mode 100644 index 00000000..890166a2 --- /dev/null +++ b/packages/essreduce/tests/unwrap/workflow_test.py @@ -0,0 +1,183 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) 2025 Scipp contributors (https://github.com/scipp) +import numpy as np +import pytest +import sciline +import scipp as sc +import scippnexus as snx +from scipp.testing import assert_identical + +from ess.reduce import unwrap +from ess.reduce.nexus.types import ( + AnyRun, + DiskChoppers, + EmptyDetector, + NeXusData, + NeXusDetectorName, + Position, + RawDetector, + SampleRun, +) +from ess.reduce.unwrap import ( + GenericUnwrapWorkflow, + LookupTableWorkflow, + fakes, +) + +sl = pytest.importorskip("sciline") + + +@pytest.fixture +def workflow() -> GenericUnwrapWorkflow: + sizes = {'detector_number': 10} + calibrated_beamline = sc.DataArray( + data=sc.ones(sizes=sizes), + coords={ + "position": sc.spatial.as_vectors( + sc.zeros(sizes=sizes, unit='m'), + sc.zeros(sizes=sizes, unit='m'), + sc.linspace("detector_number", 79, 81, 10, unit='m'), + ), + "detector_number": sc.array( + dims=["detector_number"], values=np.arange(10), unit=None + ), + }, + ) + + events = sc.DataArray( + data=sc.ones(dims=["event"], shape=[1000]), + coords={ + "event_time_offset": sc.linspace( + "event", 0.0, 1000.0 / 14, num=1000, unit="ms" + ).to(unit="ns"), + "event_id": sc.array( + dims=["event"], values=np.arange(1000) % 10, unit=None + ), + }, + ) + nexus_data = sc.DataArray( + sc.bins( + begin=sc.array(dims=["pulse"], values=[0], unit=None), + data=events, + dim="event", + ) + ) + + wf = GenericUnwrapWorkflow(run_types=[SampleRun], monitor_types=[]) + wf[NeXusDetectorName] = "detector" + 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') + wf[Position[snx.NXsource, SampleRun]] = sc.vector([0, 0, 0], unit='m') + + return wf + + +def test_LookupTableWorkflow_can_compute_lut(): + wf = LookupTableWorkflow() + wf[DiskChoppers[AnyRun]] = fakes.psc_choppers() + wf[unwrap.NumberOfSimulatedNeutrons] = 10_000 + wf[unwrap.LtotalRange] = ( + sc.scalar(75.0, unit="m"), + sc.scalar(85.0, unit="m"), + ) + 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 + assert lut.pulse_stride is not None + assert lut.pulse_period is not None + assert lut.choppers is not None + + +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]) + # By default, the workflow tries to load the LUT from file + with pytest.raises(sciline.UnsatisfiedRequirement): + _ = workflow.compute(unwrap.LookupTable) + with pytest.raises(sciline.UnsatisfiedRequirement): + _ = workflow.compute(unwrap.WavelengthDetector[SampleRun]) + + lut_wf = LookupTableWorkflow() + lut_wf[DiskChoppers[AnyRun]] = fakes.psc_choppers() + lut_wf[unwrap.NumberOfSimulatedNeutrons] = 10_000 + lut_wf[unwrap.LtotalRange] = ( + sc.scalar(75.0, unit="m"), + sc.scalar(85.0, unit="m"), + ) + lut_wf[unwrap.SourcePosition] = fakes.source_position() + table = lut_wf.compute(unwrap.LookupTable) + + workflow[unwrap.LookupTable] = table + detector = workflow.compute(unwrap.WavelengthDetector[SampleRun]) + assert 'wavelength' in detector.bins.coords + + +def test_GenericUnwrapWorkflow_with_lut_from_file( + workflow, tmp_path: pytest.TempPathFactory +): + lut_wf = LookupTableWorkflow() + lut_wf[DiskChoppers[AnyRun]] = fakes.psc_choppers() + lut_wf[unwrap.NumberOfSimulatedNeutrons] = 10_000 + lut_wf[unwrap.LtotalRange] = ( + sc.scalar(75.0, unit="m"), + sc.scalar(85.0, unit="m"), + ) + lut_wf[unwrap.SourcePosition] = fakes.source_position() + lut = lut_wf.compute(unwrap.LookupTable) + lut.save_hdf5(filename=tmp_path / "lut.h5") + + 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 + 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(unwrap.WavelengthDetector[SampleRun]) + assert 'wavelength' in detector.bins.coords + + +def test_GenericUnwrapWorkflow_with_lut_from_file_old_format( + workflow, tmp_path: pytest.TempPathFactory +): + lut_wf = LookupTableWorkflow() + lut_wf[DiskChoppers[AnyRun]] = fakes.psc_choppers() + lut_wf[unwrap.NumberOfSimulatedNeutrons] = 10_000 + lut_wf[unwrap.LtotalRange] = ( + sc.scalar(75.0, unit="m"), + sc.scalar(85.0, unit="m"), + ) + lut_wf[unwrap.SourcePosition] = fakes.source_position() + lut = lut_wf.compute(unwrap.LookupTable) + old_lut = sc.DataArray( + data=lut.array.data, + coords={ + "distance": lut.array.coords["distance"], + "event_time_offset": lut.array.coords["event_time_offset"], + "pulse_period": lut.pulse_period, + "pulse_stride": sc.scalar(lut.pulse_stride, unit=None), + "distance_resolution": lut.distance_resolution, + "time_resolution": lut.time_resolution, + }, + ) + old_lut.save_hdf5(filename=tmp_path / "lut.h5") + + 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 + assert_identical(lut.distance_resolution, loaded_lut.distance_resolution) + assert_identical(lut.time_resolution, loaded_lut.time_resolution) + assert loaded_lut.choppers is None # No chopper info in old format + + detector = workflow.compute(unwrap.WavelengthDetector[SampleRun]) + assert 'wavelength' in detector.bins.coords diff --git a/pixi.lock b/pixi.lock index 1d0a1367..72c02f2e 100644 --- a/pixi.lock +++ b/pixi.lock @@ -182,7 +182,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/73/53/a2eec9df77ffdb199cd52b4346a8329f29c0af02364e1efcdc4a6db2e692/scipp-26.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/75/6b/5fb17ba057f4b74e9625459f664c3eb37632c183f2648c6158327dc1aa78/scippneutron-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/9f/47/72fd8b2e60a957c9dc1c9a6171a9b016a45120e7f258851f16b940c731a5/scitiff-26.1.0-py3-none-any.whl @@ -330,7 +330,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/55/ba24a6e3e60857eab1cb0024d3b6b11f937252fa71e2b96a8391072b63a1/scipp-26.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/75/6b/5fb17ba057f4b74e9625459f664c3eb37632c183f2648c6158327dc1aa78/scippneutron-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/df/75/b4ce781849931fef6fd529afa6b63711d5a733065722d0c3e2724af9e40a/scipy-1.17.1-cp311-cp311-macosx_10_14_x86_64.whl - pypi: https://files.pythonhosted.org/packages/9f/47/72fd8b2e60a957c9dc1c9a6171a9b016a45120e7f258851f16b940c731a5/scitiff-26.1.0-py3-none-any.whl @@ -478,7 +478,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/55/ba24a6e3e60857eab1cb0024d3b6b11f937252fa71e2b96a8391072b63a1/scipp-26.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/75/6b/5fb17ba057f4b74e9625459f664c3eb37632c183f2648c6158327dc1aa78/scippneutron-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/58/bccc2861b305abdd1b8663d6130c0b3d7cc22e8d86663edbc8401bfd40d4/scipy-1.17.1-cp311-cp311-macosx_12_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/9f/47/72fd8b2e60a957c9dc1c9a6171a9b016a45120e7f258851f16b940c731a5/scitiff-26.1.0-py3-none-any.whl @@ -630,7 +630,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/40/ec93fa158fb2f0a0ea88efe19a34267955b6cec809866e7f934618ff476f/scipp-26.3.0-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/75/6b/5fb17ba057f4b74e9625459f664c3eb37632c183f2648c6158327dc1aa78/scippneutron-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/9f/47/72fd8b2e60a957c9dc1c9a6171a9b016a45120e7f258851f16b940c731a5/scitiff-26.1.0-py3-none-any.whl @@ -863,7 +863,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/73/53/a2eec9df77ffdb199cd52b4346a8329f29c0af02364e1efcdc4a6db2e692/scipp-26.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/75/6b/5fb17ba057f4b74e9625459f664c3eb37632c183f2648c6158327dc1aa78/scippneutron-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/9f/47/72fd8b2e60a957c9dc1c9a6171a9b016a45120e7f258851f16b940c731a5/scitiff-26.1.0-py3-none-any.whl @@ -1056,7 +1056,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/55/ba24a6e3e60857eab1cb0024d3b6b11f937252fa71e2b96a8391072b63a1/scipp-26.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/75/6b/5fb17ba057f4b74e9625459f664c3eb37632c183f2648c6158327dc1aa78/scippneutron-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/df/75/b4ce781849931fef6fd529afa6b63711d5a733065722d0c3e2724af9e40a/scipy-1.17.1-cp311-cp311-macosx_10_14_x86_64.whl - pypi: https://files.pythonhosted.org/packages/9f/47/72fd8b2e60a957c9dc1c9a6171a9b016a45120e7f258851f16b940c731a5/scitiff-26.1.0-py3-none-any.whl @@ -1249,7 +1249,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/55/ba24a6e3e60857eab1cb0024d3b6b11f937252fa71e2b96a8391072b63a1/scipp-26.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/75/6b/5fb17ba057f4b74e9625459f664c3eb37632c183f2648c6158327dc1aa78/scippneutron-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/58/bccc2861b305abdd1b8663d6130c0b3d7cc22e8d86663edbc8401bfd40d4/scipy-1.17.1-cp311-cp311-macosx_12_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/9f/47/72fd8b2e60a957c9dc1c9a6171a9b016a45120e7f258851f16b940c731a5/scitiff-26.1.0-py3-none-any.whl @@ -1447,7 +1447,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/40/ec93fa158fb2f0a0ea88efe19a34267955b6cec809866e7f934618ff476f/scipp-26.3.0-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/75/6b/5fb17ba057f4b74e9625459f664c3eb37632c183f2648c6158327dc1aa78/scippneutron-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/9f/47/72fd8b2e60a957c9dc1c9a6171a9b016a45120e7f258851f16b940c731a5/scitiff-26.1.0-py3-none-any.whl @@ -1698,7 +1698,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/73/53/a2eec9df77ffdb199cd52b4346a8329f29c0af02364e1efcdc4a6db2e692/scipp-26.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/75/6b/5fb17ba057f4b74e9625459f664c3eb37632c183f2648c6158327dc1aa78/scippneutron-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl @@ -1893,7 +1893,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/55/ba24a6e3e60857eab1cb0024d3b6b11f937252fa71e2b96a8391072b63a1/scipp-26.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/75/6b/5fb17ba057f4b74e9625459f664c3eb37632c183f2648c6158327dc1aa78/scippneutron-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/df/75/b4ce781849931fef6fd529afa6b63711d5a733065722d0c3e2724af9e40a/scipy-1.17.1-cp311-cp311-macosx_10_14_x86_64.whl - pypi: https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl @@ -2088,7 +2088,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/55/ba24a6e3e60857eab1cb0024d3b6b11f937252fa71e2b96a8391072b63a1/scipp-26.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/75/6b/5fb17ba057f4b74e9625459f664c3eb37632c183f2648c6158327dc1aa78/scippneutron-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/58/bccc2861b305abdd1b8663d6130c0b3d7cc22e8d86663edbc8401bfd40d4/scipy-1.17.1-cp311-cp311-macosx_12_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl @@ -2286,7 +2286,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/40/ec93fa158fb2f0a0ea88efe19a34267955b6cec809866e7f934618ff476f/scipp-26.3.0-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/75/6b/5fb17ba057f4b74e9625459f664c3eb37632c183f2648c6158327dc1aa78/scippneutron-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl @@ -2521,7 +2521,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/73/53/a2eec9df77ffdb199cd52b4346a8329f29c0af02364e1efcdc4a6db2e692/scipp-26.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/75/6b/5fb17ba057f4b74e9625459f664c3eb37632c183f2648c6158327dc1aa78/scippneutron-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl @@ -2697,7 +2697,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/55/ba24a6e3e60857eab1cb0024d3b6b11f937252fa71e2b96a8391072b63a1/scipp-26.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/75/6b/5fb17ba057f4b74e9625459f664c3eb37632c183f2648c6158327dc1aa78/scippneutron-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/df/75/b4ce781849931fef6fd529afa6b63711d5a733065722d0c3e2724af9e40a/scipy-1.17.1-cp311-cp311-macosx_10_14_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl @@ -2873,7 +2873,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/55/ba24a6e3e60857eab1cb0024d3b6b11f937252fa71e2b96a8391072b63a1/scipp-26.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/75/6b/5fb17ba057f4b74e9625459f664c3eb37632c183f2648c6158327dc1aa78/scippneutron-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/58/bccc2861b305abdd1b8663d6130c0b3d7cc22e8d86663edbc8401bfd40d4/scipy-1.17.1-cp311-cp311-macosx_12_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl @@ -3054,7 +3054,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/40/ec93fa158fb2f0a0ea88efe19a34267955b6cec809866e7f934618ff476f/scipp-26.3.0-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/75/6b/5fb17ba057f4b74e9625459f664c3eb37632c183f2648c6158327dc1aa78/scippneutron-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl @@ -3258,7 +3258,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/73/53/a2eec9df77ffdb199cd52b4346a8329f29c0af02364e1efcdc4a6db2e692/scipp-26.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/75/6b/5fb17ba057f4b74e9625459f664c3eb37632c183f2648c6158327dc1aa78/scippneutron-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/9f/47/72fd8b2e60a957c9dc1c9a6171a9b016a45120e7f258851f16b940c731a5/scitiff-26.1.0-py3-none-any.whl @@ -3398,7 +3398,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/55/ba24a6e3e60857eab1cb0024d3b6b11f937252fa71e2b96a8391072b63a1/scipp-26.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/75/6b/5fb17ba057f4b74e9625459f664c3eb37632c183f2648c6158327dc1aa78/scippneutron-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/df/75/b4ce781849931fef6fd529afa6b63711d5a733065722d0c3e2724af9e40a/scipy-1.17.1-cp311-cp311-macosx_10_14_x86_64.whl - pypi: https://files.pythonhosted.org/packages/9f/47/72fd8b2e60a957c9dc1c9a6171a9b016a45120e7f258851f16b940c731a5/scitiff-26.1.0-py3-none-any.whl @@ -3538,7 +3538,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/55/ba24a6e3e60857eab1cb0024d3b6b11f937252fa71e2b96a8391072b63a1/scipp-26.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/75/6b/5fb17ba057f4b74e9625459f664c3eb37632c183f2648c6158327dc1aa78/scippneutron-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/58/bccc2861b305abdd1b8663d6130c0b3d7cc22e8d86663edbc8401bfd40d4/scipy-1.17.1-cp311-cp311-macosx_12_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/9f/47/72fd8b2e60a957c9dc1c9a6171a9b016a45120e7f258851f16b940c731a5/scitiff-26.1.0-py3-none-any.whl @@ -3684,7 +3684,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/40/ec93fa158fb2f0a0ea88efe19a34267955b6cec809866e7f934618ff476f/scipp-26.3.0-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/75/6b/5fb17ba057f4b74e9625459f664c3eb37632c183f2648c6158327dc1aa78/scippneutron-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/9f/47/72fd8b2e60a957c9dc1c9a6171a9b016a45120e7f258851f16b940c731a5/scitiff-26.1.0-py3-none-any.whl @@ -3877,7 +3877,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/73/53/a2eec9df77ffdb199cd52b4346a8329f29c0af02364e1efcdc4a6db2e692/scipp-26.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/75/6b/5fb17ba057f4b74e9625459f664c3eb37632c183f2648c6158327dc1aa78/scippneutron-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl @@ -4013,7 +4013,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/55/ba24a6e3e60857eab1cb0024d3b6b11f937252fa71e2b96a8391072b63a1/scipp-26.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/75/6b/5fb17ba057f4b74e9625459f664c3eb37632c183f2648c6158327dc1aa78/scippneutron-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/df/75/b4ce781849931fef6fd529afa6b63711d5a733065722d0c3e2724af9e40a/scipy-1.17.1-cp311-cp311-macosx_10_14_x86_64.whl - pypi: https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl @@ -4149,7 +4149,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/55/ba24a6e3e60857eab1cb0024d3b6b11f937252fa71e2b96a8391072b63a1/scipp-26.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/75/6b/5fb17ba057f4b74e9625459f664c3eb37632c183f2648c6158327dc1aa78/scippneutron-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/58/bccc2861b305abdd1b8663d6130c0b3d7cc22e8d86663edbc8401bfd40d4/scipy-1.17.1-cp311-cp311-macosx_12_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl @@ -4289,7 +4289,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/40/ec93fa158fb2f0a0ea88efe19a34267955b6cec809866e7f934618ff476f/scipp-26.3.0-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/75/6b/5fb17ba057f4b74e9625459f664c3eb37632c183f2648c6158327dc1aa78/scippneutron-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl @@ -4466,7 +4466,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/73/53/a2eec9df77ffdb199cd52b4346a8329f29c0af02364e1efcdc4a6db2e692/scipp-26.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/75/6b/5fb17ba057f4b74e9625459f664c3eb37632c183f2648c6158327dc1aa78/scippneutron-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl @@ -4584,7 +4584,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/55/ba24a6e3e60857eab1cb0024d3b6b11f937252fa71e2b96a8391072b63a1/scipp-26.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/75/6b/5fb17ba057f4b74e9625459f664c3eb37632c183f2648c6158327dc1aa78/scippneutron-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/df/75/b4ce781849931fef6fd529afa6b63711d5a733065722d0c3e2724af9e40a/scipy-1.17.1-cp311-cp311-macosx_10_14_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl @@ -4702,7 +4702,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/55/ba24a6e3e60857eab1cb0024d3b6b11f937252fa71e2b96a8391072b63a1/scipp-26.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/75/6b/5fb17ba057f4b74e9625459f664c3eb37632c183f2648c6158327dc1aa78/scippneutron-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/58/bccc2861b305abdd1b8663d6130c0b3d7cc22e8d86663edbc8401bfd40d4/scipy-1.17.1-cp311-cp311-macosx_12_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl @@ -4826,7 +4826,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/40/ec93fa158fb2f0a0ea88efe19a34267955b6cec809866e7f934618ff476f/scipp-26.3.0-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/75/6b/5fb17ba057f4b74e9625459f664c3eb37632c183f2648c6158327dc1aa78/scippneutron-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl @@ -4939,7 +4939,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/73/53/a2eec9df77ffdb199cd52b4346a8329f29c0af02364e1efcdc4a6db2e692/scipp-26.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/75/6b/5fb17ba057f4b74e9625459f664c3eb37632c183f2648c6158327dc1aa78/scippneutron-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/9f/47/72fd8b2e60a957c9dc1c9a6171a9b016a45120e7f258851f16b940c731a5/scitiff-26.1.0-py3-none-any.whl @@ -5039,7 +5039,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/55/ba24a6e3e60857eab1cb0024d3b6b11f937252fa71e2b96a8391072b63a1/scipp-26.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/75/6b/5fb17ba057f4b74e9625459f664c3eb37632c183f2648c6158327dc1aa78/scippneutron-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/df/75/b4ce781849931fef6fd529afa6b63711d5a733065722d0c3e2724af9e40a/scipy-1.17.1-cp311-cp311-macosx_10_14_x86_64.whl - pypi: https://files.pythonhosted.org/packages/9f/47/72fd8b2e60a957c9dc1c9a6171a9b016a45120e7f258851f16b940c731a5/scitiff-26.1.0-py3-none-any.whl @@ -5140,7 +5140,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/55/ba24a6e3e60857eab1cb0024d3b6b11f937252fa71e2b96a8391072b63a1/scipp-26.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/75/6b/5fb17ba057f4b74e9625459f664c3eb37632c183f2648c6158327dc1aa78/scippneutron-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/58/bccc2861b305abdd1b8663d6130c0b3d7cc22e8d86663edbc8401bfd40d4/scipy-1.17.1-cp311-cp311-macosx_12_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/9f/47/72fd8b2e60a957c9dc1c9a6171a9b016a45120e7f258851f16b940c731a5/scitiff-26.1.0-py3-none-any.whl @@ -5241,7 +5241,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/40/ec93fa158fb2f0a0ea88efe19a34267955b6cec809866e7f934618ff476f/scipp-26.3.0-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/75/6b/5fb17ba057f4b74e9625459f664c3eb37632c183f2648c6158327dc1aa78/scippneutron-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/9f/47/72fd8b2e60a957c9dc1c9a6171a9b016a45120e7f258851f16b940c731a5/scitiff-26.1.0-py3-none-any.whl @@ -5358,7 +5358,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/73/53/a2eec9df77ffdb199cd52b4346a8329f29c0af02364e1efcdc4a6db2e692/scipp-26.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/75/6b/5fb17ba057f4b74e9625459f664c3eb37632c183f2648c6158327dc1aa78/scippneutron-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl @@ -5454,7 +5454,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/55/ba24a6e3e60857eab1cb0024d3b6b11f937252fa71e2b96a8391072b63a1/scipp-26.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/75/6b/5fb17ba057f4b74e9625459f664c3eb37632c183f2648c6158327dc1aa78/scippneutron-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/df/75/b4ce781849931fef6fd529afa6b63711d5a733065722d0c3e2724af9e40a/scipy-1.17.1-cp311-cp311-macosx_10_14_x86_64.whl - pypi: https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl @@ -5551,7 +5551,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/55/ba24a6e3e60857eab1cb0024d3b6b11f937252fa71e2b96a8391072b63a1/scipp-26.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/75/6b/5fb17ba057f4b74e9625459f664c3eb37632c183f2648c6158327dc1aa78/scippneutron-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/58/bccc2861b305abdd1b8663d6130c0b3d7cc22e8d86663edbc8401bfd40d4/scipy-1.17.1-cp311-cp311-macosx_12_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl @@ -5646,7 +5646,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/40/ec93fa158fb2f0a0ea88efe19a34267955b6cec809866e7f934618ff476f/scipp-26.3.0-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/75/6b/5fb17ba057f4b74e9625459f664c3eb37632c183f2648c6158327dc1aa78/scippneutron-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl @@ -5747,7 +5747,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/73/53/a2eec9df77ffdb199cd52b4346a8329f29c0af02364e1efcdc4a6db2e692/scipp-26.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/75/6b/5fb17ba057f4b74e9625459f664c3eb37632c183f2648c6158327dc1aa78/scippneutron-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl @@ -5825,7 +5825,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/55/ba24a6e3e60857eab1cb0024d3b6b11f937252fa71e2b96a8391072b63a1/scipp-26.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/75/6b/5fb17ba057f4b74e9625459f664c3eb37632c183f2648c6158327dc1aa78/scippneutron-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/df/75/b4ce781849931fef6fd529afa6b63711d5a733065722d0c3e2724af9e40a/scipy-1.17.1-cp311-cp311-macosx_10_14_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl @@ -5904,7 +5904,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/55/ba24a6e3e60857eab1cb0024d3b6b11f937252fa71e2b96a8391072b63a1/scipp-26.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/75/6b/5fb17ba057f4b74e9625459f664c3eb37632c183f2648c6158327dc1aa78/scippneutron-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/58/bccc2861b305abdd1b8663d6130c0b3d7cc22e8d86663edbc8401bfd40d4/scipy-1.17.1-cp311-cp311-macosx_12_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl @@ -5983,7 +5983,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/40/ec93fa158fb2f0a0ea88efe19a34267955b6cec809866e7f934618ff476f/scipp-26.3.0-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/75/6b/5fb17ba057f4b74e9625459f664c3eb37632c183f2648c6158327dc1aa78/scippneutron-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl @@ -6850,7 +6850,7 @@ packages: timestamp: 1758743805063 - pypi: ./packages/essimaging name: essimaging - version: 26.1.1.dev1756+ga2bca475.d20260317 + version: 26.1.1.dev1781+gee0f6314 sha256: 355348e060bcdb6ce30b35b84dac63c1701f6a82af28492ac4b26afb5e59b5a7 requires_dist: - dask>=2022.1.0 @@ -6883,8 +6883,8 @@ packages: requires_python: '>=3.11' - pypi: ./packages/essnmx name: essnmx - version: 26.3.1.dev11+ga2bca475.d20260317 - sha256: 8fd84277936e9ac1e25c1dff71ce73e5dd43dd925ecf56912ef6597925167b0e + version: 26.3.1.dev36+gee0f6314.d20260313 + sha256: 72f9c838cf718dea47dc86355e437514d9a65672a5591d2f7ad9c49c4f4a73f8 requires_dist: - dask>=2022.1.0 - essreduce>=26.2.1 @@ -6893,7 +6893,7 @@ packages: - sciline>=24.6.0 - scipp>=25.3.0 - scippnexus>=23.12.0 - - scippneutron>=26.2.0 + - scippneutron>=26.3.0 - pooch>=1.5 - pandas>=2.1.2 - gemmi>=0.6.6 @@ -6918,7 +6918,7 @@ packages: requires_python: '>=3.11' - pypi: ./packages/essreduce name: essreduce - version: 26.3.2.dev541+g94e29e6e.d20260317 + version: 26.3.2.dev569+gee0f6314 sha256: da2355e81efef8083540fefb2101259cb0fe5809aae38b0884487acac71e1df8 requires_dist: - sciline>=25.11.0 @@ -7844,7 +7844,6 @@ packages: - libstdcxx >=14 - libzlib >=1.3.1,<2.0a0 license: MIT - license_family: MIT purls: [] size: 3069804 timestamp: 1773391781117 @@ -7863,7 +7862,6 @@ packages: - libglib >=2.86.4,<3.0a0 - libzlib >=1.3.1,<2.0a0 license: MIT - license_family: MIT purls: [] size: 1717259 timestamp: 1773392322842 @@ -7882,7 +7880,6 @@ packages: - libglib >=2.86.4,<3.0a0 - libzlib >=1.3.1,<2.0a0 license: MIT - license_family: MIT purls: [] size: 1635918 timestamp: 1773392848411 @@ -7902,7 +7899,6 @@ packages: - vc >=14.2,<15 - vc14_runtime >=14.29.30139 license: MIT - license_family: MIT purls: [] size: 1286958 timestamp: 1773392007609 @@ -11812,10 +11808,10 @@ packages: - nodejs ; extra == 'all' - pythreejs ; extra == 'all' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/75/6b/5fb17ba057f4b74e9625459f664c3eb37632c183f2648c6158327dc1aa78/scippneutron-26.2.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/fb/46/e50b38629e9e3f4a1dd55fb36d8b8abd1d59768c31151c1c8ed696f7b865/scippneutron-26.3.0-py3-none-any.whl name: scippneutron - version: 26.2.0 - sha256: 1d124a52dc8d5148d1a56b2551fb2aa333c5ec725456d05e35e4417fff913e7c + version: 26.3.0 + sha256: 6bc9e36f68059bb792460cc897e6247236289f170134a953ed9fee8578872dd7 requires_dist: - python-dateutil>=2.8 - email-validator>=2