/** Create a simple WCS given a scaler, CoordinateSystem and Projection. */ public WCS(CoordinateSystem csys, Projection proj, Scaler scale) throws TransformationException { this.csys = csys; this.proj = proj; this.scale = scale; add(csys.getSphereDistorter()); add(csys.getRotater()); add(proj.getRotater()); add(proj.getProjecter()); add(proj.getDistorter()); add(scale); setWCSScale(scale); }
/** * Write FITS WCS keywords given key values. Only relatively simple WCSs are handled here. We * assume we are dealing with axes 1 and 2. * * @param h The header to be updated. * @param s A Scaler giving the transformation between standard projection coordinates and * pixel/device coordinates. * @param projString A three character string giving the projection used. Supported projections * are: "Tan", "Sin", "Ait", "Car", "Zea". * @param coordString A string giving the coordinate system used. The first character gives the * general frame. For most frames the remainder of the string gives the equinox of the * coordinate system. E.g., J2000, B1950, Galactic, "E2000", "H2020.10375". */ public void updateHeader( Header h, Scaler s, double[] crval, String projString, String coordString) throws Exception { if (proj.isFixedProjection()) { h.addValue("CRVAL1", toDegrees(proj.getReferencePoint()[0]), "Fixed reference center"); h.addValue("CRVAL2", toDegrees(proj.getReferencePoint()[1]), "Fixed reference center"); } else { h.addValue("CRVAL1", crval[0], "Reference longitude"); h.addValue("CRVAL2", crval[1], "Reference latitude"); } coordString = coordString.toUpperCase(); String[] prefixes = new String[2]; char c = coordString.charAt(0); if (c == 'J' || c == 'I') { h.addValue("RADESYS", "FK5", "Coordinate system"); prefixes[0] = "RA--"; prefixes[1] = "DEC-"; } else if (c == 'B') { h.addValue("RADESYS", "FK4", "Coordinate system"); prefixes[0] = "RA--"; prefixes[1] = "DEC-"; } else { prefixes[0] = c + "LON"; prefixes[1] = c + "LAT"; } if (c != 'G' && c != 'I') { try { double equinox = Double.parseDouble(coordString.substring(1)); h.addValue("EQUINOX", equinox, "Epoch of the equinox"); } catch (Exception e) { // Couldn't parse out the equinox } } if (c == 'I') { h.addValue("EQUINOX", 2000, "ICRS coordinates"); } String upProj = projString.toUpperCase(); h.addValue("CTYPE1", prefixes[0] + "-" + upProj, "Coordinates -- projection"); h.addValue("CTYPE2", prefixes[1] + "-" + upProj, "Coordinates -- projection"); // Note that the scaler transforms from the standard projection // coordinates to the pixel coordinates. // P = P0 + M X where X is the standard coordinates and P is the // pixel coordinates. So the reference pixels are just the constants // in the scaler. // Remember that FITS pixels are offset by 0.5 from 0 offset pixels. h.addValue("CRPIX1", s.x0 + 0.5, "X reference pixel"); h.addValue("CRPIX2", s.y0 + 0.5, "Y reference pixel"); // Remember that the FITS values are of the form // X = M(P-P0) // so we'll need to invert the scaler. // // Do we need a matrix? if (abs(s.a01) < 1.e-14 && abs(s.a10) < 1.e-14) { // No cross terms, so we'll just use CDELTs h.addValue("CDELT1", toDegrees(1 / s.a00), "X scale"); h.addValue("CDELT2", toDegrees(1 / s.a11), "Y scale"); } else { // We have cross terms. It's simplest // just to use the CD matrix and not worry about // normalization. First invert the matrix to get // the transformation in the direction that FITS uses. Scaler rev = s.inverse(); h.addValue("CD1_1", toDegrees(rev.a00), "Matrix element"); h.addValue("CD1_2", toDegrees(rev.a01), "Matrix element"); h.addValue("CD2_1", toDegrees(rev.a10), "Matrix element"); h.addValue("CD2_2", toDegrees(rev.a11), "Matrix element"); } }
/** Handle the NEAT special projection */ private void doNeatWCS() throws TransformationException { // The NEAT transformation from the standard spherical system // includes: // Transformation to J2000 spherical coordinates (a null operation) // A tangent plane projection to the standard plane // A scaler transformation to corrected pixel coordinates. // A distorter to distorted pixel coordinates // A scaler transformation of distorted coordinates to actual pixels CoordinateSystem csys = CoordinateSystem.factory("J2000"); this.csys = csys; // The RA0/DEC0 pair are the actual center of the projection. double cv1 = toRadians(h.getDoubleValue("RA0")); double cv2 = toRadians(h.getDoubleValue("DEC0")); wcsKeys.put("CRVAL1", toDegrees(cv1)); wcsKeys.put("CRVAL2", toDegrees(cv2)); Projection proj = new Projection("Tan", new double[] {cv1, cv2}); this.proj = proj; double cd1 = toRadians(h.getDoubleValue("CDELT1")); double cd2 = toRadians(h.getDoubleValue("CDELT2")); wcsKeys.put("CDELT1", toDegrees(cd1)); wcsKeys.put("CDELT2", toDegrees(cd2)); wcsScale = abs(cd1); double cp1 = h.getDoubleValue("CRPIX1"); double cp2 = h.getDoubleValue("CRPIX2"); wcsKeys.put("CPRIX1", cp1); wcsKeys.put("CRPIX2", cp2); wcsKeys.put("CTYPE1", "RA---XTN"); wcsKeys.put("CTYPE2", "DEC--XTN"); Scaler s1 = new Scaler(0., 0., -1 / cd1, 0, 0, -1 / cd2); // Note that the the A0,A1,A2, B0,B1,B2 rotation // is relative to the original pixel values, so // we need to put this in the secondary scaler. // double x0 = h.getDoubleValue("X0"); double y0 = h.getDoubleValue("Y0"); Distorter dis = new skyview.geometry.distorter.Neat( h.getDoubleValue("RADIAL"), h.getDoubleValue("XRADIAL"), h.getDoubleValue("YRADIAL")); double a0 = h.getDoubleValue("A0"); double a1 = h.getDoubleValue("A1"); double a2 = h.getDoubleValue("A2"); double b0 = h.getDoubleValue("B0"); double b1 = h.getDoubleValue("B1"); double b2 = h.getDoubleValue("B2"); // The reference pixel is to be computed in the distorted frame. double[] cpix = new double[] {cp1, cp2}; double[] cout = new double[2]; dis.transform(cpix, cout); Scaler s2 = new Scaler( cout[0] - a0 - a1 * x0 - a2 * y0, cout[1] - b0 - b2 * x0 - b1 * y0, -(1 + a1), -a2, -b2, -(1 + b1)); wcsKeys.put("A0", a0); wcsKeys.put("A1", a1); wcsKeys.put("A2", a2); wcsKeys.put("B0", b0); wcsKeys.put("B1", b1); wcsKeys.put("B2", b2); this.distort = dis; add(csys.getSphereDistorter()); add(csys.getRotater()); add(proj.getRotater()); add(proj.getProjecter()); this.scale = s1; // Note that s1 is defined from the projection plane to the pixels coordinates, // so we don't need to invert it. // // But the second scaler, s2, used in the NEAT correction // is defined in the direction from pixels to sphere, so we // need to take its inverse. this.scale = this.scale.add(s2.inverse()); add(this.scale); add(dis); }
/** Handle a DSS projection */ private void doDSSWCS() throws TransformationException { double plateRA = h.getDoubleValue("PLTRAH") + h.getDoubleValue("PLTRAM") / 60 + h.getDoubleValue("PLTRAS") / 3600; plateRA = toRadians(15 * plateRA); wcsKeys.put("PLTRAH", h.getDoubleValue("PLTRAH")); wcsKeys.put("PLTRAM", h.getDoubleValue("PLTRAM")); wcsKeys.put("PLTRAS", h.getDoubleValue("PLTRAS")); double plateDec = h.getDoubleValue("PLTDECD") + h.getDoubleValue("PLTDECM") / 60 + h.getDoubleValue("PLTDECS") / 3600; plateDec = toRadians(plateDec); if (h.getStringValue("PLTDECSN").substring(0, 1).equals("-")) { plateDec = -plateDec; } wcsKeys.put("PLTDECD", h.getDoubleValue("PLTDECD")); wcsKeys.put("PLTDECM", h.getDoubleValue("PLTDECM")); wcsKeys.put("PLTDECS", h.getDoubleValue("PLTDECS")); wcsKeys.put("PLTDECSN", h.getStringValue("PLTDECSN")); double plateScale = h.getDoubleValue("PLTSCALE"); double xPixelSize = h.getDoubleValue("XPIXELSZ"); double yPixelSize = h.getDoubleValue("YPIXELSZ"); wcsKeys.put("PLTSCALE", plateScale); wcsKeys.put("XPIXELSZ", xPixelSize); wcsKeys.put("YPIXELSZ", yPixelSize); double[] xCoeff = new double[20]; double[] yCoeff = new double[20]; for (int i = 1; i <= 20; i += 1) { xCoeff[i - 1] = h.getDoubleValue("AMDX" + i); yCoeff[i - 1] = h.getDoubleValue("AMDY" + i); wcsKeys.put("AMDX" + i, xCoeff[i - 1]); wcsKeys.put("AMDY" + i, yCoeff[i - 1]); } double[] ppo = new double[6]; for (int i = 1; i <= 6; i += 1) { ppo[i - 1] = h.getDoubleValue("PPO" + i); wcsKeys.put("PPO" + i, ppo[i - 1]); } double plateCenterX = ppo[2]; double plateCenterY = ppo[5]; double cdelt1 = -plateScale / 1000 * xPixelSize / 3600; double cdelt2 = plateScale / 1000 * yPixelSize / 3600; wcsScale = abs(cdelt1); // This gives cdelts in degrees per pixel. // CNPIX pixels use a have the first pixel going from 1 - 2 so they are // off by 0.5 from FITS (which in turn is offset by 0.5 from the internal // scaling, but we handle that elsewhere). double crpix1 = plateCenterX / xPixelSize - h.getDoubleValue("CNPIX1", 0) - 0.5; double crpix2 = plateCenterY / yPixelSize - h.getDoubleValue("CNPIX2", 0) - 0.5; wcsKeys.put("CNPIX1", h.getDoubleValue("CNPIX1", 0)); wcsKeys.put("CNPIX2", h.getDoubleValue("CNPIX2", 0)); Projection proj = new Projection("Tan", new double[] {plateRA, plateDec}); this.proj = proj; CoordinateSystem coords = CoordinateSystem.factory("J2000"); this.csys = coords; cdelt1 = toRadians(cdelt1); cdelt2 = toRadians(cdelt2); Scaler s = new Scaler(-cdelt1 * crpix1, -cdelt2 * crpix2, cdelt1, 0, 0, cdelt2); // Got the transformers ready. Add them in properly. add(coords.getSphereDistorter()); add(coords.getRotater()); add(proj.getRotater()); add(proj.getProjecter()); this.distort = new skyview.geometry.distorter.DSS( plateRA, plateDec, xPixelSize, yPixelSize, plateScale, ppo, xCoeff, yCoeff); add(this.distort); this.scale = s.inverse(); add(this.scale); }
private void extractProjection() throws TransformationException { Projection proj = null; Scaler ncpScale = null; String lonType = h.getStringValue("CTYPE" + lonAxis).substring(5, 8); String latType = h.getStringValue("CTYPE" + latAxis).substring(5, 8); if (!lonType.equals(latType)) { throw new TransformationException( "Inconsistent projection in FITS header: " + lonType + "," + latType); } if (lonType.equals("AIT")) { proj = new Projection("Ait"); } else if (lonType.equals("CAR")) { proj = new Projection("Car"); // Allow non-central latitudes for the Cartesian projection. try { double lon = h.getDoubleValue("CRVAL" + lonAxis); if (lon != 0) { proj.setReference(toRadians(lon), 0); } } catch (Exception e) { System.err.println("Unable to read reference longitude in Cartesian projection"); } } else if (lonType.equals("CSC")) { proj = new Projection("Csc"); } else if (lonType.equals("SFL") || lonType.equals("GLS")) { proj = new Projection("Sfl"); } else if (lonType.equals("TOA")) { proj = new Projection("Toa"); } else { double crval1 = h.getDoubleValue("CRVAL" + lonAxis, NaN); double crval2 = h.getDoubleValue("CRVAL" + latAxis, NaN); if (isNaN(crval1 + crval2)) { throw new TransformationException("Unable to find reference coordinates in FITS header"); } wcsKeys.put("CRVAL1", crval1); wcsKeys.put("CRVAL2", crval2); if (lonType.equals("TAN") || lonType.equals("SIN") || lonType.equals("ZEA") || lonType.equals("ARC") || lonType.equals("STG")) { String type = lonType.substring(0, 1) + lonType.substring(1, 3).toLowerCase(); proj = new Projection(type, new double[] {toRadians(crval1), toRadians(crval2)}); double lonpole = h.getDoubleValue("LONPOLE", NaN); if (!isNaN(lonpole)) { wcsKeys.put("LONPOLE", lonpole); } // ---- Following is probably erroneous ----- // The WCS standard indicates that the default LONPOLE for // a projection is 180 when the CRVAL latitude is less than // the native latitude of the projection (90 degrees for the projections // handled here) and 0 otherwise. This means that for a projection // around the pole the default lonpole is 0. Some data (the SFD surveys) // seem to require that we do a rotation of 180 degrees to accommodate // this. However we do not implement this unless the LONPOLE is // explicitly given since this seems non-intuitive to me and I suspect // that a user who is not careful enough to specify a LONPOLE in this // situation probably doesn't understand what is going on anyway. // ----- We now assume that our standard processing of // ----- zenithal projections handles lonpole of 180 and that // ----- this is the default for all zenithal images. // ----- Previously we assumed that we were using lonPole=0 at // ----- at the poles, but we weren't.... // if (!isNaN(lonpole)) { double lonDefault = 180; if (lonpole != lonDefault) { Rotater r = proj.getRotater(); Rotater lon = new Rotater("Z", toRadians(lonpole - lonDefault), 0, 0); if (r != null) { proj.setRotater(r.add(lon)); } else { proj.setRotater(lon); } } } } else if (lonType.equals("NCP")) { // Sin projection with projection centered at pole. double[] xproj = new double[] {toRadians(crval1), PI / 2}; if (crval2 < 0) { xproj[1] = -xproj[1]; } double poleOffset = sin(xproj[1] - toRadians(crval2)); // Have we handled South pole here? proj = new Projection("Sin", xproj); // NCP scales the Y-axis to accommodate the distortion of the SIN projection away // from the pole. ncpScale = new Scaler(0, poleOffset, 1, 0, 0, 1); ncpScale = ncpScale.add(new Scaler(0., 0., 1, 0, 0, 1 / sin(toRadians(crval2)))); } else { throw new TransformationException("Unsupported projection type:" + lonType); } } this.proj = proj; if (ncpScale != null) { this.scale = ncpScale; } add(proj.getRotater()); add(proj.getProjecter()); add(ncpScale); // Ignored if null }