Skip to content

Commit b4255dc

Browse files
committed
Apply fancy indexing after dark subtraction
`pull_spots()` performs fancy indexing on the imageseries [here](https://github.com/HEXRD/hexrd/blob/625a7950e61aa63f528b343dcc8e82c3b9d9f5f4/hexrd/core/instrument/hedm_instrument.py#L1932). If a `ProcessedImageSeries` was used that had dark background subtraction first, then the fancy indexing would cause errors to occur. The reason was that the fancy indexing was being performed *before* the dark background subtraction, which meant that the fancy indexed image would not have a shape that matched the dark background subtraction. We need to delay fancy indexing until after all operations have finished, so that the fancy indexing doesn't interfere with them. This PR fixes the issue. Fixes: #903 Signed-off-by: Patrick Avery <patrick.avery@kitware.com>
1 parent 625a795 commit b4255dc

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)