def test_tabular1d_inverse(): """Test that the Tabular1D inverse is defined""" points = np.arange(5) values = np.array([1.5, 3.4, 6.7, 7, 32]) t = models.Tabular1D(points, values) result = t.inverse((3.4, 6.7)) assert_allclose(result, np.array((1., 2.))) # Check that it works for descending values in lookup_table t2 = models.Tabular1D(points, values[::-1]) assert_allclose(t2.inverse.points[0], t2.lookup_table[::-1]) result2 = t2.inverse((7, 6.7)) assert_allclose(result2, np.array((1., 2.))) # Check that it errors on double-valued lookup_table points = np.arange(5) values = np.array([1.5, 3.4, 3.4, 32, 25]) t = models.Tabular1D(points, values) with pytest.raises(NotImplementedError): t.inverse((3.4, 7.)) # Check that Tabular2D.inverse raises an error table = np.arange(5*5).reshape(5, 5) points = np.arange(0, 5) points = (points, points) t3 = models.Tabular2D(points=points, lookup_table=table) with pytest.raises(NotImplementedError): t3.inverse((3, 3))
def test_tabular_str(): points = np.arange(5) lt = np.arange(5) t = models.Tabular1D(points, lt) assert str(t) == ("Model: Tabular1D\n" "N_inputs: 1\n" "N_outputs: 1\n" "Parameters: \n" " points: (array([0, 1, 2, 3, 4]),)\n" " lookup_table: [0 1 2 3 4]\n" " method: linear\n" " fill_value: nan\n" " bounds_error: True") table = np.arange(5 * 5).reshape(5, 5) points = np.arange(0, 5) points = (points, points) t = models.Tabular2D(points=points, lookup_table=table) assert str(t) == ( "Model: Tabular2D\n" "N_inputs: 2\n" "N_outputs: 1\n" "Parameters: \n" " points: (array([0, 1, 2, 3, 4]), array([0, 1, 2, 3, 4]))\n" " lookup_table: [[ 0 1 2 3 4]\n" " [ 5 6 7 8 9]\n" " [10 11 12 13 14]\n" " [15 16 17 18 19]\n" " [20 21 22 23 24]]\n" " method: linear\n" " fill_value: nan\n" " bounds_error: True")
def fitswcs_transform_from_model(wcsinfo, wavetable=None): """ Create a WCS object using from datamodel.meta.wcsinfo. Transforms assume 0-based coordinates. Parameters ---------- wcsinfo : dict-like ``~jwst.meta.wcsinfo`` structure. Return ------ transform : `~astropy.modeling.core.Model` WCS forward transform - from pixel to world coordinates. """ spatial_axes, spectral_axes, unknown = gwutils.get_axes(wcsinfo) transform = gwutils.make_fitswcs_transform(wcsinfo) if spectral_axes: sp_axis = spectral_axes[0] if wavetable is None: # Subtract one from CRPIX which is 1-based. spectral_transform = astmodels.Shift(-(wcsinfo['CRPIX'][sp_axis] - 1)) | \ astmodels.Scale(wcsinfo['CDELT'][sp_axis]) | \ astmodels.Shift(wcsinfo['CRVAL'][sp_axis]) else: # Wave dimension is an array that needs to be converted to a table waves = wavetable['wavelength'].flatten() spectral_transform = astmodels.Tabular1D(lookup_table=waves) transform = transform & spectral_transform return transform
def test_tabular_with_bounding_box(): points = np.arange(5) values = np.array([1.5, 3.4, 6.7, 7, 32]) t = models.Tabular1D(points, values) result = t(1, with_bounding_box=True) assert result == 3.4 assert t.inverse(result, with_bounding_box=True) == 1.
def test_tabular_bounding_box_with_units(): points = np.arange(5) * u.pix lt = np.arange(5) * u.AA t = models.Tabular1D(points, lt) result = t(1 * u.pix, with_bounding_box=True) assert result == 1. * u.AA assert t.inverse(result, with_bounding_box=True) == 1 * u.pix
def test_format_output(): points = np.arange(5) values = np.array([1.5, 3.4, 6.7, 7, 32]) t = models.Tabular1D(points, values) pipe = [('detector', t), ('world', None)] w = wcs.WCS(pipe) assert_allclose(w(1), 3.4) assert_allclose(w([1, 2]), [3.4, 6.7]) assert np.isscalar(w(1))
def test_units_with_bounding_box(): points = np.arange(10, 20) table = np.arange(10) * u.Angstrom t = models.Tabular1D(points, lookup_table=table) assert isinstance(t(10), u.Quantity) assert isinstance(t(10, with_bounding_box=True), u.Quantity) assert_quantity_allclose(t(10), t(10, with_bounding_box=True))
def test_bare_baseframe(): # This is a regression test for the following call: frame = cf.CoordinateFrame(1, "SPATIAL", (0,), unit=(u.km,)) assert u.allclose(frame.coordinate_to_quantity((1*u.m,)), 1*u.m) # Now also setup the same situation through the whole call stack to be safe. w = WCS(forward_transform=m.Tabular1D(points=np.arange(10)*u.pix, lookup_table=np.arange(10)*u.km), output_frame=frame, input_frame=cf.CoordinateFrame(1, "PIXEL", (0,), unit=(u.pix,), name="detector_frame") ) assert u.allclose(w.world_to_pixel(0*u.km), 0)
def test_tabular_evaluate(): points = np.arange(5) lt = np.arange(5)[::-1] t = models.Tabular1D(points, lt) assert (t.evaluate([1, 2, 3]) == [3, 2, 1]).all() assert (t.evaluate(np.array([1, 2, 3]) * u.m) == [3, 2, 1]).all() t.n_outputs = 2 value = [np.array([3, 2, 1]), np.array([1, 2, 3])] with mk.patch.object(tabular_models, 'interpn', autospec=True, return_value=value) as mkInterpn: outputs = t.evaluate([1, 2, 3]) for index, output in enumerate(outputs): assert np.all(value[index] == output) assert mkInterpn.call_count == 1
def test_tabular_model(tmpdir): points = np.arange(0, 5) values = [1., 10, 2, 45, -3] model = astmodels.Tabular1D(points=points, lookup_table=values) tree = {'model': model} helpers.assert_roundtrip_tree(tree, tmpdir) table = np.array([[ 3., 0., 0.], [ 0., 2., 0.], [ 0., 0., 0.]]) points = ([1, 2, 3], [1, 2, 3]) model2 = astmodels.Tabular2D(points, lookup_table=table, bounds_error=False, fill_value=None, method='nearest') tree = {'model': model2} helpers.assert_roundtrip_tree(tree, tmpdir)
def gwcs_stokes_lookup(): transform = models.Tabular1D([0, 1, 2, 3] * u.pix, [0, 1, 2, 3] * u.one, method="nearest", fill_value=np.nan, bounds_error=False) frame = cf.StokesFrame() detector_frame = cf.CoordinateFrame(name="detector", naxes=1, axes_order=(0, ), axes_type=("pixel", ), axes_names=("x", ), unit=(u.pix, )) return wcs.WCS(forward_transform=transform, output_frame=frame, input_frame=detector_frame)
def generate_lookup_table(lookup_table, interpolation='linear', points_unit=u.pix, **kwargs): if not isinstance(lookup_table, u.Quantity): raise TypeError("lookup_table must be a Quantity.") # The integer location is at the centre of the pixel. points = (np.arange(lookup_table.size) - 0) * points_unit kwargs = { 'bounds_error': False, 'fill_value': np.nan, 'method': interpolation, **kwargs } return m.Tabular1D(points, lookup_table, **kwargs)
def test_tabular_repr(): points = np.arange(5) lt = np.arange(5) t = models.Tabular1D(points, lt) assert repr(t) ==\ "<Tabular1D(points=(array([0, 1, 2, 3, 4]),), lookup_table=[0 1 2 3 4])>" table = np.arange(5 * 5).reshape(5, 5) points = np.arange(0, 5) points = (points, points) t = models.Tabular2D(points=points, lookup_table=table) assert repr(t) ==\ "<Tabular2D(points=(array([0, 1, 2, 3, 4]), array([0, 1, 2, 3, 4])), " +\ "lookup_table=[[ 0 1 2 3 4]\n" +\ " [ 5 6 7 8 9]\n" +\ " [10 11 12 13 14]\n" +\ " [15 16 17 18 19]\n" +\ " [20 21 22 23 24]])>"
assert_allclose(positional, model(1, 2)) with pytest.raises(ValueError): model(1, 2, 3) with pytest.raises(ValueError): model(1) with pytest.raises(ValueError): model(1, 2, t=12, r=3) @pytest.mark.parametrize('model', [ models.Gaussian1D(), models.Polynomial1D(1, ), models.Tabular1D(lookup_table=np.ones((5, ))), models.Rotation2D(), models.Pix2Sky_TAN() ]) @pytest.mark.skipif('not HAS_SCIPY') def test_call_keyword_args_1(model): """ Test calling a model with positional, keywrd and a mixture of both arguments. """ positional = model(1) assert_allclose(positional, model(x=1)) model.inputs = ('r', ) assert_allclose(positional, model(r=1)) with pytest.raises(ValueError):
def test_tabular_with_bounding_box(): points = np.arange(5) values = np.array([1.5, 3.4, 6.7, 7, 32]) t = models.Tabular1D(points, values) result = t(1, with_bounding_box=True)
def test_bounding_box_with_units(): points = np.arange(5)*u.pix lt = np.arange(5)*u.AA t = models.Tabular1D(points, lt) assert(t(1*u.pix, with_bounding_box=True) == 1.*u.AA)
def v2v3lamtoxy(v2in, v3in, lamin, stype): # Open relevant distortion file specfile = fits.open(get_fitsreffile()) # Convert input vectors to numpy arrays v2 = np.array(v2in) v3 = np.array(v3in) lam = np.array(lamin) # Global header hdr = specfile[0].header # File data lrsdata = np.array([l for l in specfile[1].data]) # Define zero points (in subarray frame), subarray location, subarray size if (stype.lower() == 'slit'): xsub0, ysub0 = 0, 0 xsub1, ysub1 = 1031, 1023 zero_point = hdr['imx'] - 1, hdr['imy'] - 1 elif (stype.lower() == 'slitless'): xsub0, ysub0 = 0, 528 xsub1, ysub1 = 71, 415 zero_point = hdr['imxsltl'] - 1 - xsub0, hdr['imysltl'] - 1 - ysub0 else: print('Invalid operation type: specify either slit or slitless') # In the lrsdata reference table, X_center,y_center,wavelength describe the location of the # centroid trace along the detector in pixels relative to nominal location. # The box corners for this wavelength are x0,y0(ul) x1,y1 (ur) x2,y2(lr) x3,y3(ll) # Use these to create the bounding box for all valid detector locations in units of subarray pixels xcen = lrsdata[:, 0] ycen = lrsdata[:, 1] wavetab = lrsdata[:, 2] x0 = lrsdata[:, 3] y0 = lrsdata[:, 4] x1 = lrsdata[:, 5] y2 = lrsdata[:, 8] bb = ((x0.min() - 0.5 + zero_point[0], x1.max() + 0.5 + zero_point[0]), (y2.min() - 0.5 + zero_point[1], y0.max() + 0.5 + zero_point[1])) # Now deal with the fact that the spectral trace isn't perfectly up and down along detector. # This information is contained in the xcenter/ycenter values in the CDP table, but we'll handle it # as a simple rotation using a linear fit to this relation provided by the CDP. z = np.polyfit(xcen, ycen, 1) slope = 1. / z[0] traceangle = np.arctan(slope) * 180. / np.pi # trace angle in degrees rot = models.Rotation2D(traceangle) # Rotation model # Now include this rotation in our overall transform # First shift to a frame relative to the trace zeropoint, then apply the rotation # to correct for the curved trace. End in a rotated frame relative to zero at the reference point # and where yrot is aligned with the spectral trace) xysubtoxyrot = models.Shift(-zero_point[0]) & models.Shift( -zero_point[1]) | rot # Work out the spectral component of the transform # First compute the reference trace in the rotated-Y frame xcenrot, ycenrot = rot(xcen, ycen) # The input table of wavelengths isn't perfect, and the delta-wavelength steps show some unphysical behaviour # Therefore fit with a spline for the ycenrot->wavelength transform # Reverse vectors so that yinv is increasing (needed for spline fitting function) wavetab = lrsdata[:, 2] yrev = ycenrot[::-1] wrev = wavetab[::-1] # Spline fit with enforced smoothness spl = UnivariateSpline(yrev, wrev, s=0.002) # Evaluate the fit at the rotated-y reference points wavereference = spl(yrev) # wavereference now contains the wavelengths corresponding to regularly-sampled ycenrot, create the model wavemodel = models.Tabular1D(lookup_table=wavereference, points=yrev, name='waveref', bounds_error=False, fill_value=np.nan) # Now construct the inverse spectral transform. # First we need to create a spline going from wavereference -> ycenrot spl2 = UnivariateSpline(wavereference[::-1], ycenrot, s=0.002) # Compute the nominal x,y pixels in subarray frame for this v2,v3 xnom, ynom = mt.v2v3toxy(v2, v3, 'F770W') xnom = xnom - xsub0 ynom = ynom - ysub0 # Compute this in the rotated frame xrot, _ = xysubtoxyrot(xnom, ynom) # Get the real yrot from the wavelength yrot = spl2(lam) # Convert rotated x,y to subarray x,y xsub, ysub = xysubtoxyrot.inverse(xrot, yrot) return xsub, ysub
def lrs_distortion(input_model, reference_files): """ The LRS-FIXEDSLIT and LRS-SLITLESS WCS pipeline. Transform from subarray (x, y) to (v2, v3, lambda) using the "specwcs" and "distortion" reference files. """ # subarray to full array transform subarray2full = subarray_transform(input_model) # full array to v2v3 transform for the ordinary imager with DistortionModel(reference_files['distortion']) as dist: distortion = dist.model # Combine models to create subarray to v2v3 distortion if subarray2full is not None: subarray_dist = subarray2full | distortion else: subarray_dist = distortion # Read in the reference table data and get the zero point (SIAF reference point) # of the LRS in the subarray ref frame with fits.open(reference_files['specwcs']) as ref: lrsdata = np.array([l for l in ref[1].data]) # Get the zero point from the reference data. # The zero_point is X, Y (which should be COLUMN, ROW) # These are 1-indexed in CDP-7 (i.e., SIAF convention) so must be converted to 0-indexed if input_model.meta.exposure.type.lower() == 'mir_lrs-fixedslit': zero_point = ref[0].header['imx'] - 1, ref[0].header['imy'] - 1 elif input_model.meta.exposure.type.lower() == 'mir_lrs-slitless': zero_point = ref[0].header['imxsltl'] - 1, ref[0].header[ 'imysltl'] - 1 # Transform to slitless subarray from full array zero_point = subarray2full.inverse(zero_point[0], zero_point[1]) # In the lrsdata reference table, X_center,y_center,wavelength describe the location of the # centroid trace along the detector in pixels relative to nominal location. # x0,y0(ul) x1,y1 (ur) x2,y2(lr) x3,y3(ll) define corners of the box within which the distortion # and wavelength calibration was derived xcen = lrsdata[:, 0] ycen = lrsdata[:, 1] wavetab = lrsdata[:, 2] x0 = lrsdata[:, 3] y0 = lrsdata[:, 4] x1 = lrsdata[:, 5] y2 = lrsdata[:, 8] # If in fixed slit mode, define the bounding box using the corner locations provided in # the CDP reference file. if input_model.meta.exposure.type.lower() == 'mir_lrs-fixedslit': bb_sub = ((np.floor(x0.min() + zero_point[0]) - 0.5, np.ceil(x1.max() + zero_point[0]) + 0.5), (np.floor(y2.min() + zero_point[1]) - 0.5, np.ceil(y0.max() + zero_point[1]) + 0.5)) # If in slitless mode, define the bounding box X locations using the subarray x boundaries # and the y locations using the corner locations in the CDP reference file. Make sure to # omit the 4 reference pixels on the left edge of slitless subarray. if input_model.meta.exposure.type.lower() == 'mir_lrs-slitless': bb_sub = ((input_model.meta.subarray.xstart - 1 + 4 - 0.5, input_model.meta.subarray.xsize - 1 + 0.5), (np.floor(y2.min() + zero_point[1]) - 0.5, np.ceil(y0.max() + zero_point[1]) + 0.5)) # Find the ROW of the zero point row_zero_point = zero_point[1] # The inputs to the "detector_to_v2v3" transform are # - the indices in x spanning the entire image row # - y is the y-value of the zero point # This is equivalent of making a vector of x, y locations for # every pixel in the reference row const1d = models.Const1D(row_zero_point) const1d.inverse = models.Const1D(row_zero_point) det_to_v2v3 = models.Identity(1) & const1d | subarray_dist # Now deal with the fact that the spectral trace isn't perfectly up and down along detector. # This information is contained in the xcenter/ycenter values in the CDP table, but we'll handle it # as a simple rotation using a linear fit to this relation provided by the CDP. z = np.polyfit(xcen, ycen, 1) slope = 1. / z[0] traceangle = np.arctan(slope) * 180. / np.pi # trace angle in degrees rot = models.Rotation2D(traceangle) # Rotation model # Now include this rotation in our overall transform # First shift to a frame relative to the trace zeropoint, then apply the rotation # to correct for the curved trace. End in a rotated frame relative to zero at the reference point # and where yrot is aligned with the spectral trace) xysubtoxyrot = models.Shift(-zero_point[0]) & models.Shift( -zero_point[1]) | rot # Next shift back to the subarray frame, and then map to v2v3 xyrottov2v3 = models.Shift(zero_point[0]) & models.Shift( zero_point[1]) | det_to_v2v3 # The two models together xysubtov2v3 = xysubtoxyrot | xyrottov2v3 # Work out the spectral component of the transform # First compute the reference trace in the rotated-Y frame xcenrot, ycenrot = rot(xcen, ycen) # The input table of wavelengths isn't perfect, and the delta-wavelength # steps show some unphysical behaviour # Therefore fit with a spline for the ycenrot->wavelength transform # Reverse vectors so that yinv is increasing (needed for spline fitting function) yrev = ycenrot[::-1] wrev = wavetab[::-1] # Spline fit with enforced smoothness spl = UnivariateSpline(yrev, wrev, s=0.002) # Evaluate the fit at the rotated-y reference points wavereference = spl(yrev) # wavereference now contains the wavelengths corresponding to regularly-sampled ycenrot, create the model wavemodel = models.Tabular1D(lookup_table=wavereference, points=yrev, name='waveref', bounds_error=False, fill_value=np.nan) # Now construct the inverse spectral transform. # First we need to create a spline going from wavereference -> ycenrot spl2 = UnivariateSpline(wavereference[::-1], ycenrot, s=0.002) # Make a uniform grid of wavelength points from min to max, sampled according # to the minimum delta in the input table dw = np.amin(np.absolute(np.diff(wavereference))) wmin = np.amin(wavereference) wmax = np.amax(wavereference) wgrid = np.arange(wmin, wmax, dw) # Evaluate the rotated y locations of the grid ygrid = spl2(wgrid) # ygrid now contains the rotated y pixel locations corresponding to # regularly-sampled wavelengths, create the model wavemodel.inverse = models.Tabular1D(lookup_table=ygrid, points=wgrid, name='waverefinv', bounds_error=False, fill_value=np.nan) # Wavelength barycentric correction 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) wavemodel = wavemodel | velocity_corr log.info("Applied Barycentric velocity correction : {}".format( velocity_corr[1].amplitude.value)) # Construct the full distortion model (xsub,ysub -> v2,v3,wavelength) lrs_wav_model = xysubtoxyrot | models.Mapping([1], n_inputs=2) | wavemodel dettotel = models.Mapping((0, 1, 0, 1)) | xysubtov2v3 & lrs_wav_model # Construct the inverse distortion model (v2,v3,wavelength -> xsub,ysub) # Transform to get xrot from v2,v3 v2v3toxrot = subarray_dist.inverse | xysubtoxyrot | models.Mapping( [0], n_inputs=2) # wavemodel.inverse gives yrot from wavelength # v2,v3,lambda -> xrot,yrot xform1 = v2v3toxrot & wavemodel.inverse dettotel.inverse = xform1 | xysubtoxyrot.inverse # Bounding box is the subarray bounding box, because we're assuming subarray coordinates passed in dettotel.bounding_box = bb_sub[::-1] return dettotel
astropy_models.Sky2Pix_ZenithalPerspective(mu=1.5, gamma=15.0), # astropy.modeling.rotations astropy_models.EulerAngleRotation(23, 14, 2.3, axes_order="xzx"), astropy_models.RotateCelestial2Native(5.63, -72.5, 180), astropy_models.RotateCelestial2Native(5.63 * u.deg, -72.5 * u.deg, 180 * u.deg), astropy_models.RotateNative2Celestial(5.63, -72.5, 180), astropy_models.RotateNative2Celestial(5.63 * u.deg, -72.5 * u.deg, 180 * u.deg), astropy_models.Rotation2D(angle=1.51), astropy_models.RotationSequence3D([1.2, 2.3, 3.4, .3], "xyzx"), astropy_models.SphericalRotationSequence([1.2, 2.3, 3.4, .3], "xyzy"), # astropy.modeling.tabular astropy_models.Tabular1D(points=np.arange(0, 5), lookup_table=[1., 10, 2, 45, -3]), astropy_models.Tabular1D(points=np.arange(0, 5) * u.pix, lookup_table=[1., 10, 2, 45, -3] * u.nm), astropy_models.Tabular2D( points=([1, 2, 3], [1, 2, 3]), lookup_table=np.arange(0, 9).reshape(3, 3), bounds_error=False, fill_value=None, method="nearest", ), astropy_models.Tabular2D( points=([1, 2, 3], [1, 2, 3]) * u.pix, lookup_table=np.arange(0, 9).reshape(3, 3) * u.nm, bounds_error=False, fill_value=None, method="nearest",