diff --git a/changes/8847.master_background.rst b/changes/8847.master_background.rst new file mode 100644 index 0000000000..f1b516a1f2 --- /dev/null +++ b/changes/8847.master_background.rst @@ -0,0 +1 @@ +Include resample, pixel_replace and extract_1d into MOS background pipeline diff --git a/docs/jwst/master_background/description.rst b/docs/jwst/master_background/description.rst index 96f90e82aa..d54b1eb0e6 100644 --- a/docs/jwst/master_background/description.rst +++ b/docs/jwst/master_background/description.rst @@ -376,6 +376,7 @@ as follows: extended sources (appropriate for background signal), and saving the extended source correction arrays for each slit in an internal copy of the data model #. If a user-supplied master background spectrum is **not** given, the + :ref:`pixel_replace `, :ref:`resample_spec ` and :ref:`extract_1d ` steps are applied to the calibrated background slits, resulting in extracted 1D background spectra diff --git a/jwst/master_background/master_background_mos_step.py b/jwst/master_background/master_background_mos_step.py index 78a6bcfb96..d5ad86b711 100644 --- a/jwst/master_background/master_background_mos_step.py +++ b/jwst/master_background/master_background_mos_step.py @@ -9,6 +9,9 @@ from ..flatfield import flat_field_step from ..pathloss import pathloss_step from ..photom import photom_step +from ..pixel_replace import pixel_replace_step +from ..resample import resample_spec_step +from ..extract_1d import extract_1d_step from ..stpipe import Pipeline __all__ = ['MasterBackgroundMosStep'] @@ -62,6 +65,9 @@ class MasterBackgroundMosStep(Pipeline): 'pathloss': pathloss_step.PathLossStep, 'barshadow': barshadow_step.BarShadowStep, 'photom': photom_step.PhotomStep, + 'pixel_replace': pixel_replace_step.PixelReplaceStep, + 'resample_spec': resample_spec_step.ResampleSpecStep, + 'extract_1d': extract_1d_step.Extract1dStep, } # No need to prefetch. This will have been done by the parent step. @@ -170,7 +176,7 @@ def process(self, data): return result def set_pars_from_parent(self): - """Set substep parameters from the parents substeps""" + """Set substep parameters from the parents substeps when needed""" if not self.parent: return @@ -188,6 +194,21 @@ def set_pars_from_parent(self): del pars[par] getattr(self, step).update_pars(pars) + def _extend_bg_slits(self, pre_calibrated): + # Copy dedicated background slitlets to a temporary model + bkg_model = datamodels.MultiSlitModel() + bkg_model.update(pre_calibrated) + slits = [] + for slit in pre_calibrated.slits: + if nirspec_utils.is_background_msa_slit(slit): + self.log.info(f'Using background slitlet {slit.source_name}') + slits.append(slit) + if len(slits) == 0: + self.log.warning('No background slitlets found; skipping master bkg correction') + return None + bkg_model.slits.extend(slits) + return bkg_model + def _classify_slits(self, data): """Determine how many Slits are background and source types @@ -269,9 +290,16 @@ def _calc_master_background( master_background = user_background bkg_x1d_spectra = None else: - self.log.debug('Calculating 1D master background') - master_background, bkg_x1d_spectra = nirspec_utils.create_background_from_multislit( - pre_calibrated, sigma_clip=sigma_clip, median_kernel=median_kernel) + self.log.info('Creating MOS master background from background slitlets') + bkg_model = self._extend_bg_slits(pre_calibrated) + if bkg_model is not None: + bkg_model = self.pixel_replace.run(bkg_model) + bkg_model = self.resample_spec.run(bkg_model) + bkg_x1d_spectra = self.extract_1d.run(bkg_model) + master_background = nirspec_utils.create_background_from_multispec( + bkg_x1d_spectra, sigma_clip=sigma_clip, median_kernel=median_kernel) + else: + master_background = None if master_background is None: self.log.debug('No master background could be calculated. Returning None') return None, None, None diff --git a/jwst/master_background/nirspec_utils.py b/jwst/master_background/nirspec_utils.py index 862e6b82af..3420deca6a 100644 --- a/jwst/master_background/nirspec_utils.py +++ b/jwst/master_background/nirspec_utils.py @@ -3,7 +3,6 @@ from scipy.signal import medfilt -from stdatamodels.jwst import datamodels log = logging.getLogger(__name__) log.setLevel(logging.DEBUG) @@ -78,14 +77,14 @@ def map_to_science_slits(input_model, master_bkg): return output_model -def create_background_from_multislit(input_model, sigma_clip=3, median_kernel=1): +def create_background_from_multispec(bkg_model, sigma_clip=3, median_kernel=1): """Create a 1D master background spectrum from a set of calibrated background MOS slitlets in the input - MultiSlitModel. + MultiSpecModel. Parameters ---------- - input_model : `~jwst.datamodels.MultiSlitModel` + bkg_model : `~jwst.datamodels.MultiSpecModel` The input data model containing all slit instances. sigma_clip : None or float, optional Optional factor for sigma clipping outliers when combining background spectra. @@ -101,36 +100,11 @@ def create_background_from_multislit(input_model, sigma_clip=3, median_kernel=1) x1d: `jwst.datamodels.MultiSpecModel` The 1D extracted background spectra of the inputs. """ - from ..resample import resample_spec_step - from ..extract_1d import extract_1d_step from ..combine_1d.combine1d import combine_1d_spectra - log.info('Creating MOS master background from background slitlets') - - # Copy dedicated background slitlets to a temporary model - bkg_model = datamodels.MultiSlitModel() - bkg_model.update(input_model) - slits = [] - for slit in input_model.slits: - if is_background_msa_slit(slit): - log.info(f'Using background slitlet {slit.source_name}') - slits.append(slit) - - if len(slits) == 0: - log.warning('No background slitlets found; skipping master bkg correction') - return None - - bkg_model.slits.extend(slits) - - # Apply resample_spec and extract_1d to all background slitlets - log.info('Applying resampling and 1D extraction to background slits') - resamp = resample_spec_step.ResampleSpecStep.call(bkg_model) - x1d = extract_1d_step.Extract1dStep.call(resamp) - # Call combine_1d to combine the 1D background spectra log.info('Combining 1D background spectra into master background') - master_bkg = combine_1d_spectra( - x1d, exptime_key='exposure_time', sigma_clip=sigma_clip) + master_bkg = combine_1d_spectra(bkg_model, exptime_key='exposure_time', sigma_clip=sigma_clip) # If requested, apply a moving-median boxcar filter to the master background spectrum # Round down even kernel sizes because only odd kernel sizes are supported. @@ -150,10 +124,7 @@ def create_background_from_multislit(input_model, sigma_clip=3, median_kernel=1) kernel_size=[median_kernel] ) - del bkg_model - del resamp - - return master_bkg, x1d + return master_bkg def correct_nrs_ifu_bkg(input_model): diff --git a/jwst/master_background/tests/test_master_background_mos.py b/jwst/master_background/tests/test_master_background_mos.py index b683f49616..f0fc67042d 100644 --- a/jwst/master_background/tests/test_master_background_mos.py +++ b/jwst/master_background/tests/test_master_background_mos.py @@ -2,7 +2,7 @@ import pytest from astropy.io import fits from astropy.table import Table -from stdatamodels.jwst.datamodels import ImageModel +from stdatamodels.jwst.datamodels import ImageModel, MultiSlitModel from jwst.stpipe import query_step_status from jwst.assign_wcs import AssignWcsStep @@ -10,6 +10,9 @@ from jwst.extract_2d.tests.test_nirspec import create_nirspec_hdul from jwst.master_background import MasterBackgroundMosStep from jwst.master_background import nirspec_utils +from jwst.pixel_replace import PixelReplaceStep +from jwst.resample import ResampleSpecStep +from jwst.extract_1d import Extract1dStep def create_msa_hdul(): @@ -74,6 +77,7 @@ def nirspec_msa_metfl(tmp_path): hdul.close() return filename + @pytest.fixture def nirspec_msa_extracted2d(nirspec_msa_rate, nirspec_msa_metfl): model = ImageModel(nirspec_msa_rate) @@ -82,6 +86,20 @@ def nirspec_msa_extracted2d(nirspec_msa_rate, nirspec_msa_metfl): return model +def mk_multispec(model): + specs_model = MultiSlitModel() + specs_model.update(model) + slits = [] + for slit in model.slits: + if nirspec_utils.is_background_msa_slit(slit): + slits.append(slit) + specs_model.slits.extend(slits) + specs_model = PixelReplaceStep.call(specs_model) + specs_model = ResampleSpecStep.call(specs_model) + specs_model = Extract1dStep.call(specs_model) + return specs_model + + def test_master_background_mos(nirspec_msa_extracted2d): model = nirspec_msa_extracted2d @@ -97,43 +115,47 @@ def test_master_background_mos(nirspec_msa_extracted2d): # Check that a background was subtracted from the science data assert not np.allclose(sci_orig, sci_bkgsub) - model.close() - result.close() + del model + del result -def test_create_background_from_multislit(nirspec_msa_extracted2d): +def test_create_background_from_multispec(nirspec_msa_extracted2d): model = nirspec_msa_extracted2d - # Insert a outliers into one of the background spectra + # Insert outliers into one of the background spectra nypix = len(model.slits[0].data) nxpix = len(model.slits[0].data) - model.slits[0].data[nypix//2,nxpix//2-1:nxpix//2+1] = 10 + model.slits[0].data[nypix//2, nxpix//2-1:nxpix//2+1] = 10 + + specs_model = mk_multispec(model) # First check that we can make a master background from the inputs # Check that with sigma_clip=None, the outlier is retained - master_background, _ = nirspec_utils.create_background_from_multislit( - model, sigma_clip=None) + master_background = nirspec_utils.create_background_from_multispec( + specs_model, sigma_clip=None) assert np.any(master_background.spec[0].spec_table['surf_bright'] > 1) # Confirm that using a median_filter will filter out the outlier - master_background, _ = nirspec_utils.create_background_from_multislit( - model, median_kernel=4) + master_background = nirspec_utils.create_background_from_multispec( + specs_model, median_kernel=4) assert np.allclose(master_background.spec[0].spec_table['surf_bright'], 1) # Confirm that using a sigma clipping when combining background spectra # removes the outlier - master_background, _ = nirspec_utils.create_background_from_multislit( - model, sigma_clip=3) + master_background = nirspec_utils.create_background_from_multispec( + specs_model, sigma_clip=3) assert np.allclose(master_background.spec[0].spec_table['surf_bright'], 1) - model.close() + del model + del specs_model + def test_map_to_science_slits(nirspec_msa_extracted2d): model = nirspec_msa_extracted2d + specs_model = mk_multispec(model) - master_background, _ = nirspec_utils.create_background_from_multislit( - model) + master_background = nirspec_utils.create_background_from_multispec(specs_model) # Check that the master background is expanded to the shape of the input slits mb_multislit = nirspec_utils.map_to_science_slits(model, master_background) @@ -145,13 +167,15 @@ def test_map_to_science_slits(nirspec_msa_extracted2d): nonzero = slit_data != 0 assert np.allclose(slit_data[nonzero], 1) - model.close() + del model + del specs_model + def test_apply_master_background(nirspec_msa_extracted2d): model = nirspec_msa_extracted2d + specs_model = mk_multispec(model) - master_background, _ = nirspec_utils.create_background_from_multislit( - model) + master_background = nirspec_utils.create_background_from_multispec(specs_model) mb_multislit = nirspec_utils.map_to_science_slits(model, master_background) result = nirspec_utils.apply_master_background(model, mb_multislit, inverse=False) @@ -175,7 +199,8 @@ def test_apply_master_background(nirspec_msa_extracted2d): assert np.any(diff != 0) assert np.allclose(diff[diff != 0], -1) - model.close() - result.close() + del model + del result + del specs_model