Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/essdiffraction/src/ess/dream/io/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
23 changes: 23 additions & 0 deletions packages/essdiffraction/src/ess/dream/io/_common.py
Original file line number Diff line number Diff line change
@@ -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
13 changes: 4 additions & 9 deletions packages/essdiffraction/src/ess/dream/io/cif.py
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -19,6 +18,8 @@
)
from scippneutron.io import cif

from ._common import prepare_reduced_data


def prepare_reduced_tof_cif(
da: IntensityTof,
Expand Down Expand Up @@ -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)
Expand All @@ -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
36 changes: 36 additions & 0 deletions packages/essdiffraction/src/ess/dream/io/xye.py
Original file line number Diff line number Diff line change
@@ -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")
5 changes: 5 additions & 0 deletions packages/essdiffraction/tests/dream/geant4_reduction_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -31,6 +32,7 @@
LookupTableFilename,
MonitorFilename,
NeXusDetectorName,
OutFilename,
ReducedTofCIF,
SampleRun,
TofMask,
Expand Down Expand Up @@ -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)
Expand All @@ -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)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the reason why we don't use bind_and_call for CIF files because we want to add the result.comment?

At first glance it seems strange that we use one method for CIF file and another for XYE file.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could handle the comment differently. And having a free-form comment for XYE or other files could be useful, too. The difference comes mainly from there being this CIF object that you build in memory. So the workflow has something to return. But we don't have that for other formats. (We do for SQW but that is way more complicated.)
We can talk about a good, common interface if you like.



def _assert_contains_source_info(cif_content: str) -> None:
assert 'diffrn_source.beamline DREAM' in cif_content
Expand Down
Loading