Source code for jwst.extract_1d.extract_1d_step
from stdatamodels.jwst import datamodels
from jwst.datamodels import ModelContainer, SourceModelContainer
from ..stpipe import Step
from . import extract
from .soss_extract import soss_extract
__all__ = ["Extract1dStep"]
[docs]
class Extract1dStep(Step):
"""Extract a 1-d spectrum from 2-d data
Attributes
----------
smoothing_length : int or None
If not None, the background regions (if any) will be smoothed
with a boxcar function of this width along the dispersion
direction. This should be an odd integer.
bkg_fit : str
A string indicating the type of fitting to be applied to
background values in each column (or row, if the dispersion is
vertical). Allowed values are `poly`, `mean`, and `median`.
Default is `None`.
bkg_order : int or None
If not None, a polynomial with order `bkg_order` will be fit to
each column (or row, if the dispersion direction is vertical)
of the background region or regions. For a given column (row),
one polynomial will be fit to all background regions. The
polynomial will be evaluated at each pixel of the source
extraction region(s) along the column (row), and the fitted value
will be subtracted from the data value at that pixel.
If both `smoothing_length` and `bkg_order` are not None, the
boxcar smoothing will be done first.
bkg_sigma_clip : float
Background sigma clipping value to use on background to remove outliers
and maximize the quality of the 1d spectrum
log_increment : int
if `log_increment` is greater than 0 (the default is 50) and the
input data are multi-integration (which can be CubeModel or
SlitModel), a message will be written to the log with log level
INFO every `log_increment` integrations. This is intended to
provide progress information when invoking the step interactively.
subtract_background : bool or None
A flag which indicates whether the background should be subtracted.
If None, the value in the extract_1d reference file will be used.
If not None, this parameter overrides the value in the
extract_1d reference file.
use_source_posn : bool or None
If True, the source and background extraction positions specified in
the extract1d reference file (or the default position, if there is no
reference file) will be shifted to account for the computed position
of the source in the data. If None (the default), the values in the
reference file will be used. Aperture offset is determined by computing
the pixel location of the source based on its RA and Dec. It does not
make sense to apply aperture offsets for extended sources, so this
parameter can be overridden (set to False) internally by the step.
center_xy : int or None
A list of 2 pixel coordinate values at which to place the center
of the IFU extraction aperture, overriding any centering done by the step.
Two values, in x,y order, are used for extraction from IFU cubes.
Default is None.
apply_apcorr : bool
Switch to select whether or not to apply an APERTURE correction during
the Extract1dStep. Default is True
ifu_autocen : bool
Switch to turn on auto-centering for point source spectral extraction
in IFU mode. Default is False.
ifu_rfcorr : bool
Switch to select whether or not to apply a 1d residual fringe correction
for MIRI MRS IFU spectra. Default is False.
ifu_set_srctype : str
For MIRI MRS IFU data override srctype and set it to either POINT or EXTENDED.
ifu_rscale : float
For MRS IFU data a value for changing the extraction radius. The value provided is
the number of PSF FWHMs to use for the extraction radius. Values accepted are between
0.5 to 3.0. The default extraction size is set to 2 * FWHM. Values below 2 will result
in a smaller radius, a value of 2 results in no change to the radius and a value above
2 results in a larger extraction radius.
soss_atoca : bool, default=False
Switch to toggle extraction of SOSS data with the ATOCA algorithm.
WARNING: ATOCA results not fully validated, and require the photom step
be turned off. Default is False, meaning SOSS data use box extraction.
soss_threshold : float
Threshold value above which a pixel will be included when modeling the SOSS
trace in ATOCA. Default is 0.01.
soss_n_os : int
Oversampling factor of the underlying wavelength grid when modeling the SOSS
trace in ATOCA. Default is 2.
soss_transform : list[float]
Rotation applied to the reference files to match the observation orientation.
Default is None.
soss_tikfac : float
The regularization factor used for extraction in ATOCA. If left to default
value of None, ATOCA will find an optimized value.
soss_width : float
Aperture width used to extract the SOSS spectrum from the decontaminated
trace in ATOCA. Default is 40.
soss_bad_pix : str
Method used to handle bad pixels, accepts either "model" or "masking". Default
method is "model".
soss_modelname : str
Filename for optional model output of ATOCA traces and pixel weights.
soss_estimate : str or SpecModel or None
Filename or SpecModel of the estimate of the target flux. The estimate must
be a SpecModel with wavelength and flux values.
soss_wave_grid_in : str or SossWaveGrid or None
Filename or SossWaveGrid containing the wavelength grid used by ATOCA
to model each pixel valid pixel of the detector. If not given, the grid is determined
based on an estimate of the flux (soss_estimate), the relative tolerance (soss_rtol)
required on each pixel model and the maximum grid size (soss_max_grid_size).
soss_wave_grid_out : str or None
Filename to hold the wavelength grid calculated by ATOCA.
soss_rtol : float
The relative tolerance needed on a pixel model. It is used to determine the sampling
of the soss_wave_grid when not directly given.
soss_max_grid_size: int
Maximum grid size allowed. It is used when soss_wave_grid is not provided
to make sure the computation time or the memory used stays reasonable.
"""
class_alias = "extract_1d"
spec = """
smoothing_length = integer(default=None) # background smoothing size
bkg_fit = option("poly", "mean", "median", None, default=None) # background fitting type
bkg_order = integer(default=None, min=0) # order of background polynomial fit
bkg_sigma_clip = float(default=3.0) # background sigma clipping threshold
log_increment = integer(default=50) # increment for multi-integration log messages
subtract_background = boolean(default=None) # subtract background?
use_source_posn = boolean(default=None) # use source coords to center extractions?
center_xy = float_list(min=2, max=2, default=None) # IFU extraction x/y center
apply_apcorr = boolean(default=True) # apply aperture corrections?
ifu_autocen = boolean(default=False) # Auto source centering for IFU point source data.
ifu_rfcorr = boolean(default=False) # Apply 1d residual fringe correction
ifu_set_srctype = option("POINT", "EXTENDED", None, default=None) # user-supplied source type
ifu_rscale = float(default=None, min=0.5, max=3) # Radius in terms of PSF FWHM to scale extraction radii
soss_atoca = boolean(default=True) # use ATOCA algorithm
soss_threshold = float(default=1e-2) # TODO: threshold could be removed from inputs. Its use is too specific now.
soss_n_os = integer(default=2) # minimum oversampling factor of the underlying wavelength grid used when modeling trace.
soss_wave_grid_in = input_file(default = None) # Input wavelength grid used to model the detector
soss_wave_grid_out = string(default = None) # Output wavelength grid solution filename
soss_estimate = input_file(default = None) # Estimate used to generate the wavelength grid
soss_rtol = float(default=1.0e-4) # Relative tolerance needed on a pixel model
soss_max_grid_size = integer(default=20000) # Maximum grid size, if wave_grid not specified
soss_transform = list(default=None, min=3, max=3) # rotation applied to the ref files to match observation.
soss_tikfac = float(default=None) # regularization factor for NIRISS SOSS extraction
soss_width = float(default=40.) # aperture width used to extract the 1D spectrum from the de-contaminated trace.
soss_bad_pix = option("model", "masking", default="masking") # method used to handle bad pixels
soss_modelname = output_file(default = None) # Filename for optional model output of traces and pixel weights
"""
reference_file_types = ['extract1d', 'apcorr', 'wavemap', 'spectrace', 'specprofile', 'speckernel']
[docs]
def process(self, input):
"""Execute the step.
Parameters
----------
input: JWST data model
Returns
-------
JWST data model
This will be `input_model` if the step was skipped; otherwise,
it will be a model containing 1-D extracted spectra.
"""
# Open the input and figure out what type of model it is
input_model = datamodels.open(input)
was_source_model = False # default value
if isinstance(input_model, datamodels.CubeModel):
# It's a 3-D multi-integration model
self.log.debug('Input is a CubeModel for a multiple integ. file')
elif isinstance(input_model, datamodels.ImageModel):
# It's a single 2-D image. This could be a resampled 2-D image
self.log.debug('Input is an ImageModel')
elif isinstance(input_model, SourceModelContainer):
self.log.debug('Input is a SourceModelContainer')
was_source_model = True
elif isinstance(input_model, ModelContainer):
self.log.debug('Input is a ModelContainer')
elif isinstance(input_model, datamodels.MultiSlitModel):
# If input is a 3D rateints (which is unsupported) skip the step
if len((input_model[0]).shape) == 3:
self.log.warning('3D input is unsupported; step will be skipped')
input_model.meta.cal_step.extract_1d = 'SKIPPED'
return input_model
self.log.debug('Input is a MultiSlitModel')
elif isinstance(input_model, datamodels.MultiExposureModel):
self.log.warning('Input is a MultiExposureModel, '
'which is not currently supported')
elif isinstance(input_model, datamodels.IFUCubeModel):
self.log.debug('Input is an IFUCubeModel')
elif isinstance(input_model, datamodels.SlitModel):
# NRS_BRIGHTOBJ and MIRI LRS fixed-slit (resampled) modes
self.log.debug('Input is a SlitModel')
else:
self.log.error(f'Input is a {str(type(input_model))}, ')
self.log.error('which was not expected for extract_1d')
self.log.error('extract_1d will be skipped.')
input_model.meta.cal_step.extract_1d = 'SKIPPED'
return input_model
if isinstance(input_model, datamodels.IFUCubeModel):
exp_type = input_model.meta.exposure.type
elif isinstance(input_model, ModelContainer):
exp_type = input_model[0].meta.exposure.type
else:
exp_type = None
if self.ifu_rfcorr:
if exp_type != "MIR_MRS":
self.log.warning("The option to apply a residual refringe correction is"
f" not supported for {input_model.meta.exposure.type} data.")
if self.ifu_rscale is not None:
if exp_type != "MIR_MRS":
self.log.warning("The option to change the extraction radius is"
f" not supported for {input_model.meta.exposure.type} data.")
if self.ifu_set_srctype is not None:
if exp_type != "MIR_MRS":
self.log.warning("The option to change the source type is"
f" not supported for {input_model.meta.exposure.type} data.")
# ______________________________________________________________________
# Do the extraction for ModelContainer - this might only be WFSS data
if isinstance(input_model, ModelContainer):
# This is the branch WFSS data take
if len(input_model) > 1:
self.log.debug(f"Input contains {len(input_model)} items")
# --------------------------------------------------------------
# Data is WFSS
if input_model[0].meta.exposure.type in extract.WFSS_EXPTYPES:
# For WFSS level-3, the input is a single entry of a
# SourceContainer, which contains a list of multiple
# SlitModels for a single source. Send the whole list
# into extract1d and put all results in a single product.
apcorr_ref = (
self.get_reference_file(input_model[0], 'apcorr') if self.apply_apcorr is True else 'N/A'
)
if apcorr_ref == 'N/A':
self.log.info('APCORR reference file name is "N/A"')
self.log.info('APCORR will NOT be applied')
else:
self.log.info(f'Using APCORR file {apcorr_ref}')
extract_ref = 'N/A'
self.log.info('No EXTRACT1D reference file will be used')
result = extract.run_extract1d(
input_model,
extract_ref,
apcorr_ref,
self.smoothing_length,
self.bkg_fit,
self.bkg_order,
self.bkg_sigma_clip,
self.log_increment,
self.subtract_background,
self.use_source_posn,
self.center_xy,
self.ifu_autocen,
self.ifu_rfcorr,
self.ifu_set_srctype,
self.ifu_rscale,
was_source_model=was_source_model
)
# Set the step flag to complete
result.meta.cal_step.extract_1d = 'COMPLETE'
# --------------------------------------------------------------
# Data is a ModelContainer but is not WFSS
else:
result = ModelContainer()
for model in input_model:
# Get the reference file names
extract_ref = self.get_reference_file(model, 'extract1d')
self.log.info(f'Using EXTRACT1D reference file {extract_ref}')
apcorr_ref = self.get_reference_file(model, 'apcorr') if self.apply_apcorr is True else 'N/A'
if apcorr_ref == 'N/A':
self.log.info('APCORR reference file name is "N/A"')
self.log.info('APCORR will NOT be applied')
else:
self.log.info(f'Using APCORR file {apcorr_ref}')
temp = extract.run_extract1d(
model,
extract_ref,
apcorr_ref,
self.smoothing_length,
self.bkg_fit,
self.bkg_order,
self.bkg_sigma_clip,
self.log_increment,
self.subtract_background,
self.use_source_posn,
self.center_xy,
self.ifu_autocen,
self.ifu_rfcorr,
self.ifu_set_srctype,
self.ifu_rscale,
was_source_model=was_source_model,
)
# Set the step flag to complete in each MultiSpecModel
temp.meta.cal_step.extract_1d = 'COMPLETE'
result.append(temp)
del temp
# ------------------------------------------------------------------------
# Still in ModelContainer type, but only 1 model
elif len(input_model) == 1:
if input_model[0].meta.exposure.type in extract.WFSS_EXPTYPES:
extract_ref = 'N/A'
self.log.info('No EXTRACT1D reference file will be used')
else:
# Get the extract1d reference file name for the one model in input
extract_ref = self.get_reference_file(input_model[0], 'extract1d')
self.log.info(f'Using EXTRACT1D reference file {extract_ref}')
apcorr_ref = self.get_reference_file(input_model[0], 'apcorr') if self.apply_apcorr is True else 'N/A'
if apcorr_ref == 'N/A':
self.log.info('APCORR reference file name is "N/A"')
self.log.info('APCORR will NOT be applied')
else:
self.log.info(f'Using APCORR file {apcorr_ref}')
result = extract.run_extract1d(
input_model[0],
extract_ref,
apcorr_ref,
self.smoothing_length,
self.bkg_fit,
self.bkg_order,
self.bkg_sigma_clip,
self.log_increment,
self.subtract_background,
self.use_source_posn,
self.center_xy,
self.ifu_autocen,
self.ifu_rfcorr,
self.ifu_set_srctype,
self.ifu_rscale,
was_source_model=was_source_model,
)
# Set the step flag to complete
result.meta.cal_step.extract_1d = 'COMPLETE'
else:
self.log.error('Input model is empty;')
self.log.error('extract_1d will be skipped.')
return input_model
# ______________________________________________________________________
# Data that is not a ModelContainer (IFUCube and other single models)
else:
# Data is NRISS SOSS observation.
if input_model.meta.exposure.type == 'NIS_SOSS':
self.log.info(
'Input is a NIRISS SOSS observation, the specialized SOSS extraction (ATOCA) will be used.')
# Set the filter configuration
if input_model.meta.instrument.filter == 'CLEAR':
self.log.info('Exposure is through the GR700XD + CLEAR (science).')
soss_filter = 'CLEAR'
else:
self.log.error('The SOSS extraction is implemented for the CLEAR filter only.'
f'Requested filter is {input_model.meta.instrument.filter}.')
self.log.error('extract_1d will be skipped.')
input_model.meta.cal_step.extract_1d = 'SKIPPED'
return input_model
# Set the subarray mode being processed
if input_model.meta.subarray.name == 'SUBSTRIP256':
self.log.info('Exposure is in the SUBSTRIP256 subarray.')
self.log.info('Traces 1 and 2 will be modelled and decontaminated before extraction.')
subarray = 'SUBSTRIP256'
elif input_model.meta.subarray.name == 'SUBSTRIP96':
self.log.info('Exposure is in the SUBSTRIP96 subarray.')
self.log.info('Traces of orders 1 and 2 will be modelled but only order 1'
' will be decontaminated before extraction.')
subarray = 'SUBSTRIP96'
else:
self.log.error('The SOSS extraction is implemented for the SUBSTRIP256'
'and SUBSTRIP96 subarrays only. Subarray is currently '
f'{input_model.meta.subarray.name}.')
self.log.error('Extract1dStep will be skipped.')
input_model.meta.cal_step.extract_1d = 'SKIPPED'
return input_model
# Load reference files.
spectrace_ref_name = self.get_reference_file(input_model, 'spectrace')
wavemap_ref_name = self.get_reference_file(input_model, 'wavemap')
specprofile_ref_name = self.get_reference_file(input_model, 'specprofile')
speckernel_ref_name = self.get_reference_file(input_model, 'speckernel')
# Build SOSS kwargs dictionary.
soss_kwargs = dict()
soss_kwargs['threshold'] = self.soss_threshold
soss_kwargs['n_os'] = self.soss_n_os
soss_kwargs['tikfac'] = self.soss_tikfac
soss_kwargs['width'] = self.soss_width
soss_kwargs['bad_pix'] = self.soss_bad_pix
soss_kwargs['transform'] = self.soss_transform
soss_kwargs['subtract_background'] = self.subtract_background
soss_kwargs['rtol'] = self.soss_rtol
soss_kwargs['max_grid_size'] = self.soss_max_grid_size
soss_kwargs['wave_grid_in'] = self.soss_wave_grid_in
soss_kwargs['wave_grid_out'] = self.soss_wave_grid_out
soss_kwargs['estimate'] = self.soss_estimate
soss_kwargs['atoca'] = self.soss_atoca
# Set flag to output the model and the tikhonov tests
soss_kwargs['model'] = True if self.soss_modelname else False
# Run the extraction.
result, ref_outputs, atoca_outputs = soss_extract.run_extract1d(
input_model,
spectrace_ref_name,
wavemap_ref_name,
specprofile_ref_name,
speckernel_ref_name,
subarray,
soss_filter,
soss_kwargs)
# Set the step flag to complete
if result is None:
return None
else:
result.meta.cal_step.extract_1d = 'COMPLETE'
result.meta.target.source_type = None
input_model.close()
if self.soss_modelname:
soss_modelname = self.make_output_path(
basepath=self.soss_modelname,
suffix='SossExtractModel'
)
ref_outputs.save(soss_modelname)
if self.soss_modelname:
soss_modelname = self.make_output_path(
basepath=self.soss_modelname,
suffix='AtocaSpectra'
)
atoca_outputs.save(soss_modelname)
else:
# Get the reference file names
if input_model.meta.exposure.type in extract.WFSS_EXPTYPES:
extract_ref = 'N/A'
self.log.info('No EXTRACT1D reference file will be used')
else:
extract_ref = self.get_reference_file(input_model, 'extract1d')
self.log.info(f'Using EXTRACT1D reference file {extract_ref}')
apcorr_ref = self.get_reference_file(input_model, 'apcorr') if self.apply_apcorr is True else 'N/A'
if apcorr_ref == 'N/A':
self.log.info('APCORR reference file name is "N/A"')
self.log.info('APCORR will NOT be applied')
else:
self.log.info(f'Using APCORR file {apcorr_ref}')
result = extract.run_extract1d(
input_model,
extract_ref,
apcorr_ref,
self.smoothing_length,
self.bkg_fit,
self.bkg_order,
self.bkg_sigma_clip,
self.log_increment,
self.subtract_background,
self.use_source_posn,
self.center_xy,
self.ifu_autocen,
self.ifu_rfcorr,
self.ifu_set_srctype,
self.ifu_rscale,
was_source_model=False,
)
# Set the step flag to complete
result.meta.cal_step.extract_1d = 'COMPLETE'
input_model.close()
return result