essreduce 25.1.1__py3-none-any.whl → 25.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ess/reduce/__init__.py +4 -4
- ess/reduce/live/raw.py +173 -15
- ess/reduce/live/roi.py +115 -0
- ess/reduce/nexus/__init__.py +10 -10
- ess/reduce/nexus/_nexus_loader.py +61 -22
- ess/reduce/streaming.py +53 -3
- ess/reduce/time_of_flight/__init__.py +4 -18
- ess/reduce/time_of_flight/fakes.py +29 -47
- ess/reduce/time_of_flight/simulation.py +6 -3
- ess/reduce/time_of_flight/to_events.py +12 -7
- ess/reduce/time_of_flight/toa_to_tof.py +361 -353
- ess/reduce/time_of_flight/types.py +13 -53
- ess/reduce/widgets/__init__.py +9 -10
- {essreduce-25.1.1.dist-info → essreduce-25.2.1.dist-info}/METADATA +1 -1
- {essreduce-25.1.1.dist-info → essreduce-25.2.1.dist-info}/RECORD +19 -18
- {essreduce-25.1.1.dist-info → essreduce-25.2.1.dist-info}/LICENSE +0 -0
- {essreduce-25.1.1.dist-info → essreduce-25.2.1.dist-info}/WHEEL +0 -0
- {essreduce-25.1.1.dist-info → essreduce-25.2.1.dist-info}/entry_points.txt +0 -0
- {essreduce-25.1.1.dist-info → essreduce-25.2.1.dist-info}/top_level.txt +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# SPDX-License-Identifier: BSD-3-Clause
|
|
2
|
-
# Copyright (c)
|
|
2
|
+
# Copyright (c) 2025 Scipp contributors (https://github.com/scipp)
|
|
3
3
|
|
|
4
4
|
"""
|
|
5
5
|
Utilities for computing real neutron time-of-flight from chopper settings and
|
|
@@ -7,52 +7,38 @@ neutron time-of-arrival at the detectors.
|
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
from .simulation import simulate_beamline
|
|
10
|
-
from .toa_to_tof import default_parameters, resample_tof_data, providers, TofWorkflow
|
|
11
10
|
from .to_events import to_events
|
|
11
|
+
from .toa_to_tof import default_parameters, providers, resample_tof_data
|
|
12
12
|
from .types import (
|
|
13
13
|
DistanceResolution,
|
|
14
|
-
FrameFoldedTimeOfArrival,
|
|
15
|
-
FramePeriod,
|
|
16
14
|
LookupTableRelativeErrorThreshold,
|
|
17
15
|
Ltotal,
|
|
18
16
|
LtotalRange,
|
|
19
|
-
MaskedTimeOfFlightLookupTable,
|
|
20
|
-
PivotTimeAtDetector,
|
|
21
17
|
PulsePeriod,
|
|
22
18
|
PulseStride,
|
|
23
19
|
PulseStrideOffset,
|
|
24
20
|
RawData,
|
|
25
21
|
ResampledTofData,
|
|
26
22
|
SimulationResults,
|
|
27
|
-
TimeOfArrivalMinusPivotTimeModuloPeriod,
|
|
28
23
|
TimeOfFlightLookupTable,
|
|
24
|
+
TimeResolution,
|
|
29
25
|
TofData,
|
|
30
|
-
UnwrappedTimeOfArrival,
|
|
31
|
-
UnwrappedTimeOfArrivalMinusPivotTime,
|
|
32
26
|
)
|
|
33
27
|
|
|
34
|
-
|
|
35
28
|
__all__ = [
|
|
36
29
|
"DistanceResolution",
|
|
37
|
-
"FrameFoldedTimeOfArrival",
|
|
38
|
-
"FramePeriod",
|
|
39
30
|
"LookupTableRelativeErrorThreshold",
|
|
40
31
|
"Ltotal",
|
|
41
32
|
"LtotalRange",
|
|
42
|
-
"MaskedTimeOfFlightLookupTable",
|
|
43
|
-
"PivotTimeAtDetector",
|
|
44
33
|
"PulsePeriod",
|
|
45
34
|
"PulseStride",
|
|
46
35
|
"PulseStrideOffset",
|
|
47
36
|
"RawData",
|
|
48
37
|
"ResampledTofData",
|
|
49
38
|
"SimulationResults",
|
|
50
|
-
"TimeOfArrivalMinusPivotTimeModuloPeriod",
|
|
51
39
|
"TimeOfFlightLookupTable",
|
|
40
|
+
"TimeResolution",
|
|
52
41
|
"TofData",
|
|
53
|
-
"TofWorkflow",
|
|
54
|
-
"UnwrappedTimeOfArrival",
|
|
55
|
-
"UnwrappedTimeOfArrivalMinusPivotTime",
|
|
56
42
|
"default_parameters",
|
|
57
43
|
"providers",
|
|
58
44
|
"resample_tof_data",
|
|
@@ -21,6 +21,7 @@ class FakeBeamline:
|
|
|
21
21
|
monitors: dict[str, sc.Variable],
|
|
22
22
|
run_length: sc.Variable,
|
|
23
23
|
events_per_pulse: int = 200000,
|
|
24
|
+
seed: int | None = None,
|
|
24
25
|
source: Callable | None = None,
|
|
25
26
|
):
|
|
26
27
|
import math
|
|
@@ -35,7 +36,10 @@ class FakeBeamline:
|
|
|
35
36
|
# Create a source
|
|
36
37
|
if source is None:
|
|
37
38
|
self.source = tof_pkg.Source(
|
|
38
|
-
facility="ess",
|
|
39
|
+
facility="ess",
|
|
40
|
+
neutrons=self.events_per_pulse,
|
|
41
|
+
pulses=self.npulses,
|
|
42
|
+
seed=seed,
|
|
39
43
|
)
|
|
40
44
|
else:
|
|
41
45
|
self.source = source(pulses=self.npulses)
|
|
@@ -69,34 +73,10 @@ class FakeBeamline:
|
|
|
69
73
|
self.model_result = self.model.run()
|
|
70
74
|
|
|
71
75
|
def get_monitor(self, name: str) -> sc.DataGroup:
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
period = sc.reciprocal(self.frequency)
|
|
75
|
-
|
|
76
|
-
detector = self.model_result.detectors[name]
|
|
77
|
-
raw_data = detector.data.flatten(to="event")
|
|
78
|
-
# Select only the neutrons that make it to the detector
|
|
76
|
+
nx_event_data = self.model_result.to_nxevent_data(name)
|
|
77
|
+
raw_data = self.model_result.detectors[name].data.flatten(to="event")
|
|
79
78
|
raw_data = raw_data[~raw_data.masks["blocked_by_others"]].copy()
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
# Format the data in a way that resembles data loaded from NeXus
|
|
83
|
-
event_data = raw_data.copy(deep=False)
|
|
84
|
-
dt = period.to(unit="us")
|
|
85
|
-
event_time_zero = (dt * (event_data.coords["toa"] // dt)).to(dtype=int) + start
|
|
86
|
-
raw_data.coords["event_time_zero"] = event_time_zero
|
|
87
|
-
event_data.coords["event_time_zero"] = event_time_zero
|
|
88
|
-
event_data.coords["event_time_offset"] = (
|
|
89
|
-
event_data.coords.pop("toa").to(unit="s") % period
|
|
90
|
-
)
|
|
91
|
-
del event_data.coords["tof"]
|
|
92
|
-
del event_data.coords["speed"]
|
|
93
|
-
del event_data.coords["time"]
|
|
94
|
-
del event_data.coords["wavelength"]
|
|
95
|
-
|
|
96
|
-
return (
|
|
97
|
-
event_data.group("event_time_zero").rename_dims(event_time_zero="pulse"),
|
|
98
|
-
raw_data.group("event_time_zero").rename_dims(event_time_zero="pulse"),
|
|
99
|
-
)
|
|
79
|
+
return nx_event_data, raw_data
|
|
100
80
|
|
|
101
81
|
|
|
102
82
|
wfm1_chopper = DiskChopper(
|
|
@@ -194,25 +174,6 @@ pol_chopper = DiskChopper(
|
|
|
194
174
|
radius=sc.scalar(30.0, unit="cm"),
|
|
195
175
|
)
|
|
196
176
|
|
|
197
|
-
pulse_skipping = DiskChopper(
|
|
198
|
-
frequency=sc.scalar(-7.0, unit="Hz"),
|
|
199
|
-
beam_position=sc.scalar(0.0, unit="deg"),
|
|
200
|
-
phase=sc.scalar(0.0, unit="deg"),
|
|
201
|
-
axle_position=sc.vector(value=[0, 0, 30.0], unit="m"),
|
|
202
|
-
slit_begin=sc.array(
|
|
203
|
-
dims=["cutout"],
|
|
204
|
-
values=np.array([40.0]),
|
|
205
|
-
unit="deg",
|
|
206
|
-
),
|
|
207
|
-
slit_end=sc.array(
|
|
208
|
-
dims=["cutout"],
|
|
209
|
-
values=np.array([140.0]),
|
|
210
|
-
unit="deg",
|
|
211
|
-
),
|
|
212
|
-
slit_height=sc.scalar(10.0, unit="cm"),
|
|
213
|
-
radius=sc.scalar(30.0, unit="cm"),
|
|
214
|
-
)
|
|
215
|
-
|
|
216
177
|
|
|
217
178
|
def wfm_choppers():
|
|
218
179
|
return {
|
|
@@ -238,3 +199,24 @@ def psc_choppers():
|
|
|
238
199
|
)
|
|
239
200
|
for name, ch in wfm_choppers().items()
|
|
240
201
|
}
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def pulse_skipping_chopper():
|
|
205
|
+
return DiskChopper(
|
|
206
|
+
frequency=sc.scalar(-7.0, unit="Hz"),
|
|
207
|
+
beam_position=sc.scalar(0.0, unit="deg"),
|
|
208
|
+
phase=sc.scalar(0.0, unit="deg"),
|
|
209
|
+
axle_position=sc.vector(value=[0, 0, 30.0], unit="m"),
|
|
210
|
+
slit_begin=sc.array(
|
|
211
|
+
dims=["cutout"],
|
|
212
|
+
values=np.array([40.0]),
|
|
213
|
+
unit="deg",
|
|
214
|
+
),
|
|
215
|
+
slit_end=sc.array(
|
|
216
|
+
dims=["cutout"],
|
|
217
|
+
values=np.array([140.0]),
|
|
218
|
+
unit="deg",
|
|
219
|
+
),
|
|
220
|
+
slit_height=sc.scalar(10.0, unit="cm"),
|
|
221
|
+
radius=sc.scalar(30.0, unit="cm"),
|
|
222
|
+
)
|
|
@@ -11,6 +11,7 @@ from .types import SimulationResults
|
|
|
11
11
|
def simulate_beamline(
|
|
12
12
|
choppers: Mapping[str, DiskChopper],
|
|
13
13
|
neutrons: int = 1_000_000,
|
|
14
|
+
pulses: int = 1,
|
|
14
15
|
seed: int | None = None,
|
|
15
16
|
facility: str = 'ess',
|
|
16
17
|
) -> SimulationResults:
|
|
@@ -26,6 +27,8 @@ def simulate_beamline(
|
|
|
26
27
|
for more information.
|
|
27
28
|
neutrons:
|
|
28
29
|
Number of neutrons to simulate.
|
|
30
|
+
pulses:
|
|
31
|
+
Number of pulses to simulate.
|
|
29
32
|
seed:
|
|
30
33
|
Seed for the random number generator used in the simulation.
|
|
31
34
|
facility:
|
|
@@ -47,9 +50,9 @@ def simulate_beamline(
|
|
|
47
50
|
)
|
|
48
51
|
for name, ch in choppers.items()
|
|
49
52
|
]
|
|
50
|
-
source = tof.Source(facility=facility, neutrons=neutrons, seed=seed)
|
|
53
|
+
source = tof.Source(facility=facility, neutrons=neutrons, pulses=pulses, seed=seed)
|
|
51
54
|
if not tof_choppers:
|
|
52
|
-
events = source.data.squeeze()
|
|
55
|
+
events = source.data.squeeze().flatten(to='event')
|
|
53
56
|
return SimulationResults(
|
|
54
57
|
time_of_arrival=events.coords["time"],
|
|
55
58
|
speed=events.coords["speed"],
|
|
@@ -61,7 +64,7 @@ def simulate_beamline(
|
|
|
61
64
|
results = model.run()
|
|
62
65
|
# Find name of the furthest chopper in tof_choppers
|
|
63
66
|
furthest_chopper = max(tof_choppers, key=lambda c: c.distance)
|
|
64
|
-
events = results[furthest_chopper.name].data.squeeze()
|
|
67
|
+
events = results[furthest_chopper.name].data.squeeze().flatten(to='event')
|
|
65
68
|
events = events[
|
|
66
69
|
~(events.masks["blocked_by_others"] | events.masks["blocked_by_me"])
|
|
67
70
|
]
|
|
@@ -34,15 +34,20 @@ def to_events(
|
|
|
34
34
|
rng = np.random.default_rng()
|
|
35
35
|
event_coords = {}
|
|
36
36
|
edge_dims = []
|
|
37
|
-
midp_dims =
|
|
37
|
+
midp_dims = set()
|
|
38
|
+
midp_coord_names = []
|
|
38
39
|
# Separate bin-edge and midpoints coords
|
|
39
|
-
for
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
for name in da.coords:
|
|
41
|
+
dims = da.coords[name].dims
|
|
42
|
+
is_edges = False if not dims else da.coords.is_edges(name)
|
|
43
|
+
if is_edges:
|
|
44
|
+
if name in dims:
|
|
45
|
+
edge_dims.append(name)
|
|
42
46
|
else:
|
|
43
|
-
|
|
47
|
+
midp_coord_names.append(name)
|
|
48
|
+
midp_dims.update(set(dims))
|
|
44
49
|
|
|
45
|
-
edge_sizes = {dim: da.sizes[dim] for dim in edge_dims}
|
|
50
|
+
edge_sizes = {dim: da.sizes[da.coords[dim].dim] for dim in edge_dims}
|
|
46
51
|
for dim in edge_dims:
|
|
47
52
|
coord = da.coords[dim]
|
|
48
53
|
left = sc.broadcast(coord[dim, :-1], sizes=edge_sizes).values
|
|
@@ -102,5 +107,5 @@ def to_events(
|
|
|
102
107
|
dims=[*edge_dims, event_dim], to=event_dim
|
|
103
108
|
)
|
|
104
109
|
return new.assign_coords(
|
|
105
|
-
{dim: da.coords[dim].copy() for dim in
|
|
110
|
+
{dim: da.coords[dim].copy() for dim in midp_coord_names}
|
|
106
111
|
).assign_masks({key: mask.copy() for key, mask in other_masks.items()})
|