essreduce 25.11.3__py3-none-any.whl → 25.11.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ess/reduce/live/raw.py +183 -5
- {essreduce-25.11.3.dist-info → essreduce-25.11.4.dist-info}/METADATA +1 -1
- {essreduce-25.11.3.dist-info → essreduce-25.11.4.dist-info}/RECORD +7 -7
- {essreduce-25.11.3.dist-info → essreduce-25.11.4.dist-info}/WHEEL +0 -0
- {essreduce-25.11.3.dist-info → essreduce-25.11.4.dist-info}/entry_points.txt +0 -0
- {essreduce-25.11.3.dist-info → essreduce-25.11.4.dist-info}/licenses/LICENSE +0 -0
- {essreduce-25.11.3.dist-info → essreduce-25.11.4.dist-info}/top_level.txt +0 -0
ess/reduce/live/raw.py
CHANGED
|
@@ -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]
|
|
@@ -10,7 +10,7 @@ ess/reduce/workflow.py,sha256=738-lcdgsORYfQ4A0UTk2IgnbVxC3jBdpscpaOFIpdc,3114
|
|
|
10
10
|
ess/reduce/data/__init__.py,sha256=uDtqkmKA_Zwtj6II25zntz9T812XhdCn3tktYev4uyY,486
|
|
11
11
|
ess/reduce/data/_registry.py,sha256=50qY36y5gd2i3JP0Ks6bXApGcW6l70qA6riO0m9Bz4o,11475
|
|
12
12
|
ess/reduce/live/__init__.py,sha256=jPQVhihRVNtEDrE20PoKkclKV2aBF1lS7cCHootgFgI,204
|
|
13
|
-
ess/reduce/live/raw.py,sha256=
|
|
13
|
+
ess/reduce/live/raw.py,sha256=CkPqp4VMNvj0IcFPp1J0n7sVt5PNKdIXnDlALCg9W_Q,31031
|
|
14
14
|
ess/reduce/live/roi.py,sha256=Hs-pW98k41WU6Kl3UQ41kQawk80c2QNOQ_WNctLzDPE,3795
|
|
15
15
|
ess/reduce/live/workflow.py,sha256=bsbwvTqPhRO6mC__3b7MgU7DWwAnOvGvG-t2n22EKq8,4285
|
|
16
16
|
ess/reduce/nexus/__init__.py,sha256=xXc982vZqRba4jR4z5hA2iim17Z7niw4KlS1aLFbn1Q,1107
|
|
@@ -41,9 +41,9 @@ ess/reduce/widgets/_spinner.py,sha256=2VY4Fhfa7HMXox2O7UbofcdKsYG-AJGrsgGJB85nDX
|
|
|
41
41
|
ess/reduce/widgets/_string_widget.py,sha256=iPAdfANyXHf-nkfhgkyH6gQDklia0LebLTmwi3m-iYQ,1482
|
|
42
42
|
ess/reduce/widgets/_switchable_widget.py,sha256=fjKz99SKLhIF1BLgGVBSKKn3Lu_jYBwDYGeAjbJY3Q8,2390
|
|
43
43
|
ess/reduce/widgets/_vector_widget.py,sha256=aTaBqCFHZQhrIoX6-sSqFWCPePEW8HQt5kUio8jP1t8,1203
|
|
44
|
-
essreduce-25.11.
|
|
45
|
-
essreduce-25.11.
|
|
46
|
-
essreduce-25.11.
|
|
47
|
-
essreduce-25.11.
|
|
48
|
-
essreduce-25.11.
|
|
49
|
-
essreduce-25.11.
|
|
44
|
+
essreduce-25.11.4.dist-info/licenses/LICENSE,sha256=nVEiume4Qj6jMYfSRjHTM2jtJ4FGu0g-5Sdh7osfEYw,1553
|
|
45
|
+
essreduce-25.11.4.dist-info/METADATA,sha256=Foul2luwyG1rhulA2Q5KlXbxtrYFGlp99ApSkGjAKOE,1937
|
|
46
|
+
essreduce-25.11.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
47
|
+
essreduce-25.11.4.dist-info/entry_points.txt,sha256=PMZOIYzCifHMTe4pK3HbhxUwxjFaZizYlLD0td4Isb0,66
|
|
48
|
+
essreduce-25.11.4.dist-info/top_level.txt,sha256=0JxTCgMKPLKtp14wb1-RKisQPQWX7i96innZNvHBr-s,4
|
|
49
|
+
essreduce-25.11.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|