diff --git a/changes/8952.firstframe.rst b/changes/8952.firstframe.rst new file mode 100644 index 0000000000..ff41581d2f --- /dev/null +++ b/changes/8952.firstframe.rst @@ -0,0 +1 @@ +Update the firstframe step to optionally not flag when a ramp saturates in group 3 \ No newline at end of file diff --git a/docs/jwst/firstframe/arguments.rst b/docs/jwst/firstframe/arguments.rst index e0d91688e0..0bbf2623f6 100644 --- a/docs/jwst/firstframe/arguments.rst +++ b/docs/jwst/firstframe/arguments.rst @@ -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. diff --git a/jwst/firstframe/firstframe_step.py b/jwst/firstframe/firstframe_step.py index 47e9fcb72b..0e4d593b01 100755 --- a/jwst/firstframe/firstframe_step.py +++ b/jwst/firstframe/firstframe_step.py @@ -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): @@ -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 diff --git a/jwst/firstframe/firstframe_sub.py b/jwst/firstframe/firstframe_sub.py index a535fb203e..401904fa10 100644 --- a/jwst/firstframe/firstframe_sub.py +++ b/jwst/firstframe/firstframe_sub.py @@ -10,7 +10,7 @@ log.setLevel(logging.DEBUG) -def do_correction(output): +def do_correction(output, bright_use_group1=False): """ Short Summary ------------- @@ -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 ------- @@ -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 diff --git a/jwst/firstframe/tests/test_firstframe.py b/jwst/firstframe/tests/test_firstframe.py index 7d48d5bfce..9cf4437fad 100644 --- a/jwst/firstframe/tests/test_firstframe.py +++ b/jwst/firstframe/tests/test_firstframe.py @@ -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 @@ -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') \ No newline at end of file