diff --git a/packages/essdiffraction/src/ess/dream/io/__init__.py b/packages/essdiffraction/src/ess/dream/io/__init__.py index 8521592b4..a19560936 100644 --- a/packages/essdiffraction/src/ess/dream/io/__init__.py +++ b/packages/essdiffraction/src/ess/dream/io/__init__.py @@ -5,7 +5,8 @@ from .cif import prepare_reduced_tof_cif from .geant4 import load_geant4_csv +from .xye import save_xye providers = (prepare_reduced_tof_cif,) -__all__ = ["load_geant4_csv", "prepare_reduced_tof_cif", "providers"] +__all__ = ["load_geant4_csv", "prepare_reduced_tof_cif", "providers", "save_xye"] diff --git a/packages/essdiffraction/src/ess/dream/io/_common.py b/packages/essdiffraction/src/ess/dream/io/_common.py new file mode 100644 index 000000000..efd85c93e --- /dev/null +++ b/packages/essdiffraction/src/ess/dream/io/_common.py @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) 2026 Scipp contributors (https://github.com/scipp) + +import scipp as sc +from scipp.core import irreducible_mask + + +def prepare_reduced_data(da: sc.DataArray) -> sc.DataArray: + """Prepare reduced data for saving.""" + if da.ndim != 1: + raise sc.DimensionError(f"Can only save 1D data, got {da.sizes}") + + hist = da.hist() if da.is_binned else da.copy(deep=False) + hist.coords[hist.dim] = sc.midpoints(hist.coords[hist.dim]) + + if hist.masks: + # No file format we use here supports masks, so the next + # best thing is to zero out masked data: + hist.data = hist.data.copy() + hist.values *= irreducible_mask(hist.masks, hist.dim).values + hist.masks.clear() + + return hist diff --git a/packages/essdiffraction/src/ess/dream/io/cif.py b/packages/essdiffraction/src/ess/dream/io/cif.py index df7ac4716..8a547800c 100644 --- a/packages/essdiffraction/src/ess/dream/io/cif.py +++ b/packages/essdiffraction/src/ess/dream/io/cif.py @@ -1,9 +1,8 @@ # SPDX-License-Identifier: BSD-3-Clause -# Copyright (c) 2023 Scipp contributors (https://github.com/scipp) +# Copyright (c) 2026 Scipp contributors (https://github.com/scipp) """CIF writer for DREAM.""" -import scipp as sc from ess.powder.calibration import OutputCalibrationData from ess.powder.types import ( Beamline, @@ -19,6 +18,8 @@ ) from scippneutron.io import cif +from ._common import prepare_reduced_data + def prepare_reduced_tof_cif( da: IntensityTof, @@ -126,7 +127,7 @@ def _prepare_reduced_tof_cif_impl( reducers: ReducerSoftware, calibration: OutputCalibrationData, ) -> ReducedTofCIF: - to_save = _prepare_data(da) + to_save = prepare_reduced_data(da) return ReducedTofCIF( cif.CIF('reduced_tof') .with_measurement(measurement) @@ -136,9 +137,3 @@ def _prepare_reduced_tof_cif_impl( .with_powder_calibration(calibration.to_cif_format()) .with_reduced_powder_data(to_save) ) - - -def _prepare_data(da: sc.DataArray) -> sc.DataArray: - hist = da.copy(deep=False) if da.bins is None else da.hist() - hist.coords[hist.dim] = sc.midpoints(hist.coords[hist.dim]) - return hist diff --git a/packages/essdiffraction/src/ess/dream/io/xye.py b/packages/essdiffraction/src/ess/dream/io/xye.py new file mode 100644 index 000000000..f98bd0341 --- /dev/null +++ b/packages/essdiffraction/src/ess/dream/io/xye.py @@ -0,0 +1,36 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) 2026 Scipp contributors (https://github.com/scipp) + +"""XYE writer for DREAM.""" + +import scippneutron as scn +from ess.powder.types import IntensityTof, OutFilename + +from ._common import prepare_reduced_data + + +def save_xye(filename: OutFilename, da: IntensityTof) -> None: + """Save reduced data to an XYE file. + + This function can be used as + + .. code-block:: python + + from ess.powder.types import OutFilename + from ess.dream.io import save_xye + + workflow = ... + workflow[OutFilename] = "..." + workflow.bind_and_call(save_xye) + + Note that this function is not suitable as a provider as it + has side effects (writes a file). + + Parameters + ---------- + filename: + Path of a file to write to. + da: + Reduced 1d data with a ``'tof'`` dimension and coordinate. + """ + scn.io.save_xye(filename, prepare_reduced_data(da), coord="tof") diff --git a/packages/essdiffraction/tests/dream/geant4_reduction_test.py b/packages/essdiffraction/tests/dream/geant4_reduction_test.py index 31bc513c1..779127038 100644 --- a/packages/essdiffraction/tests/dream/geant4_reduction_test.py +++ b/packages/essdiffraction/tests/dream/geant4_reduction_test.py @@ -10,6 +10,7 @@ import scipp as sc import scipp.testing from ess import dream, powder +from ess.dream.io import save_xye from ess.dream.workflows import ( DreamGeant4MonitorHistogramWorkflow, DreamGeant4MonitorIntegratedWorkflow, @@ -31,6 +32,7 @@ LookupTableFilename, MonitorFilename, NeXusDetectorName, + OutFilename, ReducedTofCIF, SampleRun, TofMask, @@ -289,6 +291,7 @@ def test_pipeline_save_data_to_disk(output_folder: Path): detector_name="mantle", run_norm=powder.RunNormalization.proton_charge ) + wf[OutFilename] = output_folder / "dream_reduced.xye" wf[Filename[SampleRun]] = dream.data.simulated_diamond_sample(small=False) wf[Filename[VanadiumRun]] = dream.data.simulated_vanadium_sample(small=False) wf[Filename[EmptyCanRun]] = dream.data.simulated_empty_can(small=False) @@ -302,6 +305,8 @@ def test_pipeline_save_data_to_disk(output_folder: Path): """ result.save(output_folder / "dream_reduced.cif") + wf.bind_and_call(save_xye) + def _assert_contains_source_info(cif_content: str) -> None: assert 'diffrn_source.beamline DREAM' in cif_content