Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JP-3799: First frame bright #8952

Merged
merged 17 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/8952.firstframe.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Update the firstframe step to optionally not flag when a ramp saturates in group 3
11 changes: 10 additions & 1 deletion docs/jwst/firstframe/arguments.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
Step Arguments
==============

The first frame correction has no step-specific arguments.
The ``firstframe`` step has the following step-specific arguments.

``--bright_use_group1`` (boolean, default=False)
If True, setting the group 1 groupdq to DO_NOT_USE will not be done
for pixels that have the saturation flag set for group 3.
This will allow a slope to be determined for pixels that saturate in group 3.
This change in flagging will only impact pixels that saturate in group 3, the behavior
for all other pixels will be unchanged.
The `bright_use_group1` flag can be set for all data, only data/pixels that saturate
in group 3 will see a difference in behavior.
13 changes: 8 additions & 5 deletions jwst/firstframe/firstframe_step.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class FirstFrameStep(Step):
class_alias = "firstframe"

spec = """
bright_use_group1 = boolean(default=False) # do not flag group1 if group3 is saturated
"""

def process(self, step_input):
Expand All @@ -25,16 +26,18 @@ def process(self, step_input):

# check the data is MIRI data
detector = input_model.meta.instrument.detector.upper()
if detector[:3] != 'MIR':
self.log.warning('First Frame Correction is only for MIRI data')
self.log.warning('First frame step will be skipped')
input_model.meta.cal_step.firstframe = 'SKIPPED'
if detector[:3] != "MIR":
self.log.warning("First Frame Correction is only for MIRI data")
self.log.warning("First frame step will be skipped")
input_model.meta.cal_step.firstframe = "SKIPPED"
return input_model

# Cork on a copy
result = input_model.copy()

# Do the firstframe correction subtraction
result = firstframe_sub.do_correction(result)
result = firstframe_sub.do_correction(
result, bright_use_group1=self.bright_use_group1
)

return result
30 changes: 24 additions & 6 deletions jwst/firstframe/firstframe_sub.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
log.setLevel(logging.DEBUG)


def do_correction(output):
def do_correction(output, bright_use_group1=False):
"""
Short Summary
-------------
Expand All @@ -22,6 +22,8 @@ def do_correction(output):
----------
output: data model object
science data to be corrected
bright_use_group1: boolean
do not flag group1 for bright pixels = group 3 saturated

Returns
-------
Expand All @@ -36,13 +38,29 @@ def do_correction(output):
# Update the step status, and if ngroups > 3, set all GROUPDQ in
# the first group to 'DO_NOT_USE'
if sci_ngroups > 3:
output.groupdq[:, 0, :, :] = \
np.bitwise_or(output.groupdq[:, 0, :, :], dqflags.group['DO_NOT_USE'])
if bright_use_group1:
# do not set DO_NOT_USE in the case where saturation happens in
# group3 as in this case the first frame effect is small compared to the
# signal in group2-group1
svals = (output.groupdq[:, 2, :, :] & dqflags.group["SATURATED"]) > 0
tvals = output.groupdq[:, 0, :, :]
tvals[~svals] = np.bitwise_or(
(output.groupdq[:, 0, :, :])[~svals], dqflags.group["DO_NOT_USE"]
)
output.groupdq[:, 0, :, :] = tvals
log.info(
f"FirstFrame Sub: bright_first_frame set, #{np.sum(svals)} bright pixels group1 not set to DO_NOT_USE"
)
else:
output.groupdq[:, 0, :, :] = np.bitwise_or(
output.groupdq[:, 0, :, :], dqflags.group["DO_NOT_USE"]
)

log.debug("FirstFrame Sub: resetting GROUPDQ in first frame to DO_NOT_USE")
output.meta.cal_step.firstframe = 'COMPLETE'
else: # too few groups
output.meta.cal_step.firstframe = "COMPLETE"
else: # too few groups
log.warning("Too few groups to apply correction")
log.warning("Step will be skipped")
output.meta.cal_step.firstframe = 'SKIPPED'
output.meta.cal_step.firstframe = "SKIPPED"

return output
59 changes: 58 additions & 1 deletion jwst/firstframe/tests/test_firstframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
def test_firstframe_set_groupdq():
"""
Test if the firstframe code set the groupdq flag on the first
group to 'do_not_use' for 5 integrations
group to 'do_not_use' for 5 groups
"""

# size of integration
Expand Down Expand Up @@ -216,3 +216,60 @@ def test_miri():
dq_diff,
err_msg='Diff in groupdq flags is not '
+ 'equal to DO_NOT_USE')


def test_firstframe_bright_use_group1():
"""
Test if the firstframe code when bright_use_group1 is set to True.

The groupdq flag for group 1 should not be set to DO_NOT_USE for the pixels that saturate
the 3rd group. Otherwise, all other pixels should have their group1 groupdq
flags set to DO_NOT_USE.
"""

# size of integration
ngroups = 5
xsize = 1032
ysize = 1024

# create the data and groupdq arrays
csize = (1, ngroups, ysize, xsize)
data = np.full(csize, 1.0)
groupdq = np.zeros(csize, dtype=int)

# set a fraction of the pixels to saturate in between the 2nd and 3rd groups
groupdq[0, 2, 0:100, :] = dqflags.group['SATURATED']

# set a fraction of the pixels to saturate in between the 1st and 2nd groups
groupdq[0, 2, 200:300, :] = dqflags.group['SATURATED']
groupdq[0, 1, 200:300, :] = dqflags.group['SATURATED']

# create a JWST datamodel for MIRI data
dm_ramp = RampModel(data=data, groupdq=groupdq)

# run the first frame correction step on a copy (the detection to make the copy or
# not would have happened at _step.py)
dm_ramp_firstframe = do_correction(dm_ramp.copy(), bright_use_group1=True)

# check that the difference in the groupdq flags is equal to
# the 'do_not_use' flag
dq_diff = dm_ramp_firstframe.groupdq[0, 0, :, :] - dm_ramp.groupdq[0, 0, :, :]

expected_diff = np.full((ysize, xsize), dqflags.group['DO_NOT_USE'], dtype=int)
expected_diff[0:100, :] = 0
expected_diff[200:300, :] = 0

np.testing.assert_array_equal(expected_diff,
dq_diff,
err_msg='Diff in groupdq flags is not '
+ 'equal to the DO_NOT_USE flag')

# test that the groupdq flags are not changed for the rest of the groups
dq_diff = (dm_ramp_firstframe.groupdq[0, 1:ngroups, :, :]
- dm_ramp.groupdq[0, 1:ngroups, :, :])
np.testing.assert_array_equal(np.full((ngroups - 1, ysize, xsize),
0,
dtype=int),
dq_diff,
err_msg='n >= 2 groupdq flags changes '
+ 'and they should not be')
Loading