Skip to content

Commit 4e17bfc

Browse files
authored
Merge pull request #904 from HEXRD/apply-fancing-indexing-after-dark
Apply fancy indexing after dark subtraction
2 parents 625a795 + b4255dc commit 4e17bfc

File tree

2 files changed

+47
-15
lines changed

2 files changed

+47
-15
lines changed

hexrd/core/imageseries/process.py

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -76,34 +76,37 @@ def _process_frame(self, key):
7676
# note: key refers to original imageseries
7777
oplist = self.oplist
7878

79+
# Separate the frame index from any fancy indexing (row/col slices).
80+
# Fancy indexing must be deferred until after all operations are
81+
# applied, otherwise operations like dark subtraction will receive
82+
# a spatially-indexed subarray that doesn't match the full-frame
83+
# operation data (e.g. full-size dark image).
84+
if isinstance(key, int):
85+
idx = key
86+
rest = []
87+
else:
88+
idx = key[0]
89+
rest = key[1:]
90+
7991
# when rectangle is the first operation we can try to call the
8092
# optimized version. If the adapter provides one it should be
8193
# significantly faster if not it will fallback to the same
8294
# implementation that _rectangle provides.
8395
if oplist and oplist[0][0] == self.RECT:
84-
region = oplist[0][1]
85-
if isinstance(key, int):
86-
idx = key
87-
rest = []
88-
else:
89-
# Handle fancy indexing
90-
idx = key[0]
91-
rest = key[1:]
92-
93-
img = self._rectangle_optimized(idx, region)
94-
95-
if rest:
96-
img = img[*rest]
97-
96+
img = self._rectangle_optimized(idx, oplist[0][1])
9897
# remove the first operation since we already used it
9998
oplist = oplist[1:]
10099
else:
101-
img = self._imser[key]
100+
img = self._imser[idx]
102101

103102
for k, d in oplist:
104103
func = self._opdict[k]
105104
img = func(img, d)
106105

106+
# Apply fancy indexing after all operations
107+
if rest:
108+
img = img[*rest]
109+
107110
return img
108111

109112
def _subtract_dark(self, img, dark):

tests/core/imageseries/test_process.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,35 @@ def test_op_rectangle(mock_series):
114114
assert res.shape == (2, 2)
115115

116116

117+
def test_dark_before_rectangle_with_fancy_indexing():
118+
"""Regression: fancy indexing with dark+rectangle must apply ops first.
119+
120+
This reproduces the actual bug from pull_spots, which accesses pixels
121+
via omega_image_series[frame_idx, row_indices, col_indices]. When dark
122+
is before rectangle in the oplist (the normal GUI ordering), fancy
123+
indexing was being applied to the raw image BEFORE dark subtraction,
124+
producing a small subarray that couldn't broadcast with the full dark.
125+
"""
126+
data = np.arange(16, dtype=np.float32).reshape(4, 4) + 10
127+
imser = MockImageSeries(shape=(4, 4), data={0: data})
128+
dark = np.full((4, 4), 2.0)
129+
roi = ((1, 3), (1, 3))
130+
oplist = [('dark', dark), ('rectangle', roi)]
131+
ps = ProcessedImageSeries(imser, oplist)
132+
133+
# Access with fancy indexing like pull_spots does
134+
res = ps[0, 0, 0]
135+
# Frame 0 data is all 10+offset, dark is 2, rectangle crops (1:3,1:3)
136+
# After dark: data - 2, then rectangle crops rows 1-2, cols 1-2
137+
# So pixel [0,0] of the result = data[1,1] - 2 = 15 - 2 = 13
138+
expected_full = data[1:3, 1:3] - 2.0
139+
assert res == expected_full[0, 0]
140+
141+
# Also test with slice indexing
142+
res_slice = ps[(0, slice(0, 2))]
143+
np.testing.assert_array_equal(res_slice, expected_full)
144+
145+
117146
def test_op_rectangle_optimized(mock_series):
118147
oplist = [('rectangle', ((0, 2), (0, 2)))]
119148
ps = ProcessedImageSeries(mock_series, oplist)

0 commit comments

Comments
 (0)