diff --git a/docs/user-guide/dream/dream-advanced-powder-reduction.ipynb b/docs/user-guide/dream/dream-advanced-powder-reduction.ipynb index bb3ca987..cb98646e 100644 --- a/docs/user-guide/dream/dream-advanced-powder-reduction.ipynb +++ b/docs/user-guide/dream/dream-advanced-powder-reduction.ipynb @@ -65,6 +65,7 @@ "workflow[CaveMonitorPosition] = sc.vector([0.0, 0.0, -4220.0], unit=\"mm\")\n", "\n", "workflow[dream.InstrumentConfiguration] = dream.InstrumentConfiguration.high_flux_BC215\n", + "workflow[LookupTableFilename] = \"DREAM-high-flux-wavelength-lut-5m-80m-bc215.h5\"\n", "# Select a detector bank:\n", "workflow[NeXusDetectorName] = \"mantle\"\n", "# We drop uncertainties where they would otherwise lead to correlations:\n", @@ -186,6 +187,7 @@ "workflow[CaveMonitorPosition] = sc.vector([0.0, 0.0, -4220.0], unit=\"mm\")\n", "\n", "workflow[dream.InstrumentConfiguration] = dream.InstrumentConfiguration.high_flux_BC215\n", + "workflow[LookupTableFilename] = \"DREAM-high-flux-wavelength-lut-5m-80m-bc215.h5\"\n", "# Select a detector bank:\n", "workflow[NeXusDetectorName] = \"mantle\"\n", "# We drop uncertainties where they would otherwise lead to correlations:\n", @@ -261,6 +263,7 @@ "workflow[CalibrationFilename] = None\n", "\n", "workflow[dream.InstrumentConfiguration] = dream.InstrumentConfiguration.high_flux_BC215\n", + "workflow[LookupTableFilename] = \"DREAM-high-flux-wavelength-lut-5m-80m-bc215.h5\"\n", "# Select a detector bank:\n", "workflow[NeXusDetectorName] = \"mantle\"\n", "# We drop uncertainties where they would otherwise lead to correlations:\n", @@ -375,6 +378,7 @@ "workflow[CalibrationFilename] = None\n", "\n", "workflow[dream.InstrumentConfiguration] = dream.InstrumentConfiguration.high_flux_BC215\n", + "workflow[LookupTableFilename] = \"DREAM-high-flux-wavelength-lut-5m-80m-bc215.h5\"\n", "# We drop uncertainties where they would otherwise lead to correlations:\n", "workflow[UncertaintyBroadcastMode] = UncertaintyBroadcastMode.drop\n", "# Edges for binning in d-spacing:\n", @@ -610,6 +614,14 @@ " vmin=1e-3,\n", ")" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dd340637-933f-4c73-b996-285b7394bd03", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { @@ -628,7 +640,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.14" + "version": "3.12.12" } }, "nbformat": 4, diff --git a/docs/user-guide/dream/dream-make-tof-lookup-table.ipynb b/docs/user-guide/dream/dream-make-tof-lookup-table.ipynb index 3a1515d8..8edefb6b 100644 --- a/docs/user-guide/dream/dream-make-tof-lookup-table.ipynb +++ b/docs/user-guide/dream/dream-make-tof-lookup-table.ipynb @@ -5,9 +5,9 @@ "id": "0", "metadata": {}, "source": [ - "# Create a time-of-flight lookup table for DREAM\n", + "# Create a wavelength lookup table for DREAM\n", "\n", - "This notebook shows how to create a time-of-flight lookup table for the DREAM instrument." + "This notebook shows how to create a wavelength lookup table for the DREAM instrument." ] }, { @@ -18,7 +18,7 @@ "outputs": [], "source": [ "import scipp as sc\n", - "from ess.reduce import time_of_flight\n", + "from ess.reduce import kinematics as kin\n", "from ess.reduce.nexus.types import AnyRun\n", "from ess.dream.beamline import InstrumentConfiguration, choppers" ] @@ -60,17 +60,17 @@ "metadata": {}, "outputs": [], "source": [ - "wf = time_of_flight.TofLookupTableWorkflow()\n", + "wf = kin.LookupTableWorkflow()\n", "\n", - "wf[time_of_flight.LtotalRange] = sc.scalar(5.0, unit=\"m\"), sc.scalar(80.0, unit=\"m\")\n", - "wf[time_of_flight.NumberOfSimulatedNeutrons] = 200_000 # Increase this number for more reliable results\n", - "wf[time_of_flight.SourcePosition] = sc.vector([0, 0, 0], unit='m')\n", - "wf[time_of_flight.DiskChoppers[AnyRun]] = disk_choppers\n", - "wf[time_of_flight.DistanceResolution] = sc.scalar(0.1, unit=\"m\")\n", - "wf[time_of_flight.TimeResolution] = sc.scalar(250.0, unit='us')\n", - "wf[time_of_flight.PulsePeriod] = 1.0 / sc.scalar(14.0, unit=\"Hz\")\n", - "wf[time_of_flight.PulseStride] = 1\n", - "wf[time_of_flight.PulseStrideOffset] = None" + "wf[kin.LtotalRange] = sc.scalar(5.0, unit=\"m\"), sc.scalar(80.0, unit=\"m\")\n", + "wf[kin.NumberOfSimulatedNeutrons] = 5_000_000 # Increase this number for more reliable results\n", + "wf[kin.SourcePosition] = sc.vector([0, 0, 0], unit='m')\n", + "wf[kin.DiskChoppers[AnyRun]] = disk_choppers\n", + "wf[kin.DistanceResolution] = sc.scalar(0.1, unit=\"m\")\n", + "wf[kin.TimeResolution] = sc.scalar(250.0, unit='us')\n", + "wf[kin.PulsePeriod] = 1.0 / sc.scalar(14.0, unit=\"Hz\")\n", + "wf[kin.PulseStride] = 1\n", + "wf[kin.PulseStrideOffset] = None" ] }, { @@ -88,7 +88,7 @@ "metadata": {}, "outputs": [], "source": [ - "table = wf.compute(time_of_flight.TimeOfFlightLookupTable)\n", + "table = wf.compute(kin.LookupTable)\n", "table.array" ] }, @@ -117,8 +117,16 @@ "metadata": {}, "outputs": [], "source": [ - "table.save_hdf5('DREAM-high-flux-tof-lut-5m-80m.h5')" + "table.save_hdf5('DREAM-high-flux-wavelength-lut-5m-80m-bc215.h5')" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a43b15e7-a225-4e73-b037-78f01dbefcf7", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { diff --git a/docs/user-guide/dream/dream-powder-reduction.ipynb b/docs/user-guide/dream/dream-powder-reduction.ipynb index b067bca1..18c65f51 100644 --- a/docs/user-guide/dream/dream-powder-reduction.ipynb +++ b/docs/user-guide/dream/dream-powder-reduction.ipynb @@ -56,7 +56,7 @@ "metadata": {}, "outputs": [], "source": [ - "workflow = dream.DreamGeant4Workflow(\n", + "wf = dream.DreamGeant4Workflow(\n", " run_norm=powder.RunNormalization.monitor_histogram,\n", ")" ] @@ -77,26 +77,27 @@ "metadata": {}, "outputs": [], "source": [ - "workflow[Filename[SampleRun]] = dream.data.simulated_diamond_sample()\n", - "workflow[Filename[VanadiumRun]] = dream.data.simulated_vanadium_sample()\n", - "workflow[Filename[EmptyCanRun]] = dream.data.simulated_empty_can()\n", - "workflow[CalibrationFilename] = None\n", + "wf[Filename[SampleRun]] = dream.data.simulated_diamond_sample()\n", + "wf[Filename[VanadiumRun]] = dream.data.simulated_vanadium_sample()\n", + "wf[Filename[EmptyCanRun]] = dream.data.simulated_empty_can()\n", + "wf[CalibrationFilename] = None\n", "\n", - "workflow[MonitorFilename[SampleRun]] = dream.data.simulated_monitor_diamond_sample()\n", - "workflow[MonitorFilename[VanadiumRun]] = dream.data.simulated_monitor_vanadium_sample()\n", - "workflow[MonitorFilename[EmptyCanRun]] = dream.data.simulated_monitor_empty_can()\n", - "workflow[CaveMonitorPosition] = sc.vector([0.0, 0.0, -4220.0], unit=\"mm\")\n", + "wf[MonitorFilename[SampleRun]] = dream.data.simulated_monitor_diamond_sample()\n", + "wf[MonitorFilename[VanadiumRun]] = dream.data.simulated_monitor_vanadium_sample()\n", + "wf[MonitorFilename[EmptyCanRun]] = dream.data.simulated_monitor_empty_can()\n", + "wf[CaveMonitorPosition] = sc.vector([0.0, 0.0, -4220.0], unit=\"mm\")\n", "\n", - "workflow[dream.InstrumentConfiguration] = dream.InstrumentConfiguration.high_flux_BC215\n", + "wf[dream.InstrumentConfiguration] = dream.InstrumentConfiguration.high_flux_BC215\n", + "wf[LookupTableFilename] = \"DREAM-high-flux-wavelength-lut-5m-80m-bc215.h5\"\n", "# Select a detector bank:\n", - "workflow[NeXusDetectorName] = \"mantle\"\n", + "wf[NeXusDetectorName] = \"mantle\"\n", "# We drop uncertainties where they would otherwise lead to correlations:\n", - "workflow[UncertaintyBroadcastMode] = UncertaintyBroadcastMode.drop\n", + "wf[UncertaintyBroadcastMode] = UncertaintyBroadcastMode.drop\n", "# Edges for binning in d-spacing:\n", - "workflow[DspacingBins] = sc.linspace(\"dspacing\", 0.3, 2.3434, 201, unit=\"angstrom\")\n", + "wf[DspacingBins] = sc.linspace(\"dspacing\", 0.3, 2.3434, 201, unit=\"angstrom\")\n", "\n", "# Do not mask any pixels / voxels:\n", - "workflow = powder.with_pixel_mask_filenames(workflow, [])" + "wf = powder.with_pixel_mask_filenames(wf, [])" ] }, { @@ -114,6 +115,26 @@ "If we didn't want to subtract an empty can measurement from the sample measurement, we would instead request `IofDspacing[SampleRun]` and `ReducedTofCIF`." ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "5c212c16-08dc-4582-891c-7938b2cc75bd", + "metadata": {}, + "outputs": [], + "source": [ + "wf.compute(LookupTableRelativeErrorThreshold)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "40e1be11-41f0-4839-9efa-e3e1de650808", + "metadata": {}, + "outputs": [], + "source": [ + "wf.visualize(ReducedEmptyCanSubtractedTofCIF, graph_attr={\"rankdir\": \"LR\"})" + ] + }, { "cell_type": "code", "execution_count": null, @@ -121,7 +142,7 @@ "metadata": {}, "outputs": [], "source": [ - "results = workflow.compute([\n", + "results = wf.compute([\n", " EmptyCanSubtractedIofDspacing,\n", " ReducedEmptyCanSubtractedTofCIF\n", "])\n", @@ -229,7 +250,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.10" + "version": "3.12.12" } }, "nbformat": 4, diff --git a/src/ess/dream/workflows.py b/src/ess/dream/workflows.py index 9c8d9ce6..7d344535 100644 --- a/src/ess/dream/workflows.py +++ b/src/ess/dream/workflows.py @@ -6,6 +6,10 @@ import sciline import scipp as sc import scippnexus as snx +from ess.reduce.nexus.types import DetectorBankSizes, NeXusName +from ess.reduce.parameter import parameter_mappers +from ess.reduce.unwrap import GenericUnwrapWorkflow +from ess.reduce.workflow import register_workflow from scippneutron.metadata import Software from ess.powder import providers as powder_providers @@ -18,21 +22,17 @@ CaveMonitorPosition, # Should this be a DREAM-only parameter? EmptyCanRun, KeepEvents, + LookupTableFilename, LookupTableRelativeErrorThreshold, PixelMaskFilename, Position, ReducerSoftware, SampleRun, - TimeOfFlightLookupTableFilename, TofMask, TwoThetaMask, VanadiumRun, WavelengthMask, ) -from ess.reduce.nexus.types import DetectorBankSizes, NeXusName -from ess.reduce.parameter import parameter_mappers -from ess.reduce.time_of_flight import GenericTofWorkflow -from ess.reduce.workflow import register_workflow from .beamline import InstrumentConfiguration from .io.cif import ( @@ -72,7 +72,7 @@ def _get_lookup_table_filename_from_configuration( configuration: InstrumentConfiguration, -) -> TimeOfFlightLookupTableFilename: +) -> LookupTableFilename: from .data import tof_lookup_table_high_flux match configuration: @@ -83,13 +83,13 @@ def _get_lookup_table_filename_from_configuration( case InstrumentConfiguration.high_resolution: raise NotImplementedError("High resolution configuration not yet supported") - return TimeOfFlightLookupTableFilename(out) + return LookupTableFilename(out) def _collect_reducer_software() -> ReducerSoftware: return ReducerSoftware( [ - Software.from_package_metadata('essdiffraction'), + # Software.from_package_metadata('essdiffraction'), Software.from_package_metadata('scippneutron'), Software.from_package_metadata('scipp'), ] @@ -99,7 +99,7 @@ def _collect_reducer_software() -> ReducerSoftware: def DreamWorkflow(**kwargs) -> sciline.Pipeline: """ Dream generic workflow with default parameters. - The workflow is based on the GenericTofWorkflow. + The workflow is based on the GenericUnwrapWorkflow. It can load data from a NeXus file recorded on the DREAM instrument, and can compute time-of-flight for the neutron events. @@ -110,9 +110,9 @@ def DreamWorkflow(**kwargs) -> sciline.Pipeline: ---------- kwargs: Additional keyword arguments are forwarded to the base - :func:`GenericTofWorkflow`. + :func:`GenericUnwrapWorkflow`. """ - wf = GenericTofWorkflow( + wf = GenericUnwrapWorkflow( run_types=[SampleRun, VanadiumRun, EmptyCanRun], monitor_types=[BunkerMonitor, CaveMonitor], **kwargs, diff --git a/src/ess/powder/conversion.py b/src/ess/powder/conversion.py index 42f21743..6008b42c 100644 --- a/src/ess/powder/conversion.py +++ b/src/ess/powder/conversion.py @@ -14,6 +14,8 @@ from .types import ( CalibrationData, CorrectedDetector, + DspacingDetector, + DspacingMonitor, ElasticCoordTransformGraph, EmptyCanSubtractedIntensityTof, EmptyCanSubtractedIofDspacing, @@ -25,8 +27,8 @@ Position, RunType, SampleRun, - TofDetector, - TofMonitor, + # TofDetector, + # TofMonitor, WavelengthDetector, WavelengthMonitor, ) @@ -98,31 +100,21 @@ def _consume_positions(position, sample_position, source_position): def to_dspacing_with_calibration( data: sc.DataArray, calibration: sc.Dataset, + graph: dict, ) -> sc.DataArray: """ Transform coordinates to d-spacing from calibration parameters. - - Computes d-spacing from time-of-flight stored in `data`. - - Attention - --------- - `data` may have a wavelength coordinate and dimension, - but those are discarded. - Only the stored time-of-flight is used, that is, any modifications to - the wavelength coordinate after it was computed from time-of-flight are lost. - - Raises - ------ - KeyError - If `data` does not contain a 'tof' coordinate. + Computes d-spacing from wavelength stored in `data`. Parameters ---------- data: - Input data in tof or wavelength dimension. - Must have a tof coordinate. + Input data in wavelength dimension. + Must have a wavelength coordinate. calibration: Calibration data. + graph: + Graph for the coordinate transformation, used to restore tof from wavelength. Returns ------- @@ -134,9 +126,10 @@ def to_dspacing_with_calibration( ess.powder.conversions.dspacing_from_diff_calibration """ out = merge_calibration(into=data, calibration=calibration) - out = _restore_tof_if_in_wavelength(out) + # Restore tof from wavelength + out = out.transform_coords("tof", graph=graph, keep_intermediate=False) - graph = {"dspacing": _dspacing_from_diff_calibration} + pos_graph = {"dspacing": _dspacing_from_diff_calibration} # `_dspacing_from_diff_calibration` does not need positions but conceptually, # the conversion maps from positions to d-spacing. # The mechanism with `_tag_positions_consumed` is meant to ensure that, @@ -145,10 +138,10 @@ def to_dspacing_with_calibration( if "position" in out.coords or ( out.bins is not None and "position" in out.bins.coords ): - graph["_tag_positions_consumed"] = _consume_positions + pos_graph["_tag_positions_consumed"] = _consume_positions else: - graph["_tag_positions_consumed"] = lambda: sc.scalar(0) - out = out.transform_coords("dspacing", graph=graph, keep_intermediate=False) + pos_graph["_tag_positions_consumed"] = lambda: sc.scalar(0) + out = out.transform_coords("dspacing", graph=pos_graph, keep_intermediate=False) out.coords.pop("_tag_positions_consumed", None) return CorrectedDetector[RunType](out) @@ -178,7 +171,7 @@ def powder_coordinate_transformation_graph( return ElasticCoordTransformGraph( { **scn.conversion.graph.beamline.beamline(scatter=True), - **scn.conversion.graph.tof.elastic("tof"), + **scn.conversion.graph.tof.elastic("wavelength"), 'source_position': lambda: source_position, 'sample_position': lambda: sample_position, 'gravity': lambda: gravity, @@ -186,27 +179,27 @@ def powder_coordinate_transformation_graph( ) -def _restore_tof_if_in_wavelength(data: sc.DataArray) -> sc.DataArray: - out = data.copy(deep=False) - outer = out.coords.get("wavelength", None) - if out.bins is not None: - binned = out.bins.coords.get("wavelength", None) - else: - binned = None +# def _restore_tof_from_wavelength(data: sc.DataArray) -> sc.DataArray: +# out = data.copy(deep=False) +# outer = out.coords.get("wavelength", None) +# if out.bins is not None: +# binned = out.bins.coords.get("wavelength", None) +# else: +# binned = None - if outer is not None or binned is not None: - get_logger().info("Discarded coordinate 'wavelength' in favor of 'tof'.") +# if outer is not None or binned is not None: +# get_logger().info("Discarded coordinate 'wavelength' in favor of 'tof'.") - if "wavelength" in out.dims: - out = out.rename_dims(wavelength="tof") - return out +# if "wavelength" in out.dims: +# out = out.rename_dims(wavelength="tof") +# return out def add_scattering_coordinates_from_positions( - data: TofDetector[RunType], + data: WavelengthDetector[RunType], graph: ElasticCoordTransformGraph[RunType], calibration: CalibrationData, -) -> WavelengthDetector[RunType]: +) -> DspacingDetector[RunType]: """ Add ``wavelength``, ``two_theta`` and ``dspacing`` coordinates to the data. The input ``data`` must have a ``tof`` coordinate, as well as the necessary @@ -226,7 +219,7 @@ def add_scattering_coordinates_from_positions( keep_intermediate=False, ) out = convert_to_dspacing(out, graph, calibration) - return WavelengthDetector[RunType](out) + return DspacingDetector[RunType](out) def convert_to_dspacing( @@ -237,7 +230,7 @@ def convert_to_dspacing( if calibration is None: out = data.transform_coords(["dspacing"], graph=graph, keep_intermediate=False) else: - out = to_dspacing_with_calibration(data, calibration=calibration) + out = to_dspacing_with_calibration(data, calibration=calibration, graph=graph) for key in ("wavelength", "two_theta"): if key in out.coords.keys(): out.coords.set_aligned(key, False) @@ -293,7 +286,7 @@ def powder_monitor_coordinate_transformation_graph( return MonitorCoordTransformGraph( { **scn.conversion.graph.beamline.beamline(scatter=False), - **scn.conversion.graph.tof.elastic("tof"), + **scn.conversion.graph.tof.elastic("wavelength"), 'source_position': lambda: source_position, 'sample_position': lambda: sample_position, 'gravity': lambda: gravity, @@ -301,20 +294,20 @@ def powder_monitor_coordinate_transformation_graph( ) -def convert_monitor_to_wavelength( - monitor: TofMonitor[RunType, MonitorType], - graph: MonitorCoordTransformGraph[RunType], -) -> WavelengthMonitor[RunType, MonitorType]: - return WavelengthMonitor[RunType, MonitorType]( - monitor.transform_coords("wavelength", graph=graph, keep_intermediate=False) - ) +# def convert_monitor_to_wavelength( +# monitor: TofMonitor[RunType, MonitorType], +# graph: MonitorCoordTransformGraph[RunType], +# ) -> WavelengthMonitor[RunType, MonitorType]: +# return WavelengthMonitor[RunType, MonitorType]( +# monitor.transform_coords("wavelength", graph=graph, keep_intermediate=False) +# ) providers = ( add_scattering_coordinates_from_positions, convert_reduced_to_tof, convert_reduced_to_empty_can_subtracted_tof, - convert_monitor_to_wavelength, + # convert_monitor_to_wavelength, powder_coordinate_transformation_graph, powder_monitor_coordinate_transformation_graph, ) diff --git a/src/ess/powder/types.py b/src/ess/powder/types.py index db5b8065..65d12b2e 100644 --- a/src/ess/powder/types.py +++ b/src/ess/powder/types.py @@ -14,12 +14,11 @@ import sciline import scipp as sc -from scippneutron.io import cif -from scippneutron.metadata import Person, Software - 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 +from scippneutron.io import cif +from scippneutron.metadata import Person, Software EmptyDetector = reduce_t.EmptyDetector EmptyMonitor = reduce_t.EmptyMonitor @@ -36,12 +35,12 @@ DetectorBankSizes = reduce_t.DetectorBankSizes -TofDetector = tof_t.TofDetector -TofMonitor = tof_t.TofMonitor -PulseStrideOffset = tof_t.PulseStrideOffset -TimeOfFlightLookupTable = tof_t.TimeOfFlightLookupTable -TimeOfFlightLookupTableFilename = tof_t.TimeOfFlightLookupTableFilename -LookupTableRelativeErrorThreshold = tof_t.LookupTableRelativeErrorThreshold +WavelengthDetector = unwrap_t.WavelengthDetector +WavelengthMonitor = unwrap_t.WavelengthMonitor +PulseStrideOffset = unwrap_t.PulseStrideOffset +LookupTable = unwrap_t.LookupTable +LookupTableFilename = unwrap_t.LookupTableFilename +LookupTableRelativeErrorThreshold = unwrap_t.LookupTableRelativeErrorThreshold SampleRun = reduce_t.SampleRun VanadiumRun = reduce_t.VanadiumRun @@ -95,7 +94,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: """Detector calibration data.""" -class WavelengthDetector(sciline.Scope[RunType, sc.DataArray], sc.DataArray): +class DspacingDetector(sciline.Scope[RunType, sc.DataArray], sc.DataArray): """Data with scattering coordinates computed for all events: wavelength, 2theta, d-spacing.""" @@ -170,9 +169,7 @@ class MonitorFilename(sciline.Scope[RunType, Path], Path): """ -class WavelengthMonitor( - sciline.Scope[RunType, MonitorType, sc.DataArray], sc.DataArray -): +class DspacingMonitor(sciline.Scope[RunType, MonitorType, sc.DataArray], sc.DataArray): """Monitor histogram in wavelength.""" diff --git a/src/ess/powder/workflow.py b/src/ess/powder/workflow.py index 8fbe0b3d..a0ca3828 100644 --- a/src/ess/powder/workflow.py +++ b/src/ess/powder/workflow.py @@ -2,17 +2,17 @@ from .masking import apply_masks from .types import ( CorrectedDetector, + DspacingDetector, MaskedDetectorIDs, RunType, TofMask, TwoThetaMask, - WavelengthDetector, WavelengthMask, ) def add_masks_and_corrections( - da: WavelengthDetector[RunType], + da: DspacingDetector[RunType], masked_pixel_ids: MaskedDetectorIDs, tof_mask_func: TofMask, wavelength_mask_func: WavelengthMask,