Skip to content

Commit 2fba55e

Browse files
Merge pull request #144 from scipp/subtract-empty-can
Subtract sample run by empty can
2 parents d6b516c + f066620 commit 2fba55e

File tree

7 files changed

+178
-11
lines changed

7 files changed

+178
-11
lines changed

docs/user-guide/dream/dream-powder-reduction.ipynb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,10 @@
5656
"metadata": {},
5757
"outputs": [],
5858
"source": [
59-
"workflow = dream.DreamGeant4Workflow(run_norm=powder.RunNormalization.monitor_histogram)"
59+
"workflow = dream.DreamGeant4Workflow(\n",
60+
" run_norm=powder.RunNormalization.monitor_histogram,\n",
61+
" subtract_empty_can=True,\n",
62+
")"
6063
]
6164
},
6265
{

src/ess/dream/data.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,10 @@ def _make_pooch():
2929
"DREAM_simple_pwd_workflow/Cave_TOF_Monitor_vana_inc_coh.dat": "md5:701d66792f20eb283a4ce76bae0c8f8f", # noqa: E501
3030
"DREAM-high-flux-tof-lookup-table.h5": "md5:1b95a359fa7b0d8b4277806ece9bf279", # noqa: E501
3131
# Smaller files for unit tests
32-
"DREAM_simple_pwd_workflow/TEST_data_dream_diamond_vana_container_sample_union.csv.zip": "md5:018a87e0934c1dd0f07a708e9d497891", # noqa: E501
33-
"DREAM_simple_pwd_workflow/TEST_data_dream_vana_container_sample_union.csv.zip": "md5:d244126cd8012f9ed186f4e08a19f88d", # noqa: E501
34-
"DREAM_simple_pwd_workflow/TEST_data_dream_vanadium.csv.zip": "md5:178f9bea9f35dbdef693e38ff893c258", # noqa: E501
32+
"DREAM_simple_pwd_workflow/TEST_data_dream_diamond_vana_container_sample_union.csv.zip": "md5:405df9b5ade9d61ab71fe8d8c19bb51b", # noqa: E501
33+
"DREAM_simple_pwd_workflow/TEST_data_dream_vana_container_sample_union.csv.zip": "md5:20186119d1debfb0c2352f9db384cd0a", # noqa: E501
34+
"DREAM_simple_pwd_workflow/TEST_data_dream_vanadium.csv.zip": "md5:e5624a973f30dfe440642319c5bf0da6", # noqa: E501
35+
"DREAM_simple_pwd_workflow/TEST_data_dream_vanadium_inc_coh.csv.zip": "md5:a2e7756b264f65ab5ed7ff7fb5eca280", # noqa: E501
3536
"TEST_data_dream0_new_hkl_Si_pwd.csv.zip": "md5:df6c41f4b7b21e129915808f625828f6", # noqa: E501
3637
"TEST_data_dream_with_sectors.csv.zip": "md5:2a6b5e40e6b67f6c71b25373bf4b11a1", # noqa: E501
3738
# The TEST_DREAM_nexus_sorted-2023-12-07.nxs file was created using the
@@ -147,7 +148,9 @@ def simulated_vanadium_sample(*, small: bool = False) -> str:
147148
```
148149
"""
149150
prefix = "TEST_" if small else ""
150-
return get_path(f"DREAM_simple_pwd_workflow/{prefix}data_dream_vanadium.csv.zip")
151+
return get_path(
152+
f"DREAM_simple_pwd_workflow/{prefix}data_dream_vanadium_inc_coh.csv.zip"
153+
)
151154

152155

153156
def simulated_vanadium_sample_incoherent(*, small: bool = False) -> str:

src/ess/dream/io/geant4.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from scippneutron.metadata import ESS_SOURCE
99

1010
from ess.powder.types import (
11+
BackgroundRun,
1112
Beamline,
1213
CalibratedBeamline,
1314
CalibratedDetector,
@@ -320,7 +321,7 @@ def LoadGeant4Workflow() -> sciline.Pipeline:
320321
Workflow for loading NeXus data.
321322
"""
322323
wf = GenericTofWorkflow(
323-
run_types=[SampleRun, VanadiumRun], monitor_types=[CaveMonitor]
324+
run_types=[SampleRun, VanadiumRun, BackgroundRun], monitor_types=[CaveMonitor]
324325
)
325326
wf.insert(extract_geant4_detector)
326327
wf.insert(load_geant4_csv)

src/ess/dream/workflow.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@
1515
from ess.powder.conversion import convert_monitor_to_wavelength
1616
from ess.powder.correction import (
1717
RunNormalization,
18+
add_empty_can_subtraction,
1819
insert_run_normalization,
1920
)
2021
from ess.powder.types import (
2122
AccumulatedProtonCharge,
23+
BackgroundRun,
2224
CaveMonitorPosition, # Should this be a DREAM-only parameter?
2325
MonitorType,
2426
PixelMaskFilename,
@@ -75,10 +77,13 @@ def default_parameters() -> dict:
7577
return {
7678
Position[snx.NXsample, SampleRun]: sample_position,
7779
Position[snx.NXsample, VanadiumRun]: sample_position,
80+
Position[snx.NXsample, BackgroundRun]: sample_position,
7881
Position[snx.NXsource, SampleRun]: source_position,
7982
Position[snx.NXsource, VanadiumRun]: source_position,
83+
Position[snx.NXsource, BackgroundRun]: source_position,
8084
AccumulatedProtonCharge[SampleRun]: charge,
8185
AccumulatedProtonCharge[VanadiumRun]: charge,
86+
AccumulatedProtonCharge[BackgroundRun]: charge,
8287
TofMask: None,
8388
WavelengthMask: None,
8489
TwoThetaMask: None,
@@ -110,16 +115,34 @@ def convert_dream_monitor_to_wavelength(
110115
return convert_monitor_to_wavelength(monitor)
111116

112117

113-
def DreamGeant4Workflow(*, run_norm: RunNormalization) -> sciline.Pipeline:
118+
def DreamGeant4Workflow(
119+
*, run_norm: RunNormalization, subtract_empty_can: bool = False
120+
) -> sciline.Pipeline:
114121
"""
115122
Workflow with default parameters for the Dream Geant4 simulation.
123+
124+
Parameters
125+
----------
126+
run_norm:
127+
Select how to normalize each run (sample, vanadium, etc.).
128+
subtract_empty_can:
129+
If ``True``, subtract the same data by an empty can / empty instrument
130+
measurement before normalizing by vanadium.
131+
This requires specifying a filename parameter for the empty can run.
132+
133+
Returns
134+
-------
135+
:
136+
A workflow object for DREAM.
116137
"""
117138
wf = LoadGeant4Workflow()
118139
for provider in itertools.chain(powder_providers, _dream_providers):
119140
wf.insert(provider)
120141
wf.insert(convert_dream_monitor_to_wavelength)
121142
wf.insert(resample_monitor_time_of_flight_data)
122143
insert_run_normalization(wf, run_norm)
144+
if subtract_empty_can:
145+
add_empty_can_subtraction(wf)
123146
for key, value in itertools.chain(
124147
default_parameters().items(), time_of_flight.default_parameters().items()
125148
):

src/ess/powder/correction.py

Lines changed: 114 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
from ._util import event_or_outer_coord
1313
from .types import (
1414
AccumulatedProtonCharge,
15+
BackgroundRun,
16+
BackgroundSubtractedData,
17+
BackgroundSubtractedDataTwoTheta,
1518
CaveMonitor,
1619
DataWithScatteringCoordinates,
1720
FocussedDataDspacing,
@@ -154,13 +157,49 @@ def _normalize_by_vanadium(
154157
return normed
155158

156159

160+
def normalize_by_vanadium_dspacing_removed_empty_can(
161+
data: BackgroundSubtractedData[SampleRun],
162+
vanadium: FocussedDataDspacing[VanadiumRun],
163+
uncertainty_broadcast_mode: UncertaintyBroadcastMode,
164+
) -> IofDspacing:
165+
"""
166+
Normalize sample data by a vanadium measurement and return intensity vs. d-spacing.
167+
168+
Parameters
169+
----------
170+
data:
171+
Sample data where events from an empty can / empty instrument measurement
172+
have been subtracted.
173+
vanadium:
174+
Vanadium data.
175+
uncertainty_broadcast_mode:
176+
Choose how uncertainties of vanadium are broadcast to the sample data.
177+
Defaults to ``UncertaintyBroadcastMode.fail``.
178+
179+
Returns
180+
-------
181+
:
182+
``data / vanadium``.
183+
May contain a mask "zero_vanadium" which is ``True``
184+
for bins where vanadium is zero.
185+
186+
See also
187+
--------
188+
normalize_by_vanadium_dspacing:
189+
The same function but using data where no empty can has been subtracted.
190+
"""
191+
return IofDspacing(
192+
_normalize_by_vanadium(data, vanadium, uncertainty_broadcast_mode)
193+
)
194+
195+
157196
def normalize_by_vanadium_dspacing(
158197
data: FocussedDataDspacing[SampleRun],
159198
vanadium: FocussedDataDspacing[VanadiumRun],
160199
uncertainty_broadcast_mode: UncertaintyBroadcastMode,
161200
) -> IofDspacing:
162201
"""
163-
Normalize sample data by a vanadium measurement and return intensity vs d-spacing.
202+
Normalize sample data by a vanadium measurement and return intensity vs. d-spacing.
164203
165204
Parameters
166205
----------
@@ -178,19 +217,62 @@ def normalize_by_vanadium_dspacing(
178217
``data / vanadium``.
179218
May contain a mask "zero_vanadium" which is ``True``
180219
for bins where vanadium is zero.
220+
221+
See also
222+
--------
223+
normalize_by_vanadium_dspacing_removed_empty_can:
224+
The same function but using data where events from an empty can /
225+
empty instrument measurement have been subtracted.
181226
"""
182227
return IofDspacing(
183228
_normalize_by_vanadium(data, vanadium, uncertainty_broadcast_mode)
184229
)
185230

186231

232+
def normalize_by_vanadium_dspacing_and_two_theta_removed_empty_can(
233+
data: BackgroundSubtractedDataTwoTheta[SampleRun],
234+
vanadium: FocussedDataDspacingTwoTheta[VanadiumRun],
235+
uncertainty_broadcast_mode: UncertaintyBroadcastMode,
236+
) -> IofDspacingTwoTheta:
237+
"""
238+
Normalize sample data by a vanadium measurement and return intensity vs.
239+
(d-spacing, 2theta).
240+
241+
Parameters
242+
----------
243+
data:
244+
Sample data where events from an empty can / empty instrument measurement
245+
have been subtracted.
246+
vanadium:
247+
Vanadium data.
248+
uncertainty_broadcast_mode:
249+
Choose how uncertainties of vanadium are broadcast to the sample data.
250+
Defaults to ``UncertaintyBroadcastMode.fail``.
251+
252+
Returns
253+
-------
254+
:
255+
``data / vanadium``.
256+
May contain a mask "zero_vanadium" which is ``True``
257+
for bins where vanadium is zero.
258+
259+
See also
260+
--------
261+
normalize_by_vanadium_dspacing_and_two_theta:
262+
The same function but using data where no empty can has been subtracted.
263+
"""
264+
return IofDspacingTwoTheta(
265+
_normalize_by_vanadium(data, vanadium, uncertainty_broadcast_mode)
266+
)
267+
268+
187269
def normalize_by_vanadium_dspacing_and_two_theta(
188270
data: FocussedDataDspacingTwoTheta[SampleRun],
189271
vanadium: FocussedDataDspacingTwoTheta[VanadiumRun],
190272
uncertainty_broadcast_mode: UncertaintyBroadcastMode,
191273
) -> IofDspacingTwoTheta:
192274
"""
193-
Normalize sample data by a vanadium measurement and return intensity vs
275+
Normalize sample data by a vanadium measurement and return intensity vs.
194276
(d-spacing, 2theta).
195277
196278
Parameters
@@ -209,6 +291,12 @@ def normalize_by_vanadium_dspacing_and_two_theta(
209291
``data / vanadium``.
210292
May contain a mask "zero_vanadium" which is ``True``
211293
for bins where vanadium is zero.
294+
295+
See also
296+
--------
297+
normalize_by_vanadium_dspacing_and_two_theta_removed_empty_can:
298+
The same function but using data where events from an empty can /
299+
empty instrument measurement have been subtracted.
212300
"""
213301
return IofDspacingTwoTheta(
214302
_normalize_by_vanadium(data, vanadium, uncertainty_broadcast_mode)
@@ -335,6 +423,22 @@ def _shallow_copy(da: sc.DataArray) -> sc.DataArray:
335423
return out
336424

337425

426+
def subtract_background(
427+
data: FocussedDataDspacing[SampleRun],
428+
background: FocussedDataDspacing[BackgroundRun],
429+
) -> BackgroundSubtractedData[SampleRun]:
430+
return BackgroundSubtractedData[SampleRun](data.bins.concatenate(-background))
431+
432+
433+
def subtract_background_two_theta(
434+
data: FocussedDataDspacingTwoTheta[SampleRun],
435+
background: FocussedDataDspacingTwoTheta[BackgroundRun],
436+
) -> BackgroundSubtractedDataTwoTheta[SampleRun]:
437+
return BackgroundSubtractedDataTwoTheta[SampleRun](
438+
data.bins.concatenate(-background)
439+
)
440+
441+
338442
class RunNormalization(enum.Enum):
339443
"""Type of normalization applied to each run."""
340444

@@ -356,7 +460,15 @@ def insert_run_normalization(
356460
workflow.insert(normalize_by_proton_charge)
357461

358462

463+
def add_empty_can_subtraction(workflow: sciline.Pipeline) -> None:
464+
"""Insert providers to subtract empty can events from sample data."""
465+
workflow.insert(normalize_by_vanadium_dspacing_removed_empty_can)
466+
workflow.insert(normalize_by_vanadium_dspacing_and_two_theta_removed_empty_can)
467+
468+
359469
providers = (
470+
subtract_background,
471+
subtract_background_two_theta,
360472
normalize_by_proton_charge,
361473
normalize_by_vanadium_dspacing,
362474
normalize_by_vanadium_dspacing_and_two_theta,

src/ess/powder/types.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
TimeOfFlightLookupTableFilename = tof_t.TimeOfFlightLookupTableFilename
5959
SimulationResults = tof_t.SimulationResults
6060

61-
RunType = TypeVar("RunType", SampleRun, VanadiumRun)
61+
RunType = TypeVar("RunType", SampleRun, VanadiumRun, BackgroundRun)
6262
MonitorType = TypeVar("MonitorType", CaveMonitor, BunkerMonitor)
6363

6464

@@ -175,6 +175,16 @@ class NormalizedRunData(sciline.Scope[RunType, sc.DataArray], sc.DataArray):
175175
"""Data that has been normalized by proton charge."""
176176

177177

178+
class BackgroundSubtractedData(sciline.Scope[RunType, sc.DataArray], sc.DataArray):
179+
"""Data where background has been subtracted."""
180+
181+
182+
class BackgroundSubtractedDataTwoTheta(
183+
sciline.Scope[RunType, sc.DataArray], sc.DataArray
184+
):
185+
"""Data with 2theta bins where background has been subtracted."""
186+
187+
178188
PixelMaskFilename = NewType("PixelMaskFilename", str)
179189
"""Filename of a pixel mask."""
180190

tests/dream/geant4_reduction_test.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,16 @@ def params_for_det(request):
101101

102102

103103
@pytest.fixture
104-
def workflow(params_for_det):
104+
def workflow_no_empy_can(params_for_det):
105105
return make_workflow(params_for_det, run_norm=powder.RunNormalization.proton_charge)
106106

107107

108+
@pytest.fixture
109+
def workflow(workflow_no_empy_can):
110+
powder.correction.add_empty_can_subtraction(workflow_no_empy_can)
111+
return workflow_no_empy_can
112+
113+
108114
def make_workflow(params_for_det, *, run_norm):
109115
wf = dream.DreamGeant4Workflow(run_norm=run_norm)
110116
for key, value in params_for_det.items():
@@ -119,6 +125,15 @@ def test_pipeline_can_compute_dspacing_result(workflow):
119125
assert sc.identical(result.coords['dspacing'], params[DspacingBins])
120126

121127

128+
def test_pipeline_can_compute_dspacing_result_without_empty_can(workflow_no_empy_can):
129+
workflow_no_empy_can[Filename[BackgroundRun]] = None
130+
workflow_no_empy_can[MonitorFilename[BackgroundRun]] = None
131+
workflow_no_empy_can = powder.with_pixel_mask_filenames(workflow_no_empy_can, [])
132+
result = workflow_no_empy_can.compute(IofDspacing)
133+
assert result.sizes == {'dspacing': len(params[DspacingBins]) - 1}
134+
assert sc.identical(result.coords['dspacing'], params[DspacingBins])
135+
136+
122137
def test_pipeline_can_compute_dspacing_result_using_lookup_table_filename(workflow):
123138
workflow = powder.with_pixel_mask_filenames(workflow, [])
124139
workflow[TimeOfFlightLookupTableFilename] = dream.data.tof_lookup_table_high_flux()

0 commit comments

Comments
 (0)