Exemple #1
0
def test_model_set_raises_value_error(expr, result):
    """Check that creating model sets with components whose _n_models are
       different raise a value error
    """
    with pytest.raises(ValueError):
        s = expr(Const1D((2, 2), n_models=2), Const1D(3, n_models=1))
Exemple #2
0
def extract_grism_objects(input_model,
                          grism_objects=None,
                          reference_files=None,
                          extract_orders=None,
                          mmag_extract=None,
                          compute_wavelength=True,
                          wfss_extract_half_height=None,
                          nbright=None):
    """
    Extract 2d boxes around each objects spectra for each order.

    Parameters
    ----------
    input_model : `~jwst.datamodels.ImageModel`
        An instance of an ImageModel, this is the grism image

    grism_objects : list(GrismObject)
        A list of GrismObjects

    reference_files : dict
        Needs to include the name of the wavelengthrange reference file

    extract_orders : int
        Spectral orders to extract

    mmag_extract : float
        Sources with magnitudes fainter than this minimum magnitude extraction
        cutoff will not be extracted

    compute_wavelength : bool
        Compute a wavelength array for the datamodel.

    wfss_extract_half_height : int, (optional)
        Cross-dispersion extraction half height in pixels, WFSS mode.
        Overwrites the computed extraction height.

    nbright : int
        Number of brightest objects to extract for WFSS mode.

    Returns
    -------
    output_model : `~jwst.datamodels.MultiSlitModel`


    Notes
    -----
    This method supports NRC_WFSS and NIS_WFSS only

    GrismObject is a named tuple which contains distilled
    information about each catalog object. It can be created
    by calling jwst.assign_wcs.util.create_grism_bbox() which
    will return a list of GrismObjects that contain the bounding
    boxes that will be used to define the 2d extraction area.

    For each spectral order, the configuration file contains a
    magnitude-cutoff value. The total list of objects to extract is limited
    by both MMAG_EXTRACT and NBRIGHT. Sources with magnitudes fainter than the
    extraction cutoff (MMAG_EXTRACT) will not be extracted, but are
    accounted for when computing the spectral contamination and background
    estimates. The default value is 99 right now.
    NBRIGHT further limits the list to the NBRIGHT brightest objects.
    The default value is 999 right now.

    The sensitivity information from the original aXe style configuration
    file needs to be modified by the passband of the filter used for
    the direct image to get the min and max wavelengths
    which correspond to t=0 and t=1, this currently has been done by the team
    and the min and max wavelengths to use to calculate t are stored in the
    grism reference file as wavelengthrange.

    Step 1: Convert the source catalog from the reference frame of the
            uberimage to that of the dispersed image.  For the Vanilla
            Pipeline we assume that the pointing information in the file
            headers is sufficient.  This will be strictly true if all images
            were obtained in a single visit (same guide stars).

    Step 2: Record source information for each object in the catalog: position
            (RA and Dec), shape (A_IMAGE, B_IMAGE, THETA_IMAGE), and all
            available magnitudes, and minimum bounding boxes

    Step 3: Compute the trace and wavelength solutions for each object in the
            catalog and for each spectral order.  Record this information.

    Step 4: Compute the WIDTH of each spectral subwindow, which may be fixed or
            variable. The cross-dispersion size is taken from the minimum
            bounding box.
    """
    if reference_files is None or not reference_files:
        raise TypeError("Expected a dictionary for reference_files")

    if grism_objects is None:
        # get the wavelengthrange reference file from the input_model
        if ('wavelengthrange' not in reference_files
                or reference_files['wavelengthrange'] in ['N/A', '']):
            raise ValueError("Expected name of wavelengthrange reference file")
        else:
            grism_objects = util.create_grism_bbox(
                input_model,
                reference_files,
                extract_orders=extract_orders,
                mmag_extract=mmag_extract,
                wfss_extract_half_height=wfss_extract_half_height,
                nbright=nbright)
            log.info(
                "Grism object list created from source catalog: {0:s}".format(
                    input_model.meta.source_catalog))

    if not isinstance(grism_objects, list):
        raise TypeError("Expected input grism objects to be a list")
    if len(grism_objects) == 0:
        raise ValueError("No grism objects created from source catalog")

    log.info("Extracting %d grism objects", len(grism_objects))
    output_model = datamodels.MultiSlitModel()
    output_model.update(input_model)

    # One WCS model can be used to govern all the extractions
    # and in fact the model transforms rely on the full frame
    # coordinates of the input pixel location. So the WCS
    # attached to the extraction is just a copy of the
    # input_model WCS with a shift transform to the corner
    # of the subarray. They also depend on the source object
    # center, this information will be saved to the meta of
    # the output model as source_[x/y]pos
    inwcs = input_model.meta.wcs

    # For easy reference here, GrismObjects has:
    #
    # xcenter,ycenter: in direct image pixels
    # order_bounding in grism_detector pixels
    # sky_centroid: SkyCoord of object center
    # sky_bbox_ :lower and upper bounding box in SkyCoord
    # sid: catalog ID of the object

    slits = []
    for obj in grism_objects:
        for order in obj.order_bounding.keys():

            # Add the shift to the lower corner to each subarray WCS object
            # The shift should just be the lower bounding box corner
            # also replace the object center location inputs to the GrismDispersion
            # model with the known object center and order information (in pixels of direct image)
            # This is changes the user input to the model from (x,y,x0,y0,order) -> (x,y)
            #
            # The bounding boxes here are also limited to the size of the detector
            # The check for boxes entirely off the detector is done in create_grism_bbox right now
            y, x = obj.order_bounding[order]

            # limit the boxes to the detector
            ymin = clamp(y[0], 0, input_model.meta.subarray.ysize)
            ymax = clamp(y[1], 0, input_model.meta.subarray.ysize)
            xmin = clamp(x[0], 0, input_model.meta.subarray.xsize)
            xmax = clamp(x[1], 0, input_model.meta.subarray.xsize)

            # don't extract anything that ended up with zero dimensions in one axis
            # this means that it was identified as a partial order but only on one
            # row or column of the detector
            if (((ymax - ymin) > 0) and ((xmax - xmin) > 0)):
                subwcs = copy.deepcopy(inwcs)
                log.info("Subarray extracted for obj: {} order: {}:".format(
                    obj.sid, order))
                log.info("Subarray extents are: "
                         "(xmin:{}, xmax:{}), (ymin:{}, ymax:{})".format(
                             xmin, xmax, ymin, ymax))

                # only the first two numbers in the Mapping are used
                # the order and source position are put directly into
                # the new wcs for the subarray for the forward transform
                xcenter_model = Const1D(obj.xcentroid)
                xcenter_model.inverse = Const1D(obj.xcentroid)

                ycenter_model = Const1D(obj.ycentroid)
                ycenter_model.inverse = Const1D(obj.ycentroid)

                order_model = Const1D(order)
                order_model.inverse = Const1D(order)

                tr = inwcs.get_transform('grism_detector', 'detector')

                tr = Mapping((0, 1, 0, 0,
                              0)) | (Shift(xmin) & Shift(ymin) & xcenter_model
                                     & ycenter_model & order_model) | tr

                ext_data = input_model.data[ymin:ymax + 1,
                                            xmin:xmax + 1].copy()
                ext_err = input_model.err[ymin:ymax + 1, xmin:xmax + 1].copy()
                ext_dq = input_model.dq[ymin:ymax + 1, xmin:xmax + 1].copy()
                if input_model.var_poisson is not None and np.size(
                        input_model.var_poisson) > 0:
                    var_poisson = input_model.var_poisson[ymin:ymax + 1,
                                                          xmin:xmax +
                                                          1].copy()
                else:
                    var_poisson = None
                if input_model.var_rnoise is not None and np.size(
                        input_model.var_rnoise) > 0:
                    var_rnoise = input_model.var_rnoise[ymin:ymax + 1,
                                                        xmin:xmax + 1].copy()
                else:
                    var_rnoise = None
                if input_model.var_flat is not None and np.size(
                        input_model.var_flat) > 0:
                    var_flat = input_model.var_flat[ymin:ymax + 1,
                                                    xmin:xmax + 1].copy()
                else:
                    var_flat = None

                tr.bounding_box = util.transform_bbox_from_shape(
                    ext_data.shape)
                subwcs.set_transform('grism_detector', 'detector', tr)

                new_slit = datamodels.SlitModel(data=ext_data,
                                                err=ext_err,
                                                dq=ext_dq,
                                                var_poisson=var_poisson,
                                                var_rnoise=var_rnoise,
                                                var_flat=var_flat)
                new_slit.meta.wcsinfo.spectral_order = order
                new_slit.meta.wcsinfo.dispersion_direction = \
                    input_model.meta.wcsinfo.dispersion_direction
                new_slit.meta.wcsinfo.specsys = input_model.meta.wcsinfo.specsys
                new_slit.meta.coordinates = input_model.meta.coordinates
                new_slit.meta.wcs = subwcs
                if compute_wavelength:
                    log.debug("Computing wavelengths")
                    new_slit.wavelength = compute_wavelength_array(new_slit)

                # set x/ystart values relative to the image (screen) frame.
                # The overall subarray offset is recorded in model.meta.subarray.
                # nslit = obj.sid - 1  # catalog id starts at zero
                new_slit.name = "{0}".format(obj.sid)
                new_slit.is_extended = obj.is_extended
                new_slit.xstart = xmin + 1  # fits pixels
                new_slit.xsize = ext_data.shape[1]
                new_slit.ystart = ymin + 1  # fits pixels
                new_slit.ysize = ext_data.shape[0]
                new_slit.source_xpos = float(obj.xcentroid)
                new_slit.source_ypos = float(obj.ycentroid)
                new_slit.source_id = obj.sid
                new_slit.bunit_data = input_model.meta.bunit_data
                new_slit.bunit_err = input_model.meta.bunit_err
                slits.append(new_slit)
    output_model.slits.extend(slits)
    # In the case that there are no spectra to extract deleting the variables
    # will fail so add the try block.
    try:
        del subwcs
    except UnboundLocalError:
        pass
    try:
        del new_slit
    except UnboundLocalError:
        pass
    # del subwcs
    # del new_slit
    log.info("Finished extractions")
    return output_model
Exemple #3
0
def test_model_set(expr, result):
    s = expr(Const1D((2, 2), n_models=2), Const1D((3, 3), n_models=2))
    out = s(0, model_set_axis=False)
    assert_array_equal(out, result)
Exemple #4
0
def create_spectral_wcs(ra, dec, wavelength):
    """Assign a WCS for sky coordinates and a table of wavelengths

    Parameters
    ----------
    ra: float
        The right ascension (in degrees) at the nominal location of the
        entrance aperture (slit).

    dec: float
        The declination (in degrees) at the nominal location of the
        entrance aperture.

    wavelength: ndarray
        The wavelength in microns at each pixel of the extracted spectrum.

    Returns:
    --------
    wcs: a gwcs.wcs.WCS object
        This takes a float or sequence of float and returns a tuple of
        the right ascension, declination, and wavelength (or sequence of
        wavelengths) at the pixel(s) specified by the input argument.
    """

    input_frame = cf.CoordinateFrame(naxes=1,
                                     axes_type=("SPATIAL", ),
                                     axes_order=(0, ),
                                     unit=(u.pix, ),
                                     name="pixel_frame")

    sky = cf.CelestialFrame(name='sky',
                            axes_order=(0, 1),
                            reference_frame=coord.ICRS())
    spec = cf.SpectralFrame(name='spectral',
                            axes_order=(2, ),
                            unit=(u.micron, ),
                            axes_names=('wavelength', ))
    world = cf.CompositeFrame([sky, spec], name='world')

    pixel = np.arange(len(wavelength), dtype=float)
    tab = Mapping((0, 0, 0)) | \
        Const1D(ra) & Const1D(dec) & Tabular1D(points=pixel,
                                               lookup_table=wavelength,
                                               bounds_error=False,
                                               fill_value=None)
    tab.name = "pixel_to_world"

    if all(np.diff(wavelength) > 0):
        tab.inverse = Mapping((2, )) | Tabular1D(
            points=wavelength,
            lookup_table=pixel,
            bounds_error=False,
        )
    elif all(np.diff(wavelength) < 0):
        tab.inverse = Mapping((2, )) | Tabular1D(
            points=wavelength[::-1],
            lookup_table=pixel[::-1],
            bounds_error=False,
        )
    else:
        log.warning(
            "Wavelengths are not strictly monotonic, inverse transform is not set"
        )

    pipeline = [(input_frame, tab), (world, None)]

    return WCS(pipeline)
Exemple #5
0
def extract_tso_object(input_model,
                       reference_files=None,
                       tsgrism_extract_height=None,
                       extract_orders=None,
                       compute_wavelength=True):
    """
    Extract the spectrum for a NIRCam TSO grism observation.

    Parameters
    ----------
    input_model : `~jwst.datamodels.CubeModel` or `~jwst.datamodels.ImageModel`
        The input TSO data is an instance of a CubeModel (3D) or ImageModel (2D)

    reference_files : dict
        Needs to include the name of the wavelengthrange reference file

    tsgrism_extract_height : int
        The extraction height, in total, for the spectrum in the
        cross-dispersion direction. If this is other than None,
        it will override the team default of 64 pixels. The team
        wants the source centered near row 34, so the extraction
        height is not the same on either size of the central row.

    extract_orders : list[ints]
        This is an optional parameter that will override the
        orders specified for extraction in the wavelengthrange
        reference file.

    compute_wavelength : bool
        Compute a wavelength array for the datamodel.

    Returns
    -------
    output_model : `~jwst.datamodels.SlitModel`


    Notes
    -----
    This method supports NRC_TSGRISM only, where only one bright object is
    considered in the field, so there's no catalog of sources and the object
    is assumed to have been observed at the aperture reference position.
    The aperture reference location is read during level-1b (uncal) product
    creation by the "set_telescope_pointing" script from the SIAF entries
    XSciRef and YSciRef (reference location in the science frame) and saved as
    "meta.wcsinfo.siaf_xref_sci" and "meta.wcsinfo.siaf_yref_sci" (FITS header
    keywords XREF_SCI and YREF_SCI).

    Because this mode has a single known source location, the utilities used
    in the WFSS modes are overkill. Instead, similar structures are created
    during the extract2d process and then directly used.

    https://jwst-docs.stsci.edu/near-infrared-camera/nircam-observing-modes/nircam-time-series-observations/nircam-grism-time-series
    """

    # Check for reference files
    if not isinstance(reference_files, dict):
        raise TypeError("Expected a dictionary for reference_files")

    # Check for wavelengthrange reference file
    if 'wavelengthrange' not in reference_files:
        raise KeyError("No wavelengthrange reference file specified")

    # If an extraction height is not supplied, default to entire
    # cross-dispersion size of the data array
    if tsgrism_extract_height is None:
        tsgrism_extract_height = input_model.meta.subarray.ysize
    log.info("Setting extraction height to {}".format(tsgrism_extract_height))

    # Get the disperser parameters that have the wave limits
    with WavelengthrangeModel(reference_files['wavelengthrange']) as f:
        if (f.meta.instrument.name != 'NIRCAM'
                or f.meta.exposure.type != 'NRC_TSGRISM'):
            raise ValueError(
                "Wavelengthrange reference file is not for NIRCAM TSGRISM mode!"
            )
        wavelengthrange = f.wavelengthrange
        ref_extract_orders = f.extract_orders

    # If user-supplied spectral orders are not provided,
    # default to extracting only the 1st order
    if extract_orders is None:
        log.info("Using default order extraction from reference file")
        extract_orders = ref_extract_orders
        available_orders = [
            x[1] for x in extract_orders
            if x[0] == input_model.meta.instrument.filter
        ].pop()
    else:
        if (not isinstance(extract_orders, list)
                or not all(isinstance(item, int) for item in extract_orders)):
            raise TypeError(
                "Expected extract_orders to be a list of integers.")
        available_orders = extract_orders

    if len(available_orders) > 1:
        raise NotImplementedError("Multiple order extraction for TSO is "
                                  "not currently implemented.")

    # Check for the existence of the aperture reference location meta data
    if (input_model.meta.wcsinfo.siaf_xref_sci is None
            or input_model.meta.wcsinfo.siaf_yref_sci is None):
        raise ValueError('XREF_SCI and YREF_SCI are required for TSO mode.')

    # Create the extracted output as a SlitModel
    log.info("Extracting order: {}".format(available_orders))
    output_model = datamodels.SlitModel()
    output_model.update(input_model)
    subwcs = copy.deepcopy(input_model.meta.wcs)

    # Loop over spectral orders
    for order in available_orders:
        range_select = [
            (x[2], x[3]) for x in wavelengthrange
            if (x[0] == order and x[1] == input_model.meta.instrument.filter)
        ]

        # Use the filter that was in front of the grism for translation
        lmin, lmax = range_select.pop()

        # Create the order bounding box
        source_xpos = input_model.meta.wcsinfo.siaf_xref_sci - 1  # remove FITS 1-indexed offset
        source_ypos = input_model.meta.wcsinfo.siaf_yref_sci - 1  # remove FITS 1-indexed offset
        transform = input_model.meta.wcs.get_transform('direct_image',
                                                       'grism_detector')
        xmin, ymin, _ = transform(source_xpos, source_ypos, lmin, order)
        xmax, ymax, _ = transform(source_xpos, source_ypos, lmax, order)

        # Add the shift to the lower corner to the subarray WCS object.
        # The shift should just be the lower bounding box corner.
        # Also replace the object center location inputs to the GrismDispersion
        # model with the known object center and order information (in pixels of direct image)
        # This changes the user input to the model from (x,y,x0,y0,order) -> (x,y)
        #
        # The bounding box is limited to the size of the detector in the dispersion direction
        # and 64 pixels in the cross-dispersion direction (at request of instrument team).
        #
        # The team wants the object to fall near row 34 for all cutouts, but the default cutout
        # height is 64 pixels (32 on either side). So bump the extraction ycenter, when necessary,
        # so that the height is 30 above and 34 below (in full frame) the object center.
        bump = source_ypos - 34
        extract_y_center = source_ypos - bump

        splitheight = int(tsgrism_extract_height / 2)
        below = extract_y_center - splitheight
        if below == 34:
            extract_y_min = 0
            extract_y_max = extract_y_center + splitheight
        elif below < 0:
            extract_y_min = 0
            extract_y_max = tsgrism_extract_height - 1
        else:
            extract_y_min = extract_y_center - 34  # always return source at row 34 in cutout
            extract_y_max = extract_y_center + tsgrism_extract_height - 34 - 1

        # Check for bad results
        if extract_y_min > extract_y_max:
            raise ValueError(
                "Something bad happened calculating extraction y-size")

        # Limit the bounding box to the detector edges
        ymin, ymax = (max(extract_y_min, 0),
                      min(extract_y_max, input_model.meta.subarray.ysize))
        xmin, xmax = (max(xmin, 0), min(xmax, input_model.meta.subarray.xsize))

        # The order and source position are put directly into the new WCS of the subarray
        # for the forward transform.
        #
        # NOTE NOTE NOTE  2020-02-14
        # We would normally use x-axis (along dispersion) extraction limits calculated
        # above based on the min/max wavelength range and the source position to do the
        # subarray extraction and set the subarray WCS accordingly. HOWEVER, the NIRCam
        # team has asked for all data along the dispersion direction to be included in
        # subarray cutout, so here we override the xmin/xmax values calculated above and
        # instead hardwire the extraction limits for the x (dispersion) direction to
        # cover the entire range of the data and use this new minimum x value in the
        # subarray WCS transform. If the team ever decides to change the extraction limits,
        # the following two constants must be modified accordingly.
        xmin_ext = 0  # hardwire min x for extraction to zero
        xmax_ext = input_model.data.shape[
            -1] - 1  # hardwire max x for extraction to size of data

        order_model = Const1D(order)
        order_model.inverse = Const1D(order)
        tr = input_model.meta.wcs.get_transform('grism_detector',
                                                'direct_image')
        tr = Mapping(
            (0, 1, 0)) | Shift(xmin_ext) & Shift(ymin) & order_model | tr
        subwcs.set_transform('grism_detector', 'direct_image', tr)

        xmin = int(xmin)
        xmax = int(xmax)
        ymin = int(ymin)
        ymax = int(ymax)

        log.info("WCS made explicit for order: {}".format(order))
        log.info("Spectral trace extents: (xmin: {}, ymin: {}), "
                 "(xmax: {}, ymax: {})".format(xmin, ymin, xmax, ymax))
        log.info("Extraction limits: (xmin: {}, ymin: {}), "
                 "(xmax: {}, ymax: {})".format(xmin_ext, ymin, xmax_ext, ymax))

        # Cut out the subarray from the input data arrays
        ext_data = input_model.data[..., ymin:ymax + 1,
                                    xmin_ext:xmax_ext + 1].copy()
        ext_err = input_model.err[..., ymin:ymax + 1,
                                  xmin_ext:xmax_ext + 1].copy()
        ext_dq = input_model.dq[..., ymin:ymax + 1,
                                xmin_ext:xmax_ext + 1].copy()
        if input_model.var_poisson is not None and np.size(
                input_model.var_poisson) > 0:
            var_poisson = input_model.var_poisson[..., ymin:ymax + 1,
                                                  xmin_ext:xmax_ext +
                                                  1].copy()
        else:
            var_poisson = None
        if input_model.var_rnoise is not None and np.size(
                input_model.var_rnoise) > 0:
            var_rnoise = input_model.var_rnoise[..., ymin:ymax + 1,
                                                xmin_ext:xmax_ext + 1].copy()
        else:
            var_rnoise = None
        if input_model.var_flat is not None and np.size(
                input_model.var_flat) > 0:
            var_flat = input_model.var_flat[..., ymin:ymax + 1,
                                            xmin_ext:xmax_ext + 1].copy()
        else:
            var_flat = None

        # Finish populating the output model and meta data
        if output_model.meta.model_type == "SlitModel":
            output_model.data = ext_data
            output_model.err = ext_err
            output_model.dq = ext_dq
            output_model.var_poisson = var_poisson
            output_model.var_rnoise = var_rnoise
            output_model.var_flat = var_flat
            output_model.meta.wcs = subwcs
            output_model.meta.wcs.bounding_box = util.wcs_bbox_from_shape(
                ext_data.shape)
            output_model.meta.wcsinfo.siaf_yref_sci = 34  # update for the move, vals are the same
            output_model.meta.wcsinfo.siaf_xref_sci = input_model.meta.wcsinfo.siaf_xref_sci
            output_model.meta.wcsinfo.spectral_order = order
            output_model.meta.wcsinfo.dispersion_direction = \
                input_model.meta.wcsinfo.dispersion_direction
            if compute_wavelength:
                log.debug("Computing wavelengths (this takes a while ...)")
                output_model.wavelength = compute_wavelength_array(
                    output_model)
            output_model.name = '1'
            output_model.source_type = input_model.meta.target.source_type
            output_model.source_name = input_model.meta.target.catalog_name
            output_model.source_alias = input_model.meta.target.proposer_name
            output_model.xstart = 1  # FITS pixels are 1-indexed
            output_model.xsize = ext_data.shape[-1]
            output_model.ystart = ymin + 1  # FITS pixels are 1-indexed
            output_model.ysize = ext_data.shape[-2]
            output_model.source_xpos = source_xpos
            output_model.source_ypos = 34
            output_model.source_id = 1
            output_model.bunit_data = input_model.meta.bunit_data
            output_model.bunit_err = input_model.meta.bunit_err
            if hasattr(input_model, 'int_times'):
                output_model.int_times = input_model.int_times.copy()

    del subwcs
    log.info("Finished extraction")
    return output_model
Exemple #6
0
def gwa_to_ifuslit(slits, input_model, disperser, reference_files):
    """
    GWA to SLIT transform.

    Parameters
    ----------
    slits : list
        A list of slit IDs for all IFU slits 0-29.
    disperser : dict
        A corrected disperser ASDF object.
    filter : str
        The filter used.
    grating : str
        The grating used in the observation.
    reference_files: dict
        Dictionary with reference files returned by CRDS.

    Returns
    -------
    model : `~jwst.transforms.Gwa2Slit` model.
        Transform from GWA frame to SLIT frame.
   """
    ymin = -.55
    ymax = .55

    wrange = (input_model.meta.wcsinfo.waverange_start,
              input_model.meta.wcsinfo.waverange_end),
    order = input_model.meta.wcsinfo.spectral_order
    agreq = angle_from_disperser(disperser, input_model)
    lgreq = wavelength_from_disperser(disperser, input_model)

    # The wavelength units up to this point are
    # meters as required by the pipeline but the desired output wavelength units is microns.
    # So we are going to Scale the spectral units by 1e6 (meters -> microns)
    if input_model.meta.instrument.filter == 'OPAQUE':
        lgreq = lgreq | Scale(1e6)

    collimator2gwa = collimator_to_gwa(reference_files, disperser)
    mask = mask_slit(ymin, ymax)

    ifuslicer = AsdfFile.open(reference_files['ifuslicer'])
    ifupost = AsdfFile.open(reference_files['ifupost'])
    slit_models = []
    ifuslicer_model = ifuslicer.tree['model']
    for slit in slits:
        slitdata = ifuslicer.tree['data'][slit]
        slitdata_model = get_slit_location_model(slitdata)
        ifuslicer_transform = (slitdata_model | ifuslicer_model)
        ifupost_transform = ifupost.tree[slit]['model']
        msa2gwa = ifuslicer_transform | ifupost_transform | collimator2gwa
        gwa2msa = gwa_to_ymsa(msa2gwa)  # TODO: Use model sets here
        bgwa2msa = Mapping((0, 1, 0, 1), n_inputs=3) | \
                 Const1D(0) * Identity(1) & Const1D(-1) * Identity(1) & Identity(2) | \
                 Identity(1) & gwa2msa & Identity(2) | \
                 Mapping((0, 1, 0, 1, 2, 3)) | Identity(2) & msa2gwa & Identity(2) | \
                 Mapping((0, 1, 2, 3, 5), n_inputs=7) | Identity(2) & lgreq | mask

        # msa to before_gwa
        msa2bgwa = msa2gwa & Identity(1) | Mapping((3, 0, 1, 2)) | agreq
        bgwa2msa.inverse = msa2bgwa
        slit_models.append(bgwa2msa)

    ifuslicer.close()
    ifupost.close()
    return Gwa2Slit(slits, slit_models)
Exemple #7
0
def gwa_to_slit(open_slits, input_model, disperser, reference_files):
    """
    GWA to SLIT transform.

    Parameters
    ----------
    open_slits : list
        A list of slit IDs for all open shutters/slitlets.
    disperser : dict
        A corrected disperser ASDF object.
    filter : str
        The filter used.
    grating : str
        The grating used in the observation.
    reference_files: dict
        Dictionary with reference files returned by CRDS.

    Returns
    -------
    model : `~jwst.transforms.Gwa2Slit` model.
        Transform from GWA frame to SLIT frame.
    """
    wrange = (input_model.meta.wcsinfo.waverange_start,
              input_model.meta.wcsinfo.waverange_end),
    order = input_model.meta.wcsinfo.spectral_order

    agreq = angle_from_disperser(disperser, input_model)
    collimator2gwa = collimator_to_gwa(reference_files, disperser)
    lgreq = wavelength_from_disperser(disperser, input_model)

    # The wavelength units up to this point are
    # meters as required by the pipeline but the desired output wavelength units is microns.
    # So we are going to Scale the spectral units by 1e6 (meters -> microns)
    if input_model.meta.instrument.filter == 'OPAQUE':
        lgreq = lgreq | Scale(1e6)

    msa = AsdfFile.open(reference_files['msa'])
    slit_models = []
    for quadrant in range(1, 6):
        slits_in_quadrant = [s for s in open_slits if s.quadrant == quadrant]
        log.info("There are {0} open slits in quadrant {1}".format(
            len(slits_in_quadrant), quadrant))
        if any(slits_in_quadrant):
            msa_model = msa.tree[quadrant]['model']
            log.info(
                "Getting slits location for quadrant {0}".format(quadrant))
            msa_data = msa.tree[quadrant]['data']
            for slit in slits_in_quadrant:
                mask = mask_slit(slit.ymin, slit.ymax)
                slit_id = slit.shutter_id
                slitdata = msa_data[slit_id]
                slitdata_model = get_slit_location_model(slitdata)
                msa_transform = slitdata_model | msa_model
                msa2gwa = (msa_transform | collimator2gwa)
                gwa2msa = gwa_to_ymsa(msa2gwa)  # TODO: Use model sets here
                bgwa2msa = Mapping((0, 1, 0, 1), n_inputs=3) | \
                    Const1D(0) * Identity(1) & Const1D(-1) * Identity(1) & Identity(2) | \
                    Identity(1) & gwa2msa & Identity(2) | \
                    Mapping((0, 1, 0, 1, 2, 3)) | Identity(2) & msa2gwa & Identity(2) | \
                    Mapping((0, 1, 2, 3, 5), n_inputs=7) | Identity(2) & lgreq | mask
                #Mapping((0, 1, 2, 5), n_inputs=7) | Identity(2) & lgreq | mask
                # and modify lgreq to accept alpha_in, beta_in, alpha_out
                # msa to before_gwa
                msa2bgwa = msa2gwa & Identity(1) | Mapping(
                    (3, 0, 1, 2)) | agreq
                bgwa2msa.inverse = msa2bgwa
                slit_models.append(bgwa2msa)
    msa.close()
    return Gwa2Slit(open_slits, slit_models)
Exemple #8
0
def _make_gwcs_wcs(fits_hdr):
    hdr = fits.Header.fromfile(get_pkg_data_filename(fits_hdr))
    fw = fitswcs.WCS(hdr)

    a_order = hdr['A_ORDER']
    a_coeff = {}
    for i in range(a_order + 1):
        for j in range(a_order + 1 - i):
            key = 'A_{:d}_{:d}'.format(i, j)
            if key in hdr:
                a_coeff[key] = hdr[key]

    b_order = hdr['B_ORDER']
    b_coeff = {}
    for i in range(b_order + 1):
        for j in range(b_order + 1 - i):
            key = 'B_{:d}_{:d}'.format(i, j)
            if key in hdr:
                b_coeff[key] = hdr[key]

    distortion = polynomial.SIP(
        fw.wcs.crpix,
        fw.sip.a_order,
        fw.sip.b_order,
        a_coeff,
        b_coeff
    ) + Identity(2)

    unit_conv = Scale(1.0 / 3600.0, name='arcsec_to_deg_1D')
    unit_conv = unit_conv & unit_conv
    unit_conv.name = 'arcsec_to_deg_2D'

    unit_conv_inv = Scale(3600.0, name='deg_to_arcsec_1D')
    unit_conv_inv = unit_conv_inv & unit_conv_inv
    unit_conv_inv.name = 'deg_to_arcsec_2D'

    c2s = CartesianToSpherical(name='c2s', wrap_lon_at=180)
    s2c = SphericalToCartesian(name='s2c', wrap_lon_at=180)
    c2tan = ((Mapping((0, 1, 2), name='xyz') /
              Mapping((0, 0, 0), n_inputs=3, name='xxx')) |
             Mapping((1, 2), name='xtyt'))
    c2tan.name = 'Cartesian 3D to TAN'

    tan2c = (Mapping((0, 0, 1), n_inputs=2, name='xtyt2xyz') |
             (Const1D(1, name='one') & Identity(2, name='I(2D)')))
    tan2c.name = 'TAN to cartesian 3D'

    tan2c.inverse = c2tan
    c2tan.inverse = tan2c

    aff = AffineTransformation2D(matrix=fw.wcs.cd)

    offx = Shift(-fw.wcs.crpix[0])
    offy = Shift(-fw.wcs.crpix[1])

    s = 5e-6
    scale = Scale(s) & Scale(s)

    distortion |= (offx & offy) | scale | tan2c | c2s | unit_conv_inv

    taninv = s2c | c2tan
    tan = Pix2Sky_TAN()
    n2c = RotateNative2Celestial(fw.wcs.crval[0], fw.wcs.crval[1], 180)
    wcslin = unit_conv | taninv | scale.inverse | aff | tan | n2c

    sky_frm = cf.CelestialFrame(reference_frame=coord.ICRS())
    det_frm = cf.Frame2D(name='detector')
    v2v3_frm = cf.Frame2D(
        name="v2v3",
        unit=(u.arcsec, u.arcsec),
        axes_names=('x', 'y'),
        axes_order=(0, 1)
    )
    pipeline = [(det_frm, distortion), (v2v3_frm, wcslin), (sky_frm, None)]

    gw = gwcs.WCS(input_frame=det_frm, output_frame=sky_frm,
                  forward_transform=pipeline)
    gw.crpix = fw.wcs.crpix
    gw.crval = fw.wcs.crval

    # sanity check:
    for _ in range(100):
        x = np.random.randint(1, fw.pixel_shape[0])
        y = np.random.randint(1, fw.pixel_shape[0])
        assert np.allclose(gw(x, y), fw.all_pix2world(x, y, 1),
                           rtol=0, atol=1e-11)

    return gw
Exemple #9
0
def imaging(input_model, reference_files):
    """
    Imaging pipeline

    frames : detector, gwa, msa, sky
    """
    # Get the corrected disperser model
    disperser = get_disperser(input_model, reference_files['disperser'])

    # DMS to SCA transform
    dms2detector = dms_to_sca(input_model)
    # DETECTOR to GWA transform
    det2gwa = detector_to_gwa(reference_files,
                              input_model.meta.instrument.detector, disperser)

    gwa_through = Const1D(-1) * Identity(1) & Const1D(-1) * Identity(
        1) & Identity(1)

    angles = [
        disperser['theta_x'], disperser['theta_y'], disperser['theta_z'],
        disperser['tilt_y']
    ]
    rotation = Rotation3DToGWA(angles, axes_order="xyzy",
                               name='rotaton').inverse
    dircos2unitless = DirCos2Unitless(name='directional_cosines2unitless')

    col = AsdfFile.open(reference_files['collimator']).tree['model']

    # Get the default spectral order and wavelength range and record them in the model.
    sporder, wrange = get_spectral_order_wrange(
        input_model, reference_files['wavelengthrange'])
    input_model.meta.wcsinfo.waverange_start = wrange[0]
    input_model.meta.wcsinfo.waverange_end = wrange[1]
    input_model.meta.wcsinfo.spectral_order = sporder

    lam = wrange[0] + (wrange[1] - wrange[0]) * .5

    lam_model = Mapping((0, 1, 1)) | Identity(2) & Const1D(lam)

    gwa2msa = gwa_through | rotation | dircos2unitless | col | lam_model
    gwa2msa.inverse = col.inverse | dircos2unitless.inverse | rotation.inverse | gwa_through

    # Create coordinate frames in the NIRSPEC WCS pipeline
    # "detector", "gwa", "msa", "oteip", "v2v3", "world"
    det, sca, gwa, msa_frame, oteip, v2v3, world = create_imaging_frames()
    if input_model.meta.instrument.filter != 'OPAQUE':
        # MSA to OTEIP transform
        msa2ote = msa_to_oteip(reference_files)
        msa2oteip = msa2ote | Mapping((0, 1), n_inputs=3)
        msa2oteip.inverse = Mapping((0, 1, 0, 1)) | msa2ote.inverse | Mapping(
            (0, 1), n_inputs=3)
        # OTEIP to V2,V3 transform
        with AsdfFile.open(reference_files['ote']) as f:
            oteip2v23 = f.tree['model'].copy()

        # V2, V3 to world (RA, DEC) transform
        tel2sky = pointing.v23tosky(input_model)

        imaging_pipeline = [(det, dms2detector), (sca, det2gwa),
                            (gwa, gwa2msa), (msa_frame, msa2oteip),
                            (oteip, oteip2v23), (v2v3, tel2sky), (world, None)]
    else:
        # convert to microns if the pipeline ends earlier
        gwa2msa = (gwa2msa | Identity(2) & Scale(10**6)).rename('gwa2msa')
        imaging_pipeline = [(det, dms2detector), (sca, det2gwa),
                            (gwa, gwa2msa), (msa_frame, None)]

    return imaging_pipeline
    def evaluate(self, x, y, x0, y0, order):
        """Return the valid pixel(s) and wavelengths given x0, y0, x, y, order
        Parameters
        ----------
        x0: int,float,list
            Source object x-center
        y0: int,float,list
            Source object y-center
        x :  int,float,list
            Input x location
        y :  int,float,list
            Input y location
        order : int
            Spectral order to use

        Returns
        -------
        x0, y0, lambda, order in the direct image for the pixel that was
        specified as input using the wavelength l and spectral order

        Notes
        -----
        I kept the possibility of having a rotation like NIRISS, although I
        don't know if there is a use case for it for WFC3.

        The two `flatten` lines may need to be uncommented if we want to use
        this for array input.
        """
        try:
            iorder = self._order_mapping[int(order.flatten()[0])]
        except AttributeError:
            iorder = self._order_mapping[order]
        except KeyError:
            raise ValueError("Specified order is not available")

        # The next two lines are to get around the fact that
        # modeling.standard_broadcasting=False does not work.
        #x00 = x0.flatten()[0]
        #y00 = y0.flatten()[0]

        t = np.linspace(0, 1, 10)  #sample t
        xmodel = self.xmodels[iorder]
        ymodel = self.ymodels[iorder]
        lmodel = self.lmodels[iorder]

        dx = xmodel.evaluate(x0, y0, t)
        dy = ymodel.evaluate(x0, y0, t)

        if self.theta != 0.0:
            rotate = Rotation2D(self.theta)
            dx, dy = rotate(dx, dy)

        so = np.argsort(dx)
        tab = Tabular1D(dx[so], t[so], bounds_error=False, fill_value=None)

        dxr = astmath.SubtractUfunc()
        wavelength = dxr | tab | lmodel
        model = Mapping(
            (2, 3, 0, 2,
             4)) | Const1D(x0) & Const1D(y0) & wavelength & Const1D(order)
        return model(x, y, x0, y0, order)
Exemple #11
0
def centroid_1dg(data, error=None, mask=None):
    """
    Calculate the centroid of a 2D array by fitting 1D Gaussians to the
    marginal ``x`` and ``y`` distributions of the array.

    Invalid values (e.g. NaNs or infs) in the ``data`` or ``error``
    arrays are automatically masked.  The mask for invalid values
    represents the combination of the invalid-value masks for the
    ``data`` and ``error`` arrays.

    Parameters
    ----------
    data : array_like
        The 2D data array.

    error : array_like, optional
        The 2D array of the 1-sigma errors of the input ``data``.

    mask : array_like (bool), optional
        A boolean mask, with the same shape as ``data``, where a `True`
        value indicates the corresponding element of ``data`` is masked.

    Returns
    -------
    centroid : `~numpy.ndarray`
        The ``x, y`` coordinates of the centroid.
    """

    data = np.ma.asanyarray(data)

    if mask is not None and mask is not np.ma.nomask:
        mask = np.asanyarray(mask)
        if data.shape != mask.shape:
            raise ValueError('data and mask must have the same shape.')
        data.mask |= mask

    if np.any(~np.isfinite(data)):
        data = np.ma.masked_invalid(data)
        warnings.warn(
            'Input data contains input values (e.g. NaNs or infs), '
            'which were automatically masked.', AstropyUserWarning)

    if error is not None:
        error = np.ma.masked_invalid(error)
        if data.shape != error.shape:
            raise ValueError('data and error must have the same shape.')
        data.mask |= error.mask

        error.mask = data.mask
        xy_error = [np.sqrt(np.ma.sum(error**2, axis=i)) for i in [0, 1]]
        xy_weights = [(1.0 / xy_error[i].clip(min=1.e-30)) for i in [0, 1]]
    else:
        xy_weights = [np.ones(data.shape[i]) for i in [1, 0]]

    # assign zero weight where an entire row or column is masked
    if np.any(data.mask):
        bad_idx = [np.all(data.mask, axis=i) for i in [0, 1]]
        for i in [0, 1]:
            xy_weights[i][bad_idx[i]] = 0.

    xy_data = [np.ma.sum(data, axis=i).data for i in [0, 1]]

    constant_init = np.ma.min(data)
    centroid = []
    for (data_i, weights_i) in zip(xy_data, xy_weights):
        params_init = gaussian1d_moments(data_i)
        g_init = Const1D(constant_init) + Gaussian1D(*params_init)
        fitter = LevMarLSQFitter()
        x = np.arange(data_i.size)
        g_fit = fitter(g_init, x, data_i, weights=weights_i)
        centroid.append(g_fit.mean_1.value)

    return np.array(centroid)
Exemple #12
0
    def build_nirspec_output_wcs(self, refmodel=None):
        """
        Create a spatial/spectral WCS covering footprint of the input
        """
        if not refmodel:
            refmodel = self.input_models[0]
        refwcs = refmodel.meta.wcs
        bb = refwcs.bounding_box

        ref_det2slit = refwcs.get_transform('detector', 'slit_frame')
        ref_slit2world = refwcs.get_transform('slit_frame', 'world')

        grid = x, y = wcstools.grid_from_bounding_box(bb, step=(1, 1))
        Grid = namedtuple('Grid', refwcs.slit_frame.axes_names)
        grid_slit = Grid(*ref_det2slit(*grid))

        # Compute spatial transform from detector to slit
        ref_wavelength = np.nanmean(grid_slit.wavelength)

        # find the number of pixels sampled by a single shutter
        fid = np.array([[0., 0.], [-.5, .5], np.repeat(ref_wavelength, 2)])
        slit_extents = np.array(ref_det2slit.inverse(*fid)).T
        pix_per_shutter = np.linalg.norm(slit_extents[0] - slit_extents[1])

        # Get min and max of slit in pixel units
        ymin = np.nanmin(grid_slit.y_slit) * pix_per_shutter
        ymax = np.nanmax(grid_slit.y_slit) * pix_per_shutter
        slit_height_pix = int(abs(ymax - ymin + 0.5))

        # Compute grid of wavelengths and make tabular model w/inverse
        lookup_table = np.nanmean(grid_slit.wavelength, axis=0)
        wavelength_transform = Tabular1D(lookup_table=lookup_table,
                                         bounds_error=False,
                                         fill_value=np.nan)
        wavelength_transform.inverse = Tabular1D(
            points=lookup_table,
            lookup_table=np.arange(grid_slit.wavelength.shape[1]),
            bounds_error=False,
            fill_value=np.nan)

        # Define detector to slit transforms
        yslit_transform = Scale(-1 / pix_per_shutter) | Shift(
            ymax / pix_per_shutter)
        xslit_transform = Const1D(-0.)
        xslit_transform.inverse = Const1D(0.)

        # Construct the final transform
        coord_mapping = Mapping((0, 1, 0))
        coord_mapping.inverse = Mapping((2, 1))
        the_transform = xslit_transform & yslit_transform & wavelength_transform
        out_det2slit = coord_mapping | the_transform

        # Create coordinate frames
        det = cf.Frame2D(name='detector', axes_order=(0, 1))
        slit_spatial = cf.Frame2D(name='slit_spatial',
                                  axes_order=(0, 1),
                                  unit=("", ""),
                                  axes_names=('x_slit', 'y_slit'))
        spec = cf.SpectralFrame(name='spectral',
                                axes_order=(2, ),
                                unit=(u.micron, ),
                                axes_names=('wavelength', ))
        slit_frame = cf.CompositeFrame([slit_spatial, spec], name='slit_frame')
        sky = cf.CelestialFrame(name='sky',
                                axes_order=(0, 1),
                                reference_frame=coord.ICRS())
        world = cf.CompositeFrame([sky, spec], name='world')

        pipeline = [(det, out_det2slit), (slit_frame, ref_slit2world),
                    (world, None)]

        output_wcs = WCS(pipeline)

        self.data_size = (slit_height_pix, len(lookup_table))
        bounding_box = resample_utils.bounding_box_from_shape(self.data_size)
        output_wcs.bounding_box = bounding_box
        return output_wcs
Exemple #13
0
def tsgrism(input_model, reference_files):
    """Create WCS pipeline for a NIRCAM Time Series Grism observation.

    Parameters
    ----------
    input_model : `~jwst.datamodels.ImagingModel`
        The input datamodel, derived from datamodels
    reference_files : dict
        Dictionary of reference file names {reftype: reference file name}.

    Returns
    -------
    pipeline : list
        The pipeline list that is returned is suitable for
        input into  gwcs.wcs.WCS to create a GWCS object.

    Notes
    -----
    The TSGRISM mode should function effectively like the grism mode
    except that subarrays will be allowed. Since the transform models
    depend on the original full frame coordinates of the observation,
    the regular grism transforms will need to be shifted to the full
    frame coordinates around the trace transform.

    TSGRISM is only slated to work with GRISMR and Mod A
    """

    # The input is the grism image
    if not isinstance(input_model, CubeModel):
        raise TypeError('The input data model must be a CubeModel.')

    # make sure this is a grism image
    if "NRC_TSGRISM" != input_model.meta.exposure.type:
        raise ValueError(
            'The input exposure is not a NIRCAM time series grism')

    if input_model.meta.instrument.module != "A":
        raise ValueError('NRC_TSGRISM mode only supports module A')

    if input_model.meta.instrument.pupil != "GRISMR":
        raise ValueError('NRC_TSGRIM mode only supports GRISMR')

    gdetector = cf.Frame2D(name='grism_detector',
                           axes_order=(0, 1),
                           unit=(u.pix, u.pix))
    detector = cf.Frame2D(name='full_detector',
                          axes_order=(0, 1),
                          unit=(u.pix, u.pix))
    v2v3 = cf.Frame2D(name='v2v3', axes_order=(0, 1), unit=(u.deg, u.deg))
    world = cf.CelestialFrame(reference_frame=coord.ICRS(), name='world')

    # translate the x,y detector-in to x,y detector out coordinates
    # Get the disperser parameters which are defined as a model for each
    # spectral order
    with NIRCAMGrismModel(reference_files['specwcs']) as f:
        displ = f.displ
        dispx = f.dispx
        dispy = f.dispy
        invdispx = f.invdispx
        invdispl = f.invdispl
        orders = f.orders

    # now create the appropriate model for the grismr
    det2det = NIRCAMForwardRowGrismDispersion(orders,
                                              lmodels=displ,
                                              xmodels=invdispx,
                                              ymodels=dispy)

    det2det.inverse = NIRCAMBackwardGrismDispersion(orders,
                                                    lmodels=invdispl,
                                                    xmodels=dispx,
                                                    ymodels=dispy)

    # Add in the wavelength shift from the velocity dispersion
    try:
        velosys = input_model.meta.wcsinfo.velosys
    except AttributeError:
        pass
    if velosys is not None:
        velocity_corr = velocity_correction(input_model.meta.wcsinfo.velosys)
        log.info("Added Barycentric velocity correction: {}".format(
            velocity_corr[1].amplitude.value))
        det2det = det2det | Mapping(
            (0, 1, 2, 3)) | Identity(2) & velocity_corr & Identity(1)

    # input into the forward transform is x,y,x0,y0,order
    # where x,y is the pixel location in the grism image
    # and x0,y0 is the source location in the "direct" image
    # For this mode, the source is always at crpix1,crpis2
    # discussion with nadia that wcsinfo might not be available
    # here but crpix info could be in wcs.source_location or similar
    # TSGRISM mode places the sources at crpix, and all subarrays
    # begin at 0,0, so no need to translate the crpix to full frame
    # because they already are in full frame coordinates.
    xc, yc = (input_model.meta.wcsinfo.crpix1, input_model.meta.wcsinfo.crpix2)
    xcenter = Const1D(xc)
    xcenter.inverse = Const1D(xc)
    ycenter = Const1D(yc)
    ycenter.inverse = Const1D(yc)

    setra = Const1D(input_model.meta.wcsinfo.crval1)
    setra.inverse = Const1D(input_model.meta.wcsinfo.crval1)
    setdec = Const1D(input_model.meta.wcsinfo.crval2)
    setdec.inverse = Const1D(input_model.meta.wcsinfo.crval2)

    # x, y, order in goes to transform to full array location and order
    # get the shift to full frame coordinates
    sub_trans = subarray_transform(input_model)
    if sub_trans is not None:
        sub2direct = (sub_trans & Identity(1) | Mapping(
            (0, 1, 0, 1, 2)) | (Identity(2) & xcenter & ycenter & Identity(1))
                      | det2det)
    else:
        sub2direct = (Mapping(
            (0, 1, 0, 1, 2)) | (Identity(2) & xcenter & ycenter & Identity(1))
                      | det2det)

    # take us from full frame detector to v2v3
    distortion = imaging_distortion(input_model, reference_files) & Identity(2)

    # v2v3 to the sky
    # remap the tel2sky inverse as well since we can feed it the values of
    # crval1, crval2 which correspond to crpix1, crpix2. This leaves
    # us with a calling structure:
    #  (x, y, order) <-> (wavelength, order)
    tel2sky = pointing.v23tosky(input_model) & Identity(2)
    t2skyinverse = tel2sky.inverse
    newinverse = Mapping(
        (0, 1, 0, 1)) | setra & setdec & Identity(2) | t2skyinverse
    tel2sky.inverse = newinverse

    pipeline = [(gdetector, sub2direct), (detector, distortion),
                (v2v3, tel2sky), (world, None)]

    return pipeline
Exemple #14
0
def niriss_soss(input_model, reference_files):
    """
    The NIRISS SOSS WCS pipeline.

    Parameters
    ----------
    input_model : `~jwst.datamodel.DataModel`
        Input datamodel for processing
    reference_files : dict
        The dictionary of reference file names and their associated files
        {reftype: reference file name}.

    Returns
    -------
    pipeline : list
        The pipeline list that is returned is suitable for
        input into  gwcs.wcs.WCS to create a GWCS object.

    Notes
    -----
    It includes tWO coordinate frames -
    "detector" and "world".

    It uses the "specwcs" reference file.
    """

    # Get the target RA and DEC, they will be used for setting the WCS RA
    # and DEC based on a conversation with Kevin Volk.
    try:
        target_ra = float(input_model.meta.target.ra)
        target_dec = float(input_model.meta.target.dec)
    except TypeError:
        # There was an error getting the target RA and DEC, so we are not going to continue.
        raise ValueError('Problem getting the TARG_RA or TARG_DEC from input model {}'.format(input_model))

    # Define the frames
    detector = cf.Frame2D(name='detector', axes_order=(0, 1), unit=(u.pix, u.pix))
    spec = cf.SpectralFrame(name='spectral', axes_order=(2,), unit=(u.micron,),
                            axes_names=('wavelength',))
    sky = cf.CelestialFrame(reference_frame=coord.ICRS(),
                            axes_names=('ra', 'dec'),
                            axes_order=(0, 1), unit=(u.deg, u.deg), name='sky')
    world = cf.CompositeFrame([sky, spec], name='world')

    try:
        # We'd like to open this file as a DataModel, so we can consolidate
        # the S3 URI handling to one place.  The S3-related code here can
        # be removed once that has been changed.
        if s3_utils.is_s3_uri(reference_files['specwcs']):
            wl = asdf.open(s3_utils.get_object(reference_files['specwcs']))
        else:
            wl = asdf.open(reference_files['specwcs'])
        with wl:
            wl1 = wl.tree[1].copy()
            wl2 = wl.tree[2].copy()
            wl3 = wl.tree[3].copy()
    except Exception:
        raise IOError('Error reading wavelength correction from {}'.format(reference_files['specwcs']))

    try:
        velosys = input_model.meta.wcsinfo.velosys
    except AttributeError:
        pass
    else:
        if velosys is not None:
            velocity_corr = velocity_correction(input_model.meta.wcsinfo.velosys)
            wl1 = wl1 | velocity_corr
            wl2 = wl2 | velocity_corr
            wl2 = wl3 | velocity_corr
            log.info("Applied Barycentric velocity correction: {}".format(velocity_corr[1].amplitude.value))

    # Reverse the order of inputs passed to Tabular because it's in python order in modeling.
    # Consider changing it in modelng ?
    cm_order1 = (Mapping((0, 1, 1, 0)) |
                 (Const1D(target_ra) & Const1D(target_dec) & wl1)
                 ).rename('Order1')
    cm_order2 = (Mapping((0, 1, 1, 0)) |
                 (Const1D(target_ra) & Const1D(target_dec) & wl2)
                 ).rename('Order2')
    cm_order3 = (Mapping((0, 1, 1, 0)) |
                 (Const1D(target_ra) & Const1D(target_dec) & wl3)
                 ).rename('Order3')

    subarray2full = subarray_transform(input_model)
    if subarray2full is not None:
        cm_order1 = subarray2full | cm_order1
        cm_order2 = subarray2full | cm_order2
        cm_order3 = subarray2full | cm_order3

        bbox = ((-0.5, input_model.meta.subarray.ysize - 0.5),
                (-0.5, input_model.meta.subarray.xsize - 0.5))
        cm_order1.bounding_box = bbox
        cm_order2.bounding_box = bbox
        cm_order3.bounding_box = bbox

    # Define the transforms, they should accept (x,y) and return (ra, dec, lambda)
    soss_model = NirissSOSSModel([1, 2, 3],
                                 [cm_order1, cm_order2, cm_order3]
                                 ).rename('3-order SOSS Model')

    # Define the pipeline based on the frames and models above.
    pipeline = [(detector, soss_model),
                (world, None)
                ]

    return pipeline
Exemple #15
0
def extract_tso_object(input_model,
                       reference_files=None,
                       extract_height=None,
                       extract_orders=None):
    """
    Extract the spectrum for a NIRCAM TSO observation.

    Parameters
    ----------
    input_model : `~jwst.datamodels.CubeModel`
        The input TSO image as an instance of a CubeModel

    reference_files : dict
        Needs to include the name of the wavelengthrange reference file

    extract_height : int
        The extraction height, in total, for the spectra in the
        cross-dispersion direction. If this is other than None,
        it will override the team default of 64 pixels. The team
        wants the source centered near row 34, so the extraction
        height is not the same on either size of the central pixel.

    extract_orders : list[ints]
        This is an optional parameter that will override the
        orders specified for extraction in the wavelengthrange
        reference file.

    Returns
    -------
    output_model : `~jwst.datamodels.SlitModel`


    Notes
    -----
    This method supports NRC_TSGRISM only, where only one
    bright object is considered in the field, so there's
    no catalog of sources and the object is assumed to
    have been observed at the aperture location crpix1/2.
    CRPIX1/2 are read from the SIAF and saved as
    "meta.wcsinfo.siaf_xref_sci" and "meta.wcsinfo.siaf_yref_sci".

    GrismObject is a named tuple which contains distilled
    information about each catalog object and the bounding
    boxes that will be used to define the 2d extraction area.

    Since this mode has a single known source location the utilities
    used in the WFSS modes are overkill, instead, similar structures
    are created during the extrac 2d process and then directly used.

    https://jwst-docs.stsci.edu/display/JTI/NIRCam+Grism+Time+Series
    """

    if not isinstance(reference_files, dict):
        raise TypeError("Expected a dictionary for reference_files")

    if 'wavelengthrange' not in reference_files.keys():
        raise KeyError("No wavelengthrange reference file specified")

    if not isinstance(input_model, CubeModel):
        raise TypeError('The input data model is not a CubeModel.')

    if extract_height is None:
        extract_height = input_model.meta.subarray.ysize
    log.info("Setting extraction height to {}".format(extract_height))

    # Get the disperser parameters which have the wave limits
    with WavelengthrangeModel(reference_files['wavelengthrange']) as f:
        if (f.meta.instrument.name != 'NIRCAM'):
            raise ValueError("Wavelengthrange reference file not for NIRCAM!")
        if (f.meta.exposure.type != 'NRC_TSGRISM'):
            raise ValueError("Wavelengthrange reference file not for TSGRISM")
        wavelengthrange = f.wavelengthrange
        ref_extract_orders = f.extract_orders

    # this supports the user override and overriding the WFSS order extraction
    # when called from the pipeline only the first order is extracted
    if extract_orders is None:
        log.info("Using default order extraction from reference file")
        extract_orders = ref_extract_orders
        available_orders = [x[1] for x in extract_orders if x[0] == input_model.meta.instrument.filter].pop()
    else:
        if isinstance(extract_orders, list):
            if not all(isinstance(item, int) for item in extract_orders):
                raise TypeError("Expected extract_orders to be a list of ints")
        else:
            raise TypeError("Expected extract_orders to be a list of ints")
        available_orders = extract_orders

    if len(available_orders) > 1:
        raise NotImplementedError("Multiple order extraction for TSO not currently implemented")

    log.info("Extracting order: {}".format(available_orders))
    output_model = datamodels.SlitModel()
    output_model.update(input_model)
    subwcs = copy.deepcopy(input_model.meta.wcs)

    for order in available_orders:
        range_select = [(x[2], x[3]) for x in wavelengthrange if (x[0] == order and x[1] == input_model.meta.instrument.filter)]
        # All objects in the catalog will use the same filter for translation
        # that filter is the one that was used in front of the grism
        lmin, lmax = range_select.pop()

        # create the order bounding box
        source_xpos = input_model.meta.wcsinfo.siaf_xref_sci - 1  # remove fits
        source_ypos = input_model.meta.wcsinfo.siaf_yref_sci - 1  # remove fits
        transform = input_model.meta.wcs.get_transform('full_detector', 'grism_detector')
        xmin, ymin, _ = transform(source_xpos,
                                  source_ypos,
                                  lmin,
                                  order)
        xmax, ymax, _ = transform(source_xpos,
                                  source_ypos,
                                  lmax,
                                  order)

        # Add the shift to the lower corner to each subarray WCS object
        # The shift should just be the lower bounding box corner
        # also replace the object center location inputs to the GrismDispersion
        # model with the known object center and order information (in pixels of direct image)
        # This is changes the user input to the model from (x,y,x0,y0,order) -> (x,y)
        #
        # The bounding boxes here are also limited to the size of the detector in the
        # dispersion direction and 64 pixels in the cross-dispersion
        # The check for boxes entirely off the detector is done in create_grism_bbox right now

        # The team wants the object to fall near  row 34 for all cutouts,
        # but the default cutout height is 64pixel (32 on either side)
        # so use crpix2 when it equals 34, but  bump the ycenter by 2 pixel
        # in the case that it's 32 so that the height is 30 above and 34 below (in full frame)
        bump = source_ypos - 34
        extract_y_center = source_ypos - bump

        splitheight = int(extract_height / 2)
        below = extract_y_center - splitheight
        if below == 34:
            extract_y_min = 0
            extract_y_max = extract_y_center + splitheight
        elif below < 0:
            extract_y_min = 0
            extract_y_max = extract_height - 1
        else:
            extract_y_min = extract_y_center - 34  # always return source at pixel 34 in cutout.
            extract_y_max = extract_y_center + extract_height - 34 - 1

        if (extract_y_min > extract_y_max):
            raise ValueError("Something bad happened calculating extraction y-size")

        # limit the bounding box to the detector
        ymin, ymax = (max(extract_y_min, 0), min(extract_y_max, input_model.meta.subarray.ysize))
        xmin, xmax = (max(xmin, 0), min(xmax, input_model.meta.subarray.xsize))
        log.info("xmin, xmax: {} {}  ymin, ymax: {} {}".format(xmin, xmax, ymin, ymax))

        # the order and source position are put directly into
        # the new wcs for the subarray for the forward transform
        order_model = Const1D(order)
        order_model.inverse = Const1D(order)
        tr = input_model.meta.wcs.get_transform('grism_detector', 'full_detector')
        tr = Mapping((0, 1, 0)) | Shift(xmin) & Shift(ymin) & order_model | tr
        subwcs.set_transform('grism_detector', 'full_detector', tr)

        xmin = int(xmin)
        xmax = int(xmax)
        ymin = int(ymin)
        ymax = int(ymax)

        # cut it out, keep the entire row though
        ext_data = input_model.data[:, ymin: ymax + 1, :].copy()
        ext_err = input_model.err[:, ymin: ymax + 1, :].copy()
        ext_dq = input_model.dq[:, ymin: ymax + 1, :].copy()

        log.info("WCS made explicit for order: {}".format(order))
        log.info("Trace extents are: (xmin:{}, ymin:{}), (xmax:{}, ymax:{})".format(xmin, ymin, xmax, ymax))

        if output_model.meta.model_type == "SlitModel":
            output_model.data = ext_data
            output_model.err = ext_err
            output_model.dq = ext_dq
            output_model.meta.wcs = subwcs
            output_model.meta.wcs.bounding_box = util.wcs_bbox_from_shape(ext_data.shape)
            output_model.meta.wcsinfo.siaf_yref_sci = 34  # update for the move, vals are the same
            output_model.meta.wcsinfo.siaf_xref_sci = input_model.meta.wcsinfo.siaf_xref_sci
            output_model.meta.wcsinfo.spectral_order = order
            output_model.name = str('TSO object')
            output_model.xstart = 1  # fits pixels
            output_model.xsize = ext_data.shape[2]
            output_model.ystart = 1  # fits pixels
            output_model.ysize = ext_data.shape[1]
            output_model.source_xpos = source_xpos
            output_model.source_ypos = 34
            output_model.source_id = 1
            output_model.bunit_data = input_model.meta.bunit_data
            output_model.bunit_err = input_model.meta.bunit_err

    del subwcs
    log.info("Finished extractions")
    return output_model
Exemple #16
0
def niriss_soss(input_model, reference_files):
    """
    The NIRISS SOSS pipeline includes 3 coordinate frames -
    detector, focal plane and sky

    reference_files={'specwcs': 'soss_wavelengths_configuration.asdf'}
    """

    # Get the target RA and DEC, they will be used for setting the WCS RA and DEC based on a conversation
    # with Kevin Volk.
    try:
        target_ra = float(input_model['meta.target.ra'])
        target_dec = float(input_model['meta.target.dec'])
    except:
        # There was an error getting the target RA and DEC, so we are not going to continue.
        raise ValueError(
            'Problem getting the TARG_RA or TARG_DEC from input model {}'.
            format(input_model))

    # Define the frames
    detector = cf.Frame2D(name='detector',
                          axes_order=(0, 1),
                          unit=(u.pix, u.pix))
    spec = cf.SpectralFrame(name='spectral',
                            axes_order=(2, ),
                            unit=(u.micron, ),
                            axes_names=('wavelength', ))
    sky = cf.CelestialFrame(reference_frame=coord.ICRS(),
                            axes_names=('ra', 'dec'),
                            axes_order=(0, 1),
                            unit=(u.deg, u.deg),
                            name='sky')
    world = cf.CompositeFrame([sky, spec], name='world')
    try:
        with AsdfFile.open(reference_files['specwcs']) as wl:
            wl1 = wl.tree[1].copy()
            wl2 = wl.tree[2].copy()
            wl3 = wl.tree[3].copy()
    except Exception as e:
        raise IOError('Error reading wavelength correction from {}'.format(
            reference_files['specwcs']))

    subarray2full = subarray_transform(input_model)

    # Reverse the order of inputs passed to Tabular because it's in python order in modeling.
    # Consider changing it in modelng ?
    cm_order1 = subarray2full | (Mapping((0, 1, 1, 0)) | \
                                 (Const1D(target_ra) & Const1D(target_dec) & wl1)
                                 ).rename('Order1')
    cm_order2 = subarray2full | (Mapping((0, 1, 1, 0)) | \
                                 (Const1D(target_ra) & Const1D(target_dec) & wl2)
                                 ).rename('Order2')
    cm_order3 = subarray2full | (Mapping((0, 1, 1, 0)) | \
                                 (Const1D(target_ra) & Const1D(target_dec) & wl3)
                                 ).rename('Order3')

    # Define the transforms, they should accept (x,y) and return (ra, dec, lambda)
    soss_model = NirissSOSSModel(
        [1, 2, 3],
        [cm_order1, cm_order2, cm_order3]).rename('3-order SOSS Model')

    # Define the pipeline based on the frames and models above.
    pipeline = [(detector, soss_model), (world, None)]

    return pipeline