essreduce 25.11.3__tar.gz → 25.11.4__tar.gz
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.
- {essreduce-25.11.3 → essreduce-25.11.4}/PKG-INFO +1 -1
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/live/raw.py +183 -5
- {essreduce-25.11.3 → essreduce-25.11.4}/src/essreduce.egg-info/PKG-INFO +1 -1
- {essreduce-25.11.3 → essreduce-25.11.4}/tests/live/raw_test.py +374 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/.copier-answers.ess.yml +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/.copier-answers.yml +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/.github/ISSUE_TEMPLATE/high-level-requirement.yml +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/.github/dependabot.yml +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/.github/workflows/ci.yml +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/.github/workflows/docs.yml +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/.github/workflows/nightly_at_main.yml +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/.github/workflows/nightly_at_main_lower_bound.yml +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/.github/workflows/nightly_at_release.yml +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/.github/workflows/python-version-ci +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/.github/workflows/release.yml +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/.github/workflows/test.yml +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/.github/workflows/unpinned.yml +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/.github/workflows/weekly_windows_macos.yml +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/.gitignore +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/.pre-commit-config.yaml +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/.python-version +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/CODE_OF_CONDUCT.md +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/CONTRIBUTING.md +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/LICENSE +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/MANIFEST.in +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/README.md +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/docs/_static/anaconda-icon.js +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/docs/_static/favicon.svg +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/docs/_static/logo-dark.svg +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/docs/_static/logo.svg +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/docs/_templates/class-template.rst +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/docs/_templates/doc_version.html +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/docs/_templates/module-template.rst +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/docs/about/index.md +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/docs/api-reference/index.md +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/docs/conf.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/docs/developer/coding-conventions.md +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/docs/developer/dependency-management.md +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/docs/developer/getting-started.md +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/docs/developer/gui.ipynb +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/docs/developer/index.md +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/docs/index.md +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/docs/user-guide/index.md +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/docs/user-guide/installation.md +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/docs/user-guide/reduction-workflow-guidelines.md +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/docs/user-guide/tof/dream.ipynb +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/docs/user-guide/tof/frame-unwrapping.ipynb +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/docs/user-guide/tof/index.md +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/docs/user-guide/tof/wfm.ipynb +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/docs/user-guide/widget.md +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/pyproject.toml +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/requirements/base.in +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/requirements/base.txt +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/requirements/basetest.in +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/requirements/basetest.txt +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/requirements/ci.in +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/requirements/ci.txt +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/requirements/dev.in +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/requirements/dev.txt +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/requirements/docs.in +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/requirements/docs.txt +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/requirements/make_base.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/requirements/mypy.in +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/requirements/mypy.txt +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/requirements/nightly.in +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/requirements/nightly.txt +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/requirements/static.in +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/requirements/static.txt +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/requirements/test.in +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/requirements/test.txt +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/requirements/wheels.in +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/requirements/wheels.txt +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/resources/logo.svg +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/setup.cfg +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/__init__.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/data/__init__.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/data/_registry.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/live/__init__.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/live/roi.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/live/workflow.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/logging.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/nexus/__init__.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/nexus/_nexus_loader.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/nexus/json_generator.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/nexus/json_nexus.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/nexus/types.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/nexus/workflow.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/normalization.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/parameter.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/py.typed +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/scripts/grow_nexus.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/streaming.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/time_of_flight/__init__.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/time_of_flight/eto_to_tof.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/time_of_flight/fakes.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/time_of_flight/interpolator_numba.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/time_of_flight/interpolator_scipy.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/time_of_flight/lut.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/time_of_flight/resample.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/time_of_flight/types.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/time_of_flight/workflow.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/ui.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/uncertainty.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/widgets/__init__.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/widgets/_base.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/widgets/_binedges_widget.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/widgets/_bounds_widget.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/widgets/_config.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/widgets/_filename_widget.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/widgets/_linspace_widget.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/widgets/_optional_widget.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/widgets/_spinner.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/widgets/_string_widget.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/widgets/_switchable_widget.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/widgets/_vector_widget.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/ess/reduce/workflow.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/essreduce.egg-info/SOURCES.txt +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/essreduce.egg-info/dependency_links.txt +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/essreduce.egg-info/entry_points.txt +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/essreduce.egg-info/requires.txt +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/src/essreduce.egg-info/top_level.txt +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/tests/accumulators_test.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/tests/conftest.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/tests/live/roi_test.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/tests/nexus/json_generator_test.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/tests/nexus/json_nexus_examples/array_dataset.json +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/tests/nexus/json_nexus_examples/dataset.json +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/tests/nexus/json_nexus_examples/detector.json +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/tests/nexus/json_nexus_examples/entry.json +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/tests/nexus/json_nexus_examples/event_data.json +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/tests/nexus/json_nexus_examples/instrument.json +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/tests/nexus/json_nexus_examples/log.json +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/tests/nexus/json_nexus_test.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/tests/nexus/nexus_loader_test.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/tests/nexus/workflow_test.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/tests/normalization_test.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/tests/package_test.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/tests/scripts/test_grow_nexus.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/tests/streaming_test.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/tests/time_of_flight/interpolator_test.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/tests/time_of_flight/lut_test.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/tests/time_of_flight/resample_tests.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/tests/time_of_flight/unwrap_test.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/tests/time_of_flight/wfm_test.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/tests/time_of_flight/workflow_test.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/tests/uncertainty_test.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/tests/widget_test.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/tools/shrink_nexus.py +0 -0
- {essreduce-25.11.3 → essreduce-25.11.4}/tox.ini +0 -0
|
@@ -21,7 +21,7 @@ options:
|
|
|
21
21
|
from __future__ import annotations
|
|
22
22
|
|
|
23
23
|
from collections.abc import Callable, Sequence
|
|
24
|
-
from math import ceil
|
|
24
|
+
from math import ceil, prod
|
|
25
25
|
from typing import Literal, NewType
|
|
26
26
|
|
|
27
27
|
import numpy as np
|
|
@@ -138,6 +138,139 @@ class Histogrammer:
|
|
|
138
138
|
return self._hist(replicated, coords=self._coords) / self.replicas
|
|
139
139
|
|
|
140
140
|
|
|
141
|
+
class LogicalView:
|
|
142
|
+
"""
|
|
143
|
+
Logical view for detector data.
|
|
144
|
+
|
|
145
|
+
Implements a view by applying a user-defined transform (e.g., fold or slice
|
|
146
|
+
operations) optionally followed by reduction (summing) over specified dimensions.
|
|
147
|
+
Transformation and reduction must be specified separately for `LogicalView` to
|
|
148
|
+
construct a mapping from output indices to input indices. So `transform` must not
|
|
149
|
+
perform any reductions.
|
|
150
|
+
|
|
151
|
+
This class provides both data transformation (__call__) and index mapping
|
|
152
|
+
(input_indices) using the same transform, ensuring consistency for ROI filtering.
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
def __init__(
|
|
156
|
+
self,
|
|
157
|
+
transform: Callable[[sc.DataArray], sc.DataArray],
|
|
158
|
+
reduction_dim: str | list[str] | None = None,
|
|
159
|
+
input_sizes: dict[str, int] | None = None,
|
|
160
|
+
):
|
|
161
|
+
"""
|
|
162
|
+
Create a logical view.
|
|
163
|
+
|
|
164
|
+
Parameters
|
|
165
|
+
----------
|
|
166
|
+
transform:
|
|
167
|
+
Callable that transforms input data by reshaping or selecting pixels.
|
|
168
|
+
Examples:
|
|
169
|
+
- Fold: lambda da: da.fold('x', {'x': 512, 'x_bin': 2})
|
|
170
|
+
- Slice: lambda da: da['z', 0] (select front layer of volume)
|
|
171
|
+
- Combined: lambda da: da.fold('x', {'x': 4, 'z': 8})['z', 0]
|
|
172
|
+
reduction_dim:
|
|
173
|
+
Dimension(s) to sum over after applying transform. If None or empty,
|
|
174
|
+
no reduction is performed (pure transform).
|
|
175
|
+
Example: 'x_bin' or ['x_bin', 'y_bin']
|
|
176
|
+
input_sizes:
|
|
177
|
+
Dictionary defining the input dimension sizes.
|
|
178
|
+
Required for input_indices().
|
|
179
|
+
If not provided, input_indices() will raise an error.
|
|
180
|
+
When used with RollingDetectorView, this is automatically
|
|
181
|
+
inferred from detector_number.
|
|
182
|
+
"""
|
|
183
|
+
self._transform = transform
|
|
184
|
+
if reduction_dim is None:
|
|
185
|
+
self._reduction_dim = []
|
|
186
|
+
elif isinstance(reduction_dim, str):
|
|
187
|
+
self._reduction_dim = [reduction_dim]
|
|
188
|
+
else:
|
|
189
|
+
self._reduction_dim = reduction_dim
|
|
190
|
+
self._input_sizes = input_sizes
|
|
191
|
+
|
|
192
|
+
@property
|
|
193
|
+
def replicas(self) -> int:
|
|
194
|
+
"""Number of replicas. Always 1 for LogicalView."""
|
|
195
|
+
return 1
|
|
196
|
+
|
|
197
|
+
def __call__(self, da: sc.DataArray) -> sc.DataArray:
|
|
198
|
+
"""
|
|
199
|
+
Apply transform and optionally sum over reduction dimensions.
|
|
200
|
+
|
|
201
|
+
Parameters
|
|
202
|
+
----------
|
|
203
|
+
da:
|
|
204
|
+
Data to downsample.
|
|
205
|
+
|
|
206
|
+
Returns
|
|
207
|
+
-------
|
|
208
|
+
:
|
|
209
|
+
Transformed (and optionally reduced) data array.
|
|
210
|
+
"""
|
|
211
|
+
transformed = self._transform(da)
|
|
212
|
+
if self._reduction_dim:
|
|
213
|
+
return transformed.sum(self._reduction_dim)
|
|
214
|
+
return transformed
|
|
215
|
+
|
|
216
|
+
def input_indices(self) -> sc.DataArray:
|
|
217
|
+
"""
|
|
218
|
+
Create index mapping for ROI filtering.
|
|
219
|
+
|
|
220
|
+
Returns a DataArray mapping output pixels to input indices (as indices into
|
|
221
|
+
the flattened input array). If reduction dimensions are specified, returns
|
|
222
|
+
binned data where each output pixel contains a list of contributing input
|
|
223
|
+
indices. If no reduction, returns dense indices (1:1 mapping).
|
|
224
|
+
|
|
225
|
+
Returns
|
|
226
|
+
-------
|
|
227
|
+
:
|
|
228
|
+
DataArray mapping output pixels to input indices.
|
|
229
|
+
|
|
230
|
+
Raises
|
|
231
|
+
------
|
|
232
|
+
ValueError:
|
|
233
|
+
If input_sizes was not provided during initialization.
|
|
234
|
+
"""
|
|
235
|
+
if self._input_sizes is None:
|
|
236
|
+
raise ValueError(
|
|
237
|
+
"input_sizes is required for input_indices(). "
|
|
238
|
+
"Provide it during LogicalView initialization."
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
# Create sequential indices (0, 1, 2, ...) and fold to input shape
|
|
242
|
+
total_size = prod(self._input_sizes.values())
|
|
243
|
+
indices = sc.arange('_temp', total_size, dtype='int64', unit=None)
|
|
244
|
+
indices = indices.fold(dim='_temp', sizes=self._input_sizes)
|
|
245
|
+
|
|
246
|
+
# Apply transform to get the grouping structure
|
|
247
|
+
transformed = self._transform(sc.DataArray(data=indices))
|
|
248
|
+
|
|
249
|
+
if not self._reduction_dim:
|
|
250
|
+
# No reduction: 1:1 mapping, return dense indices
|
|
251
|
+
return sc.DataArray(data=transformed.data)
|
|
252
|
+
|
|
253
|
+
# Flatten reduction dimensions to a single dimension.
|
|
254
|
+
# First transpose to make reduction dims contiguous at the end.
|
|
255
|
+
output_dims = [d for d in transformed.dims if d not in self._reduction_dim]
|
|
256
|
+
transformed = transformed.transpose(output_dims + self._reduction_dim)
|
|
257
|
+
transformed = transformed.flatten(dims=self._reduction_dim, to='_reduction')
|
|
258
|
+
|
|
259
|
+
# Convert dense array to binned structure where each output pixel
|
|
260
|
+
# contains a bin with the indices of contributing input pixels.
|
|
261
|
+
bin_size = transformed.sizes['_reduction']
|
|
262
|
+
output_shape = [transformed.sizes[d] for d in output_dims]
|
|
263
|
+
data_flat = transformed.data.flatten(to='_flat')
|
|
264
|
+
begin = sc.array(
|
|
265
|
+
dims=output_dims,
|
|
266
|
+
values=np.arange(0, data_flat.size, bin_size, dtype=np.int64).reshape(
|
|
267
|
+
output_shape
|
|
268
|
+
),
|
|
269
|
+
unit=None,
|
|
270
|
+
)
|
|
271
|
+
return sc.DataArray(sc.bins(begin=begin, dim='_flat', data=data_flat))
|
|
272
|
+
|
|
273
|
+
|
|
141
274
|
class Detector:
|
|
142
275
|
def __init__(self, detector_number: sc.Variable):
|
|
143
276
|
self._data = sc.DataArray(
|
|
@@ -220,6 +353,48 @@ class RollingDetectorView(Detector):
|
|
|
220
353
|
self._cumulative: sc.DataArray
|
|
221
354
|
self.clear_counts()
|
|
222
355
|
|
|
356
|
+
@staticmethod
|
|
357
|
+
def with_logical_view(
|
|
358
|
+
*,
|
|
359
|
+
detector_number: sc.Variable,
|
|
360
|
+
window: int,
|
|
361
|
+
transform: Callable[[sc.DataArray], sc.DataArray],
|
|
362
|
+
reduction_dim: str | list[str] | None = None,
|
|
363
|
+
) -> RollingDetectorView:
|
|
364
|
+
"""
|
|
365
|
+
Create a RollingDetectorView with a LogicalView projection.
|
|
366
|
+
|
|
367
|
+
This factory method creates a LogicalView with input_sizes
|
|
368
|
+
automatically inferred from detector_number.sizes.
|
|
369
|
+
|
|
370
|
+
Parameters
|
|
371
|
+
----------
|
|
372
|
+
detector_number:
|
|
373
|
+
Detector number for each pixel.
|
|
374
|
+
window:
|
|
375
|
+
Size of the rolling window.
|
|
376
|
+
transform:
|
|
377
|
+
Transform function for the LogicalView.
|
|
378
|
+
reduction_dim:
|
|
379
|
+
Reduction dimension(s) for the LogicalView. If None or empty,
|
|
380
|
+
no reduction is performed (pure transform).
|
|
381
|
+
|
|
382
|
+
Returns
|
|
383
|
+
-------
|
|
384
|
+
:
|
|
385
|
+
RollingDetectorView with LogicalView projection.
|
|
386
|
+
"""
|
|
387
|
+
view = LogicalView(
|
|
388
|
+
transform=transform,
|
|
389
|
+
reduction_dim=reduction_dim,
|
|
390
|
+
input_sizes=dict(detector_number.sizes),
|
|
391
|
+
)
|
|
392
|
+
return RollingDetectorView(
|
|
393
|
+
detector_number=detector_number,
|
|
394
|
+
window=window,
|
|
395
|
+
projection=view,
|
|
396
|
+
)
|
|
397
|
+
|
|
223
398
|
@property
|
|
224
399
|
def max_window(self) -> int:
|
|
225
400
|
return self._window
|
|
@@ -249,9 +424,11 @@ class RollingDetectorView(Detector):
|
|
|
249
424
|
def make_roi_filter(self) -> roi.ROIFilter:
|
|
250
425
|
"""Return a ROI filter operating via the projection plane of the view."""
|
|
251
426
|
norm = 1.0
|
|
252
|
-
if
|
|
427
|
+
# Use duck typing: check if projection has input_indices method
|
|
428
|
+
if hasattr(self._projection, 'input_indices'):
|
|
253
429
|
indices = self._projection.input_indices()
|
|
254
|
-
|
|
430
|
+
# Get replicas property if it exists (Histogrammer has it, default to 1.0)
|
|
431
|
+
norm = getattr(self._projection, 'replicas', 1.0)
|
|
255
432
|
else:
|
|
256
433
|
indices = sc.ones(sizes=self.data.sizes, dtype='int32', unit=None)
|
|
257
434
|
indices = sc.cumsum(indices, mode='exclusive')
|
|
@@ -292,10 +469,11 @@ class RollingDetectorView(Detector):
|
|
|
292
469
|
if not sc.identical(det_num, self.detector_number):
|
|
293
470
|
raise sc.CoordError("Mismatching detector numbers in weights.")
|
|
294
471
|
weights = weights.data
|
|
295
|
-
|
|
472
|
+
# Use duck typing: check for apply_full method (Histogrammer)
|
|
473
|
+
if hasattr(self._projection, 'apply_full'):
|
|
296
474
|
xs = self._projection.apply_full(weights) # Use all replicas
|
|
297
475
|
elif self._projection is not None:
|
|
298
|
-
xs = self._projection(weights)
|
|
476
|
+
xs = self._projection(weights) # LogicalDownsampler or callable
|
|
299
477
|
else:
|
|
300
478
|
xs = weights.copy()
|
|
301
479
|
nonempty = xs.values[xs.values > 0]
|
|
@@ -547,3 +547,377 @@ def test_transform_weights_raises_given_DataArray_with_bad_det_num() -> None:
|
|
|
547
547
|
)
|
|
548
548
|
with pytest.raises(sc.CoordError):
|
|
549
549
|
view.transform_weights(weights)
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
class TestLogicalView:
|
|
553
|
+
"""Tests for LogicalView class."""
|
|
554
|
+
|
|
555
|
+
def test_single_dim_downsampling(self) -> None:
|
|
556
|
+
"""Test basic 1D downsampling with transform + reduction."""
|
|
557
|
+
|
|
558
|
+
# Transform: fold into 4 groups of 2
|
|
559
|
+
def transform(da: sc.DataArray) -> sc.DataArray:
|
|
560
|
+
return da.fold(
|
|
561
|
+
dim='x_pixel_offset', sizes={'x_pixel_offset': 4, 'x_bin': 2}
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
view = raw.LogicalView(
|
|
565
|
+
transform=transform,
|
|
566
|
+
reduction_dim='x_bin',
|
|
567
|
+
)
|
|
568
|
+
|
|
569
|
+
# Create test data: each pixel has value equal to its index
|
|
570
|
+
data = sc.DataArray(
|
|
571
|
+
data=sc.arange('x_pixel_offset', 8, dtype='float64', unit='counts')
|
|
572
|
+
)
|
|
573
|
+
|
|
574
|
+
# Apply downsampling
|
|
575
|
+
result = view(data)
|
|
576
|
+
|
|
577
|
+
# Should sum pairs: [0+1, 2+3, 4+5, 6+7] = [1, 5, 9, 13]
|
|
578
|
+
expected = sc.array(
|
|
579
|
+
dims=['x_pixel_offset'],
|
|
580
|
+
values=[1.0, 5.0, 9.0, 13.0],
|
|
581
|
+
unit='counts',
|
|
582
|
+
)
|
|
583
|
+
assert sc.allclose(result.data, expected)
|
|
584
|
+
assert result.sizes == {'x_pixel_offset': 4}
|
|
585
|
+
|
|
586
|
+
def test_multi_dim_downsampling(self) -> None:
|
|
587
|
+
"""Test 2D downsampling similar to _resize_image example."""
|
|
588
|
+
|
|
589
|
+
# Transform: fold both dimensions for 8x8 -> 4x4 (2x2 binning in each dimension)
|
|
590
|
+
def transform(da: sc.DataArray) -> sc.DataArray:
|
|
591
|
+
da = da.fold(dim='x_pixel_offset', sizes={'x_pixel_offset': 4, 'x_bin': 2})
|
|
592
|
+
da = da.fold(dim='y_pixel_offset', sizes={'y_pixel_offset': 4, 'y_bin': 2})
|
|
593
|
+
return da
|
|
594
|
+
|
|
595
|
+
view = raw.LogicalView(
|
|
596
|
+
transform=transform,
|
|
597
|
+
reduction_dim=['x_bin', 'y_bin'],
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
# Create test data: constant value of 1 everywhere
|
|
601
|
+
data = sc.DataArray(
|
|
602
|
+
data=sc.ones(
|
|
603
|
+
sizes={'x_pixel_offset': 8, 'y_pixel_offset': 8}, unit='counts'
|
|
604
|
+
)
|
|
605
|
+
)
|
|
606
|
+
|
|
607
|
+
# Apply downsampling
|
|
608
|
+
result = view(data)
|
|
609
|
+
|
|
610
|
+
# Each output pixel should be sum of 2x2=4 input pixels
|
|
611
|
+
expected = sc.full(
|
|
612
|
+
dims=['x_pixel_offset', 'y_pixel_offset'],
|
|
613
|
+
shape=[4, 4],
|
|
614
|
+
value=4.0,
|
|
615
|
+
unit='counts',
|
|
616
|
+
)
|
|
617
|
+
assert sc.allclose(result.data, expected)
|
|
618
|
+
assert result.sizes == {'x_pixel_offset': 4, 'y_pixel_offset': 4}
|
|
619
|
+
|
|
620
|
+
def test_input_indices_single_dim(self) -> None:
|
|
621
|
+
"""Test that input_indices creates correct binned mapping for 1D."""
|
|
622
|
+
|
|
623
|
+
def transform(da: sc.DataArray) -> sc.DataArray:
|
|
624
|
+
return da.fold(
|
|
625
|
+
dim='x_pixel_offset', sizes={'x_pixel_offset': 4, 'x_bin': 2}
|
|
626
|
+
)
|
|
627
|
+
|
|
628
|
+
view = raw.LogicalView(
|
|
629
|
+
transform=transform,
|
|
630
|
+
reduction_dim='x_bin',
|
|
631
|
+
input_sizes={'x_pixel_offset': 8},
|
|
632
|
+
)
|
|
633
|
+
|
|
634
|
+
# Get index mapping
|
|
635
|
+
indices = view.input_indices()
|
|
636
|
+
|
|
637
|
+
# Should be binned data with 4 bins, each containing 2 indices
|
|
638
|
+
assert indices.sizes == {'x_pixel_offset': 4}
|
|
639
|
+
assert indices.bins is not None
|
|
640
|
+
|
|
641
|
+
# Check each bin contains the correct indices
|
|
642
|
+
# Bin 0: [0, 1], Bin 1: [2, 3], Bin 2: [4, 5], Bin 3: [6, 7]
|
|
643
|
+
# Extract all bin contents using the bins accessor
|
|
644
|
+
bin_sizes = indices.bins.size()
|
|
645
|
+
assert all(bin_sizes.values == 2) # Each bin should have 2 indices
|
|
646
|
+
|
|
647
|
+
# Check total count
|
|
648
|
+
assert indices.bins.size().sum().value == 8
|
|
649
|
+
|
|
650
|
+
def test_input_indices_multi_dim(self) -> None:
|
|
651
|
+
"""Test that input_indices creates correct binned mapping for 2D."""
|
|
652
|
+
|
|
653
|
+
def transform(da: sc.DataArray) -> sc.DataArray:
|
|
654
|
+
da = da.fold(dim='x_pixel_offset', sizes={'x_pixel_offset': 2, 'x_bin': 2})
|
|
655
|
+
da = da.fold(dim='y_pixel_offset', sizes={'y_pixel_offset': 2, 'y_bin': 2})
|
|
656
|
+
return da
|
|
657
|
+
|
|
658
|
+
view = raw.LogicalView(
|
|
659
|
+
transform=transform,
|
|
660
|
+
reduction_dim=['x_bin', 'y_bin'],
|
|
661
|
+
input_sizes={'x_pixel_offset': 4, 'y_pixel_offset': 4},
|
|
662
|
+
)
|
|
663
|
+
|
|
664
|
+
# Get index mapping
|
|
665
|
+
indices = view.input_indices()
|
|
666
|
+
|
|
667
|
+
# Should be binned data with 2x2 output bins
|
|
668
|
+
assert indices.sizes == {'x_pixel_offset': 2, 'y_pixel_offset': 2}
|
|
669
|
+
assert indices.bins is not None
|
|
670
|
+
|
|
671
|
+
# Each bin should contain 2x2=4 indices from the flattened input
|
|
672
|
+
bin_sizes = indices.bins.size()
|
|
673
|
+
assert all(bin_sizes.values.ravel() == 4) # Each bin should have 4 indices
|
|
674
|
+
|
|
675
|
+
# Check total count: 4x4 input pixels -> 2x2 output bins
|
|
676
|
+
assert indices.bins.size().sum().value == 16
|
|
677
|
+
|
|
678
|
+
def test_with_varying_input_values(self) -> None:
|
|
679
|
+
"""Test that downsampling correctly sums varying input values."""
|
|
680
|
+
|
|
681
|
+
def transform(da: sc.DataArray) -> sc.DataArray:
|
|
682
|
+
return da.fold(
|
|
683
|
+
dim='x_pixel_offset', sizes={'x_pixel_offset': 3, 'x_bin': 2}
|
|
684
|
+
)
|
|
685
|
+
|
|
686
|
+
view = raw.LogicalView(
|
|
687
|
+
transform=transform,
|
|
688
|
+
reduction_dim='x_bin',
|
|
689
|
+
)
|
|
690
|
+
|
|
691
|
+
# Create test data with specific values: [10, 20, 30, 40, 50, 60]
|
|
692
|
+
data = sc.DataArray(
|
|
693
|
+
data=sc.array(
|
|
694
|
+
dims=['x_pixel_offset'],
|
|
695
|
+
values=[10.0, 20.0, 30.0, 40.0, 50.0, 60.0],
|
|
696
|
+
unit='counts',
|
|
697
|
+
)
|
|
698
|
+
)
|
|
699
|
+
|
|
700
|
+
result = view(data)
|
|
701
|
+
|
|
702
|
+
# Should sum pairs: [10+20, 30+40, 50+60] = [30, 70, 110]
|
|
703
|
+
expected = sc.array(
|
|
704
|
+
dims=['x_pixel_offset'],
|
|
705
|
+
values=[30.0, 70.0, 110.0],
|
|
706
|
+
unit='counts',
|
|
707
|
+
)
|
|
708
|
+
assert sc.allclose(result.data, expected)
|
|
709
|
+
|
|
710
|
+
def test_transform_without_reduction_slicing(self) -> None:
|
|
711
|
+
"""Test transform without reduction (slicing to select front layer)."""
|
|
712
|
+
|
|
713
|
+
# Transform: fold to 3D volume, then slice front layer
|
|
714
|
+
def transform(da: sc.DataArray) -> sc.DataArray:
|
|
715
|
+
return da.fold(dim='voxel', sizes={'x': 2, 'y': 2, 'z': 3})['z', 0]
|
|
716
|
+
|
|
717
|
+
view = raw.LogicalView(transform=transform)
|
|
718
|
+
|
|
719
|
+
# Create test data: each voxel has value equal to its index
|
|
720
|
+
data = sc.DataArray(data=sc.arange('voxel', 12, dtype='float64', unit='counts'))
|
|
721
|
+
|
|
722
|
+
result = view(data)
|
|
723
|
+
|
|
724
|
+
# fold orders: z is innermost, so z=0 gives every 3rd element starting at 0
|
|
725
|
+
# indices: 0, 3, 6, 9
|
|
726
|
+
assert result.sizes == {'x': 2, 'y': 2}
|
|
727
|
+
expected = sc.array(
|
|
728
|
+
dims=['x', 'y'],
|
|
729
|
+
values=[[0.0, 3.0], [6.0, 9.0]],
|
|
730
|
+
unit='counts',
|
|
731
|
+
)
|
|
732
|
+
assert sc.allclose(result.data, expected)
|
|
733
|
+
|
|
734
|
+
def test_transform_without_reduction_reshape(self) -> None:
|
|
735
|
+
"""Test transform without reduction (pure reshape)."""
|
|
736
|
+
|
|
737
|
+
# Transform: just fold without any reduction
|
|
738
|
+
def transform(da: sc.DataArray) -> sc.DataArray:
|
|
739
|
+
return da.fold(dim='pixel', sizes={'x': 3, 'y': 4})
|
|
740
|
+
|
|
741
|
+
view = raw.LogicalView(transform=transform)
|
|
742
|
+
|
|
743
|
+
data = sc.DataArray(data=sc.arange('pixel', 12, dtype='float64', unit='counts'))
|
|
744
|
+
|
|
745
|
+
result = view(data)
|
|
746
|
+
|
|
747
|
+
# Should just reshape, no reduction
|
|
748
|
+
assert result.sizes == {'x': 3, 'y': 4}
|
|
749
|
+
assert result.data.sum().value == 66.0 # Sum of 0..11
|
|
750
|
+
|
|
751
|
+
def test_input_indices_without_reduction(self) -> None:
|
|
752
|
+
"""Test that input_indices returns dense indices when no reduction."""
|
|
753
|
+
|
|
754
|
+
def transform(da: sc.DataArray) -> sc.DataArray:
|
|
755
|
+
return da.fold(dim='voxel', sizes={'x': 2, 'y': 2, 'z': 3})['z', 0]
|
|
756
|
+
|
|
757
|
+
view = raw.LogicalView(
|
|
758
|
+
transform=transform,
|
|
759
|
+
input_sizes={'voxel': 12},
|
|
760
|
+
)
|
|
761
|
+
|
|
762
|
+
indices = view.input_indices()
|
|
763
|
+
|
|
764
|
+
# Should be dense (not binned) - 1:1 mapping
|
|
765
|
+
assert indices.bins is None
|
|
766
|
+
assert indices.sizes == {'x': 2, 'y': 2}
|
|
767
|
+
|
|
768
|
+
# Indices should correspond to front layer of folded volume
|
|
769
|
+
# fold orders: z is innermost, so z=0 gives every 3rd index starting at 0
|
|
770
|
+
expected = sc.array(dims=['x', 'y'], values=[[0, 3], [6, 9]], unit=None)
|
|
771
|
+
assert sc.identical(indices.data, expected)
|
|
772
|
+
|
|
773
|
+
def test_input_indices_without_reduction_preserves_total_count(self) -> None:
|
|
774
|
+
"""Test that non-reducing input_indices has correct number of indices."""
|
|
775
|
+
|
|
776
|
+
def transform(da: sc.DataArray) -> sc.DataArray:
|
|
777
|
+
return da.fold(dim='pixel', sizes={'x': 4, 'y': 5})
|
|
778
|
+
|
|
779
|
+
view = raw.LogicalView(
|
|
780
|
+
transform=transform,
|
|
781
|
+
input_sizes={'pixel': 20},
|
|
782
|
+
)
|
|
783
|
+
|
|
784
|
+
indices = view.input_indices()
|
|
785
|
+
|
|
786
|
+
# Dense indices should have same total size as output shape
|
|
787
|
+
assert indices.sizes == {'x': 4, 'y': 5}
|
|
788
|
+
assert indices.data.size == 20
|
|
789
|
+
|
|
790
|
+
|
|
791
|
+
class TestRollingDetectorViewWithLogicalView:
|
|
792
|
+
"""Tests for RollingDetectorView integration with LogicalView."""
|
|
793
|
+
|
|
794
|
+
def test_as_projection(self) -> None:
|
|
795
|
+
"""Test that RollingDetectorView works with LogicalView as projection."""
|
|
796
|
+
# Create a 1D detector with 8 pixels
|
|
797
|
+
detector_number = sc.arange('x_pixel_offset', 1, 9, unit=None)
|
|
798
|
+
|
|
799
|
+
# Define downsampling transform: 8 -> 4 pixels (2x binning)
|
|
800
|
+
def transform(da: sc.DataArray) -> sc.DataArray:
|
|
801
|
+
return da.fold(
|
|
802
|
+
dim='x_pixel_offset', sizes={'x_pixel_offset': 4, 'x_bin': 2}
|
|
803
|
+
)
|
|
804
|
+
|
|
805
|
+
# Create RollingDetectorView with LogicalView using factory method
|
|
806
|
+
view = raw.RollingDetectorView.with_logical_view(
|
|
807
|
+
detector_number=detector_number,
|
|
808
|
+
window=2,
|
|
809
|
+
transform=transform,
|
|
810
|
+
reduction_dim='x_bin',
|
|
811
|
+
)
|
|
812
|
+
|
|
813
|
+
# Add some counts: pixels 1, 2, 3, 4 -> downsampled bins [0, 1]
|
|
814
|
+
view.add_counts([1, 2, 3, 4])
|
|
815
|
+
result = view.get()
|
|
816
|
+
|
|
817
|
+
# After downsampling: [1+2, 3+4] = [2, 2] for first two bins
|
|
818
|
+
assert result.sizes == {'x_pixel_offset': 4}
|
|
819
|
+
assert result['x_pixel_offset', 0].value == 2
|
|
820
|
+
assert result['x_pixel_offset', 1].value == 2
|
|
821
|
+
assert result['x_pixel_offset', 2].value == 0
|
|
822
|
+
assert result['x_pixel_offset', 3].value == 0
|
|
823
|
+
|
|
824
|
+
def test_make_roi_filter(self) -> None:
|
|
825
|
+
"""Test that make_roi_filter() works with LogicalView."""
|
|
826
|
+
detector_number = sc.arange('x_pixel_offset', 1, 9, unit=None)
|
|
827
|
+
|
|
828
|
+
def transform(da: sc.DataArray) -> sc.DataArray:
|
|
829
|
+
return da.fold(
|
|
830
|
+
dim='x_pixel_offset', sizes={'x_pixel_offset': 4, 'x_bin': 2}
|
|
831
|
+
)
|
|
832
|
+
|
|
833
|
+
view = raw.RollingDetectorView.with_logical_view(
|
|
834
|
+
detector_number=detector_number,
|
|
835
|
+
window=1,
|
|
836
|
+
transform=transform,
|
|
837
|
+
reduction_dim='x_bin',
|
|
838
|
+
)
|
|
839
|
+
|
|
840
|
+
# Should not raise - LogicalView has input_indices()
|
|
841
|
+
roi_filter = view.make_roi_filter()
|
|
842
|
+
|
|
843
|
+
# The indices should be binned data (check via private attribute for now)
|
|
844
|
+
assert roi_filter._indices.bins is not None
|
|
845
|
+
assert roi_filter._indices.sizes == {'x_pixel_offset': 4}
|
|
846
|
+
# Each bin should contain 2 indices
|
|
847
|
+
assert all(roi_filter._indices.bins.size().values == 2)
|
|
848
|
+
|
|
849
|
+
def test_transform_weights(self) -> None:
|
|
850
|
+
"""Test that transform_weights() works with LogicalView."""
|
|
851
|
+
detector_number = sc.arange('x_pixel_offset', 1, 9, unit=None)
|
|
852
|
+
|
|
853
|
+
def transform(da: sc.DataArray) -> sc.DataArray:
|
|
854
|
+
return da.fold(
|
|
855
|
+
dim='x_pixel_offset', sizes={'x_pixel_offset': 4, 'x_bin': 2}
|
|
856
|
+
)
|
|
857
|
+
|
|
858
|
+
view = raw.RollingDetectorView.with_logical_view(
|
|
859
|
+
detector_number=detector_number,
|
|
860
|
+
window=1,
|
|
861
|
+
transform=transform,
|
|
862
|
+
reduction_dim='x_bin',
|
|
863
|
+
)
|
|
864
|
+
|
|
865
|
+
# Create weights: all pixels have weight 1.0
|
|
866
|
+
weights = sc.ones(sizes={'x_pixel_offset': 8}, dtype='float32', unit='')
|
|
867
|
+
|
|
868
|
+
# Transform weights through the downsampler
|
|
869
|
+
transformed = view.transform_weights(weights)
|
|
870
|
+
|
|
871
|
+
# After downsampling: each output bin sums 2 input weights = 2.0
|
|
872
|
+
assert transformed.sizes == {'x_pixel_offset': 4}
|
|
873
|
+
expected = sc.full(
|
|
874
|
+
dims=['x_pixel_offset'], shape=[4], value=2.0, dtype='float32', unit=''
|
|
875
|
+
)
|
|
876
|
+
assert sc.allclose(transformed.data, expected)
|
|
877
|
+
|
|
878
|
+
def test_with_non_reducing_view(self) -> None:
|
|
879
|
+
"""Test RollingDetectorView with LogicalView without reduction (slicing)."""
|
|
880
|
+
# 12 voxels that will be folded into 2x2x3 and sliced to front layer
|
|
881
|
+
detector_number = sc.arange('voxel', 1, 13, unit=None)
|
|
882
|
+
|
|
883
|
+
def transform(da: sc.DataArray) -> sc.DataArray:
|
|
884
|
+
return da.fold(dim='voxel', sizes={'x': 2, 'y': 2, 'z': 3})['z', 0]
|
|
885
|
+
|
|
886
|
+
view = raw.RollingDetectorView.with_logical_view(
|
|
887
|
+
detector_number=detector_number,
|
|
888
|
+
window=2,
|
|
889
|
+
transform=transform,
|
|
890
|
+
# No reduction_dim - pure transform
|
|
891
|
+
)
|
|
892
|
+
|
|
893
|
+
# Add counts for detector_numbers that map to front layer (z=0)
|
|
894
|
+
# z is innermost, so front layer indices are 0, 3, 6, 9 (every 3rd)
|
|
895
|
+
# detector_numbers are 1-indexed, so front layer det_nums are 1, 4, 7, 10
|
|
896
|
+
view.add_counts([1, 4, 7, 10])
|
|
897
|
+
result = view.get()
|
|
898
|
+
|
|
899
|
+
assert result.sizes == {'x': 2, 'y': 2}
|
|
900
|
+
# Each front-layer pixel gets one count
|
|
901
|
+
expected = sc.array(
|
|
902
|
+
dims=['x', 'y'], values=[[1, 1], [1, 1]], dtype='int32', unit='counts'
|
|
903
|
+
)
|
|
904
|
+
assert sc.identical(result.data, expected)
|
|
905
|
+
|
|
906
|
+
def test_make_roi_filter_with_non_reducing_view(self) -> None:
|
|
907
|
+
"""Test make_roi_filter with non-reducing LogicalView returns dense indices."""
|
|
908
|
+
detector_number = sc.arange('voxel', 1, 13, unit=None)
|
|
909
|
+
|
|
910
|
+
def transform(da: sc.DataArray) -> sc.DataArray:
|
|
911
|
+
return da.fold(dim='voxel', sizes={'x': 2, 'y': 2, 'z': 3})['z', 0]
|
|
912
|
+
|
|
913
|
+
view = raw.RollingDetectorView.with_logical_view(
|
|
914
|
+
detector_number=detector_number,
|
|
915
|
+
window=1,
|
|
916
|
+
transform=transform,
|
|
917
|
+
)
|
|
918
|
+
|
|
919
|
+
roi_filter = view.make_roi_filter()
|
|
920
|
+
|
|
921
|
+
# Indices should be dense (not binned) for non-reducing view
|
|
922
|
+
assert roi_filter._indices.bins is None
|
|
923
|
+
assert roi_filter._indices.sizes == {'x': 2, 'y': 2}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|