def std_raw(self, item, dataId): exposure = super(LsstSimMapper, self).std_raw(item, dataId) md = exposure.getMetadata() if md.exists("VERSION") and md.getInt("VERSION") < 16952: # Precess crval of WCS from date of observation to J2000 epoch = exposure.getInfo().getVisitInfo().getDate().get( dafBase.DateTime.EPOCH) if math.isnan(epoch): raise RuntimeError("Date not found in raw exposure %s" % (dataId, )) wcs = exposure.getWcs() origin = wcs.getSkyOrigin() refCoord = afwCoord.Fk5Coord(origin.getLongitude(), origin.getLatitude(), epoch) newRefCoord = refCoord.precess(2000.) crval = afwGeom.PointD() crval.setX(newRefCoord.getRa().asDegrees()) crval.setY(newRefCoord.getDec().asDegrees()) wcs = afwImage.Wcs(crval, wcs.getPixelOrigin(), wcs.getCDMatrix()) exposure.setWcs(wcs) return exposure
def testGalactic(self): """Verify Galactic coordinate transforms""" # Try converting Sag-A to galactic and make sure we get the right answer # Sagittarius A (very nearly the galactic center) sagAKnownEqu = afwCoord.Fk5Coord("17:45:40.04","-29:00:28.1") sagAKnownGal = afwCoord.GalacticCoord(359.94432 * afwGeom.degrees, -0.04619 * afwGeom.degrees) sagAGal = sagAKnownEqu.toGalactic() s = ("Galactic (Sag-A): (transformed) %.5f %.5f (known) %.5f %.5f\n" % (sagAGal.getL().asDegrees(), sagAGal.getB().asDegrees(), sagAKnownGal.getL().asDegrees(), sagAKnownGal.getB().asDegrees())) print s # verify ... to 4 places, the accuracy of the galactic pole in Fk5 self.assertAlmostEqual(sagAGal.getL().asDegrees(), sagAKnownGal.getL().asDegrees(), 4) self.assertAlmostEqual(sagAGal.getB().asDegrees(), sagAKnownGal.getB().asDegrees(), 4) # make sure it transforms back ... to machine precision self.assertAlmostEqual(sagAGal.toFk5().getRa().asDegrees(), sagAKnownEqu.getRa().asDegrees(), 12) self.assertAlmostEqual(sagAGal.toFk5().getDec().asDegrees(), sagAKnownEqu.getDec().asDegrees(), 12)
def testEcliptic(self): """Verify Ecliptic Coordinate Transforms""" # Pollux alpha, delta = "07:45:18.946", "28:01:34.26" # known ecliptic coords (example from Meeus, Astro algorithms, pg 95) lamb, beta = 113.215629, 6.684170 # Try converting pollux Ra,Dec to ecliptic and check that we get the right answer polluxEqu = afwCoord.Fk5Coord(alpha, delta) polluxEcl = polluxEqu.toEcliptic() s = ("Ecliptic (Pollux): ", polluxEcl.getLambda().asDegrees(), polluxEcl.getBeta().asDegrees(), lamb, beta) print(s) # verify to precision of known values self.assertAlmostEqual(polluxEcl.getLambda().asDegrees(), lamb, 6) self.assertAlmostEqual(polluxEcl.getBeta().asDegrees(), beta, 6) # make sure it transforms back (machine precision) self.assertAlmostEqual(polluxEcl.toFk5().getRa().asDegrees(), polluxEqu.getRa().asDegrees(), 13) self.assertAlmostEqual(polluxEcl.toFk5().getDec().asDegrees(), polluxEqu.getDec().asDegrees(), 13)
def testNames(self): """Test the names of the Coords (Useful with Point2D form)""" # verify that each coordinate type can tell you what its components are # called. radec1, known1 = afwCoord.Coord(self.ra, self.dec).getCoordNames(), \ ["RA", "Dec"] radec3, known3 = afwCoord.Fk5Coord(self.ra, self.dec).getCoordNames(), \ ["RA", "Dec"] radec4, known4 = \ afwCoord.IcrsCoord(self.ra, self.dec).getCoordNames(), \ ["RA", "Dec"] lb, known5 = \ afwCoord.GalacticCoord(self.ra, self.dec).getCoordNames(), \ ["L", "B"] lambet, known6 = \ afwCoord.EclipticCoord(self.ra, self.dec).getCoordNames(), \ ["Lambda", "Beta"] observatory = afwCoord.Observatory(0 * afwGeom.degrees, 0 * afwGeom.degrees, 0) altaz = afwCoord.TopocentricCoord(self.ra, self.dec, 2000.0, observatory).getCoordNames() known7 = ["Az", "Alt"] pairs = [ [radec1, known1], [radec3, known3], [radec4, known4], [lb, known5], [lambet, known6], [altaz, known7], ] for pair, known in (pairs): self.assertEqual(pair[0], known[0]) self.assertEqual(pair[1], known[1])
def testPrecess(self): """Test precession calculations in different coordinate systems""" # Try precessing in the various coordinate systems, and check the results. ### Fk5 ### # example 21.b Meeus, pg 135, for Alpha Persei ... with proper motion alpha0, delta0 = "2:44:11.986", "49:13:42.48" # proper motions per year dAlphaS, dDeltaAS = 0.03425, -0.0895 # Angle/yr dAlpha, dDelta = (dAlphaS*15.) * afwGeom.arcseconds, (dDeltaAS) * afwGeom.arcseconds # get for 2028, Nov 13.19 epoch = dafBase.DateTime(2028, 11, 13, 4, 33, 36, dafBase.DateTime.TAI).get(dafBase.DateTime.EPOCH) # the known final answer # - actually 41.547214, 49.348483 (suspect precision error in Meeus) alphaKnown, deltaKnown = 41.547236, 49.348488 alphaPer0 = afwCoord.Fk5Coord(alpha0, delta0) alpha1 = alphaPer0.getRa() + dAlpha*(epoch - 2000.0) delta1 = alphaPer0.getDec() + dDelta*(epoch - 2000.0) alphaPer = afwCoord.Fk5Coord(alpha1, delta1).precess(epoch) print("Precession (Alpha-Per): %.6f %.6f (known) %.6f %.6f" % (alphaPer.getRa().asDegrees(), alphaPer.getDec().asDegrees(), alphaKnown, deltaKnown)) # precision 6 (with 1 digit fudged in the 'known' answers) self.assertAlmostEqual(alphaPer.getRa().asDegrees(), alphaKnown, 6) self.assertAlmostEqual(alphaPer.getDec().asDegrees(), deltaKnown, 6) # verify that toFk5(epoch) also works as precess alphaPer2 = afwCoord.Fk5Coord(alpha1, delta1).toFk5(epoch) self.assertEqual(alphaPer[0], alphaPer2[0]) self.assertEqual(alphaPer[1], alphaPer2[1]) # verify that convert(FK5, epoch) also works as precess alphaPer3 = afwCoord.Fk5Coord(alpha1, delta1).convert(afwCoord.FK5, epoch) self.assertEqual(alphaPer[0], alphaPer3[0]) self.assertEqual(alphaPer[1], alphaPer3[1]) ### Galactic ### # make sure Galactic throws an exception. As there's no epoch, there's no precess() method. gal = afwCoord.GalacticCoord(self.l * afwGeom.degrees, self.b * afwGeom.degrees) epochNew = 2010.0 self.assertRaises(AttributeError, lambda: gal.precess(epochNew)) ### Icrs ### # make sure Icrs throws an exception. As there's no epoch, there's no precess() method. icrs = afwCoord.IcrsCoord(self.l * afwGeom.degrees, self.b * afwGeom.degrees) epochNew = 2010.0 self.assertRaises(AttributeError, lambda: icrs.precess(epochNew)) ### Ecliptic ### # test for ecliptic with venus (example from meeus, pg 137) lamb2000, beta2000 = 149.48194, 1.76549 # known values for -214, June 30.0 # they're actually 118.704, 1.615, but I suspect discrepancy is a rounding error in Meeus # -- we use double precision, he carries 7 places only. # originally 214BC, but that broke the DateTime # It did work previously, so for the short term, I've taken the answer it # returns for 1920, and used that as the 'known answer' for future tests. #year = -214 #lamb214bc, beta214bc = 118.704, 1.606 year = 1920 lamb214bc, beta214bc = 148.37119237032144, 1.7610036104147864 venus2000 = afwCoord.EclipticCoord(lamb2000 * afwGeom.degrees, beta2000 * afwGeom.degrees, 2000.0) ep = dafBase.DateTime(year, 6, 30, 0, 0, 0, dafBase.DateTime.TAI).get(dafBase.DateTime.EPOCH) venus214bc = venus2000.precess(ep) print("Precession (Ecliptic, Venus): %.4f %.4f (known) %.4f %.4f" % (venus214bc.getLambda().asDegrees(), venus214bc.getBeta().asDegrees(), lamb214bc, beta214bc)) # 3 places precision (accuracy of our controls) self.assertAlmostEqual(venus214bc.getLambda().asDegrees(), lamb214bc, 3) self.assertAlmostEqual(venus214bc.getBeta().asDegrees(), beta214bc, 3) # verify that toEcliptic(ep) does the same as precess(ep) venus214bc2 = venus2000.toEcliptic(ep) self.assertEqual(venus214bc[0], venus214bc2[0]) self.assertEqual(venus214bc[1], venus214bc2[1]) # verify that convert(ECLIPTIC, ep) is the same as precess(ep) venus214bc3 = venus2000.convert(afwCoord.ECLIPTIC, ep) self.assertEqual(venus214bc[0], venus214bc3[0]) self.assertEqual(venus214bc[1], venus214bc3[1])
def readFits(fileName, hdu=0, flags=0): """Read a ds9 region file, returning a ObjectMaskCatalog object This method is called "readFits" to fool the butler. The corresponding mapper entry looks like brightObjectMask: { template: "deepCoadd/BrightObjectMasks/%(tract)d/BrightObjectMask-%(tract)d-%(patch)s-%(filter)s.reg" python: "lsst.obs.subaru.objectMasks.ObjectMaskCatalog" persistable: "PurePythonClass" storage: "FitsCatalogStorage" } and this is the only way I know to get it to read a random file type, in this case a ds9 region file. This method expects to find files named as BrightObjectMask-%(tract)d-%(patch)s-%(filter)s.reg The files should be structured as follows: # Description of catalogue as a comment # CATALOG: catalog-id-string # TRACT: 0 # PATCH: 5,4 # FILTER: HSC-I wcs; fk5 circle(RA, DEC, RADIUS) # ID: 1, mag: 12.34 box(RA, DEC, XSIZE, YSIZE, THETA) # ID: 2, mag: 23.45 ... The ", mag: XX.YY" is optional The commented lines must be present, with the relevant fields such as tract patch and filter filled in. The coordinate system must be listed as above. Each patch is specified as a box or circle, with RA, DEC, and dimensions specified in decimal degrees (with or without an explicit "d"). Only (axis-aligned) boxes and circles are currently supported as region definitions. """ log = Log.getLogger("ObjectMaskCatalog") brightObjects = ObjectMaskCatalog() checkedWcsIsFk5 = False NaN = float("NaN")*afwGeom.degrees nFormatError = 0 # number of format errors seen with open(fileName) as fd: for lineNo, line in enumerate(fd.readlines(), 1): line = line.rstrip() if re.search(r"^\s*#", line): # # Parse any line of the form "# key : value" and put them into the metadata. # # The medatdata values must be defined as outlined in the above docstring # # The value of these three keys will be checked, # so get them right! # mat = re.search(r"^\s*#\s*([a-zA-Z][a-zA-Z0-9_]+)\s*:\s*(.*)", line) if mat: key, value = mat.group(1).lower(), mat.group(2) if key == "tract": value = int(value) brightObjects.table.getMetadata().set(key, value) line = re.sub(r"^\s*#.*", "", line) if not line: continue if re.search(r"^\s*wcs\s*;\s*fk5\s*$", line, re.IGNORECASE): checkedWcsIsFk5 = True continue # This regular expression parses the regions file for each region to be masked, # with the format as specified in the above docstring. mat = re.search(r"^\s*(box|circle)" "(?:\s+|\s*\(\s*)" # open paren or space "(\d+(?:\.\d*)?([d]*))" "(?:\s+|\s*,\s*)" "([+-]?\d+(?:\.\d*)?)([d]*)" "(?:\s+|\s*,\s*)" "([+-]?\d+(?:\.\d*)?)([d]*)" "(?:\s+|\s*,\s*)?" "(?:([+-]?\d+(?:\.\d*)?)([d]*)" "\s*,\s*" "([+-]?\d+(?:\.\d*)?)([d]*)" ")?" "(?:\s*|\s*\)\s*)" # close paren or space "\s*#\s*ID:\s*(\d+)" # start comment "(?:\s*,\s*mag:\s*(\d+\.\d*))?" "\s*$", line) if mat: _type, ra, raUnit, dec, decUnit, \ param1, param1Unit, param2, param2Unit, param3, param3Unit, \ _id, mag = mat.groups() _id = int(_id) if mag is None: mag = NaN else: mag = float(mag) ra = convertToAngle(ra, raUnit, "ra", fileName, lineNo) dec = convertToAngle(dec, decUnit, "dec", fileName, lineNo) radius = NaN width = NaN height = NaN angle = NaN if _type == "box": width = convertToAngle(param1, param1Unit, "width", fileName, lineNo) height = convertToAngle(param2, param2Unit, "height", fileName, lineNo) angle = convertToAngle(param3, param3Unit, "angle", fileName, lineNo) if angle != 0.0: log.warn("Rotated boxes are not supported: \"%s\" at %s:%d" % ( line, fileName, lineNo)) nFormatError += 1 elif _type == "circle": radius = convertToAngle(param1, param1Unit, "radius", fileName, lineNo) if not (param2 is None and param3 is None): log.warn("Extra parameters for circle: \"%s\" at %s:%d" % ( line, fileName, lineNo)) nFormatError += 1 rec = brightObjects.addNew() # N.b. rec["coord"] = Coord is not supported, so we have to use the setter rec["type"] = _type rec["id"] = _id rec["mag"] = mag rec.setCoord(afwCoord.Fk5Coord(ra, dec)) rec["angle"] = angle rec["height"] = height rec["width"] = width rec["radius"] = radius else: log.warn("Unexpected line \"%s\" at %s:%d" % (line, fileName, lineNo)) nFormatError += 1 if nFormatError > 0: raise RuntimeError("Saw %d formatting errors in %s" % (nFormatError, fileName)) if not checkedWcsIsFk5: raise RuntimeError("Expected to see a line specifying an fk5 wcs in %s" % fileName) # This makes the deep copy contiguous in memory so that a ColumnView can be exposed to Numpy brightObjects._catalog = brightObjects._catalog.copy(True) return brightObjects
def main(visit, rerun, out, ccd="0..103", root=None, point=None, minchips=1, showLabels=False, boresightOnly=False, edge=0.9): pcen, rad, pcenVisit = None, None, None if point: pcenArgs = map(float, point.split(":")) if len(pcenArgs) == 2: pcenVisit, rad = pcenArgs elif len(pcenArgs) == 3: cx, cy, rad = pcenArgs pcen = afwCoord.Fk5Coord(afwGeom.Point2D(cx, cy)) else: raise ValueError( "Unable to parse center 'point', must be ra:dec:rad or visit:rad" ) print visit, rerun butler = hscUtil.getButler(rerun, root=root) visitWidths = [] visitHeights = [] dataIds = visitCcdToDataId(visit, ccd) corners = {} boresights = {} for dataId in dataIds: visit, ccd = dataId['visit'], dataId['ccd'] if boresightOnly and visit in boresights: continue print "Getting ", visit, ccd try: dataRef = hscButler.getDataRef(butler, dataId) except Exception, e: print "getDataRef() failed for ", visit, ccd, str(e) continue calfile = dataRef.get("calexp_filename", immediate=True)[0] noCal = False if not os.path.exists(calfile): print calfile + " missing." noCal = True else: # get the calexp calmd = dataRef.get("calexp_md", immediate=True) wcs = afwImage.makeWcs(calmd) w, h = calmd.get("NAXIS1"), calmd.get("NAXIS2") boresights[dataId['visit']] = [ calmd.get("RA2000"), calmd.get("DEC2000") ] # get the corners in RA,Dec ll = wcs.pixelToSky(afwGeom.Point2D(1, 1)).toFk5() lr = wcs.pixelToSky(afwGeom.Point2D(w, 1)).toFk5() ul = wcs.pixelToSky(afwGeom.Point2D(1, h)).toFk5() ur = wcs.pixelToSky(afwGeom.Point2D(w, h)).toFk5() if noCal: rawfile = dataRef.get("raw_filename", immediate=True)[0] if not os.path.exists(rawfile): print rawfile + " missing too. continuing." continue else: rawmd = dataRef.get("raw_md", immediate=True) raS, decS = rawmd.get("RA2000"), rawmd.get("DEC2000") boresights[dataId['visit']] = [raS, decS] #ra, dec = c.getRa().asDegrees(), c.getDec().asDegrees() crv1, crv2 = map(float, [rawmd.get("CRVAL1"), rawmd.get("CRVAL2")]) crp1, crp2 = map(float, [rawmd.get("CRPIX1"), rawmd.get("CRPIX2")]) useDelt = True try: cdelt1, cdelt2 = map( float, [rawmd.get("CDELT1"), rawmd.get("CDELT1")]) except: useDelt = False if not useDelt: cd11, cd12, cd21, cd22 = map(float, [ rawmd.get("CD1_1"), rawmd.get("CD1_2"), rawmd.get("CD2_1"), rawmd.get("CD2_2") ]) cdelt1 = 3600 * (cd11 + cd12) / 206265 cdelt2 = 3600 * (cd21 + cd22) / 206265 #cdelt1, cdelt2 = 1.0, 1.0 nx, ny = map(int, [rawmd.get("NAXIS1"), rawmd.get("NAXIS2")]) def trans(x, y): ra = crv1 + (crp1 - x) * cdelt1 dec = crv2 + (crp2 - y) * cdelt2 return ra, dec ll = afwCoord.Fk5Coord(afwGeom.Point2D(*trans(1, 1))) lr = afwCoord.Fk5Coord(afwGeom.Point2D(*trans(nx, 1))) ul = afwCoord.Fk5Coord(afwGeom.Point2D(*trans(1, ny))) ur = afwCoord.Fk5Coord(afwGeom.Point2D(*trans(nx, ny))) if pcenVisit and not pcen: if int(pcenVisit) == int(visit): cx, cy = boresights[pcenVisit] pcen = afwCoord.Fk5Coord(cx, cy) if visit not in corners: corners[visit] = [] corners[visit].append([ll, lr, ur, ul, ccd])
corners[visit].append([ll, lr, ur, ul, ccd]) ccdWidths = [] ccdHeights = [] for visit, cornerList in corners.items(): for bbox in cornerList: ll, lr, ul, ur, ccd = bbox keep = True if pcen: # if only doing boresights, don't bother checking the corners if boresightOnly: cx, cy = boresights[visit] sep = pcen.angularSeparation(afwCoord.Fk5Coord(cx, cy)) if sep.asDegrees() > rad: keep = False else: for c in ll, lr, ul, ur: sep = pcen.angularSeparation(c) if sep.asDegrees() > rad: keep = False if keep: ccdWidths.append(ll.getRa().asDegrees() - lr.getRa().asDegrees()) ccdHeights.append(ll.getDec().asDegrees() - ul.getDec().asDegrees()) else: if boresights:
def readFits(fileName, hdu=0, flags=0): """Read a ds9 region file, returning a ObjectMaskCatalog object This method is called "readFits" to fool the butler. The corresponding mapper entry looks like brightObjectMask: { template: "deepCoadd/BrightObjectMasks/%(tract)d/BrightObjectMask-%(tract)d-%(patch)s-%(filter)s.reg" python: "lsst.obs.subaru.objectMasks.ObjectMaskCatalog" persistable: "PurePythonClass" storage: "FitsCatalogStorage" } and this is the only way I know to get it to read a random file type, in this case a ds9 region file This method expects to find files named as BrightObjectMask-%(tract)d-%(patch)s-%(filter)s.reg The files should be structured as follows: # Description of catalogue as a comment # CATALOG: catalog-id-string # TRACT: 0 # PATCH: 5,4 # FILTER: HSC-I wcs; fk5 circle(RA, DEC, RADIUS) # ID: 1 The commented lines must be present, with the relevant fields such as tract patch and filter filled in. The coordinate system must be listed as above. Each patch is specified as a circle, with an RA, DEC, and Radius specified in decimal degrees. Only circles are supported as region definitions currently. """ log = pexLog.getDefaultLog().createChildLog("ObjectMaskCatalog") brightObjects = ObjectMaskCatalog() checkedWcsIsFk5 = False with open(fileName) as fd: for lineNo, line in enumerate(fd.readlines(), 1): line = line.rstrip() if re.search(r"^\s*#", line): # # Parse any line of the form "# key : value" and put them into the metadata. # # The medatdata values must be defined as outlined in the above docstring # # The value of these three keys will be checked, # so get them right! # mat = re.search( r"^\s*#\s*([a-zA-Z][a-zA-Z0-9_]+)\s*:\s*(.*)", line) if mat: key, value = mat.group(1).lower(), mat.group(2) if key == "tract": value = int(value) brightObjects.table.getMetadata().set(key, value) line = re.sub(r"^\s*#.*", "", line) if not line: continue if re.search(r"^\s*wcs\s*;\s*fk5\s*$", line, re.IGNORECASE): checkedWcsIsFk5 = True continue # This regular expression parses the regions file for each region to be masked, # with the format as specified in the above docstring. mat = re.search( r"^\s*circle(?:\s+|\s*\(\s*)" "(\d+(?:\.\d*)([d]*))" "(?:\s+|\s*,\s*)" "([+-]?\d+(?:\.\d*)([d]*))" "(?:\s+|\s*,\s*)" "(\d+(?:\.\d*))([d'\"]*)" "(?:\s*|\s*\)\s*)" "\s*#\s*ID:\s*(\d+)" "\s*$", line) if mat: ra, raUnit, dec, decUnit, radius, radiusUnit, _id = mat.groups( ) _id = int(_id) ra = convertToAngle(ra, raUnit, "ra", fileName, lineNo) dec = convertToAngle(dec, decUnit, "dec", fileName, lineNo) radius = convertToAngle(radius, radiusUnit, "radius", fileName, lineNo) rec = brightObjects.addNew() # N.b. rec["coord"] = Coord is not supported, so we have to use the setter rec["id"] = _id rec.setCoord(afwCoord.Fk5Coord(ra, dec)) rec["radius"] = radius else: log.warn("Unexpected line \"%s\" at %s:%d" % (line, fileName, lineNo)) if not checkedWcsIsFk5: raise RuntimeError("Expected to see a line specifying an fk5 wcs") # This makes the deep copy contiguous in memory so that a ColumnView can be exposed to Numpy brightObjects._catalog = brightObjects._catalog.copy(True) return brightObjects