modacor 1.0.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.
- modacor/__init__.py +30 -0
- modacor/dataclasses/__init__.py +0 -0
- modacor/dataclasses/basedata.py +973 -0
- modacor/dataclasses/databundle.py +23 -0
- modacor/dataclasses/helpers.py +45 -0
- modacor/dataclasses/messagehandler.py +75 -0
- modacor/dataclasses/process_step.py +233 -0
- modacor/dataclasses/process_step_describer.py +146 -0
- modacor/dataclasses/processing_data.py +59 -0
- modacor/dataclasses/trace_event.py +118 -0
- modacor/dataclasses/uncertainty_tools.py +132 -0
- modacor/dataclasses/validators.py +84 -0
- modacor/debug/pipeline_tracer.py +548 -0
- modacor/io/__init__.py +33 -0
- modacor/io/csv/__init__.py +0 -0
- modacor/io/csv/csv_sink.py +114 -0
- modacor/io/csv/csv_source.py +210 -0
- modacor/io/hdf/__init__.py +27 -0
- modacor/io/hdf/hdf_source.py +120 -0
- modacor/io/io_sink.py +41 -0
- modacor/io/io_sinks.py +61 -0
- modacor/io/io_source.py +164 -0
- modacor/io/io_sources.py +208 -0
- modacor/io/processing_path.py +113 -0
- modacor/io/tiled/__init__.py +16 -0
- modacor/io/tiled/tiled_source.py +403 -0
- modacor/io/yaml/__init__.py +27 -0
- modacor/io/yaml/yaml_source.py +116 -0
- modacor/modules/__init__.py +53 -0
- modacor/modules/base_modules/__init__.py +0 -0
- modacor/modules/base_modules/append_processing_data.py +329 -0
- modacor/modules/base_modules/append_sink.py +141 -0
- modacor/modules/base_modules/append_source.py +181 -0
- modacor/modules/base_modules/bitwise_or_masks.py +113 -0
- modacor/modules/base_modules/combine_uncertainties.py +120 -0
- modacor/modules/base_modules/combine_uncertainties_max.py +105 -0
- modacor/modules/base_modules/divide.py +82 -0
- modacor/modules/base_modules/find_scale_factor1d.py +373 -0
- modacor/modules/base_modules/multiply.py +77 -0
- modacor/modules/base_modules/multiply_databundles.py +73 -0
- modacor/modules/base_modules/poisson_uncertainties.py +69 -0
- modacor/modules/base_modules/reduce_dimensionality.py +252 -0
- modacor/modules/base_modules/sink_processing_data.py +80 -0
- modacor/modules/base_modules/subtract.py +80 -0
- modacor/modules/base_modules/subtract_databundles.py +67 -0
- modacor/modules/base_modules/units_label_update.py +66 -0
- modacor/modules/instrument_modules/__init__.py +0 -0
- modacor/modules/instrument_modules/readme.md +9 -0
- modacor/modules/technique_modules/__init__.py +0 -0
- modacor/modules/technique_modules/scattering/__init__.py +0 -0
- modacor/modules/technique_modules/scattering/geometry_helpers.py +114 -0
- modacor/modules/technique_modules/scattering/index_pixels.py +492 -0
- modacor/modules/technique_modules/scattering/indexed_averager.py +628 -0
- modacor/modules/technique_modules/scattering/pixel_coordinates_3d.py +417 -0
- modacor/modules/technique_modules/scattering/solid_angle_correction.py +63 -0
- modacor/modules/technique_modules/scattering/xs_geometry.py +571 -0
- modacor/modules/technique_modules/scattering/xs_geometry_from_pixel_coordinates.py +293 -0
- modacor/runner/__init__.py +0 -0
- modacor/runner/pipeline.py +749 -0
- modacor/runner/process_step_registry.py +224 -0
- modacor/tests/__init__.py +27 -0
- modacor/tests/dataclasses/test_basedata.py +519 -0
- modacor/tests/dataclasses/test_basedata_operations.py +439 -0
- modacor/tests/dataclasses/test_basedata_to_base_units.py +57 -0
- modacor/tests/dataclasses/test_process_step_describer.py +73 -0
- modacor/tests/dataclasses/test_processstep.py +282 -0
- modacor/tests/debug/test_tracing_integration.py +188 -0
- modacor/tests/integration/__init__.py +0 -0
- modacor/tests/integration/test_pipeline_run.py +238 -0
- modacor/tests/io/__init__.py +27 -0
- modacor/tests/io/csv/__init__.py +0 -0
- modacor/tests/io/csv/test_csv_source.py +156 -0
- modacor/tests/io/hdf/__init__.py +27 -0
- modacor/tests/io/hdf/test_hdf_source.py +92 -0
- modacor/tests/io/test_io_sources.py +119 -0
- modacor/tests/io/tiled/__init__.py +12 -0
- modacor/tests/io/tiled/test_tiled_source.py +120 -0
- modacor/tests/io/yaml/__init__.py +27 -0
- modacor/tests/io/yaml/static_data_example.yaml +26 -0
- modacor/tests/io/yaml/test_yaml_source.py +47 -0
- modacor/tests/modules/__init__.py +27 -0
- modacor/tests/modules/base_modules/__init__.py +27 -0
- modacor/tests/modules/base_modules/test_append_processing_data.py +219 -0
- modacor/tests/modules/base_modules/test_append_sink.py +76 -0
- modacor/tests/modules/base_modules/test_append_source.py +180 -0
- modacor/tests/modules/base_modules/test_bitwise_or_masks.py +264 -0
- modacor/tests/modules/base_modules/test_combine_uncertainties.py +105 -0
- modacor/tests/modules/base_modules/test_combine_uncertainties_max.py +109 -0
- modacor/tests/modules/base_modules/test_divide.py +140 -0
- modacor/tests/modules/base_modules/test_find_scale_factor1d.py +220 -0
- modacor/tests/modules/base_modules/test_multiply.py +113 -0
- modacor/tests/modules/base_modules/test_multiply_databundles.py +136 -0
- modacor/tests/modules/base_modules/test_poisson_uncertainties.py +61 -0
- modacor/tests/modules/base_modules/test_reduce_dimensionality.py +358 -0
- modacor/tests/modules/base_modules/test_sink_processing_data.py +119 -0
- modacor/tests/modules/base_modules/test_subtract.py +111 -0
- modacor/tests/modules/base_modules/test_subtract_databundles.py +136 -0
- modacor/tests/modules/base_modules/test_units_label_update.py +91 -0
- modacor/tests/modules/technique_modules/__init__.py +0 -0
- modacor/tests/modules/technique_modules/scattering/__init__.py +0 -0
- modacor/tests/modules/technique_modules/scattering/test_geometry_helpers.py +198 -0
- modacor/tests/modules/technique_modules/scattering/test_index_pixels.py +426 -0
- modacor/tests/modules/technique_modules/scattering/test_indexed_averaging.py +559 -0
- modacor/tests/modules/technique_modules/scattering/test_pixel_coordinates_3d.py +282 -0
- modacor/tests/modules/technique_modules/scattering/test_xs_geometry_from_pixel_coordinates.py +224 -0
- modacor/tests/modules/technique_modules/scattering/test_xsgeometry.py +635 -0
- modacor/tests/requirements.txt +12 -0
- modacor/tests/runner/test_pipeline.py +438 -0
- modacor/tests/runner/test_process_step_registry.py +65 -0
- modacor/tests/test_import.py +43 -0
- modacor/tests/test_modacor.py +17 -0
- modacor/tests/test_units.py +79 -0
- modacor/units.py +97 -0
- modacor-1.0.0.dist-info/METADATA +482 -0
- modacor-1.0.0.dist-info/RECORD +120 -0
- modacor-1.0.0.dist-info/WHEEL +5 -0
- modacor-1.0.0.dist-info/licenses/AUTHORS.md +11 -0
- modacor-1.0.0.dist-info/licenses/LICENSE +11 -0
- modacor-1.0.0.dist-info/licenses/LICENSE.txt +11 -0
- modacor-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
# SPDX-License-Identifier: BSD-3-Clause
|
|
2
|
+
# /usr/bin/env python3
|
|
3
|
+
# -*- coding: utf-8 -*-
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
__coding__ = "utf-8"
|
|
8
|
+
__authors__ = ["Brian R. Pauw"] # add names to the list as appropriate
|
|
9
|
+
__copyright__ = "Copyright 2025, The MoDaCor team"
|
|
10
|
+
__date__ = "30/11/2025"
|
|
11
|
+
__status__ = "Development" # "Development", "Production"
|
|
12
|
+
# end of header and standard imports
|
|
13
|
+
__version__ = "20251130.1"
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
Tests for the IndexPixels processing step.
|
|
17
|
+
|
|
18
|
+
We test:
|
|
19
|
+
- Basic azimuthal binning in Q (1D case).
|
|
20
|
+
- Basic radial binning in Psi (1D case).
|
|
21
|
+
- Unit conversion for q_limits_unit.
|
|
22
|
+
- Psi wrap-around masking for azimuthal direction.
|
|
23
|
+
- A small integration-style test using prepare_execution() + calculate().
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
import numpy as np
|
|
27
|
+
|
|
28
|
+
# import pytest
|
|
29
|
+
import pint
|
|
30
|
+
from numpy.testing import assert_array_equal
|
|
31
|
+
|
|
32
|
+
from modacor import ureg
|
|
33
|
+
from modacor.dataclasses.basedata import BaseData
|
|
34
|
+
from modacor.dataclasses.databundle import DataBundle
|
|
35
|
+
from modacor.dataclasses.processing_data import ProcessingData
|
|
36
|
+
from modacor.io.io_sources import IoSources
|
|
37
|
+
from modacor.modules.technique_modules.scattering.index_pixels import IndexPixels
|
|
38
|
+
|
|
39
|
+
# ---------------------------------------------------------------------------
|
|
40
|
+
# Small helpers
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def make_1d_signal_bundle(
|
|
45
|
+
q_values: np.ndarray,
|
|
46
|
+
psi_values: np.ndarray,
|
|
47
|
+
q_unit: pint.Unit = ureg.Unit("1/nm"),
|
|
48
|
+
psi_unit: pint.Unit = ureg.Unit("radian"),
|
|
49
|
+
) -> DataBundle:
|
|
50
|
+
"""
|
|
51
|
+
Build a minimal 1D databundle with signal, Q, Psi.
|
|
52
|
+
"""
|
|
53
|
+
n = q_values.size
|
|
54
|
+
assert psi_values.size == n
|
|
55
|
+
|
|
56
|
+
signal_bd = BaseData(
|
|
57
|
+
signal=np.ones(n, dtype=float),
|
|
58
|
+
units=ureg.dimensionless,
|
|
59
|
+
rank_of_data=1,
|
|
60
|
+
)
|
|
61
|
+
q_bd = BaseData(
|
|
62
|
+
signal=np.asarray(q_values, dtype=float),
|
|
63
|
+
units=q_unit,
|
|
64
|
+
rank_of_data=1,
|
|
65
|
+
)
|
|
66
|
+
psi_bd = BaseData(
|
|
67
|
+
signal=np.asarray(psi_values, dtype=float),
|
|
68
|
+
units=psi_unit,
|
|
69
|
+
rank_of_data=1,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# simple axis metadata (optional, but useful to check propagation)
|
|
73
|
+
axis_bd = BaseData(
|
|
74
|
+
signal=np.arange(n, dtype=float),
|
|
75
|
+
units=ureg.pixel,
|
|
76
|
+
rank_of_data=0,
|
|
77
|
+
)
|
|
78
|
+
signal_bd.axes = [axis_bd]
|
|
79
|
+
|
|
80
|
+
bundle = DataBundle(
|
|
81
|
+
{
|
|
82
|
+
"signal": signal_bd,
|
|
83
|
+
"Q": q_bd,
|
|
84
|
+
"Psi": psi_bd,
|
|
85
|
+
}
|
|
86
|
+
)
|
|
87
|
+
return bundle
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# ---------------------------------------------------------------------------
|
|
91
|
+
# Tests
|
|
92
|
+
# ---------------------------------------------------------------------------
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_indexpixels_azimuthal_basic_binning_1d_linear():
|
|
96
|
+
"""
|
|
97
|
+
Azimuthal direction:
|
|
98
|
+
- Bins along Q (linear bins).
|
|
99
|
+
- Psi ROI is full circle.
|
|
100
|
+
- Check that obvious Q values fall into expected bins.
|
|
101
|
+
"""
|
|
102
|
+
# Q values: two points clearly in each bin, others outside range
|
|
103
|
+
q_vals = np.array([0.5, 1.25, 1.75, 2.25, 2.75, 3.5], dtype=float)
|
|
104
|
+
# Psi irrelevant here beyond being finite and inside ROI
|
|
105
|
+
psi_vals = np.zeros_like(q_vals, dtype=float)
|
|
106
|
+
|
|
107
|
+
bundle = make_1d_signal_bundle(
|
|
108
|
+
q_values=q_vals,
|
|
109
|
+
psi_values=psi_vals,
|
|
110
|
+
q_unit=ureg.dimensionless,
|
|
111
|
+
psi_unit=ureg.degree,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
processing_data = ProcessingData()
|
|
115
|
+
processing_data["test"] = bundle
|
|
116
|
+
|
|
117
|
+
step = IndexPixels(io_sources=IoSources())
|
|
118
|
+
step.processing_data = processing_data
|
|
119
|
+
step._prepared_data = {}
|
|
120
|
+
|
|
121
|
+
step.configuration = {
|
|
122
|
+
"with_processing_keys": ["test"],
|
|
123
|
+
"output_processing_key": None,
|
|
124
|
+
# azimuthal: binning along Q
|
|
125
|
+
"averaging_direction": "azimuthal",
|
|
126
|
+
"n_bins": 2,
|
|
127
|
+
"bin_type": "linear",
|
|
128
|
+
"q_min": 1.0,
|
|
129
|
+
"q_max": 3.0,
|
|
130
|
+
"q_limits_unit": None, # same as Q.units
|
|
131
|
+
# full-circle Psi in degrees
|
|
132
|
+
"psi_min": 0.0,
|
|
133
|
+
"psi_max": 360.0,
|
|
134
|
+
"psi_limits_unit": "degree",
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
step.prepare_execution()
|
|
138
|
+
out = step.calculate()
|
|
139
|
+
|
|
140
|
+
assert "test" in out
|
|
141
|
+
db = out["test"]
|
|
142
|
+
assert "pixel_index" in db
|
|
143
|
+
|
|
144
|
+
pix = db["pixel_index"]
|
|
145
|
+
assert pix.signal.shape == q_vals.shape
|
|
146
|
+
assert pix.units == ureg.dimensionless
|
|
147
|
+
|
|
148
|
+
# Edges are [1, 2, 3]. With searchsorted(side="right") - 1:
|
|
149
|
+
# Q: 0.5 1.25 1.75 2.25 2.75 3.5
|
|
150
|
+
# idx: -1 0 0 1 1 -1 (plus Q-range mask)
|
|
151
|
+
expected = np.array([-1, 0, 0, 1, 1, -1], dtype=int)
|
|
152
|
+
# pixel_index stored as float; cast to int for comparison
|
|
153
|
+
result = pix.signal.astype(int)
|
|
154
|
+
assert_array_equal(result, expected)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def test_indexpixels_radial_basic_binning_1d_linear_psi():
|
|
158
|
+
"""
|
|
159
|
+
Radial direction:
|
|
160
|
+
- Bins along Psi (linear bins from psi_min to psi_max).
|
|
161
|
+
- Q ROI wide enough to include all pixels.
|
|
162
|
+
- Check straightforward bin assignment for four angles.
|
|
163
|
+
"""
|
|
164
|
+
# Four angles: 0°, 90°, 180°, 270°
|
|
165
|
+
psi_deg = np.array([0.0, 90.0, 180.0, 270.0], dtype=float)
|
|
166
|
+
psi_rad = np.deg2rad(psi_deg)
|
|
167
|
+
q_vals = np.ones_like(psi_deg, dtype=float) # simple constant Q
|
|
168
|
+
|
|
169
|
+
bundle = make_1d_signal_bundle(
|
|
170
|
+
q_values=q_vals,
|
|
171
|
+
psi_values=psi_rad,
|
|
172
|
+
q_unit=ureg.dimensionless,
|
|
173
|
+
psi_unit=ureg.radian,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
processing_data = ProcessingData()
|
|
177
|
+
processing_data["test"] = bundle
|
|
178
|
+
|
|
179
|
+
step = IndexPixels(io_sources=IoSources())
|
|
180
|
+
step.processing_data = processing_data
|
|
181
|
+
step._prepared_data = {}
|
|
182
|
+
|
|
183
|
+
step.configuration = {
|
|
184
|
+
"with_processing_keys": ["test"],
|
|
185
|
+
"output_processing_key": None,
|
|
186
|
+
"averaging_direction": "radial",
|
|
187
|
+
"n_bins": 4,
|
|
188
|
+
"bin_type": "linear",
|
|
189
|
+
# Wide Q ROI, everything included
|
|
190
|
+
"q_min": 0.0,
|
|
191
|
+
"q_max": 2.0,
|
|
192
|
+
"q_limits_unit": None,
|
|
193
|
+
# Psi bin range 0..360° in radians
|
|
194
|
+
"psi_min": 0.0,
|
|
195
|
+
"psi_max": 360.0,
|
|
196
|
+
"psi_limits_unit": "degree",
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
step.prepare_execution()
|
|
200
|
+
out = step.calculate()
|
|
201
|
+
|
|
202
|
+
db = out["test"]
|
|
203
|
+
pix = db["pixel_index"]
|
|
204
|
+
|
|
205
|
+
assert pix.signal.shape == psi_deg.shape
|
|
206
|
+
assert pix.units == ureg.dimensionless
|
|
207
|
+
|
|
208
|
+
# Edges (in radians) correspond to [0, 90, 180, 270, 360] deg.
|
|
209
|
+
# Values: 0°, 90°, 180°, 270° → bins 0, 1, 2, 3
|
|
210
|
+
expected = np.array([0, 1, 2, 3], dtype=int)
|
|
211
|
+
result = pix.signal.astype(int)
|
|
212
|
+
assert_array_equal(result, expected)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def test_indexpixels_azimuthal_q_limits_unit_conversion():
|
|
216
|
+
"""
|
|
217
|
+
Check that q_min/q_max specified in q_limits_unit are converted
|
|
218
|
+
correctly to the Q units.
|
|
219
|
+
|
|
220
|
+
Example:
|
|
221
|
+
- Q in 1/Å,
|
|
222
|
+
- q_limits_unit = 1/nm,
|
|
223
|
+
- q_min = 1.5 1/nm → 0.15 1/Å
|
|
224
|
+
- q_max = 2.5 1/nm → 0.25 1/Å
|
|
225
|
+
Only Q values in [0.15, 0.25] should be included.
|
|
226
|
+
"""
|
|
227
|
+
Q_unit = ureg.Unit("1/angstrom")
|
|
228
|
+
|
|
229
|
+
q_vals = np.array([0.10, 0.20, 0.30], dtype=float) # in 1/Å
|
|
230
|
+
psi_vals = np.zeros_like(q_vals, dtype=float)
|
|
231
|
+
|
|
232
|
+
bundle = make_1d_signal_bundle(
|
|
233
|
+
q_values=q_vals,
|
|
234
|
+
psi_values=psi_vals,
|
|
235
|
+
q_unit=Q_unit,
|
|
236
|
+
psi_unit=ureg.radian,
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
processing_data = ProcessingData()
|
|
240
|
+
processing_data["test"] = bundle
|
|
241
|
+
|
|
242
|
+
step = IndexPixels(io_sources=IoSources())
|
|
243
|
+
step.processing_data = processing_data
|
|
244
|
+
step._prepared_data = {}
|
|
245
|
+
|
|
246
|
+
step.configuration = {
|
|
247
|
+
"with_processing_keys": ["test"],
|
|
248
|
+
"output_processing_key": None,
|
|
249
|
+
"averaging_direction": "azimuthal",
|
|
250
|
+
"n_bins": 1,
|
|
251
|
+
"bin_type": "linear",
|
|
252
|
+
# In 1/nm; will be converted to 1/Å
|
|
253
|
+
"q_min": 1.5,
|
|
254
|
+
"q_max": 2.5,
|
|
255
|
+
"q_limits_unit": "1/nm",
|
|
256
|
+
# full circle in Psi
|
|
257
|
+
"psi_min": 0.0,
|
|
258
|
+
"psi_max": 2.0 * np.pi,
|
|
259
|
+
"psi_limits_unit": "radian",
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
step.prepare_execution()
|
|
263
|
+
out = step.calculate()
|
|
264
|
+
|
|
265
|
+
db = out["test"]
|
|
266
|
+
pix = db["pixel_index"]
|
|
267
|
+
|
|
268
|
+
# Only the middle value (0.20 1/Å) lies between 0.15 and 0.25
|
|
269
|
+
# and within the Q-range mask; with 1 bin, its index should be 0.
|
|
270
|
+
expected = np.array([-1, 0, -1], dtype=int)
|
|
271
|
+
result = pix.signal.astype(int)
|
|
272
|
+
assert_array_equal(result, expected)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def test_indexpixels_azimuthal_psi_wraparound_mask():
|
|
276
|
+
"""
|
|
277
|
+
Azimuthal direction with Psi wrap-around ROI:
|
|
278
|
+
psi_min > psi_max, e.g. 300°..60°.
|
|
279
|
+
|
|
280
|
+
Pixels with Psi in [300°, 360°) U [0°, 60°] should be included;
|
|
281
|
+
others should be masked to -1.
|
|
282
|
+
"""
|
|
283
|
+
psi_deg = np.array([10.0, 100.0, 200.0, 350.0], dtype=float)
|
|
284
|
+
psi_rad = np.deg2rad(psi_deg)
|
|
285
|
+
# Q values chosen to be all in-range for binning
|
|
286
|
+
q_vals = np.array([1.0, 1.5, 2.0, 2.5], dtype=float)
|
|
287
|
+
|
|
288
|
+
bundle = make_1d_signal_bundle(
|
|
289
|
+
q_values=q_vals,
|
|
290
|
+
psi_values=psi_rad,
|
|
291
|
+
q_unit=ureg.dimensionless,
|
|
292
|
+
psi_unit=ureg.radian,
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
processing_data = ProcessingData()
|
|
296
|
+
processing_data["test"] = bundle
|
|
297
|
+
|
|
298
|
+
step = IndexPixels(io_sources=IoSources())
|
|
299
|
+
step.processing_data = processing_data
|
|
300
|
+
step._prepared_data = {}
|
|
301
|
+
|
|
302
|
+
step.configuration = {
|
|
303
|
+
"with_processing_keys": ["test"],
|
|
304
|
+
"output_processing_key": None,
|
|
305
|
+
"averaging_direction": "azimuthal",
|
|
306
|
+
"n_bins": 4,
|
|
307
|
+
"bin_type": "linear",
|
|
308
|
+
"q_min": 0.5,
|
|
309
|
+
"q_max": 3.0,
|
|
310
|
+
"q_limits_unit": None,
|
|
311
|
+
# wrap-around: 300° .. 60°
|
|
312
|
+
"psi_min": 300.0,
|
|
313
|
+
"psi_max": 60.0,
|
|
314
|
+
"psi_limits_unit": "degree",
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
step.prepare_execution()
|
|
318
|
+
out = step.calculate()
|
|
319
|
+
|
|
320
|
+
db = out["test"]
|
|
321
|
+
pix = db["pixel_index"]
|
|
322
|
+
|
|
323
|
+
# The first (10°) and last (350°) pixels lie inside the wrap-around ROI.
|
|
324
|
+
# The internal binning is along Q; with 4 linear bins between 0.5 and 3.0:
|
|
325
|
+
# edges [0.5, 1.125, 1.75, 2.375, 3.0]
|
|
326
|
+
# q = [1.0, 1.5, 2.0, 2.5] -> indices [0, 1, 2, 3] if unmasked.
|
|
327
|
+
# Applying the Psi ROI:
|
|
328
|
+
# 10° -> index 0
|
|
329
|
+
# 100° -> masked -> -1
|
|
330
|
+
# 200° -> masked -> -1
|
|
331
|
+
# 350° -> index 3
|
|
332
|
+
expected = np.array([0, -1, -1, 3], dtype=int)
|
|
333
|
+
result = pix.signal.astype(int)
|
|
334
|
+
assert_array_equal(result, expected)
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def test_indexpixels_prepare_and_calculate_integration_2d():
|
|
338
|
+
"""
|
|
339
|
+
Integration-style test (2D case):
|
|
340
|
+
- Build a 2D signal, Q, Psi.
|
|
341
|
+
- Run prepare_execution() and calculate().
|
|
342
|
+
- Check that the pixel_index BaseData is present with expected shape,
|
|
343
|
+
axes, and rank_of_data.
|
|
344
|
+
"""
|
|
345
|
+
n0, n1 = 4, 6
|
|
346
|
+
spatial_shape = (n0, n1)
|
|
347
|
+
|
|
348
|
+
# Simple Q: monotonically increasing across the flattened array
|
|
349
|
+
q_vals = np.linspace(0.1, 2.0, num=n0 * n1, dtype=float).reshape(spatial_shape)
|
|
350
|
+
# Psi: zeros (full-circle ROI)
|
|
351
|
+
psi_vals = np.zeros(spatial_shape, dtype=float)
|
|
352
|
+
|
|
353
|
+
signal_bd = BaseData(
|
|
354
|
+
signal=np.ones(spatial_shape, dtype=float),
|
|
355
|
+
units=ureg.dimensionless,
|
|
356
|
+
rank_of_data=2,
|
|
357
|
+
)
|
|
358
|
+
q_bd = BaseData(
|
|
359
|
+
signal=q_vals,
|
|
360
|
+
units=ureg.Unit("1/nm"),
|
|
361
|
+
rank_of_data=2,
|
|
362
|
+
)
|
|
363
|
+
psi_bd = BaseData(
|
|
364
|
+
signal=psi_vals,
|
|
365
|
+
units=ureg.radian,
|
|
366
|
+
rank_of_data=2,
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
# Add two spatial axes for completeness
|
|
370
|
+
axis_row = BaseData(
|
|
371
|
+
signal=np.arange(n0, dtype=float),
|
|
372
|
+
units=ureg.pixel,
|
|
373
|
+
rank_of_data=0,
|
|
374
|
+
)
|
|
375
|
+
axis_col = BaseData(
|
|
376
|
+
signal=np.arange(n1, dtype=float),
|
|
377
|
+
units=ureg.pixel,
|
|
378
|
+
rank_of_data=0,
|
|
379
|
+
)
|
|
380
|
+
signal_bd.axes = [axis_row, axis_col]
|
|
381
|
+
|
|
382
|
+
bundle = DataBundle(
|
|
383
|
+
{
|
|
384
|
+
"signal": signal_bd,
|
|
385
|
+
"Q": q_bd,
|
|
386
|
+
"Psi": psi_bd,
|
|
387
|
+
}
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
processing_data = ProcessingData()
|
|
391
|
+
processing_data["image"] = bundle
|
|
392
|
+
|
|
393
|
+
step = IndexPixels(io_sources=IoSources())
|
|
394
|
+
step.processing_data = processing_data
|
|
395
|
+
step._prepared_data = {}
|
|
396
|
+
|
|
397
|
+
step.configuration = {
|
|
398
|
+
"with_processing_keys": ["image"],
|
|
399
|
+
"output_processing_key": None,
|
|
400
|
+
"averaging_direction": "azimuthal",
|
|
401
|
+
"n_bins": 8,
|
|
402
|
+
"bin_type": "log",
|
|
403
|
+
"q_min": None, # auto from data
|
|
404
|
+
"q_max": None,
|
|
405
|
+
"q_limits_unit": None,
|
|
406
|
+
"psi_min": 0.0,
|
|
407
|
+
"psi_max": 2.0 * np.pi,
|
|
408
|
+
"psi_limits_unit": "radian",
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
step.prepare_execution()
|
|
412
|
+
out = step.calculate()
|
|
413
|
+
|
|
414
|
+
assert "image" in out
|
|
415
|
+
db = out["image"]
|
|
416
|
+
assert "pixel_index" in db
|
|
417
|
+
|
|
418
|
+
pix = db["pixel_index"]
|
|
419
|
+
assert isinstance(pix, BaseData)
|
|
420
|
+
assert pix.signal.shape == spatial_shape
|
|
421
|
+
assert pix.rank_of_data == signal_bd.rank_of_data
|
|
422
|
+
|
|
423
|
+
# axes should match the last rank_of_data axes of the original signal
|
|
424
|
+
assert len(pix.axes) == len(signal_bd.axes[-signal_bd.rank_of_data :])
|
|
425
|
+
# dimensionless units
|
|
426
|
+
assert pix.units == ureg.dimensionless
|