Skip to content

Commit ef2c65f

Browse files
authored
JP-3593: 2nd group saturation part 2 (#283)
2 parents 448413b + 3b924fb commit ef2c65f

File tree

2 files changed

+75
-7
lines changed

2 files changed

+75
-7
lines changed

Diff for: changes/283.bugfix.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[saturation] Add option for using the readout pattern information to improve saturation flagging in grouped data.

Diff for: src/stcal/saturation/saturation.py

+74-7
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ def flag_saturated_pixels(
6363
updated pixel dq array
6464
"""
6565
nints, ngroups, nrows, ncols = data.shape
66+
dnu = dqflags["DO_NOT_USE"]
6667
saturated = dqflags["SATURATED"]
6768
ad_floor = dqflags["AD_FLOOR"]
6869
no_sat_check = dqflags["NO_SAT_CHECK"]
@@ -81,16 +82,11 @@ def flag_saturated_pixels(
8182
sat_thresh[no_sat_check_mask] = atod_limit + 1
8283

8384
for ints in range(nints):
85+
# Work forward through the groups for initial pass at saturation
8486
for group in range(ngroups):
8587
plane = data[ints, group, :, :]
8688

87-
if read_pattern is not None:
88-
dilution_factor = np.mean(read_pattern[group]) / read_pattern[group][-1]
89-
dilution_factor = np.where(no_sat_check_mask, 1, dilution_factor)
90-
else:
91-
dilution_factor = 1
92-
93-
flagarray, flaglowarray = plane_saturation(plane, sat_thresh * dilution_factor, dqflags)
89+
flagarray, flaglowarray = plane_saturation(plane, sat_thresh, dqflags)
9490

9591
# for saturation, the flag is set in the current plane
9692
# and all following planes.
@@ -108,6 +104,77 @@ def flag_saturated_pixels(
108104

109105
gdq[ints, group, :, :] = adjacent_pixels(gdq_slice, saturated, n_pix_grow_sat)
110106

107+
# Work backward through the groups for a second pass at saturation
108+
# This is to flag things that actually saturated in prior groups but
109+
# were not obvious because of group averaging
110+
for group in range(ngroups-2, -1, -1):
111+
plane = data[ints, group, :, :]
112+
thisdq = gdq[ints, group, :, :]
113+
nextdq = gdq[ints, group + 1, :, :]
114+
115+
# Determine the dilution factor due to group averaging
116+
if read_pattern is not None:
117+
# Single value dilution factor for this group
118+
dilution_factor = np.mean(read_pattern[group]) / read_pattern[group][-1]
119+
# Broadcast to array size
120+
dilution_factor = np.where(no_sat_check_mask, 1, dilution_factor)
121+
else:
122+
dilution_factor = 1
123+
124+
# Find where this plane looks like it might saturate given the dilution factor
125+
flagarray, _ = plane_saturation(plane, sat_thresh * dilution_factor, dqflags)
126+
127+
# Find the overlap of where this plane looks like it might saturate, was not currently
128+
# flagged as saturation or DO_NOT_USE, and the next group had saturation flagged.
129+
indx = np.where((np.bitwise_and(flagarray, saturated) != 0) & \
130+
(np.bitwise_and(thisdq, saturated) == 0) & \
131+
(np.bitwise_and(thisdq, dnu) == 0) & \
132+
(np.bitwise_and(nextdq, saturated) != 0))
133+
134+
# Reset flag array to only pixels passing this gauntlet
135+
flagarray[:] = 0
136+
flagarray[indx] = 2
137+
138+
# Grow the newly-flagged saturating pixels
139+
if n_pix_grow_sat > 0:
140+
flagarray = adjacent_pixels(flagarray, saturated, n_pix_grow_sat)
141+
142+
# Add them to the gdq array
143+
np.bitwise_or(gdq[ints, group, :, :], flagarray, gdq[ints, group, :, :])
144+
145+
# Add an additional pass to look for things saturating in the second group
146+
# that can be particularly tricky to identify
147+
if ((read_pattern is not None) & (ngroups > 2)):
148+
dq2 = gdq[ints, 1, :, :]
149+
dq3 = gdq[ints, 2, :, :]
150+
151+
# Identify groups which we wouldn't expect to saturate by the third group,
152+
# on the basis of the first group
153+
scigp1 = data[ints, 0, :, :]
154+
mask = scigp1 / np.mean(read_pattern[0]) * read_pattern[2][-1] < sat_thresh
155+
156+
# Identify groups with suspiciously large values in the second group
157+
# In the limit of groups with just nframe this just checks if second group
158+
# is over the regular saturation limit.
159+
scigp2 = data[ints, 1, :, :]
160+
mask &= scigp2 > sat_thresh / len(read_pattern[1])
161+
162+
# Identify groups that are saturated in the third group but not yet flagged in the second
163+
gp3mask = np.where((np.bitwise_and(dq3, saturated) != 0) & \
164+
(np.bitwise_and(dq2, saturated) == 0), True, False)
165+
mask &= gp3mask
166+
167+
# Flag the 2nd group for the pixels passing that gauntlet
168+
flagarray = np.zeros_like(mask,dtype='uint8')
169+
flagarray[mask] = saturated
170+
# flag any pixels that border these new pixels
171+
if n_pix_grow_sat > 0:
172+
flagarray = adjacent_pixels(flagarray, saturated, n_pix_grow_sat)
173+
174+
# Add them to the gdq array
175+
np.bitwise_or(gdq[ints, 1, :, :], flagarray, gdq[ints, 1, :, :])
176+
177+
111178
# Check ZEROFRAME.
112179
if zframe is not None:
113180
plane = zframe[ints, :, :]

0 commit comments

Comments
 (0)