def test_eval(): """ Tests evaluation of Linear1D and Planar2D with different model_set_axis.""" model = Linear1D(slope=[1, 2], intercept=[3, 4], n_models=2) p = Polynomial1D(1, c0=[3, 4], c1=[1, 2], n_models=2) assert_allclose(model(xx), p(xx)) assert_allclose(model(x, model_set_axis=False), p(x, model_set_axis=False)) with pytest.raises(ValueError): model(x) model = Linear1D(slope=[[1, 2]], intercept=[[3, 4]], n_models=2, model_set_axis=1) p = Polynomial1D(1, c0=[[3, 4]], c1=[[1, 2]], n_models=2, model_set_axis=1) assert_allclose(model(xx.T), p(xx.T)) assert_allclose(model(x, model_set_axis=False), p(x, model_set_axis=False)) with pytest.raises(ValueError): model(xx) model = Planar2D(slope_x=[1, 2], slope_y=[1, 2], intercept=[3, 4], n_models=2) y = model(xx, xx) assert y.shape == (2, 4) with pytest.raises(ValueError): model(x)
def test_fitting_shapes(): """ Test fitting model sets of Linear1D and Planar2D.""" fitter = LinearLSQFitter() model = Linear1D(slope=[1, 2], intercept=[3, 4], n_models=2) y = model(xx) fit_model = fitter(model, x, y) model = Linear1D(slope=[[1, 2]], intercept=[[3, 4]], n_models=2, model_set_axis=1) fit_model = fitter(model, x, y.T) model = Planar2D(slope_x=[1, 2], slope_y=[1, 2], intercept=[3, 4], n_models=2) y = model(xx, xx) fit_model = fitter(model, x, x, y)
def linear_time_model(cadence: u.s, reference_val: u.s = 0 * u.s): """ A linear model in a temporal dimension. The reference pixel is always 0. """ if not reference_val: reference_val = 0 * cadence.unit return Linear1D(slope=cadence / (1 * u.pix), intercept=reference_val)
def linear_solution(self): from astropy.modeling.models import Linear1D m = Linear1D(1, 0) if len(self.linepixtowl) > 1: return _linfit(m, list(self.linepixtowl.keys()), list(self.linepixtowl.values())) else: raise ValueError('cannot make linear solution w < 2 points')
def _get_wavelength_calibration(hdr): from astropy.modeling.models import Linear1D, Const1D _wcal_model = ( Const1D(hdr.get('CRVAL1')) + Linear1D(slope=hdr.get('CD1_1'), intercept=hdr.get('CRPIX1') - 1)) assert _wcal_model(0) == hdr.get('CRVAL1') return _wcal_model
def measure_SNR(spex_path, data_path, plot_path, star, ranges): """ Measure the SNR of a spectrum, by fitting straight lines to pieces of the spectrum """ # plot the spectrum to define regions without spectral lines fig, ax = plot_spectrum(star, data_path, range=[0.75, 5.6], log=True) # read in all bands and spectra for this star starobs = StarData("%s.dat" % star.lower(), path=data_path, use_corfac=True) # obtain flux values at a few wavelengths and fit a straight line through the data waves, fluxes, uncs = starobs.get_flat_data_arrays(["SpeX_SXD", "SpeX_LXD"]) print(star) for range in ranges: min_indx = np.abs(waves - range[0]).argmin() max_indx = np.abs(waves - range[1]).argmin() func = Linear1D() fit = LinearLSQFitter() fit_result = fit(func, waves[min_indx:max_indx], fluxes[min_indx:max_indx]) residu = fluxes[min_indx:max_indx] - fit_result(waves[min_indx:max_indx]) # calculate the SNR from the data data_sxd = Table.read( spex_path + star + "_sxd.txt", format="ascii", ) data_lxd = Table.read( spex_path + star + "_lxd.txt", format="ascii", ) data = vstack([data_sxd, data_lxd]) data.sort("col1") print("wave_range", range) min_indx2 = np.abs(data["col1"] - range[0]).argmin() max_indx2 = np.abs(data["col1"] - range[1]).argmin() SNR_data = np.nanmedian((data["col2"] / data["col3"])[min_indx2:max_indx2]) print("SNR from data", SNR_data) # calculate the SNR from the noise around the linear fit mean, median, stddev = sigma_clipped_stats(residu) SNR_fit = np.median(fluxes[min_indx:max_indx] / stddev) print("SNR from fit", SNR_fit) # plot the fitted lines on top of the spectrum ax.plot( waves[min_indx:max_indx], fit_result(waves[min_indx:max_indx]), lw=2, alpha=0.8, color="k", ) fig.savefig(plot_path + star + "_SNR_measure.pdf")
def test_fit_multiplied_compound_model_with_mixed_units(): """ Regression test for issue #12320 """ fitter = LevMarLSQFitter() x = np.linspace(0, 1, 101) * u.s y = np.linspace(5, 10, 101) * u.m * u.kg / u.s m1 = Linear1D(slope=5 * u.m / u.s / u.s, intercept=1.0 * u.m / u.s) m2 = Linear1D(slope=0.0 * u.kg / u.s, intercept=10.0 * u.kg) truth = m1 * m2 fit = fitter(truth, x, y) unfit_output = truth(x) fit_output = fit(x) assert unfit_output.unit == fit_output.unit == (u.kg * u.m / u.s) assert_allclose(unfit_output, fit_output) for name in truth.param_names: assert getattr(truth, name) == getattr(fit, name)
def __call__(cls, lines=None, continuum=None): """ See the `Absorption1D` initializer for details. """ mod_list = [] if continuum is not None: if issubclass(continuum.__class__, Fittable1DModel): mod_list.append(continuum) elif isinstance(continuum, six.string_types): continuum = getattr(models, continuum, 'Linear1D') mod_list.append(continuum) else: raise AttributeError("Unknown continuum type {}.".format( type(continuum))) else: continuum = Linear1D(slope=0, intercept=1) mod_list.append(continuum) if lines is not None: if isinstance(lines, list): mod_list += lines elif issubclass(lines, Fittable1DModel): mod_list.append(lines) abs_mod = np.sum(mod_list) def call(self, dispersion, uncertainty=None, dispersion_unit=None, *args, **kwargs): from ..spectra import Spectrum1D lines = abs_mod._submodels[1:] # noqa flux = super(abs_mod.__class__, self).__call__(dispersion, *args, **kwargs) spectrum = Spectrum1D(flux, dispersion=dispersion, dispersion_unit=dispersion_unit) return spectrum mod = type('Absorption1D', (abs_mod.__class__, ), {'__call__': call}) return mod()
def test_custom_continuum(): line1 = OpticalDepth1D(lambda_0=1216 * u.AA) continuum = Linear1D(slope=1 / u.AA, intercept=0 * u.Unit('')) # Create model from lambda spec_mod = Spectral1D(line1, continuum=continuum) wav = np.linspace(1100, 1300, 500) * u.AA vel = np.linspace(-100, 100, 500) * u.km / u.s # Test in wavelength and velocity spaces assert len(spec_mod(wav)) == 500 assert len(spec_mod(vel)) == 500 assert np.trapz(spec_mod(wav), x=wav) > 0 assert np.trapz(spec_mod(vel), x=vel) > 0
def generate_phi(cmau_array, cmau_ind, log_wav, z, abs_mag_bins): r''' Generate a Schechter (1976) [1]_ function from interpolated, wavelength-dependent parameters. Parameters ---------- cmau_array : numpy.ndarray A shape ``(5, 2, 4)`` array, containing the c, m, a, and u parameters for :math:`M^*_0`, :math:\phi^*_0`, :math:`\alpha`, P, and Q for "blue" and "red" galaxies respectively. cmau_ind : {0, 1} Value indicating whether we're generating galaxy densities for blue or red galaxies. log_wav : float The log-10 value of the wavelength of the particular observations, in microns. z : float The redshift at which to evaluate the Schechter luminosity function. abs_mag_bins : numpy.ndarray The absolute magnitudes at which to evaluate the Schechter function. Returns ------- phi_model : numpy.ndarray The Schechter function differential galaxy density at ``z``. References ---------- .. [1] Schechter P. (1976), ApJ, 203, 297 ''' M_star0 = function_evaluation_lookup(cmau_array, 0, cmau_ind, log_wav) phi_star0 = function_evaluation_lookup(cmau_array, 1, cmau_ind, log_wav) alpha = function_evaluation_lookup(cmau_array, 2, cmau_ind, log_wav) P = function_evaluation_lookup(cmau_array, 3, cmau_ind, log_wav) # All other parameters are a function of wavelength, but we want Q(P). Q = function_evaluation_lookup(cmau_array, 4, cmau_ind, P) # phi*(z) = phi* * 10**(0.4 P z) = phi* exp(0.4 * ln(10) P z) # and thus Exponential1D being of the form exp(x / tau), tau = 1 / (0.4 * np.log(10) * P) m_star = Linear1D(slope=-Q, intercept=M_star0) phi_star = Exponential1D(amplitude=phi_star0, tau=tau) L = 10**(-0.4 * (abs_mag_bins - m_star(z))) phi_model = 0.4 * np.log(10) * phi_star(z) * L**(alpha + 1) * np.exp(-L) return phi_model
# fit the aveDN versus diffDN combined data from all integrations # e.g., derive the non-linearity correction x = np.concatenate(x) y = np.concatenate(y) mod = fit_diffs(x, y, noexp=noexp) if noexp: polymod = mod else: polymod = mod[2] lincormod = polymod # more plots ints = range(nints) # setup ramp fit line_init = Linear1D() fit_line = LinearLSQFitter() mult_comp = False intslopes = np.zeros((nints)) linfit_metric = np.zeros((nints)) intexpamp = np.zeros((nints)) for k in range(nints): gnum, ydata = get_ramp(hdu[0], pix_x, pix_y, k) ggnum, gdata, aveDN, diffDN = get_good_ramp( gnum, ydata, keepfirst=args.keepfirst, nmax=args.nmax) # correct the ramps and plot gdata_cor = lincor_data(lincormod, gdata, aveDN, diffDN) # plot the linearied 2pt diffs versus average DN
def fit_features_spec(star, path): """ Fit the features directly from the spectrum with different profiles Parameters ---------- star : string Name of the reddened star for which to fit the features in the spectrum path : string Path to the data files Returns ------- waves : np.ndarray Numpy array with wavelengths flux_sub : np.ndarray Numpy array with continuum subtracted fluxes results : list List with the fitted models for different profiles """ # obtain the spectrum of the reddened star stardata = StarData(star + ".dat", path) npts = stardata.data["SpeX_LXD"].npts waves = stardata.data["SpeX_LXD"].waves.value flux_unc = stardata.data["SpeX_LXD"].uncs # "manually" obtain the continuum from the spectrum (i.e. read the flux at 2.4 and 3.6 micron) plot_spectrum( star, path, mlam4=True, range=[2, 4.5], exclude=["IRS", "STIS_Opt"], ) # fit the continuum reference points with a straight line ref_waves = [2.4, 3.6] fluxes = [3.33268e-12, 4.053e-12] func = Linear1D() fit = LinearLSQFitter() fit_result = fit(func, ref_waves, fluxes) # subtract the continuum from the fluxes fluxes = stardata.data["SpeX_LXD"].fluxes.value * waves ** 4 - fit_result(waves) # define different profiles # 2 Gaussians (stddev=FWHM/(2sqrt(2ln2))) gauss = Gaussian1D(mean=3, stddev=0.13) + Gaussian1D( mean=3.4, stddev=0.06, fixed={"mean": True} ) # 2 Drudes drude = Drude1D(x_0=3, fwhm=0.3) + Drude1D(x_0=3.4, fwhm=0.15, fixed={"x_0": True}) # 2 Lorentzians lorentz = Lorentz1D(x_0=3, fwhm=0.3) + Lorentz1D( x_0=3.4, fwhm=0.15, fixed={"x_0": True} ) # 2 asymmetric Gaussians Gaussian_asym = custom_model(gauss_asymmetric) gauss_asym = Gaussian_asym(x_o=3, gamma_o=0.3) + Gaussian_asym( x_o=3.4, gamma_o=0.15, fixed={"x_o": True} ) # 2 "asymmetric" Drudes Drude_asym = custom_model(drude_asymmetric) drude_asym = Drude_asym(x_o=3, gamma_o=0.3) + Drude_asym( x_o=3.4, gamma_o=0.15, fixed={"x_o": True} ) # 2 asymmetric Lorentzians Lorentzian_asym = custom_model(lorentz_asymmetric) lorentz_asym = Lorentzian_asym(x_o=3, gamma_o=0.3) + Lorentzian_asym( x_o=3.4, gamma_o=0.15, fixed={"x_o": True} ) # 1 asymmetric Drude drude_asym1 = Drude_asym(x_o=3, gamma_o=0.3) profiles = [ gauss, drude, lorentz, gauss_asym, drude_asym, lorentz_asym, drude_asym1, ] # fit the different profiles fit2 = LevMarLSQFitter() results = [] mask1 = (waves > 2.4) & (waves < 3.6) mask2 = mask1 * (npts > 0) for profile in profiles: fit_result = fit2( profile, waves[mask2], fluxes[mask2], weights=1 / flux_unc[mask2], maxiter=10000, ) results.append(fit_result) print(fit_result) print( "Chi2", np.sum(((fluxes[mask2] - fit_result(waves[mask2])) / flux_unc[mask2]) ** 2), ) return waves[mask1], fluxes[mask1], npts[mask1], results
def linear_spectral_model(spectral_width: u.nm, reference_val: u.nm): """ A linear model in a spectral dimension. The reference pixel is always 0. """ return Linear1D(slope=spectral_width / (1 * u.pix), intercept=reference_val)
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
# exponential and linear functions of redshift respectively. Samples are # generated for a given `sky_area` and over a given redshift range `z_range` up # to a limiting apparent magnitude `mag_lim` with shot noise. The values of the # parameters are taken from the B-band luminosity model for star-forming # galaxies in López-Sanjuan et al. 2017 [1]_. from astropy.cosmology import FlatLambdaCDM from astropy.modeling.models import Linear1D, Exponential1D from astropy.table import Table from astropy.units import Quantity from matplotlib import pyplot as plt import numpy as np from skypy.galaxies import schechter_lf z_range = np.linspace(0.2, 1.0, 100) m_star = Linear1D(-1.03, -20.485) phi_star = Exponential1D(0.00312608, -43.4294) alpha, mag_lim = -1.29, 30 sky_area = Quantity(2.381, "deg2") cosmology = FlatLambdaCDM(H0=70, Om0=0.3) redshift, magnitude = schechter_lf(z_range, m_star, phi_star, alpha, mag_lim, sky_area, cosmology, noise=True) # %% # ALHAMBRA B-Band Luminosity Function
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
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
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
# plot the model mod_x = np.linspace(0.0, 65000.0, num=100) ax[3].plot(mod_x, mod(mod_x) / lincormod(mod_x), label="full/linear") ints = range(nints) # apply the correction line_init = (Exponential1D( x_0=-2.0, amplitude=-500., bounds={ "amplitude": [-100000.0, -100.0], "x_0": [-4.0, 0.0] }, ) + Linear1D()) fit_line = LevMarLSQFitter() mult_comp = True line_init = Linear1D() fit_line = LinearLSQFitter() mult_comp = False intslopes = np.zeros((nints)) linfit_metric = np.zeros((nints)) intexpamp = np.zeros((nints)) for k in range(nints): gnum, ydata = get_ramp(hdu[0], pix_x, pix_y, k,