essdiffraction 23.12.0__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/diffraction/__init__.py +56 -0
- ess/diffraction/correction.py +125 -0
- ess/diffraction/external/__init__.py +141 -0
- ess/diffraction/external/powgen/__init__.py +23 -0
- ess/diffraction/external/powgen/beamline.py +65 -0
- ess/diffraction/external/powgen/data.py +159 -0
- ess/diffraction/external/powgen/instrument_view.py +42 -0
- ess/diffraction/external/powgen/types.py +19 -0
- ess/diffraction/filtering.py +126 -0
- ess/diffraction/grouping.py +81 -0
- ess/diffraction/logging.py +15 -0
- ess/diffraction/powder/__init__.py +14 -0
- ess/diffraction/powder/conversion.py +157 -0
- ess/diffraction/powder/correction.py +46 -0
- ess/diffraction/py.typed +0 -0
- ess/diffraction/smoothing.py +87 -0
- ess/diffraction/types.py +117 -0
- ess/diffraction/uncertainty.py +18 -0
- ess/dream/__init__.py +23 -0
- ess/dream/data.py +34 -0
- ess/dream/io.py +133 -0
- essdiffraction-23.12.0.dist-info/LICENSE +29 -0
- essdiffraction-23.12.0.dist-info/METADATA +77 -0
- essdiffraction-23.12.0.dist-info/RECORD +26 -0
- essdiffraction-23.12.0.dist-info/WHEEL +5 -0
- essdiffraction-23.12.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# SPDX-License-Identifier: BSD-3-Clause
|
|
2
|
+
# Copyright (c) 2023 Scipp contributors (https://github.com/scipp)
|
|
3
|
+
# @author Jan-Lukas Wynen
|
|
4
|
+
"""
|
|
5
|
+
Prototype for event filtering.
|
|
6
|
+
|
|
7
|
+
IMPORTANT Will be moved to a different place and potentially modified.
|
|
8
|
+
"""
|
|
9
|
+
from contextlib import contextmanager
|
|
10
|
+
from numbers import Real
|
|
11
|
+
|
|
12
|
+
import scipp as sc
|
|
13
|
+
|
|
14
|
+
from .types import FilteredData, RawData, RunType, TofCroppedData, ValidTofRange
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _equivalent_bin_indices(a, b) -> bool:
|
|
18
|
+
a_begin = a.bins.constituents['begin'].flatten(to='')
|
|
19
|
+
a_end = a.bins.constituents['end'].flatten(to='')
|
|
20
|
+
b_begin = b.bins.constituents['begin'].flatten(to='')
|
|
21
|
+
b_end = b.bins.constituents['end'].flatten(to='')
|
|
22
|
+
non_empty = a_begin != a_end
|
|
23
|
+
return (
|
|
24
|
+
sc.all((a_begin == b_begin)[non_empty]).value
|
|
25
|
+
and sc.all((a_end == b_end)[non_empty]).value
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@contextmanager
|
|
30
|
+
def _temporary_bin_coord(data: sc.DataArray, name: str, coord: sc.Variable) -> None:
|
|
31
|
+
if not _equivalent_bin_indices(data, coord):
|
|
32
|
+
raise ValueError("data and coord do not have equivalent bin indices")
|
|
33
|
+
coord = sc.bins(
|
|
34
|
+
data=coord.bins.constituents['data'],
|
|
35
|
+
begin=data.bins.coords['pulse_time'].bins.constituents['begin'],
|
|
36
|
+
end=data.bins.coords['pulse_time'].bins.constituents['end'],
|
|
37
|
+
dim=coord.bins.constituents['dim'],
|
|
38
|
+
)
|
|
39
|
+
data.bins.coords[name] = coord
|
|
40
|
+
yield
|
|
41
|
+
del data.bins.coords[name]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# TODO non-monotonic proton charge -> raise?
|
|
45
|
+
def _with_pulse_time_edges(da: sc.DataArray, dim: str) -> sc.DataArray:
|
|
46
|
+
pulse_time = da.coords[dim]
|
|
47
|
+
one = sc.scalar(1, dtype='int64', unit=pulse_time.unit)
|
|
48
|
+
lo = pulse_time[0] - one
|
|
49
|
+
hi = pulse_time[-1] + one
|
|
50
|
+
mid = sc.midpoints(pulse_time)
|
|
51
|
+
da.coords[dim] = sc.concat([lo, mid, hi], dim)
|
|
52
|
+
return da
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def remove_bad_pulses(
|
|
56
|
+
data: sc.DataArray, *, proton_charge: sc.DataArray, threshold_factor: Real
|
|
57
|
+
) -> sc.DataArray:
|
|
58
|
+
"""
|
|
59
|
+
assumes that there are bad pulses
|
|
60
|
+
"""
|
|
61
|
+
min_charge = proton_charge.data.mean() * threshold_factor
|
|
62
|
+
good_pulse = _with_pulse_time_edges(proton_charge >= min_charge, proton_charge.dim)
|
|
63
|
+
with _temporary_bin_coord(
|
|
64
|
+
data,
|
|
65
|
+
'good_pulse',
|
|
66
|
+
sc.lookup(good_pulse, good_pulse.dim)[data.bins.coords[good_pulse.dim]],
|
|
67
|
+
):
|
|
68
|
+
filtered = data.group(sc.array(dims=['good_pulse'], values=[True]))
|
|
69
|
+
filtered = filtered.squeeze('good_pulse').copy(deep=False)
|
|
70
|
+
del filtered.coords['good_pulse']
|
|
71
|
+
return filtered
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def crop_tof(
|
|
75
|
+
data: RawData[RunType], tof_range: ValidTofRange
|
|
76
|
+
) -> TofCroppedData[RunType]:
|
|
77
|
+
"""Remove events outside the specified TOF range.
|
|
78
|
+
|
|
79
|
+
Parameters
|
|
80
|
+
----------
|
|
81
|
+
data:
|
|
82
|
+
Data to be cropped.
|
|
83
|
+
Expected to have a coordinate called `'tof'`.
|
|
84
|
+
tof_range:
|
|
85
|
+
1d, len-2 variable containing the lower and upper bounds for
|
|
86
|
+
time-of-flight.
|
|
87
|
+
|
|
88
|
+
Returns
|
|
89
|
+
-------
|
|
90
|
+
:
|
|
91
|
+
Cropped data.
|
|
92
|
+
"""
|
|
93
|
+
return TofCroppedData[RunType](
|
|
94
|
+
data.bin(tof=tof_range.to(unit=data.coords['tof'].unit))
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def filter_events(data: TofCroppedData[RunType]) -> FilteredData[RunType]:
|
|
99
|
+
"""Remove bad events.
|
|
100
|
+
|
|
101
|
+
Attention
|
|
102
|
+
---------
|
|
103
|
+
This function currently does nothing because it is unclear how to filter
|
|
104
|
+
events at ESS.
|
|
105
|
+
In the future, this function will filter out events that
|
|
106
|
+
cannot be used for analysis.
|
|
107
|
+
|
|
108
|
+
Parameters
|
|
109
|
+
----------
|
|
110
|
+
data:
|
|
111
|
+
Input events to be filtered.
|
|
112
|
+
|
|
113
|
+
Returns
|
|
114
|
+
-------
|
|
115
|
+
:
|
|
116
|
+
`data` with bad events removed.
|
|
117
|
+
"""
|
|
118
|
+
# TODO this needs to filter by proton charge once we know how
|
|
119
|
+
return FilteredData[RunType](data)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
providers = (
|
|
123
|
+
crop_tof,
|
|
124
|
+
filter_events,
|
|
125
|
+
)
|
|
126
|
+
"""Sciline providers for event filtering."""
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# SPDX-License-Identifier: BSD-3-Clause
|
|
2
|
+
# Copyright (c) 2023 Scipp contributors (https://github.com/scipp)
|
|
3
|
+
|
|
4
|
+
from scippneutron.conversion.graph import beamline
|
|
5
|
+
|
|
6
|
+
from .types import (
|
|
7
|
+
DspacingBins,
|
|
8
|
+
DspacingData,
|
|
9
|
+
DspacingHistogram,
|
|
10
|
+
FocussedData,
|
|
11
|
+
NormalizedByVanadium,
|
|
12
|
+
RunType,
|
|
13
|
+
TwoThetaBins,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def group_by_two_theta(
|
|
18
|
+
data: DspacingData[RunType], edges: TwoThetaBins
|
|
19
|
+
) -> FocussedData[RunType]:
|
|
20
|
+
"""
|
|
21
|
+
Group data into two_theta bins.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
data:
|
|
26
|
+
Input data array with events. Must contain a coord called 'two_theta'
|
|
27
|
+
or coords that can be used to compute it.
|
|
28
|
+
edges:
|
|
29
|
+
Bin edges in two_theta. `data` is grouped into those bins.
|
|
30
|
+
|
|
31
|
+
Returns
|
|
32
|
+
-------
|
|
33
|
+
:
|
|
34
|
+
`data` grouped into two_theta bins.
|
|
35
|
+
"""
|
|
36
|
+
out = data.transform_coords('two_theta', graph=beamline.beamline(scatter=True))
|
|
37
|
+
return FocussedData[RunType](
|
|
38
|
+
out.bin(two_theta=edges.to(unit=out.coords['two_theta'].unit, copy=False))
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def merge_all_pixels(data: DspacingData[RunType]) -> FocussedData[RunType]:
|
|
43
|
+
"""Combine all pixels (spectra) of the detector.
|
|
44
|
+
|
|
45
|
+
Parameters
|
|
46
|
+
----------
|
|
47
|
+
data:
|
|
48
|
+
Input data with a `'spectrum'` dimension.
|
|
49
|
+
|
|
50
|
+
Returns
|
|
51
|
+
-------
|
|
52
|
+
:
|
|
53
|
+
The input without a `'spectrum'` dimension.
|
|
54
|
+
"""
|
|
55
|
+
return FocussedData(data.bins.concat('spectrum'))
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def finalize_histogram(
|
|
59
|
+
data: NormalizedByVanadium, edges: DspacingBins
|
|
60
|
+
) -> DspacingHistogram:
|
|
61
|
+
"""Finalize the d-spacing histogram.
|
|
62
|
+
|
|
63
|
+
Histograms the input data into the given d-spacing bins.
|
|
64
|
+
|
|
65
|
+
Parameters
|
|
66
|
+
----------
|
|
67
|
+
data:
|
|
68
|
+
Data to be histogrammed.
|
|
69
|
+
edges:
|
|
70
|
+
Bin edges in d-spacing.
|
|
71
|
+
|
|
72
|
+
Returns
|
|
73
|
+
-------
|
|
74
|
+
:
|
|
75
|
+
Histogrammed data.
|
|
76
|
+
"""
|
|
77
|
+
return DspacingHistogram(data.hist(dspacing=edges))
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
providers = (merge_all_pixels, finalize_histogram)
|
|
81
|
+
"""Sciline providers for grouping pixels."""
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# SPDX-License-Identifier: BSD-3-Clause
|
|
2
|
+
# Copyright (c) 2023 Scipp contributors (https://github.com/scipp)
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_logger() -> logging.Logger:
|
|
8
|
+
"""Return the logger for ess.diffraction.
|
|
9
|
+
|
|
10
|
+
Returns
|
|
11
|
+
-------
|
|
12
|
+
:
|
|
13
|
+
The requested logger.
|
|
14
|
+
"""
|
|
15
|
+
return logging.getLogger('scipp.ess.diffraction')
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# SPDX-License-Identifier: BSD-3-Clause
|
|
2
|
+
# Copyright (c) 2023 Scipp contributors (https://github.com/scipp)
|
|
3
|
+
"""
|
|
4
|
+
Components for powder diffraction experiments.
|
|
5
|
+
"""
|
|
6
|
+
from .conversion import providers as conversion_providers
|
|
7
|
+
from .conversion import to_dspacing_with_calibration
|
|
8
|
+
from .correction import merge_calibration
|
|
9
|
+
|
|
10
|
+
providers = (*conversion_providers,)
|
|
11
|
+
"""Sciline providers for powder diffraction."""
|
|
12
|
+
del conversion_providers
|
|
13
|
+
|
|
14
|
+
__all__ = ['merge_calibration', 'to_dspacing_with_calibration']
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# SPDX-License-Identifier: BSD-3-Clause
|
|
2
|
+
# Copyright (c) 2023 Scipp contributors (https://github.com/scipp)
|
|
3
|
+
"""
|
|
4
|
+
Coordinate transformations for powder diffraction.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import uuid
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
import scipp as sc
|
|
11
|
+
|
|
12
|
+
from ..logging import get_logger
|
|
13
|
+
from ..types import CalibrationData, DspacingData, NormalizedByProtonCharge, RunType
|
|
14
|
+
from .correction import merge_calibration
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _dspacing_from_diff_calibration_generic_impl(t, t0, a, c):
|
|
18
|
+
"""
|
|
19
|
+
This function implements the solution to
|
|
20
|
+
t = a * d^2 + c * d + t0
|
|
21
|
+
for a != 0.
|
|
22
|
+
It uses the following way of expressing the solution with an order of operations
|
|
23
|
+
that is optimized for low memory usage.
|
|
24
|
+
d = (sqrt([x-t0+t] / x) - 1) * c / (2a)
|
|
25
|
+
x = c^2 / (4a)
|
|
26
|
+
"""
|
|
27
|
+
x = c**2 / (4 * a)
|
|
28
|
+
out = (x - t0) + t
|
|
29
|
+
out /= x
|
|
30
|
+
del x
|
|
31
|
+
sc.sqrt(out, out=out)
|
|
32
|
+
out -= 1
|
|
33
|
+
out *= c / (2 * a)
|
|
34
|
+
return out
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _dspacing_from_diff_calibration_a0_impl(t, t0, c):
|
|
38
|
+
"""
|
|
39
|
+
This function implements the solution to
|
|
40
|
+
t = a * d^2 + c * d + t0
|
|
41
|
+
for a == 0.
|
|
42
|
+
"""
|
|
43
|
+
out = t - t0
|
|
44
|
+
out /= c
|
|
45
|
+
return out
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def dspacing_from_diff_calibration(
|
|
49
|
+
tof: sc.Variable,
|
|
50
|
+
tzero: sc.Variable,
|
|
51
|
+
difa: sc.Variable,
|
|
52
|
+
difc: sc.Variable,
|
|
53
|
+
_tag_positions_consumed: sc.Variable,
|
|
54
|
+
) -> sc.Variable:
|
|
55
|
+
r"""
|
|
56
|
+
Compute d-spacing from calibration parameters.
|
|
57
|
+
|
|
58
|
+
d-spacing is the positive solution of
|
|
59
|
+
|
|
60
|
+
.. math:: \mathsf{tof} = \mathsf{DIFA} * d^2 + \mathsf{DIFC} * d + t_0
|
|
61
|
+
|
|
62
|
+
This function can be used with :func:`scipp.transform_coords`.
|
|
63
|
+
|
|
64
|
+
See Also
|
|
65
|
+
--------
|
|
66
|
+
ess.diffraction.conversions.to_dspacing_with_calibration
|
|
67
|
+
"""
|
|
68
|
+
if sc.all(difa == sc.scalar(0.0, unit=difa.unit)).value:
|
|
69
|
+
return _dspacing_from_diff_calibration_a0_impl(tof, tzero, difc)
|
|
70
|
+
return _dspacing_from_diff_calibration_generic_impl(tof, tzero, difa, difc)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _restore_tof_if_in_wavelength(data: sc.DataArray) -> sc.DataArray:
|
|
74
|
+
if 'wavelength' not in data.dims:
|
|
75
|
+
return data
|
|
76
|
+
|
|
77
|
+
get_logger().info("Discarding coordinate 'wavelength' in favor of 'tof'.")
|
|
78
|
+
temp_name = uuid.uuid4().hex
|
|
79
|
+
aux = data.transform_coords(
|
|
80
|
+
temp_name,
|
|
81
|
+
{temp_name: lambda wavelength, tof: tof},
|
|
82
|
+
keep_inputs=False,
|
|
83
|
+
quiet=True,
|
|
84
|
+
)
|
|
85
|
+
return aux.transform_coords(
|
|
86
|
+
'tof', {'tof': temp_name}, keep_inputs=False, quiet=True
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _consume_positions(position, sample_position, source_position):
|
|
91
|
+
_ = position
|
|
92
|
+
_ = sample_position
|
|
93
|
+
_ = source_position
|
|
94
|
+
return sc.scalar(0)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def to_dspacing_with_calibration(
|
|
98
|
+
data: NormalizedByProtonCharge[RunType],
|
|
99
|
+
calibration: Optional[CalibrationData] = None,
|
|
100
|
+
) -> DspacingData[RunType]:
|
|
101
|
+
"""
|
|
102
|
+
Transform coordinates to d-spacing from calibration parameters.
|
|
103
|
+
|
|
104
|
+
Computes d-spacing from time-of-flight stored in `data`.
|
|
105
|
+
|
|
106
|
+
Attention
|
|
107
|
+
---------
|
|
108
|
+
`data` may have a wavelength coordinate and dimension,
|
|
109
|
+
but those are discarded.
|
|
110
|
+
Only the stored time-of-flight is used, that is, any modifications to
|
|
111
|
+
the wavelength coordinate after it was computed from time-of-flight are lost.
|
|
112
|
+
|
|
113
|
+
Raises
|
|
114
|
+
------
|
|
115
|
+
KeyError
|
|
116
|
+
If `data` does not contain a 'tof' coordinate.
|
|
117
|
+
|
|
118
|
+
Parameters
|
|
119
|
+
----------
|
|
120
|
+
data:
|
|
121
|
+
Input data in tof or wavelength dimension.
|
|
122
|
+
Must have a tof coordinate.
|
|
123
|
+
calibration:
|
|
124
|
+
Calibration data. If given, use it for the conversion.
|
|
125
|
+
Otherwise, the calibration data must be stored in `data`.
|
|
126
|
+
|
|
127
|
+
Returns
|
|
128
|
+
-------
|
|
129
|
+
:
|
|
130
|
+
A DataArray with the same data as the input and a 'dspacing' coordinate.
|
|
131
|
+
|
|
132
|
+
See Also
|
|
133
|
+
--------
|
|
134
|
+
ess.diffraction.conversions.dspacing_from_diff_calibration
|
|
135
|
+
"""
|
|
136
|
+
if calibration is not None:
|
|
137
|
+
out = merge_calibration(into=data, calibration=calibration)
|
|
138
|
+
else:
|
|
139
|
+
out = data
|
|
140
|
+
|
|
141
|
+
out = _restore_tof_if_in_wavelength(out)
|
|
142
|
+
graph = {
|
|
143
|
+
'dspacing': dspacing_from_diff_calibration,
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if 'position' in out.coords:
|
|
147
|
+
graph['_tag_positions_consumed'] = _consume_positions
|
|
148
|
+
else:
|
|
149
|
+
out.coords['_tag_positions_consumed'] = sc.scalar(0)
|
|
150
|
+
|
|
151
|
+
out = out.transform_coords('dspacing', graph=graph, keep_intermediate=False)
|
|
152
|
+
out.coords.pop('_tag_positions_consumed', None)
|
|
153
|
+
return DspacingData[RunType](out)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
providers = (to_dspacing_with_calibration,)
|
|
157
|
+
"""Sciline providers for coordinate transformations."""
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# SPDX-License-Identifier: BSD-3-Clause
|
|
2
|
+
# Copyright (c) 2023 Scipp contributors (https://github.com/scipp)
|
|
3
|
+
import scipp as sc
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def merge_calibration(*, into: sc.DataArray, calibration: sc.Dataset) -> sc.DataArray:
|
|
7
|
+
"""
|
|
8
|
+
Return a scipp.DataArray containing calibration metadata as coordinates.
|
|
9
|
+
|
|
10
|
+
Parameters
|
|
11
|
+
----------
|
|
12
|
+
into:
|
|
13
|
+
Base data and metadata for the returned object.
|
|
14
|
+
calibration:
|
|
15
|
+
Calibration parameters.
|
|
16
|
+
|
|
17
|
+
Returns
|
|
18
|
+
-------
|
|
19
|
+
:
|
|
20
|
+
Copy of `into` with additional coordinates and masks
|
|
21
|
+
from `calibration`.
|
|
22
|
+
|
|
23
|
+
See Also
|
|
24
|
+
--------
|
|
25
|
+
ess.diffraction.load_calibration
|
|
26
|
+
"""
|
|
27
|
+
dim = calibration.dim
|
|
28
|
+
if not sc.identical(into.coords[dim], calibration.coords[dim]):
|
|
29
|
+
raise ValueError(
|
|
30
|
+
f'Coordinate {dim} of calibration and target dataset do not agree.'
|
|
31
|
+
)
|
|
32
|
+
out = into.copy(deep=False)
|
|
33
|
+
for name in ('difa', 'difc', 'tzero'):
|
|
34
|
+
if name in out.coords:
|
|
35
|
+
raise ValueError(
|
|
36
|
+
f"Cannot add calibration parameter '{name}' to data, "
|
|
37
|
+
"there already is metadata with the same name."
|
|
38
|
+
)
|
|
39
|
+
out.coords[name] = calibration[name].data
|
|
40
|
+
if 'calibration' in out.masks:
|
|
41
|
+
raise ValueError(
|
|
42
|
+
"Cannot add calibration mask 'calibration' tp data, "
|
|
43
|
+
"there already is a mask with the same name."
|
|
44
|
+
)
|
|
45
|
+
out.masks['calibration'] = calibration['mask'].data
|
|
46
|
+
return out
|
ess/diffraction/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# SPDX-License-Identifier: BSD-3-Clause
|
|
2
|
+
# Copyright (c) 2023 Scipp contributors (https://github.com/scipp)
|
|
3
|
+
"""
|
|
4
|
+
Smoothing arrays data.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
import scipp as sc
|
|
10
|
+
from scipp.scipy.signal import butter
|
|
11
|
+
|
|
12
|
+
from .logging import get_logger
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _ensure_no_variances(var: sc.DataArray) -> sc.DataArray:
|
|
16
|
+
if var.variances is not None:
|
|
17
|
+
get_logger().warning(
|
|
18
|
+
'Tried to smoothen data with uncertainties. '
|
|
19
|
+
'This is not supported because the results would be highly correlated.\n'
|
|
20
|
+
'Instead, the variances are ignored and the output '
|
|
21
|
+
'will be returned without any!'
|
|
22
|
+
'\n--------------------------------------------------\n'
|
|
23
|
+
'If you know a good solution for handling uncertainties in such a case, '
|
|
24
|
+
'please contact the scipp developers! (e.g. via https://github.com/scipp)'
|
|
25
|
+
'\n--------------------------------------------------\n'
|
|
26
|
+
)
|
|
27
|
+
return sc.values(var)
|
|
28
|
+
return var
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def lowpass(
|
|
32
|
+
da: sc.DataArray, *, dim: str, N: int, Wn: sc.Variable, coord: Optional[str] = None
|
|
33
|
+
) -> sc.DataArray:
|
|
34
|
+
"""
|
|
35
|
+
Smooth data using a lowpass frequency filter.
|
|
36
|
+
|
|
37
|
+
Applies a lowpass Butterworth filter to `da.data` based on the sampling rate
|
|
38
|
+
defined by `coord`.
|
|
39
|
+
See :py:func:`scipp.signal.butter` for information on filter design.
|
|
40
|
+
|
|
41
|
+
Important
|
|
42
|
+
---------
|
|
43
|
+
If `coord` is bin-edges, it is first converted to bin-centers using
|
|
44
|
+
:func:`scipp.midpoints`.
|
|
45
|
+
This is only valid for linearly-spaced edges.
|
|
46
|
+
|
|
47
|
+
Parameters
|
|
48
|
+
----------
|
|
49
|
+
da:
|
|
50
|
+
Data to smoothen.
|
|
51
|
+
dim:
|
|
52
|
+
Dimension along which to smooth.
|
|
53
|
+
coord:
|
|
54
|
+
Name of the coordinate that defines the sampling frequency.
|
|
55
|
+
Defaults to `dim`.
|
|
56
|
+
N:
|
|
57
|
+
Order of the lowpass filter.
|
|
58
|
+
Wn:
|
|
59
|
+
Critical frequency of the filter.
|
|
60
|
+
|
|
61
|
+
Returns
|
|
62
|
+
-------
|
|
63
|
+
:
|
|
64
|
+
Smoothed `da`.
|
|
65
|
+
|
|
66
|
+
See Also
|
|
67
|
+
--------
|
|
68
|
+
scipp.signal.butter scipp.signal.sosfiltfilt
|
|
69
|
+
|
|
70
|
+
Examples
|
|
71
|
+
--------
|
|
72
|
+
|
|
73
|
+
>>> from ess.diffraction import lowpass
|
|
74
|
+
>>> x = sc.linspace(dim='x', start=1.1, stop=4.0, num=1000, unit='m')
|
|
75
|
+
>>> y = sc.sin(x * sc.scalar(1.0, unit='rad/m'))
|
|
76
|
+
>>> y += sc.sin(x * sc.scalar(400.0, unit='rad/m'))
|
|
77
|
+
>>> noisy = sc.DataArray(data=y, coords={'x': x})
|
|
78
|
+
>>> smooth = lowpass(noisy, dim='x', N=4, Wn=20 / x.unit)
|
|
79
|
+
"""
|
|
80
|
+
da = _ensure_no_variances(da)
|
|
81
|
+
coord = dim if coord is None else coord
|
|
82
|
+
|
|
83
|
+
if da.coords[coord].sizes[dim] == da.sizes[dim] + 1:
|
|
84
|
+
da = da.copy(deep=False)
|
|
85
|
+
da.coords[coord] = sc.midpoints(da.coords[coord], dim)
|
|
86
|
+
|
|
87
|
+
return butter(da.coords[coord], N=N, Wn=Wn).filtfilt(da, dim)
|
ess/diffraction/types.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# SPDX-License-Identifier: BSD-3-Clause
|
|
2
|
+
# Copyright (c) 2023 Scipp contributors (https://github.com/scipp)
|
|
3
|
+
|
|
4
|
+
"""This module defines the domain types used in ess.diffraction.
|
|
5
|
+
|
|
6
|
+
The domain types are used to define parameters and to request results from a Sciline
|
|
7
|
+
pipeline.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import NewType, TypeVar
|
|
11
|
+
|
|
12
|
+
import sciline
|
|
13
|
+
import scipp as sc
|
|
14
|
+
|
|
15
|
+
# 1 TypeVars used to parametrize the generic parts of the workflow
|
|
16
|
+
|
|
17
|
+
# 1.1 Run types
|
|
18
|
+
EmptyInstrumentRun = NewType('EmptyInstrumentRun', int)
|
|
19
|
+
"""Empty instrument run."""
|
|
20
|
+
SampleRun = NewType('SampleRun', int)
|
|
21
|
+
"""Sample run."""
|
|
22
|
+
VanadiumRun = NewType('VanadiumRun', int)
|
|
23
|
+
"""Vanadium run."""
|
|
24
|
+
RunType = TypeVar('RunType', EmptyInstrumentRun, SampleRun, VanadiumRun)
|
|
25
|
+
"""TypeVar used for specifying the run."""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# 2 Workflow parameters
|
|
29
|
+
|
|
30
|
+
CalibrationFilename = NewType('CalibrationFilename', str)
|
|
31
|
+
"""Filename of the instrument calibration file."""
|
|
32
|
+
|
|
33
|
+
DspacingBins = NewType('DSpacingBins', sc.Variable)
|
|
34
|
+
"""Bin edges for d-spacing."""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Filename(sciline.Scope[RunType, str], str):
|
|
38
|
+
"""Filename of a run."""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
OutFilename = NewType('OutFilename', str)
|
|
42
|
+
"""Filename of the output."""
|
|
43
|
+
|
|
44
|
+
TwoThetaBins = NewType('TwoThetaBins', sc.Variable)
|
|
45
|
+
"""Bin edges for grouping in 2theta.
|
|
46
|
+
|
|
47
|
+
This is used by an alternative focussing step that groups detector
|
|
48
|
+
pixels by scattering angle into bins given by these edges.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
ValidTofRange = NewType('ValidTofRange', sc.Variable)
|
|
52
|
+
"""Min and max tof value of the instrument."""
|
|
53
|
+
|
|
54
|
+
# 3 Workflow (intermediate) results
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class AccumulatedProtonCharge(sciline.Scope[RunType, sc.Variable], sc.Variable):
|
|
58
|
+
"""Total proton charge."""
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
CalibrationData = NewType('CalibrationData', sc.Dataset)
|
|
62
|
+
"""Detector calibration data."""
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class DspacingData(sciline.Scope[RunType, sc.DataArray], sc.DataArray):
|
|
66
|
+
"""Data converted to d-spacing."""
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class DspacingDataWithoutVariances(sciline.Scope[RunType, sc.DataArray], sc.DataArray):
|
|
70
|
+
"""Data converted to d-spacing where variances where removed."""
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
DspacingHistogram = NewType('DspacingHistogram', sc.DataArray)
|
|
74
|
+
"""Histogrammed intensity vs d-spacing."""
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class FilteredData(sciline.Scope[RunType, sc.DataArray], sc.DataArray):
|
|
78
|
+
"""Raw data without invalid events."""
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class FocussedData(sciline.Scope[RunType, sc.DataArray], sc.DataArray):
|
|
82
|
+
"""Intensity vs d-spacing after focussing pixels."""
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class NormalizedByProtonCharge(sciline.Scope[RunType, sc.DataArray], sc.DataArray):
|
|
86
|
+
"""Data that has been normalized by proton charge."""
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
NormalizedByVanadium = NewType('NormalizedByVanadium', sc.DataArray)
|
|
90
|
+
"""Data that has been normalized by a vanadium run."""
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class ProtonCharge(sciline.Scope[RunType, sc.DataArray], sc.DataArray):
|
|
94
|
+
"""Time-dependent proton charge."""
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
RawCalibrationData = NewType('RawCalibrationData', sc.Dataset)
|
|
98
|
+
"""Calibration data as loaded from file, needs preprocessing before using."""
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class RawData(sciline.Scope[RunType, sc.DataArray], sc.DataArray):
|
|
102
|
+
"""Raw data."""
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class RawDataWithVariances(sciline.Scope[RunType, sc.DataArray], sc.DataArray):
|
|
106
|
+
"""Raw data that has variances which need special handling."""
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class RawDataAndMetadata(sciline.Scope[RunType, sc.DataGroup], sc.DataGroup):
|
|
110
|
+
"""Raw data and associated metadata."""
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class TofCroppedData(sciline.Scope[RunType, sc.DataArray], sc.DataArray):
|
|
114
|
+
"""Raw data cropped to the valid TOF range."""
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
del sc, sciline, NewType, TypeVar
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# SPDX-License-Identifier: BSD-3-Clause
|
|
2
|
+
# Copyright (c) 2023 Scipp contributors (https://github.com/scipp)
|
|
3
|
+
"""Tools for handling statistical uncertainties."""
|
|
4
|
+
|
|
5
|
+
from .types import RawData, RawDataWithVariances, VanadiumRun
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def drop_variances(data: RawDataWithVariances[VanadiumRun]) -> RawData[VanadiumRun]:
|
|
9
|
+
res = data.copy(deep=False)
|
|
10
|
+
if res.bins is not None:
|
|
11
|
+
res.bins.constituents['data'].variances = None
|
|
12
|
+
else:
|
|
13
|
+
res.variances = None
|
|
14
|
+
return RawData[VanadiumRun](res)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
providers = (drop_variances,)
|
|
18
|
+
"""Sciline providers for handling statistical uncertainties."""
|
ess/dream/__init__.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# SPDX-License-Identifier: BSD-3-Clause
|
|
2
|
+
# Copyright (c) 2023 Scipp contributors (https://github.com/scipp)
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Components for DREAM
|
|
6
|
+
"""
|
|
7
|
+
import importlib.metadata
|
|
8
|
+
|
|
9
|
+
from . import data
|
|
10
|
+
from .io import fold_nexus_detectors, load_nexus
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
__version__ = importlib.metadata.version(__package__ or __name__)
|
|
14
|
+
except importlib.metadata.PackageNotFoundError:
|
|
15
|
+
__version__ = "0.0.0"
|
|
16
|
+
|
|
17
|
+
del importlib
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"data",
|
|
21
|
+
"fold_nexus_detectors",
|
|
22
|
+
"load_nexus",
|
|
23
|
+
]
|