Exemplo n.º 1
0
def test_tabular_in_compound():
    """
    Issue #7411 - evaluate should not change the shape of the output.
    """
    t = Tabular1D(points=([1, 5, 7],), lookup_table=[12, 15, 19],
                  bounds_error=False)
    rot = Rotation2D(2)
    p = Polynomial1D(1)
    x = np.arange(12).reshape((3, 4))
    # Create a compound model which does ot execute Tabular.__call__,
    # but model.evaluate and is followed by a Rotation2D which
    # checks the exact shapes.
    model = p & t | rot
    x1, y1 = model(x, x)
    assert x1.ndim == 2
    assert y1.ndim == 2
Exemplo n.º 2
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.
    """

    # Only the first coordinate is used.
    input_frame = cf.Frame2D(axes_order=(0, 1),
                             unit=(u.pix, 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', ))

    pixel = np.arange(len(wavelength), dtype=np.float)
    tab = Mapping((0, 0, 0)) | \
          Const1D(ra) & Const1D(dec) & Tabular1D(pixel, wavelength)

    world = cf.CompositeFrame([sky, spec], name='world')

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

    return WCS(pipeline)
Exemplo n.º 3
0
    def build_interpolated_output_wcs(self, refmodel=None):
        """
        Create a spatial/spectral WCS output frame using all the input models

        Creates output frame by linearly fitting RA, Dec along the slit and
        producing a lookup table to interpolate wavelengths in the dispersion
        direction.

        Parameters
        ----------
        refmodel : `~jwst.datamodels.DataModel`
            The reference input image from which the fiducial WCS is created.
            If not specified, the first image in self.input_models is used.

        Returns
        -------
        output_wcs : `~gwcs.WCS` object
            A gwcs WCS object defining the output frame WCS
        """

        # for each input model convert slit x,y to ra,dec,lam
        # use first input model to set spatial scale
        # use center of appended ra and dec arrays to set up
        # center of final ra,dec
        # append all ra,dec, wavelength array for each slit
        # use first model to initialize wavelenth array
        # append wavelengths that fall outside the endpoint of
        # of wavelength array when looping over additional data

        all_wavelength = []
        all_ra_slit = []
        all_dec_slit = []

        for im, model in enumerate(self.input_models):
            wcs = model.meta.wcs
            bb = wcs.bounding_box
            grid = wcstools.grid_from_bounding_box(bb)
            ra, dec, lam = np.array(wcs(*grid))
            spectral_axis = find_dispersion_axis(model)
            spatial_axis = spectral_axis ^ 1

            # Compute the wavelength array, trimming NaNs from the ends
            # In many cases, a whole slice is NaNs, so ignore those warnings
            warnings.simplefilter("ignore")
            wavelength_array = np.nanmedian(lam, axis=spectral_axis)
            warnings.resetwarnings()
            wavelength_array = wavelength_array[~np.isnan(wavelength_array)]

            # We need to estimate the spatial sampling to use for the output WCS.
            # Tt is assumed the spatial sampling is the same for all the input
            # models. So we can use the first input model to set the spatial
            # sampling.

            # Steps to do this for first input model:
            # 1. find the middle of the spectrum in wavelength
            # 2. Pull out the ra and dec at the center of the slit.
            # 3. Find the mean ra,dec and the center of the slit this will
            #    represent the tangent point
            # 4. Convert ra,dec -> tangent plane projection: x_tan,y_tan
            # 5. using x_tan, y_tan perform a linear fit to find spatial sampling
            # first input model sets intializes wavelength array and defines
            # the spatial scale of the output wcs
            if im == 0:
                for iw in wavelength_array:
                    all_wavelength.append(iw)

                lam_center_index = int(
                    (bb[spectral_axis][1] - bb[spectral_axis][0]) / 2)
                if spatial_axis == 0:
                    ra_center = ra[lam_center_index, :]
                    dec_center = dec[lam_center_index, :]
                else:
                    ra_center = ra[:, lam_center_index]
                    dec_center = dec[:, lam_center_index]
                # find the ra and dec for this slit using center of slit
                ra_center_pt = np.nanmean(ra_center)
                dec_center_pt = np.nanmean(dec_center)

                if resample_utils.is_sky_like(model.meta.wcs.output_frame):
                    # convert ra and dec to tangent projection
                    tan = Pix2Sky_TAN()
                    native2celestial = RotateNative2Celestial(
                        ra_center_pt, dec_center_pt, 180)
                    undist2sky1 = tan | native2celestial
                    # Filter out RuntimeWarnings due to computed NaNs in the WCS
                    warnings.simplefilter("ignore")
                    # at this center of slit find x,y tangent projection - x_tan, y_tan
                    x_tan, y_tan = undist2sky1.inverse(ra, dec)
                    warnings.resetwarnings()
                else:
                    # for non sky-like output frames, no need to do tangent plane projections
                    # but we still use the same variables
                    x_tan, y_tan = ra, dec

                # pull out data from center
                if spectral_axis == 0:
                    x_tan_array = x_tan.T[lam_center_index]
                    y_tan_array = y_tan.T[lam_center_index]
                else:
                    x_tan_array = x_tan[lam_center_index]
                    y_tan_array = y_tan[lam_center_index]

                x_tan_array = x_tan_array[~np.isnan(x_tan_array)]
                y_tan_array = y_tan_array[~np.isnan(y_tan_array)]

                # estimate the spatial sampling
                fitter = LinearLSQFitter()
                fit_model = Linear1D()
                xstop = x_tan_array.shape[0] / self.pscale_ratio
                xstep = 1 / self.pscale_ratio
                ystop = y_tan_array.shape[0] / self.pscale_ratio
                ystep = 1 / self.pscale_ratio
                pix_to_xtan = fitter(fit_model, np.arange(0, xstop, xstep),
                                     x_tan_array)
                pix_to_ytan = fitter(fit_model, np.arange(0, ystop, ystep),
                                     y_tan_array)

            # append all ra and dec values to use later to find min and max
            # ra and dec
            ra_use = ra.flatten()
            ra_use = ra_use[~np.isnan(ra_use)]
            dec_use = dec.flatten()
            dec_use = dec_use[~np.isnan(dec_use)]
            all_ra_slit.append(ra_use)
            all_dec_slit.append(dec_use)

            # now check wavelength array to see if we need to add to it
            this_minw = np.min(wavelength_array)
            this_maxw = np.max(wavelength_array)
            all_minw = np.min(all_wavelength)
            all_maxw = np.max(all_wavelength)

            if this_minw < all_minw:
                addpts = wavelength_array[wavelength_array < all_minw]
                for ip in range(len(addpts)):
                    all_wavelength.append(addpts[ip])
            if this_maxw > all_maxw:
                addpts = wavelength_array[wavelength_array > all_maxw]
                for ip in range(len(addpts)):
                    all_wavelength.append(addpts[ip])

        # done looping over set of models

        all_ra = np.hstack(all_ra_slit)
        all_dec = np.hstack(all_dec_slit)
        all_wave = np.hstack(all_wavelength)
        all_wave = all_wave[~np.isnan(all_wave)]
        all_wave = np.sort(all_wave, axis=None)
        # Tabular interpolation model, pixels -> lambda
        wavelength_array = np.unique(all_wave)
        # Check if the data is MIRI LRS FIXED Slit. If it is then
        # the wavelength array needs to be flipped so that the resampled
        # dispersion direction matches the disperion direction on the detector.
        if self.input_models[0].meta.exposure.type == 'MIR_LRS-FIXEDSLIT':
            wavelength_array = np.flip(wavelength_array, axis=None)

        step = 1 / self.pscale_ratio
        stop = wavelength_array.shape[0] / self.pscale_ratio
        points = np.arange(0, stop, step)
        pix_to_wavelength = Tabular1D(points=points,
                                      lookup_table=wavelength_array,
                                      bounds_error=False,
                                      fill_value=None,
                                      name='pix2wavelength')

        # Tabular models need an inverse explicitly defined.
        # If the wavelength array is decending instead of ascending, both
        # points and lookup_table need to be reversed in the inverse transform
        # for scipy.interpolate to work properly
        points = wavelength_array
        lookup_table = np.arange(0, stop, step)

        if not np.all(np.diff(wavelength_array) > 0):
            points = points[::-1]
            lookup_table = lookup_table[::-1]
        pix_to_wavelength.inverse = Tabular1D(points=points,
                                              lookup_table=lookup_table,
                                              bounds_error=False,
                                              fill_value=None,
                                              name='wavelength2pix')

        # For the input mapping, duplicate the spatial coordinate
        mapping = Mapping((spatial_axis, spatial_axis, spectral_axis))

        # Sometimes the slit is perpendicular to the RA or Dec axis.
        # For example, if the slit is perpendicular to RA, that means
        # the slope of pix_to_xtan will be nearly zero, so make sure
        # mapping.inverse uses pix_to_ytan.inverse.  The auto definition
        # of mapping.inverse is to use the 2nd spatial coordinate, i.e. Dec.

        if np.isclose(pix_to_ytan.slope, 0, atol=1e-8):
            mapping_tuple = (0, 1)
            # Account for vertical or horizontal dispersion on detector
            if spatial_axis:
                mapping.inverse = Mapping(mapping_tuple[::-1])
            else:
                mapping.inverse = Mapping(mapping_tuple)

        # The final transform
        # redefine the ra, dec center tangent point to include all data

        # check if all_ra crosses 0 degress - this makes it hard to
        # define the min and max ra correctly
        all_ra = wrap_ra(all_ra)
        ra_min = np.amin(all_ra)
        ra_max = np.amax(all_ra)

        ra_center_final = (ra_max + ra_min) / 2.0
        dec_min = np.amin(all_dec)
        dec_max = np.amax(all_dec)
        dec_center_final = (dec_max + dec_min) / 2.0
        tan = Pix2Sky_TAN()
        if len(self.input_models
               ) == 1:  # single model use ra_center_pt to be consistent
            # with how resample was done before
            ra_center_final = ra_center_pt
            dec_center_final = dec_center_pt

        if resample_utils.is_sky_like(model.meta.wcs.output_frame):
            native2celestial = RotateNative2Celestial(ra_center_final,
                                                      dec_center_final, 180)
            undist2sky = tan | native2celestial
            # find the spatial size of the output - same in x,y
            x_tan_all, _ = undist2sky.inverse(all_ra, all_dec)
        else:
            x_tan_all, _ = all_ra, all_dec
        x_min = np.amin(x_tan_all)
        x_max = np.amax(x_tan_all)
        x_size = int(np.ceil((x_max - x_min) / np.absolute(pix_to_xtan.slope)))

        # single model use size of x_tan_array
        # to be consistent with method before
        if len(self.input_models) == 1:
            x_size = len(x_tan_array)

        # define the output wcs
        if resample_utils.is_sky_like(model.meta.wcs.output_frame):
            transform = mapping | (pix_to_xtan & pix_to_ytan
                                   | undist2sky) & pix_to_wavelength
        else:
            transform = mapping | (pix_to_xtan
                                   & pix_to_ytan) & pix_to_wavelength

        det = cf.Frame2D(name='detector', axes_order=(0, 1))
        if resample_utils.is_sky_like(model.meta.wcs.output_frame):
            sky = cf.CelestialFrame(name='sky',
                                    axes_order=(0, 1),
                                    reference_frame=coord.ICRS())
        else:
            sky = cf.Frame2D(
                name=f'resampled_{model.meta.wcs.output_frame.name}',
                axes_order=(0, 1))
        spec = cf.SpectralFrame(name='spectral',
                                axes_order=(2, ),
                                unit=(u.micron, ),
                                axes_names=('wavelength', ))
        world = cf.CompositeFrame([sky, spec], name='world')

        pipeline = [(det, transform), (world, None)]

        output_wcs = WCS(pipeline)

        # import ipdb; ipdb.set_trace()

        # compute the output array size in WCS axes order, i.e. (x, y)
        output_array_size = [0, 0]
        output_array_size[spectral_axis] = int(
            np.ceil(len(wavelength_array) / self.pscale_ratio))
        output_array_size[spatial_axis] = int(
            np.ceil(x_size / self.pscale_ratio))
        # turn the size into a numpy shape in (y, x) order
        self.data_size = tuple(output_array_size[::-1])
        bounding_box = resample_utils.wcs_bbox_from_shape(self.data_size)
        output_wcs.bounding_box = bounding_box

        return output_wcs
Exemplo n.º 4
0
def test_bounding_box_with_units():
    points = np.arange(5) * u.pix
    lt = np.arange(5) * u.AA
    t = Tabular1D(points, lt)

    assert (t(1 * u.pix, with_bounding_box=True) == 1. * u.AA)
Exemplo n.º 5
0
    def build_nirspec_output_wcs(self, refmodel=None):
        """
        Create a spatial/spectral WCS covering footprint of the input
        """
        all_wcs = [m.meta.wcs for m in self.input_models if m is not refmodel]
        if refmodel:
            all_wcs.insert(0, refmodel.meta.wcs)
        else:
            refmodel = self.input_models[0]

        refwcs = refmodel.meta.wcs

        s2d = refwcs.get_transform('slit_frame', 'detector')
        d2s = refwcs.get_transform('detector', 'slit_frame')
        s2w = refwcs.get_transform('slit_frame', 'world')

        # estimate position of the target without relying in the meta.target:
        bbox = refwcs.bounding_box

        grid = wcstools.grid_from_bounding_box(bbox)
        _, s, lam = np.array(d2s(*grid))
        sd = s * refmodel.data
        ld = lam * refmodel.data
        good_s = np.isfinite(sd)
        if np.any(good_s):
            total = np.sum(refmodel.data[good_s])
            wmean_s = np.sum(sd[good_s]) / total
            wmean_l = np.sum(ld[good_s]) / total
        else:
            wmean_s = 0.5 * (refmodel.slit_ymax - refmodel.slit_ymin)
            wmean_l = d2s(*np.mean(bbox, axis=1))[2]

        targ_ra, targ_dec, _ = s2w(0, wmean_s, wmean_l)

        ref_lam = _find_nirspec_output_sampling_wavelengths(
            all_wcs,
            targ_ra, targ_dec
        )
        ref_lam = np.array(ref_lam)

        n_lam = ref_lam.size
        if not n_lam:
            raise ValueError("Not enough data to construct output WCS.")

        x_slit = np.zeros(n_lam)
        lam = 1e-6 * ref_lam

        # Find the spatial pixel scale:
        y_slit_min, y_slit_max = self._max_virtual_slit_extent(all_wcs, targ_ra, targ_dec)

        nsampl = 50
        xy_min = s2d(
            nsampl * [0],
            nsampl * [y_slit_min],
            lam[(tuple((i * n_lam) // nsampl for i in range(nsampl)), )]
        )
        xy_max = s2d(
            nsampl * [0],
            nsampl * [y_slit_max],
            lam[(tuple((i * n_lam) // nsampl for i in range(nsampl)), )]
        )

        good = np.logical_and(np.isfinite(xy_min), np.isfinite(xy_max))
        if not np.any(good):
            raise ValueError("Error estimating output WCS pixel scale.")

        xy1 = s2d(x_slit, np.full(n_lam, refmodel.slit_ymin), lam)
        xy2 = s2d(x_slit, np.full(n_lam, refmodel.slit_ymax), lam)
        xylen = np.nanmax(np.linalg.norm(np.array(xy1) - np.array(xy2), axis=0)) + 1
        pscale = (refmodel.slit_ymax - refmodel.slit_ymin) / xylen

        # compute image span along Y-axis (length of the slit in the detector plane)
        # det_slit_span = np.linalg.norm(np.subtract(xy_max, xy_min))
        det_slit_span = np.nanmax(np.linalg.norm(np.subtract(xy_max, xy_min), axis=0))
        ny = int(np.ceil(det_slit_span * self.pscale_ratio + 0.5)) + 1

        border = 0.5 * (ny - det_slit_span * self.pscale_ratio) - 0.5

        if xy_min[1][1] < xy_max[1][1]:
            y_slit_model = Linear1D(
                slope=pscale / self.pscale_ratio,
                intercept=y_slit_min - border * pscale * self.pscale_ratio
            )
        else:
            y_slit_model = Linear1D(
                slope=-pscale / self.pscale_ratio,
                intercept=y_slit_max + border * pscale * self.pscale_ratio
            )

        # extrapolate 1/2 pixel at the edges and make tabular model w/inverse:
        lam = lam.tolist()
        pixel_coord = list(range(n_lam))

        if len(pixel_coord) > 1:
            # left:
            slope = (lam[1] - lam[0]) / pixel_coord[1]
            lam.insert(0, -0.5 * slope + lam[0])
            pixel_coord.insert(0, -0.5)
            # right:
            slope = (lam[-1] - lam[-2]) / (pixel_coord[-1] - pixel_coord[-2])
            lam.append(slope * (pixel_coord[-1] + 0.5) + lam[-2])
            pixel_coord.append(pixel_coord[-1] + 0.5)

        else:
            lam = 3 * lam
            pixel_coord = [-0.5, 0, 0.5]

        wavelength_transform = Tabular1D(points=pixel_coord,
                                         lookup_table=lam,
                                         bounds_error=False, fill_value=np.nan)
        wavelength_transform.inverse = Tabular1D(points=lam,
                                                 lookup_table=pixel_coord,
                                                 bounds_error=False,
                                                 fill_value=np.nan)
        self.data_size = (ny, len(ref_lam))

        # Construct the final transform
        mapping = Mapping((0, 1, 0))
        mapping.inverse = Mapping((2, 1))
        out_det2slit = mapping | Identity(1) & y_slit_model & wavelength_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, s2w), (world, None)]
        output_wcs = WCS(pipeline)

        # Compute bounding box and output array shape.  Add one to the y (slit)
        # height to account for the half pixel at top and bottom due to pixel
        # coordinates being centers of pixels
        bounding_box = resample_utils.wcs_bbox_from_shape(self.data_size)
        output_wcs.bounding_box = bounding_box
        output_wcs.array_shape = self.data_size

        return output_wcs
Exemplo n.º 6
0
    def build_nirspec_lamp_output_wcs(self):
        """
        Create a spatial/spectral WCS output frame for NIRSpec lamp mode

        Creates output frame by linearly fitting x_msa, y_msa along the slit and
        producing a lookup table to interpolate wavelengths in the dispersion
        direction.

        Returns
        -------
        output_wcs : `~gwcs.WCS` object
            A gwcs WCS object defining the output frame WCS
        """
        model = self.input_models[0]
        wcs = model.meta.wcs
        bbox = wcs.bounding_box
        grid = wcstools.grid_from_bounding_box(bbox)
        x_msa, y_msa, lam = np.array(wcs(*grid))
        # Handle vertical (MIRI) or horizontal (NIRSpec) dispersion.  The
        # following 2 variables are 0 or 1, i.e. zero-indexed in x,y WCS order
        spectral_axis = find_dispersion_axis(model)
        spatial_axis = spectral_axis ^ 1

        # Compute the wavelength array, trimming NaNs from the ends
        # In many cases, a whole slice is NaNs, so ignore those warnings
        warnings.simplefilter("ignore")
        wavelength_array = np.nanmedian(lam, axis=spectral_axis)
        warnings.resetwarnings()
        wavelength_array = wavelength_array[~np.isnan(wavelength_array)]

        # Find the center ra and dec for this slit at central wavelength
        lam_center_index = int((bbox[spectral_axis][1] -
                                bbox[spectral_axis][0]) / 2)
        x_msa_array = x_msa.T[lam_center_index]
        y_msa_array = y_msa.T[lam_center_index]
        x_msa_array = x_msa_array[~np.isnan(x_msa_array)]
        y_msa_array = y_msa_array[~np.isnan(y_msa_array)]

        # Estimate and fit the spatial sampling
        fitter = LinearLSQFitter()
        fit_model = Linear1D()
        xstop = x_msa_array.shape[0] / self.pscale_ratio
        xstep = 1 / self.pscale_ratio
        ystop = y_msa_array.shape[0] / self.pscale_ratio
        ystep = 1 / self.pscale_ratio
        pix_to_x_msa = fitter(fit_model, np.arange(0, xstop, xstep), x_msa_array)
        pix_to_y_msa = fitter(fit_model, np.arange(0, ystop, ystep), y_msa_array)

        step = 1 / self.pscale_ratio
        stop = wavelength_array.shape[0] / self.pscale_ratio
        points = np.arange(0, stop, step)
        pix_to_wavelength = Tabular1D(points=points,
                                      lookup_table=wavelength_array,
                                      bounds_error=False, fill_value=None,
                                      name='pix2wavelength')

        # Tabular models need an inverse explicitly defined.
        # If the wavelength array is descending instead of ascending, both
        # points and lookup_table need to be reversed in the inverse transform
        # for scipy.interpolate to work properly
        points = wavelength_array
        lookup_table = np.arange(0, stop, step)

        if not np.all(np.diff(wavelength_array) > 0):
            points = points[::-1]
            lookup_table = lookup_table[::-1]
        pix_to_wavelength.inverse = Tabular1D(points=points,
                                              lookup_table=lookup_table,
                                              bounds_error=False, fill_value=None,
                                              name='wavelength2pix')

        # For the input mapping, duplicate the spatial coordinate
        mapping = Mapping((spatial_axis, spatial_axis, spectral_axis))
        mapping.inverse = Mapping((2, 1))

        # The final transform
        # define the output wcs
        transform = mapping | pix_to_x_msa & pix_to_y_msa & pix_to_wavelength

        det = cf.Frame2D(name='detector', axes_order=(0, 1))
        sky = cf.Frame2D(name=f'resampled_{model.meta.wcs.output_frame.name}', axes_order=(0, 1))
        spec = cf.SpectralFrame(name='spectral', axes_order=(2,),
                                unit=(u.micron,), axes_names=('wavelength',))
        world = cf.CompositeFrame([sky, spec], name='world')

        pipeline = [(det, transform),
                    (world, None)]

        output_wcs = WCS(pipeline)

        # Compute the output array size and bounding box
        output_array_size = [0, 0]
        output_array_size[spectral_axis] = int(np.ceil(len(wavelength_array) / self.pscale_ratio))
        x_size = len(x_msa_array)
        output_array_size[spatial_axis] = int(np.ceil(x_size / self.pscale_ratio))
        # turn the size into a numpy shape in (y, x) order
        output_wcs.array_shape = output_array_size[::-1]
        output_wcs.pixel_shape = output_array_size
        bounding_box = resample_utils.wcs_bbox_from_shape(output_array_size[::-1])
        output_wcs.bounding_box = bounding_box

        return output_wcs
Exemplo n.º 7
0
    def build_interpolated_output_wcs(self, refmodel=None):
        """
        Create a spatial/spectral WCS output frame

        Creates output frame by linearly fitting RA, Dec along the slit and
        producing a lookup table to interpolate wavelengths in the dispersion
        direction.

        Parameters
        ----------
        refmodel : `~jwst.datamodels.DataModel`
            The reference input image from which the fiducial WCS is created.
            If not specified, the first image in self.input_models is used.

        Returns
        -------
        output_wcs : `~gwcs.WCS` object
            A gwcs WCS object defining the output frame WCS
        """
        if refmodel is None:
            refmodel = self.input_models[0]
        refwcs = refmodel.meta.wcs
        bb = refwcs.bounding_box

        grid = wcstools.grid_from_bounding_box(bb)
        ra, dec, lam = np.array(refwcs(*grid))

        spectral_axis = find_dispersion_axis(lam)
        spatial_axis = spectral_axis ^ 1

        # Compute the wavelength array, trimming NaNs from the ends
        wavelength_array = np.nanmedian(lam, axis=spectral_axis)
        wavelength_array = wavelength_array[~np.isnan(wavelength_array)]

        # Compute RA and Dec up the slit (spatial direction) at the center
        # of the dispersion.  Use spectral_axis to determine slicing dimension
        lam_center_index = int(
            (bb[spectral_axis][1] - bb[spectral_axis][0]) / 2)
        if not spectral_axis:
            ra_array = ra.T[lam_center_index]
            dec_array = dec.T[lam_center_index]
        else:
            ra_array = ra[lam_center_index]
            dec_array = dec[lam_center_index]
        ra_array = ra_array[~np.isnan(ra_array)]
        dec_array = dec_array[~np.isnan(dec_array)]

        fitter = LinearLSQFitter()
        fit_model = Linear1D()
        pix_to_ra = fitter(fit_model, np.arange(ra_array.shape[0]), ra_array)
        pix_to_dec = fitter(fit_model, np.arange(dec_array.shape[0]),
                            dec_array)

        # Tabular interpolation model, pixels -> lambda
        pix_to_wavelength = Tabular1D(lookup_table=wavelength_array,
                                      bounds_error=False,
                                      fill_value=None,
                                      name='pix2wavelength')

        # Tabular models need an inverse explicitly defined.
        # If the wavelength array is decending instead of ascending, both
        # points and lookup_table need to be reversed in the inverse transform
        # for scipy.interpolate to work properly
        points = wavelength_array
        lookup_table = np.arange(wavelength_array.shape[0])
        if not np.all(np.diff(wavelength_array) > 0):
            points = points[::-1]
            lookup_table = lookup_table[::-1]
        pix_to_wavelength.inverse = Tabular1D(points=points,
                                              lookup_table=lookup_table,
                                              bounds_error=False,
                                              fill_value=None,
                                              name='wavelength2pix')

        # For the input mapping, duplicate the spatial coordinate
        mapping = Mapping((spatial_axis, spatial_axis, spectral_axis))

        # Sometimes the slit is perpendicular to the RA or Dec axis.
        # For example, if the slit is perpendicular to RA, that means
        # the slope of pix_to_ra will be nearly zero, so make sure
        # mapping.inverse uses pix_to_dec.inverse.  The auto definition
        # of mapping.inverse is to use the 2nd spatial coordinate, i.e. Dec.
        if np.isclose(pix_to_dec.slope, 0, atol=1e-8):
            mapping_tuple = (0, 1)
            # Account for vertical or horizontal dispersion on detector
            if spatial_axis:
                mapping.inverse = Mapping(mapping_tuple[::-1])
            else:
                mapping.inverse = Mapping(mapping_tuple)

        # The final transform
        transform = mapping | pix_to_ra & pix_to_dec & pix_to_wavelength

        det = cf.Frame2D(name='detector', axes_order=(0, 1))
        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')

        pipeline = [(det, transform), (world, None)]

        output_wcs = WCS(pipeline)

        # compute the output array size in WCS axes order, i.e. (x, y)
        output_array_size = [0, 0]
        output_array_size[spectral_axis] = len(wavelength_array)
        output_array_size[spatial_axis] = len(ra_array)

        # turn the size into a numpy shape in (y, x) order
        self.data_size = tuple(output_array_size[::-1])

        bounding_box = resample_utils.wcs_bbox_from_shape(self.data_size)
        output_wcs.bounding_box = bounding_box

        return output_wcs
Exemplo n.º 8
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
Exemplo n.º 9
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=np.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)
Exemplo n.º 10
0
    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)