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.
Files changed (120) hide show
  1. modacor/__init__.py +30 -0
  2. modacor/dataclasses/__init__.py +0 -0
  3. modacor/dataclasses/basedata.py +973 -0
  4. modacor/dataclasses/databundle.py +23 -0
  5. modacor/dataclasses/helpers.py +45 -0
  6. modacor/dataclasses/messagehandler.py +75 -0
  7. modacor/dataclasses/process_step.py +233 -0
  8. modacor/dataclasses/process_step_describer.py +146 -0
  9. modacor/dataclasses/processing_data.py +59 -0
  10. modacor/dataclasses/trace_event.py +118 -0
  11. modacor/dataclasses/uncertainty_tools.py +132 -0
  12. modacor/dataclasses/validators.py +84 -0
  13. modacor/debug/pipeline_tracer.py +548 -0
  14. modacor/io/__init__.py +33 -0
  15. modacor/io/csv/__init__.py +0 -0
  16. modacor/io/csv/csv_sink.py +114 -0
  17. modacor/io/csv/csv_source.py +210 -0
  18. modacor/io/hdf/__init__.py +27 -0
  19. modacor/io/hdf/hdf_source.py +120 -0
  20. modacor/io/io_sink.py +41 -0
  21. modacor/io/io_sinks.py +61 -0
  22. modacor/io/io_source.py +164 -0
  23. modacor/io/io_sources.py +208 -0
  24. modacor/io/processing_path.py +113 -0
  25. modacor/io/tiled/__init__.py +16 -0
  26. modacor/io/tiled/tiled_source.py +403 -0
  27. modacor/io/yaml/__init__.py +27 -0
  28. modacor/io/yaml/yaml_source.py +116 -0
  29. modacor/modules/__init__.py +53 -0
  30. modacor/modules/base_modules/__init__.py +0 -0
  31. modacor/modules/base_modules/append_processing_data.py +329 -0
  32. modacor/modules/base_modules/append_sink.py +141 -0
  33. modacor/modules/base_modules/append_source.py +181 -0
  34. modacor/modules/base_modules/bitwise_or_masks.py +113 -0
  35. modacor/modules/base_modules/combine_uncertainties.py +120 -0
  36. modacor/modules/base_modules/combine_uncertainties_max.py +105 -0
  37. modacor/modules/base_modules/divide.py +82 -0
  38. modacor/modules/base_modules/find_scale_factor1d.py +373 -0
  39. modacor/modules/base_modules/multiply.py +77 -0
  40. modacor/modules/base_modules/multiply_databundles.py +73 -0
  41. modacor/modules/base_modules/poisson_uncertainties.py +69 -0
  42. modacor/modules/base_modules/reduce_dimensionality.py +252 -0
  43. modacor/modules/base_modules/sink_processing_data.py +80 -0
  44. modacor/modules/base_modules/subtract.py +80 -0
  45. modacor/modules/base_modules/subtract_databundles.py +67 -0
  46. modacor/modules/base_modules/units_label_update.py +66 -0
  47. modacor/modules/instrument_modules/__init__.py +0 -0
  48. modacor/modules/instrument_modules/readme.md +9 -0
  49. modacor/modules/technique_modules/__init__.py +0 -0
  50. modacor/modules/technique_modules/scattering/__init__.py +0 -0
  51. modacor/modules/technique_modules/scattering/geometry_helpers.py +114 -0
  52. modacor/modules/technique_modules/scattering/index_pixels.py +492 -0
  53. modacor/modules/technique_modules/scattering/indexed_averager.py +628 -0
  54. modacor/modules/technique_modules/scattering/pixel_coordinates_3d.py +417 -0
  55. modacor/modules/technique_modules/scattering/solid_angle_correction.py +63 -0
  56. modacor/modules/technique_modules/scattering/xs_geometry.py +571 -0
  57. modacor/modules/technique_modules/scattering/xs_geometry_from_pixel_coordinates.py +293 -0
  58. modacor/runner/__init__.py +0 -0
  59. modacor/runner/pipeline.py +749 -0
  60. modacor/runner/process_step_registry.py +224 -0
  61. modacor/tests/__init__.py +27 -0
  62. modacor/tests/dataclasses/test_basedata.py +519 -0
  63. modacor/tests/dataclasses/test_basedata_operations.py +439 -0
  64. modacor/tests/dataclasses/test_basedata_to_base_units.py +57 -0
  65. modacor/tests/dataclasses/test_process_step_describer.py +73 -0
  66. modacor/tests/dataclasses/test_processstep.py +282 -0
  67. modacor/tests/debug/test_tracing_integration.py +188 -0
  68. modacor/tests/integration/__init__.py +0 -0
  69. modacor/tests/integration/test_pipeline_run.py +238 -0
  70. modacor/tests/io/__init__.py +27 -0
  71. modacor/tests/io/csv/__init__.py +0 -0
  72. modacor/tests/io/csv/test_csv_source.py +156 -0
  73. modacor/tests/io/hdf/__init__.py +27 -0
  74. modacor/tests/io/hdf/test_hdf_source.py +92 -0
  75. modacor/tests/io/test_io_sources.py +119 -0
  76. modacor/tests/io/tiled/__init__.py +12 -0
  77. modacor/tests/io/tiled/test_tiled_source.py +120 -0
  78. modacor/tests/io/yaml/__init__.py +27 -0
  79. modacor/tests/io/yaml/static_data_example.yaml +26 -0
  80. modacor/tests/io/yaml/test_yaml_source.py +47 -0
  81. modacor/tests/modules/__init__.py +27 -0
  82. modacor/tests/modules/base_modules/__init__.py +27 -0
  83. modacor/tests/modules/base_modules/test_append_processing_data.py +219 -0
  84. modacor/tests/modules/base_modules/test_append_sink.py +76 -0
  85. modacor/tests/modules/base_modules/test_append_source.py +180 -0
  86. modacor/tests/modules/base_modules/test_bitwise_or_masks.py +264 -0
  87. modacor/tests/modules/base_modules/test_combine_uncertainties.py +105 -0
  88. modacor/tests/modules/base_modules/test_combine_uncertainties_max.py +109 -0
  89. modacor/tests/modules/base_modules/test_divide.py +140 -0
  90. modacor/tests/modules/base_modules/test_find_scale_factor1d.py +220 -0
  91. modacor/tests/modules/base_modules/test_multiply.py +113 -0
  92. modacor/tests/modules/base_modules/test_multiply_databundles.py +136 -0
  93. modacor/tests/modules/base_modules/test_poisson_uncertainties.py +61 -0
  94. modacor/tests/modules/base_modules/test_reduce_dimensionality.py +358 -0
  95. modacor/tests/modules/base_modules/test_sink_processing_data.py +119 -0
  96. modacor/tests/modules/base_modules/test_subtract.py +111 -0
  97. modacor/tests/modules/base_modules/test_subtract_databundles.py +136 -0
  98. modacor/tests/modules/base_modules/test_units_label_update.py +91 -0
  99. modacor/tests/modules/technique_modules/__init__.py +0 -0
  100. modacor/tests/modules/technique_modules/scattering/__init__.py +0 -0
  101. modacor/tests/modules/technique_modules/scattering/test_geometry_helpers.py +198 -0
  102. modacor/tests/modules/technique_modules/scattering/test_index_pixels.py +426 -0
  103. modacor/tests/modules/technique_modules/scattering/test_indexed_averaging.py +559 -0
  104. modacor/tests/modules/technique_modules/scattering/test_pixel_coordinates_3d.py +282 -0
  105. modacor/tests/modules/technique_modules/scattering/test_xs_geometry_from_pixel_coordinates.py +224 -0
  106. modacor/tests/modules/technique_modules/scattering/test_xsgeometry.py +635 -0
  107. modacor/tests/requirements.txt +12 -0
  108. modacor/tests/runner/test_pipeline.py +438 -0
  109. modacor/tests/runner/test_process_step_registry.py +65 -0
  110. modacor/tests/test_import.py +43 -0
  111. modacor/tests/test_modacor.py +17 -0
  112. modacor/tests/test_units.py +79 -0
  113. modacor/units.py +97 -0
  114. modacor-1.0.0.dist-info/METADATA +482 -0
  115. modacor-1.0.0.dist-info/RECORD +120 -0
  116. modacor-1.0.0.dist-info/WHEEL +5 -0
  117. modacor-1.0.0.dist-info/licenses/AUTHORS.md +11 -0
  118. modacor-1.0.0.dist-info/licenses/LICENSE +11 -0
  119. modacor-1.0.0.dist-info/licenses/LICENSE.txt +11 -0
  120. 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