diff --git a/CHANGES.rst b/CHANGES.rst index f233e0dfe1..d09034444a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -110,6 +110,11 @@ datamodels extract_1d ---------- +- For IFU data (NIRSpec and MIRI) the extraction radius is now a varying size + based on wavelength. The apcorr correction is a function of wavelength and + radius size. Fixes a bug in units conversion for applying the apcorr correction. + The units are now correctly converted from arcseconds to pixels. Added an + new method to apply the apcorr correction for IFU data. - Fixed bug involving the determination of source RA/Dec for resampled Slit data. [#5353] diff --git a/jwst/datamodels/__init__.py b/jwst/datamodels/__init__.py index 569865b847..5bf9c17f87 100644 --- a/jwst/datamodels/__init__.py +++ b/jwst/datamodels/__init__.py @@ -9,7 +9,7 @@ from .apcorr import NrcImgApcorrModel, NisImgApcorrModel from .apcorr import MirLrsApcorrModel, MirMrsApcorrModel from .apcorr import NrcWfssApcorrModel, NisWfssApcorrModel -from .apcorr import NrsMosApcorrModel, NrsFsApcorrModel +from .apcorr import NrsMosApcorrModel, NrsIfuApcorrModel, NrsFsApcorrModel from .asn import AsnModel from .barshadow import BarshadowModel from .combinedspec import CombinedSpecModel @@ -21,6 +21,7 @@ from .drizpars import DrizParsModel from .drizproduct import DrizProductModel from .extract1dimage import Extract1dImageModel +from .extract1d_spec import Extract1dIFUModel from .flat import FlatModel from .fringe import FringeModel from .gain import GainModel @@ -91,7 +92,7 @@ 'AmiLgModel', 'FgsImgApcorrModel', 'MirImgApcorrModel', 'NrcImgApcorrModel', 'NisImgApcorrModel', 'MirLrsApcorrModel', 'MirMrsApcorrModel', 'NrcWfssApcorrModel', 'NisWfssApcorrModel', - 'NrsMosApcorrModel', 'NrsFsApcorrModel', + 'NrsMosApcorrModel', 'NrsFsApcorrModel','NrsIfuApcorrModel', 'AsnModel', 'BarshadowModel', 'CameraModel', 'CollimatorModel', 'CombinedSpecModel', 'ContrastModel', 'CubeModel', @@ -100,6 +101,7 @@ 'DrizParsModel', 'DrizProductModel', 'Extract1dImageModel', + 'Extract1dIFUModel', 'FilteroffsetModel', 'FlatModel', 'NirspecFlatModel', 'NirspecQuadFlatModel', 'FOREModel', 'FPAModel', diff --git a/jwst/datamodels/apcorr.py b/jwst/datamodels/apcorr.py index 03dd647894..8ab8a89c1f 100644 --- a/jwst/datamodels/apcorr.py +++ b/jwst/datamodels/apcorr.py @@ -4,7 +4,7 @@ 'NrcImgApcorrModel', 'NisImgApcorrModel', 'MirLrsApcorrModel', 'MirMrsApcorrModel', 'NrcWfssApcorrModel', 'NisWfssApcorrModel', - 'NrsMosApcorrModel', 'NrsFsApcorrModel'] + 'NrsMosApcorrModel', 'NrsIfuApcorrModel','NrsFsApcorrModel'] class FgsImgApcorrModel(ReferenceFileModel): @@ -90,14 +90,9 @@ class MirMrsApcorrModel(ReferenceFileModel): factors associated with those modes. - wavelength: float32 1D array - - nelem_wl: int16 - - radius: float32 1D array - - apcorr: float32 1D array - - apcorr_err: float32 1D array - - inner_bkg: float32 1D array - - outer_bkg: float32 1D array - - axis_ratio: float32 1D array - - axis_pa: float32 1D array + - radius: float32 2D array + - apcorr: float32 2D array + - apcorr_err: float32 2D array """ schema_url = "http://stsci.edu/schemas/jwst_datamodel/mirmrs_apcorr.schema" @@ -203,7 +198,7 @@ class NisWfssApcorrModel(ReferenceFileModel): class NrsMosApcorrModel(ReferenceFileModel): """ - A data model for NIRSpec MOS and IFU apcorr reference files. + A data model for NIRSpec MOS apcorr reference files. Parameters __________ @@ -227,6 +222,29 @@ class NrsMosApcorrModel(ReferenceFileModel): schema_url = "http://stsci.edu/schemas/jwst_datamodel/nrsmos_apcorr.schema" +class NrsIfuApcorrModel(ReferenceFileModel): + """ + A data model for NIRSpec IFU apcorr reference files. + + Parameters + __________ + apcorr_table : numpy table + Aperture correction factors table + A table-like object containing row selection criteria made up + of instrument mode parameters and aperture correction + factors associated with those modes. + + - filter: str[12] + - grating: str[15] + - wavelength: float32 1D array + - radius: float32 3D array + - apcorr: float32 3D array + - apcorr_err: float32 3D array + + """ + schema_url = "http://stsci.edu/schemas/jwst_datamodel/nrsifu_apcorr.schema" + + class NrsFsApcorrModel(ReferenceFileModel): """ A data model for NIRSpec Fixed-Slit apcorr reference files. diff --git a/jwst/datamodels/extract1d_spec.py b/jwst/datamodels/extract1d_spec.py new file mode 100644 index 0000000000..4673006c54 --- /dev/null +++ b/jwst/datamodels/extract1d_spec.py @@ -0,0 +1,30 @@ +from .reference import ReferenceFileModel + +__all__ = ['Extract1dIFUModel'] + + +class Extract1dIFUModel(ReferenceFileModel): + """ + A data model for IFU MIRI and NIRSpec extract 1d reference files. + + Parameters + __________ + extract1d_params : numpy table + Basic extract 1D parameters + - region_type: ascii + - subtract_background: bool + - method: ascii + - subpixels: int16 + extract1d_table : numpy table + extract1d parameters for varying wavelengths + A table-like object containing extract 1d parameters + based on wavelength + - wavelength: float32 1D array + - radius: float32 1D array + - inner_bkg: float32 1D array + - outer_bkg: float32 1D array + - axis_ratio: float32 1D array + - axis_pa: float32 1D array + + """ + schema_url = "http://stsci.edu/schemas/jwst_datamodel/extract1difu.schema" diff --git a/jwst/datamodels/schemas/extract1difu.schema.yaml b/jwst/datamodels/schemas/extract1difu.schema.yaml new file mode 100644 index 0000000000..eeb808daaf --- /dev/null +++ b/jwst/datamodels/schemas/extract1difu.schema.yaml @@ -0,0 +1,48 @@ +%YAML 1.1 +--- +$schema: "http://stsci.edu/schemas/asdf/asdf-schema-1.0.0" +id: "http://stsci.edu/schemas/jwst_datamodel/extract1difu.schema" +title: Default IFU Data extract 1d data model +allOf: +- $ref: referencefile.schema +- $ref: keyword_exptype.schema +- $ref: keyword_pexptype.schema +- type: object + properties: + meta: + type: object + properties: + region_type: + type: string + subtract_background: + type: boolean + method: + type: string + subpixels: + type: integer + properties: + data: + type: object + properties: + wavelength: + datatype: float32 + wavelength_units: + type: string + radius: + datatype: float32 + radius_units: + type: string + inner_bkg: + datatype: float32 + inner_bkg_units: + type: string + outer_bkg: + datatype: float32 + outer_bkg_units: + type: string + axis_ratio: + datatype: float32 + axis_pa: + datatype: float32 + axis_pa_units: + type: string diff --git a/jwst/datamodels/schemas/mirmrs_apcorr.schema.yaml b/jwst/datamodels/schemas/mirmrs_apcorr.schema.yaml index bee4fbb6f6..081acecd70 100644 --- a/jwst/datamodels/schemas/mirmrs_apcorr.schema.yaml +++ b/jwst/datamodels/schemas/mirmrs_apcorr.schema.yaml @@ -1,6 +1,6 @@ %YAML 1.1 --- -$schema: "http://stsci.edu/schemas/fits-schema/fits-schema" +$schema: "http://stsci.edu/schemas/asdf/asdf-schema-1.0.0" id: "http://stsci.edu/schemas/jwst_datamodel/mirmrs_apcorr.schema" title: MIRI MRS aperture correction data model allOf: @@ -10,36 +10,21 @@ allOf: - type: object properties: apcorr_table: - title: Aperture correction factors table - fits_hdu: APCORR - datatype: - - name: wavelength - datatype: float32 - ndim: 1 - - name: nelem_wl - datatype: int16 - - name: radius - datatype: float32 - ndim: 1 - - name: apcorr - datatype: float32 - ndim: 1 - - name: apcorr_err - datatype: float32 - ndim: 1 - - name: inner_bkg - datatype: float32 - ndim: 1 - - name: outer_bkg - datatype: float32 - ndim: 1 - - name: axis_ratio - datatype: float32 - ndim: 1 - - name: axis_pa - datatype: float32 - ndim: 1 - sizeunit: - title: Units for the SIZE or RADIUS column in the APCORR table - fits_hdu: APCORR - fits_keyword: SIZEUNIT \ No newline at end of file + type: object + properties: + channel: + type: string + band: + type: string + wavelength: + datatype: float32 + wavelength_units: + type: string + radius: + datatype: float32 + radius_units: + type: string + apcorr: + datatype: float32 + apcorr_err: + datatype: float32 diff --git a/jwst/datamodels/schemas/mirmrs_extract1d.schema.yaml b/jwst/datamodels/schemas/mirmrs_extract1d.schema.yaml new file mode 100644 index 0000000000..1b64c17b27 --- /dev/null +++ b/jwst/datamodels/schemas/mirmrs_extract1d.schema.yaml @@ -0,0 +1,41 @@ +%YAML 1.1 +--- +$schema: "http://stsci.edu/schemas/fits-schema/fits-schema" +id: "http://stsci.edu/schemas/jwst_datamodel/mirmrs_extract1d.schema" +title: Default MIRI MRS extract 1d data model +allOf: +- $ref: referencefile.schema +- type: object + properties: + extract1d_params: + title: default MIRI extract 1d parameters + fits_hdu: PARAMS + datatype: + - name: id + datatype: [ascii,10] + - name: region_type + datatype: [ascii,10] + - name: subtract_background + datatype: uint8 + - name: method + datatype: [ascii,10] + - name: subpixels + datatype: int16 + extract1d_table: + title: wavelength varying extraction parameters + fits_hdu: X1D + datatype: + - name: wavelength + datatype: float32 + - name: nelem_wl + datatype: int16 + - name: radius + datatype: float32 + - name: inner_bkg + datatype: float32 + - name: outer_bkg + datatype: float32 + - name: axis_ratio + datatype: float32 + - name: axis_pa + datatype: float32 diff --git a/jwst/datamodels/schemas/nrsifu_apcorr.schema.yaml b/jwst/datamodels/schemas/nrsifu_apcorr.schema.yaml new file mode 100644 index 0000000000..a0ed3ee559 --- /dev/null +++ b/jwst/datamodels/schemas/nrsifu_apcorr.schema.yaml @@ -0,0 +1,30 @@ +%YAML 1.1 +--- +$schema: "http://stsci.edu/schemas/asdf/asdf-schema-1.0.0" +id: "http://stsci.edu/schemas/jwst_datamodel/nrsifu_apcorr.schema" +title: NRS Ifu aperture correction data model +allOf: +- $ref: referencefile.schema +- $ref: keyword_exptype.schema +- $ref: keyword_pexptype.schema +- type: object + properties: + apcorr_table: + type: object + properties: + grating: + type: string + filter: + type: string + wavelength: + datatype: float32 + wavelength_units: + type: string + radius: + datatype: float32 + radius_units: + type: string + apcorr: + datatype: float32 + apcorr_err: + datatype: float32 diff --git a/jwst/extract_1d/apply_apcorr.py b/jwst/extract_1d/apply_apcorr.py index 6bb540b84c..ab1541bfe7 100644 --- a/jwst/extract_1d/apply_apcorr.py +++ b/jwst/extract_1d/apply_apcorr.py @@ -7,6 +7,7 @@ from ..assign_wcs.util import compute_scale from ..datamodels import MultiSlitModel +import numpy as np class ApCorrBase(abc.ABC): @@ -38,11 +39,9 @@ class ApCorrBase(abc.ABC): """ match_pars = { 'MIRI': { - 'LRS': {'subarray': ['name']}, - 'MRS': {'instrument': []}, # Only one row is available for this mode; no selection criteria + 'LRS': {'subarray': ['name']} }, 'NIRSPEC': { - 'IFU': {'instrument': ['filter', 'grating']}, 'MSASPEC': {'instrument': ['filter', 'grating']}, 'FIXEDSLIT': {'instrument': ['filter', 'grating']}, # Slit is also required; passed in as init arg 'BRIGHTOBJ': {'instrument': ['filter', 'grating']} @@ -71,7 +70,6 @@ def __init__(self, input_model: DataModel, apcorr_table: fits.FITS_rec, sizeunit self.match_keys = self._get_match_keys() self.match_pars = self._get_match_pars() self.match_pars.update(match_kwargs) - self.reference = self._reduce_reftable() self._convert_size_units() self.apcorr_func = self.approximate() @@ -79,20 +77,23 @@ def __init__(self, input_model: DataModel, apcorr_table: fits.FITS_rec, sizeunit def _convert_size_units(self): """If the SIZE or Radius column is in units of arcseconds, convert to pixels.""" if self.apcorr_sizeunits.startswith('arcsec'): + # compute_scale returns scale in degrees if self.location is not None: if isinstance(self.model, MultiSlitModel): idx = [slit.name for slit in self.model.slits].index(self.slit_name) - self.reference[self.size_key] /= compute_scale( + scale_degrees = compute_scale( self.model.slits[idx].meta.wcs, self.location, - disp_axis=self.model.slits[idx].meta.wcsinfo.dispersion_direction - ) + disp_axis=self.model.slits[idx].meta.wcsinfo.dispersion_direction) + scale_arcsec = scale_degrees*3600.00 + self.reference[self.size_key] /= scale_arcsec else: - self.reference[self.size_key] /= compute_scale( + scale_degrees = compute_scale( self.model.meta.wcs, self.location, - disp_axis=self.model.meta.wcsinfo.dispersion_direction - ) + disp_axis=self.model.meta.wcsinfo.dispersion_direction) + scale_arcsec = scale_degrees*3600.00 + self.reference[self.size_key] /= scale_arcsec else: raise ValueError( 'If the size column for the input APCORR reference file is in units with arcseconds, a location ' @@ -128,6 +129,7 @@ def _reduce_reftable(self) -> fits.FITS_record: table = self._reference_table.copy() for key, value in self.match_pars.items(): + if isinstance(value, str): # Not all files will have the same format as input model metadata values. table = table[table[key].upper() == value.upper()] else: @@ -135,7 +137,6 @@ def _reduce_reftable(self) -> fits.FITS_record: if len(table) != 1: raise ValueError('Could not resolve APCORR reference for input.') - return table[0] @abc.abstractmethod @@ -241,20 +242,98 @@ def apply(self, spec_table: fits.FITS_rec): class ApCorrRadial(ApCorrBase): """Aperture correction class used with spectral data produced from an extraction aperture radius.""" - size_key = 'radius' - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) + def __init__(self, input_model: DataModel, apcorr_table, + location: Tuple[float, float, float] = None): + + self.correction = None + self.model = input_model + self.location = location + self.reference = apcorr_table.apcorr_table + self.apcorr_sizeunits = self.reference.radius_units + self._convert_size_units() + def approximate(self): - """Generate an approximate function for interpolating apcorr values to input wavelength and radius.""" - wavelength = self.reference['wavelength'][:self.reference['nelem_wl']] - size = self.reference['radius'][:self.reference['nelem_wl']] - apcorr = self.reference['apcorr'][:self.reference['nelem_wl']] + # Base class needs this. For ApCorrRadial this is done in find_apcorr_func + pass - return interp2d(size, wavelength, apcorr) + def _convert_size_units(self): + """If the SIZE or Radius column is in units of arcseconds, convert to pixels.""" + if self.apcorr_sizeunits.startswith('arcsec'): + # compute_scale returns scale in degrees + if self.location is not None: + scale_degrees = compute_scale( + self.model.meta.wcs, + self.location, + disp_axis=self.model.meta.wcsinfo.dispersion_direction) + scale_arcsec = scale_degrees*3600.00 + self.reference.radius /= scale_arcsec + else: + raise ValueError( + 'If the size column for the input APCORR reference file is in units with arcseconds, a location ' + '(RA, DEC, wavelength) must be provided in order to compute a pixel scale to convert arcseconds to ' + 'pixels.' + ) + def apply(self, spec_table: fits.FITS_rec): + """Apply interpolated aperture correction value to source-related extraction results in-place. + + Parameters + ---------- + spec_table : `~fits.FITS_rec` + Table of aperture corrections values from apcorr reference file. + + """ + cols_to_correct = ('flux', 'surf_bright', 'error', 'sb_error') + + for i, row in enumerate(spec_table): + correction = self.apcorr_correction[i] + for col in cols_to_correct: + row[col] *= correction + + + def match_wavelengths(self, wavelength_ifu): + # given the ifu wavelength value - redefine the apcor func and radius to this wavelength + # apcor reference data + self.wavelength = self.reference.wavelength.flatten() + self.size = self.reference.radius + self.apcorr = self.reference.apcorr + + dim = self.apcorr.shape[0] + size_match = np.zeros((dim, wavelength_ifu.shape[0])) + apcorr_match = np.zeros((dim, wavelength_ifu.shape[0])) + self.apcorr_correction= [] # set up here defined in find_apcor_func + # loop over each radius dependent plane and interpolate to ifu wavelength + for i in range (dim): + radi = self.size[i,:] + frad = interp1d(self.wavelength, radi, bounds_error=False, fill_value="extrapolate") + radius_match = frad(wavelength_ifu) + size_match[i,:] = radius_match + + appi = self.apcorr[i,:] + fap = interp1d(self.wavelength, appi, bounds_error=False, fill_value="extrapolate") + ap_match = fap(wavelength_ifu) + apcorr_match[i,:] = ap_match + + self.apcorr = apcorr_match + self.size = size_match + + + def find_apcorr_func(self, iwave, radius_ifu): + # at ifu wavelength plane (iwave), the extraction radius is radius_ifu + # pull out the radius values (self.size) to use in the apcor ref file for this iwave + # self.size and self.apcorr have already been interpolated in wavelength to match the + # the ifu wavelength range. + + radius_apcor = self.size[:,iwave] + temparray=self.apcorr[:,iwave] + fap=interp1d(radius_apcor,temparray,fill_value="extrapolate") + correction=fap(radius_ifu) + self.apcorr_correction.append(correction) + return + class ApCorr(ApCorrBase): """'Default' Aperture correction class for use with most spectroscopic modes.""" size_key = 'size' @@ -297,4 +376,7 @@ def select_apcorr(input_model: DataModel) -> Union[Type[ApCorr], Type[ApCorrPhas return ApCorr if input_model.meta.instrument.name == 'NIRSPEC': - return ApCorrPhase + if input_model.meta.exposure.type.upper() == 'NRS_IFU': + return ApCorrRadial + else: + return ApCorrPhase diff --git a/jwst/extract_1d/extract.py b/jwst/extract_1d/extract.py index 73bd61eb4f..297de155e2 100644 --- a/jwst/extract_1d/extract.py +++ b/jwst/extract_1d/extract.py @@ -13,9 +13,9 @@ from .. import datamodels from ..datamodels import dqflags, SlitModel, SpecModel - from ..datamodels.apcorr import ( - MirLrsApcorrModel, MirMrsApcorrModel, NrcWfssApcorrModel, NrsFsApcorrModel, NrsMosApcorrModel, NisWfssApcorrModel + MirLrsApcorrModel, MirMrsApcorrModel, NrcWfssApcorrModel, NrsFsApcorrModel, + NrsMosApcorrModel, NrsIfuApcorrModel, NisWfssApcorrModel ) from ..assign_wcs import niriss # for specifying spectral order number @@ -35,9 +35,10 @@ """Exposure types to be regarded as wide-field slitless spectroscopy.""" # These values are used to indicate whether the input extract1d reference file -# (if any) is JSON or IMAGE. +# (if any) is JSON, IMAGE or ASDF (added for IFU data) FILE_TYPE_JSON = "JSON" FILE_TYPE_IMAGE = "IMAGE" +FILE_TYPE_ASDF = "ASDF" FILE_TYPE_OTHER = "N/A" # This is to prevent calling offset_from_offset multiple times for multi-integration data. @@ -98,14 +99,14 @@ class InvalidSpectralOrderNumberError(Extract1dError): pass -def open_extract1d_ref(refname: str) -> dict: +def open_extract1d_ref(refname: str, exptype: str) -> dict: """Open the extract1d reference file. Parameters ---------- refname : str The name of the extract1d reference file. This file is expected to be - either a JSON file giving extraction information, or a file + a JSON file or ASDF file giving extraction information, or a file containing one or more images that are to be used as masks that define the extraction region and optionally background regions. @@ -115,13 +116,24 @@ def open_extract1d_ref(refname: str) -> dict: If the extract1d reference file is in JSON format, ref_dict will be the dictionary returned by json.load(), except that the file type ('JSON') will also be included with key 'ref_file_type'. + If the extract1d reference file is in asdf format, the ref_dict will + be a dictionary containing two keys: ref_dict['ref_file_type'] = 'ASDF' + and ref_dict['ref_model']. If the reference file is an image, ref_dict will be a dictionary with two keys: ref_dict['ref_file_type'] = 'IMAGE' and ref_dict['ref_model']. The latter will be the open file handle for the jwst.datamodels object for the extract1d file. """ + if refname == "N/A": ref_dict = None + elif exptype in['MIR_MRS', 'NRS_IFU']: + # read in asdf file + extract_model = datamodels.Extract1dIFUModel(refname) + ref_dict = {} + ref_dict['ref_file_type']= FILE_TYPE_ASDF + ref_dict['ref_model'] = extract_model + else: # Try reading the file as JSON. fd = open(refname) @@ -172,12 +184,11 @@ def open_apcorr_ref(refname: str, exptype: str) -> DataModel: 'NIS_WFSS': NisWfssApcorrModel, 'NRS_BRIGHTOBJ': NrsFsApcorrModel, 'NRS_FIXEDSLIT': NrsFsApcorrModel, - 'NRS_IFU': NrsMosApcorrModel, + 'NRS_IFU': NrsIfuApcorrModel, 'NRS_MSASPEC': NrsMosApcorrModel } apcorr_model = apcorr_model_map[exptype] - return apcorr_model(refname) @@ -2509,7 +2520,7 @@ def run_extract1d( """ # Read and interpret the extract1d reference file. - ref_dict = open_extract1d_ref(extract_ref_name) + ref_dict = open_extract1d_ref(extract_ref_name, input_model.meta.exposure.type) apcorr_ref_model = None @@ -2613,6 +2624,8 @@ def do_extract1d( to specify whether the parameters are those that could be read from a JSON-format reference file (i.e. ref_dict['ref_file_type'] = "JSON") + from a asdf-format reference file + (i.e. ref_dict['ref_file_type'] = "ASDF") or parameters relevant for a reference image (i.e. ref_dict['ref_file_type'] = "IMAGE"). @@ -2649,7 +2662,7 @@ def do_extract1d( obtained by iterating over a SourceModelContainer. The default is False. - apcorr_ref_model : `~fits.FITS_rec` or None + apcorr_ref_model : `~fits.FITS_rec`, datamodel or None Table of aperture correction values from the APCORR reference file. Returns diff --git a/jwst/extract_1d/extract_1d_step.py b/jwst/extract_1d/extract_1d_step.py index a43515d811..e2ca805bc3 100644 --- a/jwst/extract_1d/extract_1d_step.py +++ b/jwst/extract_1d/extract_1d_step.py @@ -119,13 +119,17 @@ def process(self, input): input_model.meta.cal_step.extract_1d = 'SKIPPED' return input_model - # Do the extraction + + #______________________________________________________________________ + # Do the extraction for ModelContainer - this might only be WFSS data if isinstance(input_model, datamodels.ModelContainer): - # This is the branch MRS and WFSS data take + # 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 @@ -159,13 +163,11 @@ def process(self, input): ) # Set the step flag to complete result.meta.cal_step.extract_1d = 'COMPLETE' - result.meta.filetype = '1d spectrum' + #-------------------------------------------------------------- + # Data is a ModelContainer but is not WFSS + result.meta.filetype = '1d spectrum' else: - - # For MRS, the input is a container with a list of multiple - # IFUCubeModels. Work on one model at a time, creating - # separate outputs for each. result = datamodels.ModelContainer() for model in input_model: # Get the reference file names @@ -191,13 +193,14 @@ def process(self, input): self.subtract_background, self.use_source_posn, was_source_model=was_source_model, - ) + ) # Set the step flag to complete in each MultiSpecModel temp.meta.cal_step.extract_1d = 'COMPLETE' temp.meta.filetype = '1d spectrum' 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' @@ -236,10 +239,9 @@ def process(self, input): self.log.error('extract_1d will be skipped.') return input_model + #______________________________________________________________________ + # Data that is not a ModelContainer (IFUCube and other single models) else: - - # Input is a single model, resulting in a single output. - # Get the reference file names if input_model.meta.exposure.type in extract.WFSS_EXPTYPES: extract_ref = 'N/A' diff --git a/jwst/extract_1d/ifu.py b/jwst/extract_1d/ifu.py index 0ad6181253..c5be360f13 100644 --- a/jwst/extract_1d/ifu.py +++ b/jwst/extract_1d/ifu.py @@ -1,23 +1,25 @@ from distutils.version import LooseVersion import logging -import math - import numpy as np import photutils from photutils import CircularAperture, CircularAnnulus, \ RectangularAperture, aperture_photometry from .apply_apcorr import select_apcorr +from ..assign_wcs.util import compute_scale + from .. import datamodels from ..datamodels import dqflags from . import spec_wcs +from scipy.interpolate import interp1d log = logging.getLogger(__name__) log.setLevel(logging.DEBUG) # These values are used to indicate whether the input extract1d reference file -# (if any) is JSON or IMAGE. -FILE_TYPE_JSON = "JSON" +# (if any) is ASDF (default) or IMAGE + +FILE_TYPE_ASDF = "ASDF" FILE_TYPE_IMAGE = "IMAGE" # This is to prevent calling offset_from_offset multiple times for @@ -49,7 +51,7 @@ def ifu_extract1d(input_model, ref_dict, source_type, subtract_background, apcor If not None, this parameter overrides the value in the extract_1d reference file. - apcorr_ref_model : ~.fits.FITS_rec or None + apcorr_ref_model : apcorr datamodel or None Aperture correction table. Returns @@ -94,8 +96,8 @@ def ifu_extract1d(input_model, ref_dict, source_type, subtract_background, apcor extract_params['subtract_background'] = subtract_background if extract_params: - if extract_params['ref_file_type'] == FILE_TYPE_JSON: - (ra, dec, wavelength, temp_flux, background, npixels, dq, npixels_bkg) = \ + if extract_params['ref_file_type'] == FILE_TYPE_ASDF: + (ra, dec, wavelength, temp_flux, background, npixels, dq, npixels_bkg, radius_match) = \ extract_ifu(input_model, source_type, extract_params) else: # FILE_TYPE_IMAGE (ra, dec, wavelength, temp_flux, background, npixels, dq, npixels_bkg) = \ @@ -120,7 +122,6 @@ def ifu_extract1d(input_model, ref_dict, source_type, subtract_background, apcor "the flux will not be correct.") pixel_solid_angle = 1. - print(input_units_are_megajanskys,pixel_solid_angle) if input_units_are_megajanskys: # Convert flux from MJy to Jy, and convert background to MJy / sr. flux = temp_flux * 1.e6 @@ -131,7 +132,6 @@ def ifu_extract1d(input_model, ref_dict, source_type, subtract_background, apcor flux = temp_flux * pixel_solid_angle * 1.e6 # surf_bright and background were computed above del temp_flux - error = np.zeros_like(flux) sb_error = np.zeros_like(flux) berror = np.zeros_like(flux) @@ -163,10 +163,19 @@ def ifu_extract1d(input_model, ref_dict, source_type, subtract_background, apcor wl = wavelength.min() apcorr = select_apcorr(input_model)( - input_model, apcorr_ref_model.apcorr_table, apcorr_ref_model.sizeunit, location=(ra, dec, wl) + input_model, + apcorr_ref_model, + location=(ra, dec, wl) ) - print(apcorr) + # determine apcor function and apcor radius to use at each wavelength + apcorr.match_wavelengths(wavelength) + + # at each IFU wavelength we have the extraction radius defined by radius_match (radius size in pixels) + for i in range(wavelength.size): + radius = radius_match[i] + apcorr.find_apcorr_func(i, radius) + apcorr.apply(spec.spec_table) output_model.spec.append(spec) @@ -195,30 +204,27 @@ def get_extract_parameters(ref_dict, slitname): """ extract_params = {} - - if ('ref_file_type' not in ref_dict or - ref_dict['ref_file_type'] == FILE_TYPE_JSON): - extract_params['ref_file_type'] = FILE_TYPE_JSON - for aper in ref_dict['apertures']: - if 'id' in aper and aper['id'] != "dummy" and \ - (aper['id'] == slitname or aper['id'] == "ANY" or - slitname == "ANY"): - region_type = aper.get("region_type", "target") - if region_type == "target": - extract_params['x_center'] = aper.get('x_center') - extract_params['y_center'] = aper.get('y_center') - extract_params['method'] = aper.get('method', 'exact') - extract_params['subpixels'] = aper.get('subpixels', 5) - extract_params['radius'] = aper.get('radius') - extract_params['subtract_background'] = \ - aper.get('subtract_background', False) - extract_params['inner_bkg'] = aper.get('inner_bkg') - extract_params['outer_bkg'] = aper.get('outer_bkg') - extract_params['width'] = aper.get('width') - extract_params['height'] = aper.get('height') - # theta is in degrees (converted to radians later) - extract_params['theta'] = aper.get('theta', 0.) - break + if ref_dict['ref_file_type'] == FILE_TYPE_ASDF: + extract_params['ref_file_type'] = FILE_TYPE_ASDF + refmodel = ref_dict['ref_model'] + #region_type = refmodel.meta.region_type + subtract_background = refmodel.meta.subtract_background + method = refmodel.meta.method + subpixels =refmodel.meta.subpixels + + data = refmodel.data + wavelength = data.wavelength + radius = data.radius + inner_bkg = data.inner_bkg + outer_bkg = data.outer_bkg + + extract_params['subtract_background'] = bool(subtract_background) + extract_params['method'] = method + extract_params['subpixels'] = subpixels + extract_params['wavelength'] = wavelength + extract_params['radius'] = radius + extract_params['inner_bkg'] = inner_bkg + extract_params['outer_bkg'] = outer_bkg elif ref_dict['ref_file_type'] == FILE_TYPE_IMAGE: extract_params['ref_file_type'] = FILE_TYPE_IMAGE @@ -289,6 +295,9 @@ def extract_ifu(input_model, source_type, extract_params): npixels_annulus : ndarray, 1-D, float64 For each slice, this is the number of pixels that were added together to get `temp_flux` for an annulus region. + + radius_match: ndarray,1-D, float64 + The size of the extract radius in pixels used at each wavelength of the IFU cube """ data = input_model.data @@ -315,36 +324,70 @@ def extract_ifu(input_model, source_type, extract_params): ra_targ = input_model.meta.target.ra dec_targ = input_model.meta.target.dec locn = locn_from_wcs(input_model, ra_targ, dec_targ) + if locn is None or np.isnan(locn[0]): log.warning("Couldn't determine pixel location from WCS, so " "source offset correction will not be applied.") - x_center = extract_params['x_center'] - y_center = extract_params['y_center'] - if x_center is None: - x_center = float(shape[-1]) / 2. - else: - x_center = float(x_center) - if y_center is None: - y_center = float(shape[-2]) / 2. - else: - y_center = float(y_center) + + x_center = float(shape[-1]) / 2. + y_center = float(shape[-2]) / 2. + else: (x_center, y_center) = locn log.info("Using x_center = %g, y_center = %g, based on " "TARG_RA and TARG_DEC.", x_center, y_center) method = extract_params['method'] - # subpixels is only needed if method = 'subpixel'. subpixels = extract_params['subpixels'] - subtract_background = extract_params['subtract_background'] - smaller_axis = float(min(shape[-2], shape[-1])) # for defaults radius = None inner_bkg = None outer_bkg = None + width = None + height = None + theta = None + # pull wavelength plane out of input data. + # using extract 1d wavelength, interpolate the radius, inner_bkg, outer_bkg to match input wavelength - if source_type == 'EXTENDED': + # find the wavelength array of the IFU cube + x0 = float(shape[2]) / 2. + y0 = float(shape[1]) / 2. + (ra, dec, wavelength) = get_coordinates(input_model, x0, y0) + + # interpolate the extraction parameters to the wavelength of the IFU cube + radius_match = None + if source_type == 'POINT': + wave_extract = extract_params['wavelength'].flatten() + inner_bkg = extract_params['inner_bkg'].flatten() + outer_bkg = extract_params['outer_bkg'].flatten() + radius = extract_params['radius'].flatten() + + frad = interp1d(wave_extract, radius, bounds_error=False, fill_value="extrapolate") + radius_match = frad(wavelength) + # radius_match is in arc seconds - need to convert to pixels + # the spatial scale is the same for all wavelengths do we only need to call compute_scale once. + + if locn is None: + locn_use = (input_model.meta.wcsinfo.crval1, input_model.meta.wcsinfo.crval2, wavelength[0]) + else: + locn_use = (ra_targ, dec_targ, wavelength[0]) + + scale_degrees = compute_scale( + input_model.meta.wcs, + locn_use, + disp_axis=input_model.meta.wcsinfo.dispersion_direction) + + scale_arcsec = scale_degrees*3600.00 + radius_match /= scale_arcsec + + finner = interp1d(wave_extract, inner_bkg, bounds_error=False, fill_value="extrapolate") + inner_bkg_match = finner(wavelength)/scale_arcsec + + fouter = interp1d(wave_extract, outer_bkg, bounds_error=False, fill_value="extrapolate") + outer_bkg_match = fouter(wavelength)/scale_arcsec + + elif source_type == 'EXTENDED': # Ignore any input parameters, and extract the whole image. width = float(shape[-1]) height = float(shape[-2]) @@ -352,34 +395,11 @@ def extract_ifu(input_model, source_type, extract_params): y_center = height / 2. - 0.5 theta = 0. subtract_background = False - else: - radius = extract_params['radius'] - if radius is None: - radius = smaller_axis / 4. - if subtract_background: - inner_bkg = extract_params['inner_bkg'] - if inner_bkg is None: - inner_bkg = radius - outer_bkg = extract_params['outer_bkg'] - if outer_bkg is None: - outer_bkg = min(inner_bkg * math.sqrt(2.), - smaller_axis / 2. - 1.) - if inner_bkg <= 0. or outer_bkg <= 0. or inner_bkg >= outer_bkg: - log.debug("Turning background subtraction off, due to " - "the values of inner_bkg and outer_bkg.") - subtract_background = False - width = None - height = None - theta = None log.debug("IFU 1-D extraction parameters:") log.debug(" x_center = %s", str(x_center)) log.debug(" y_center = %s", str(y_center)) if source_type == 'POINT': - log.debug(" radius = %s", str(radius)) - log.debug(" subtract_background = %s", str(subtract_background)) - log.debug(" inner_bkg = %s", str(inner_bkg)) - log.debug(" outer_bkg = %s", str(outer_bkg)) log.debug(" method = %s", method) if method == "subpixel": log.debug(" subpixels = %s", str(subpixels)) @@ -392,22 +412,32 @@ def extract_ifu(input_model, source_type, extract_params): if method == "subpixel": log.debug(" subpixels = %s", str(subpixels)) - x0 = float(shape[2]) / 2. - y0 = float(shape[1]) / 2. - (ra, dec, wavelength) = get_coordinates(input_model, x0, y0) - position = (x_center, y_center) - if source_type == 'POINT': - aperture = CircularAperture(position, r=radius) - else: - aperture = RectangularAperture(position, width, height, theta) - if subtract_background and inner_bkg is not None and outer_bkg is not None: - annulus = CircularAnnulus(position, r_in=inner_bkg, r_out=outer_bkg) - else: + # get aperture for extended it will not change with wavelength + if source_type == 'EXTENDED': + aperture = RectangularAperture(position, width, height, theta) annulus = None for k in range(shape[0]): + inner_bkg = None + outer_bkg = None + + if source_type == 'POINT': + radius = radius_match[k] # this radius has been converted to pixels + aperture = CircularAperture(position, r=radius) + inner_bkg = inner_bkg_match[k] + outer_bkg = outer_bkg_match[k] + if inner_bkg <= 0. or outer_bkg <= 0. or inner_bkg >= outer_bkg: + log.debug("Turning background subtraction off, due to " + "the values of inner_bkg and outer_bkg.") + subtract_background = False + + if subtract_background and inner_bkg is not None and outer_bkg is not None: + annulus = CircularAnnulus(position, r_in=inner_bkg, r_out=outer_bkg) + else: + annulus = None + subtract_background_plane = subtract_background # Compute the area of the aperture and possibly also of the annulus. # for each wavelength bin (taking into account empty spaxels) @@ -472,14 +502,13 @@ def extract_ifu(input_model, source_type, extract_params): background[k] = float(bkg_table['aperture_sum'][0]) temp_flux[k] = temp_flux[k] - background[k] * normalization - # Check for NaNs in the wavelength array, flag them in the dq array, # and truncate the arrays if NaNs are found at endpoints (unless the # entire array is NaN). (wavelength, temp_flux, background, npixels, dq, npixels_annulus) = \ nans_in_wavelength(wavelength, temp_flux, background, npixels, dq, npixels_annulus) - return (ra, dec, wavelength, temp_flux, background, npixels, dq, npixels_annulus) + return (ra, dec, wavelength, temp_flux, background, npixels, dq, npixels_annulus, radius_match) def locn_from_wcs(input_model, ra_targ, dec_targ): @@ -724,7 +753,6 @@ def image_extract_ifu(input_model, source_type, extract_params): normalization = npixels / n_bkg del temp - # Extract the background. if mask_bkg is not None: background = (data * mask_bkg).sum(axis=2, dtype=np.float64).sum(axis=1) diff --git a/jwst/extract_1d/tests/data/jwst_nirspec_apcorr_ifu_dummy.fits b/jwst/extract_1d/tests/data/jwst_nirspec_apcorr_msa_dummy.fits similarity index 100% rename from jwst/extract_1d/tests/data/jwst_nirspec_apcorr_ifu_dummy.fits rename to jwst/extract_1d/tests/data/jwst_nirspec_apcorr_msa_dummy.fits diff --git a/jwst/extract_1d/tests/test_apply_apcorr_ifu.py b/jwst/extract_1d/tests/test_apply_apcorr_ifu.py new file mode 100644 index 0000000000..3fe1b89bb4 --- /dev/null +++ b/jwst/extract_1d/tests/test_apply_apcorr_ifu.py @@ -0,0 +1,151 @@ +import pytest +import numpy as np +from asdf import AsdfFile + +from jwst.datamodels import IFUCubeModel, NrsIfuApcorrModel, MirMrsApcorrModel +from jwst.extract_1d.apply_apcorr import ApCorrRadial, select_apcorr + + +@pytest.fixture(scope='module') +def dummy_nirspec_ref(tmpdir_factory): + + """ Generate a dummy apcorr ref file """ + filename = tmpdir_factory.mktemp('dummy_apcorr') + filename = str(filename.join('dummy_nirspec_apcorr.asdf')) + + refap = {} + refap['meta'] = {} + refap['apcorr_table'] = {} + refap['meta']['telescope'] = 'JWST' + refap['meta']['reftype'] = 'APCORR' + refap['meta']['exp_type'] = 'P_EXP_TY' + refap['meta']['detector'] = 'N/A' + refap['meta']['datamodel'] = 'NrsIfuApcorrModel' + refap['meta']['version'] = '1.0' + refap['meta']['name'] = 'NIRSPEC' + refap['meta']['origin']= 'STScI' + + dummy_wave = np.zeros(100) + 0.5 + dummy_radius = np.zeros((3,100)) + 0.5 + dummy_apcorr = np.ones((3,100)) + dummy_apcorr_error = np.zeros((3,100)) + + refap['apcorr_table'] = {} + refap['apcorr_table']['wavelength'] = dummy_wave.copy() + refap['apcorr_table']['radius'] = dummy_radius.copy() + refap['apcorr_table']['apcorr'] = dummy_apcorr.copy() + refap['apcorr_table']['apcorr_err'] = dummy_apcorr_error.copy() + refap['apcorr_table']['wavelength_units'] = 'microns' + refap['apcorr_table']['radius_units'] = 'arcseconds' + refap['apcorr_table']['filter'] = 'ANY' + refap['apcorr_table']['grating'] = 'ANY' + + + ff = AsdfFile(refap) + ff.set_array_storage(refap['apcorr_table']['wavelength'],'inline') + ff.set_array_storage(refap['apcorr_table']['radius'],'inline') + ff.set_array_storage(refap['apcorr_table']['apcorr'],'inline') + ff.set_array_storage(refap['apcorr_table']['apcorr_err'],'inline') + + ff.write_to(filename) + return filename + +@pytest.fixture(scope='module') +def dummy_miri_ref(tmpdir_factory): + + """ Generate a dummy apcorr ref file """ + filename = tmpdir_factory.mktemp('dummy_apcorr') + filename = str(filename.join('dummy_miri_apcorr.asdf')) + + refap = {} + refap['meta'] = {} + refap['apcorr_table'] = {} + refap['meta']['telescope'] = 'JWST' + refap['meta']['reftype'] = 'APCORR' + refap['meta']['exp_type'] = 'MIRI_MRS' + refap['meta']['detector'] = 'N/A' + refap['meta']['datamodel'] = 'MirMrsApcorrModel' + refap['meta']['version'] = '1.0' + refap['meta']['name'] = 'MIRI' + refap['meta']['origin']= 'STScI' + + dummy_wave = np.zeros(100) + 0.5 + dummy_radius = np.zeros((3,100)) + 0.5 + dummy_apcorr = np.ones((3,100)) + dummy_apcorr_error = np.zeros((3,100)) + + refap['apcorr_table'] = {} + refap['apcorr_table']['wavelength'] = dummy_wave.copy() + refap['apcorr_table']['radius'] = dummy_radius.copy() + refap['apcorr_table']['apcorr'] = dummy_apcorr.copy() + refap['apcorr_table']['apcorr_err'] = dummy_apcorr_error.copy() + refap['apcorr_table']['wavelength_units'] = 'microns' + refap['apcorr_table']['radius_units'] = 'arcseconds' + refap['apcorr_table']['channel'] = 'ANY' + refap['apcorr_table']['band'] = 'ANY' + + ff = AsdfFile(refap) + ff.set_array_storage(refap['apcorr_table']['wavelength'],'inline') + ff.set_array_storage(refap['apcorr_table']['radius'],'inline') + ff.set_array_storage(refap['apcorr_table']['apcorr'],'inline') + ff.set_array_storage(refap['apcorr_table']['apcorr_err'],'inline') + + ff.write_to(filename) + return filename + + +@pytest.fixture +def miri_cube(): + model = IFUCubeModel((15,10,10)) + instrument = 'MIRI' + exptype = 'MIR_MRS' + model.meta.instrument.name = instrument + model.meta.exposure.type = exptype + model.meta.instrument.channel = '1' + model.meta.instrument.band = 'SHORT' + return model + +@pytest.fixture +def nirspec_cube(): + model = IFUCubeModel((15,10,10)) + instrument = 'NIRSPEC' + exptype = 'NRS_IFU' + model.meta.instrument.name = instrument + model.meta.exposure.type = exptype + model.meta.instrument.filter = 'CLEAR' + model.meta.instrument.prism = 'PRISM' + return model + +def test_select_apcorr_miri(miri_cube): + + apcorr_cls = select_apcorr(miri_cube) + assert apcorr_cls == ApCorrRadial + +def test_select_apcorr_nirspec(nirspec_cube): + + apcorr_cls = select_apcorr(nirspec_cube) + assert apcorr_cls == ApCorrRadial + + +def test_table_type_miri(_jail, dummy_miri_ref, miri_cube): + + dummy_wave = np.zeros(100) + 0.5 + apcorr_model = MirMrsApcorrModel(dummy_miri_ref) + table = apcorr_model.apcorr_table + apcorr_model.close() + + assert table.channel == 'ANY' + assert table.band == 'ANY' + assert np.all(table.wavelength == dummy_wave) + + +def test_table_type_nirspec(_jail, dummy_nirspec_ref, nirspec_cube): + + dummy_wave = np.zeros(100) + 0.5 + apcorr_model = NrsIfuApcorrModel(dummy_nirspec_ref) + table = apcorr_model.apcorr_table + apcorr_model.close() + + assert table.filter == 'ANY' + assert table.grating == 'ANY' + assert np.all(table.wavelength == dummy_wave) diff --git a/jwst/extract_1d/tests/test_apply_apcorr.py b/jwst/extract_1d/tests/test_apply_apcorr_nonifu.py similarity index 82% rename from jwst/extract_1d/tests/test_apply_apcorr.py rename to jwst/extract_1d/tests/test_apply_apcorr_nonifu.py index a63532f878..96f971165c 100644 --- a/jwst/extract_1d/tests/test_apply_apcorr.py +++ b/jwst/extract_1d/tests/test_apply_apcorr_nonifu.py @@ -5,12 +5,12 @@ from astropy.io import fits from astropy.table import Table -from jwst.datamodels import JwstDataModel -from jwst.extract_1d.apply_apcorr import ApCorr, ApCorrRadial, ApCorrPhase, select_apcorr +from jwst.datamodels import DataModel +from jwst.extract_1d.apply_apcorr import ApCorr, ApCorrPhase, select_apcorr data_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), 'data')) NIR_TEST_FILES = { - 'ISU/MSASPEC': os.path.join(data_dir, 'jwst_nirspec_apcorr_ifu_dummy.fits'), + 'MSASPEC': os.path.join(data_dir, 'jwst_nirspec_apcorr_msa_dummy.fits'), 'FIXEDSLIT/BRIGHTOBJ': os.path.join(data_dir, 'jwst_nirspec_apcorr_fs_dummy.fits') } @@ -20,13 +20,11 @@ params=[ ('MIRI', 'MIR_LRS-FIXEDSLIT'), ('MIRI', 'MIR_LRS-SLITLESS'), - ('MIRI', 'MIR_MRS'), ('NIRCAM', 'NRC_GRISM'), ('NIRCAM', 'NRC_WFSS'), ('NIRISS', 'NIS_WFSS'), ('NIRSPEC', 'NRS_BRIGHTOBJ'), ('NIRSPEC', 'NRS_FIXEDSLIT'), - ('NIRSPEC', 'NRS_IFU'), ('NIRSPEC', 'NRS_MSASPEC') ] ) @@ -35,7 +33,7 @@ def inputs(request): instrument, exptype = request.param - dm = JwstDataModel() + dm = DataModel() dm.meta.instrument.name = instrument dm.meta.exposure.type = exptype @@ -52,15 +50,6 @@ def inputs(request): 'apcorr': np.full((2, 4, 4), 0.5) } ) - if 'MRS' in exptype: - table = Table( - { - 'wavelength': [np.arange(12), ], - 'radius': [np.arange(12), ], - 'nelem_wl': [10, ], - 'apcorr': np.full((1, 12), 0.5) - } - ) if instrument == 'NIRSPEC': # Too complicated to come up with silly example data; using "dummy" ref file dm.meta.instrument.filter = 'CLEAR' @@ -70,7 +59,7 @@ def inputs(request): dm.meta.instrument.fixed_slit = 'S200A1' table = Table.read(NIR_TEST_FILES['FIXEDSLIT/BRIGHTOBJ'], format='fits')[:2] else: - table = Table.read(NIR_TEST_FILES['ISU/MSASPEC'], format='fits')[:2] + table = Table.read(NIR_TEST_FILES['MSASPEC'], format='fits')[:2] table['APCORR'] = table['APCORR'].reshape((len(table), 3, 2048, 3)) # Reshape test data to expected shape @@ -122,13 +111,10 @@ def apcorr_instance(inputs): def test_select_apcorr(inputs): dm, _ = inputs - apcorr_cls = select_apcorr(dm) if dm.meta.instrument.name == 'NIRSPEC': - assert apcorr_cls == ApCorrPhase - elif 'MRS' in dm.meta.exposure.type: - assert apcorr_cls == ApCorrRadial + assert apcorr_cls == ApCorrPhase else: assert apcorr_cls == ApCorr @@ -137,10 +123,7 @@ class TestApCorr: def test_match_parameters(self, apcorr_instance): if apcorr_instance.model.meta.instrument.name == 'MIRI': - if 'MRS' in apcorr_instance.model.meta.exposure.type: - assert apcorr_instance.match_pars == {} - else: - assert apcorr_instance.match_pars == {'subarray': 'FULL'} + assert apcorr_instance.match_pars == {'subarray': 'FULL'} if apcorr_instance.model.meta.instrument.name == 'NIRSPEC': if 'FIXEDSLIT' in apcorr_instance.model.meta.exposure.type: diff --git a/jwst/extract_1d/tests/test_ifu_image_ref.py b/jwst/extract_1d/tests/test_ifu_image_ref.py index 097726d8b3..70d08e7071 100644 --- a/jwst/extract_1d/tests/test_ifu_image_ref.py +++ b/jwst/extract_1d/tests/test_ifu_image_ref.py @@ -15,79 +15,8 @@ outer_bkg = 16.5 method = "exact" - -def test_ifu_2d(): - """Test 1""" - - input = make_ifu_cube(data_shape, source=5., background=3.7, - x_center=x_center, y_center=y_center, - radius=radius, - inner_bkg=inner_bkg, outer_bkg=outer_bkg) - - # Create a reference dictionary to specify the extraction parameters. - # This will serve as the "truth" for comparison with the results of - # using a reference image. - ref_dict = {"ref_file_type": extract.FILE_TYPE_JSON, - "apertures": [{"id": "ANY", - "x_center": x_center, - "y_center": y_center, - "radius": radius, - "subtract_background": True, - "inner_bkg": inner_bkg, - "outer_bkg": outer_bkg, - "method": method - } - ] - } - truth = extract.do_extract1d(input, ref_dict, smoothing_length=0, - bkg_order=0, log_increment=50, - subtract_background=True) - - ref_image_2d = make_ref_image(data_shape[-2:], # 2-D ref image - x_center=x_center, y_center=y_center, - radius=radius, - inner_bkg=inner_bkg, outer_bkg=outer_bkg) - - ref_dict_2d = {"ref_file_type": extract.FILE_TYPE_IMAGE, - "ref_model": ref_image_2d} - output = extract.do_extract1d(input, ref_dict_2d, smoothing_length=0, - bkg_order=0, log_increment=50, - subtract_background=True) - - true_wl = truth.spec[0].spec_table['wavelength'] - true_flux = truth.spec[0].spec_table['flux'] - true_bkg = truth.spec[0].spec_table['background'] - - wavelength = output.spec[0].spec_table['wavelength'] - flux = output.spec[0].spec_table['flux'] - background = output.spec[0].spec_table['background'] - - """The wavelengths should agree exactly, because they were computed - in the same way for both `output` and `truth`. - The source is 5 per pixel, and since the radius is 11.5, the sum of - all those values is 2077.378; this is the flux (actually net, but net - was moved to flux). The IFU cube was made by assigning values to the - nearest pixels, however, resulting in jagged edges to the disk and - background annulus. With a radius of 11.5 (circumference = 72.26) and - outer background radius of 16.5 (circumference = 103.67), differences - of order 150 between an exact computation and nearest pixel are not - unreasonable. - """ - assert np.allclose(wavelength, true_wl, rtol=1.e-14, atol=1.e-14) - - assert np.allclose(flux, true_flux, rtol=0.06) - - assert np.allclose(background, true_bkg, rtol=0.1) - - input.close() - truth.close() - output.close() - del ref_dict_2d - ref_image_2d.close() - - def test_ifu_3d(): - """Test 2""" + """Test 1""" input = make_ifu_cube(data_shape, source=5., background=3.7, x_center=x_center, y_center=y_center, @@ -141,67 +70,6 @@ def test_ifu_3d(): ref_image_3d.close() -def test_ifu_no_background(): - """Test 3""" - - # There is a background ... - input = make_ifu_cube(data_shape, source=5., background=3.7, - x_center=x_center, y_center=y_center, - radius=radius, - inner_bkg=inner_bkg, outer_bkg=outer_bkg) - - # ... but turn off background subtraction anyway. - ref_image_2d = make_ref_image(data_shape[-2:], # 2-D ref image - x_center=x_center, y_center=y_center, - radius=radius, - inner_bkg=None, outer_bkg=None) - - # Create a reference dictionary to specify the extraction parameters. - # This will serve as the "truth" for comparison with the results of - # using a reference image. - ref_dict = {"ref_file_type": extract.FILE_TYPE_JSON, - "apertures": [{"id": "ANY", - "x_center": x_center, - "y_center": y_center, - "radius": radius, - "subtract_background": True, - "method": method - } - ] - } - # Set background subtraction to False, overriding what was specified - # in the reference dictionary. - truth = extract.do_extract1d(input, ref_dict, smoothing_length=0, - bkg_order=0, log_increment=50, - subtract_background=False) - - ref_dict_2d = {"ref_file_type": extract.FILE_TYPE_IMAGE, - "ref_model": ref_image_2d} - output = extract.do_extract1d(input, ref_dict_2d, smoothing_length=0, - bkg_order=0, log_increment=50, - subtract_background=False) - - true_wl = truth.spec[0].spec_table['wavelength'] - true_flux = truth.spec[0].spec_table['flux'] - true_bkg = truth.spec[0].spec_table['background'] - - wavelength = output.spec[0].spec_table['wavelength'] - flux = output.spec[0].spec_table['flux'] - background = output.spec[0].spec_table['background'] - - assert np.allclose(wavelength, true_wl, rtol=1.e-14, atol=1.e-14) - - assert np.allclose(flux, true_flux, rtol=0.03) - - assert np.allclose(background, true_bkg, atol=1.) - - input.close() - truth.close() - output.close() - del ref_dict_2d - ref_image_2d.close() - - def make_ifu_cube(data_shape, source=None, background=None, x_center=None, y_center=None, radius=None, inner_bkg=None, outer_bkg=None):