def __init__(self, schema, refObjLoader=None, **kwds): r"""!Create the astrometric calibration task. Most arguments are simply passed onto pipe.base.Task. \param schema An lsst::afw::table::Schema used to create the output lsst.afw.table.SourceCatalog \param refObjLoader The AstrometryTask constructor requires a refObjLoader. In order to make this task retargettable for AstrometryTask it needs to take the same arguments. This argument will be ignored since it uses its own internal loader. \param **kwds keyword arguments to be passed to the lsst.pipe.base.task.Task constructor A centroid field "centroid.distorted" (used internally during the Task's operation) will be added to the schema. """ pipeBase.Task.__init__(self, **kwds) self.distortedName = "astrom_distorted" self.centroidXKey = schema.addField(self.distortedName + "_x", type="D", doc="centroid distorted for astrometry solver") self.centroidYKey = schema.addField(self.distortedName + "_y", type="D", doc="centroid distorted for astrometry solver") self.centroidXErrKey = schema.addField(self.distortedName + "_xErr", type="F", doc="centroid distorted err for astrometry solver") self.centroidYErrKey = schema.addField(self.distortedName + "_yErr", type="F", doc="centroid distorted err for astrometry solver") self.centroidFlagKey = schema.addField(self.distortedName + "_flag", type="Flag", doc="centroid distorted flag astrometry solver") self.centroidKey = Point2DKey(self.centroidXKey, self.centroidYKey) self.centroidErrKey = CovarianceMatrix2fKey((self.centroidXErrKey, self.centroidYErrKey)) # postpone making the solver subtask because it may not be needed and is expensive to create self.solver = None
def setUp(self): # Set up a Coadd with CoaddInputs tables that have blank filter # columns to be filled in by later test code. self.coadd = ExposureF(30, 90) # WCS is arbitrary, since it'll be the same for all images wcs = makeSkyWcs(crpix=Point2D(0, 0), crval=SpherePoint(45.0, 45.0, degrees), cdMatrix=makeCdMatrix(scale=0.17 * degrees)) self.coadd.setWcs(wcs) schema = ExposureCatalog.Table.makeMinimalSchema() self.filterKey = schema.addField("filter", type=str, doc="", size=16) weightKey = schema.addField("weight", type=float, doc="") # First input image covers the first 2/3, second covers the last 2/3, # so they overlap in the middle 1/3. inputs = ExposureCatalog(schema) self.input1 = inputs.addNew() self.input1.setId(1) self.input1.setBBox(Box2I(Point2I(0, 0), Point2I(29, 59))) self.input1.setWcs(wcs) self.input1.set(weightKey, 2.0) self.input2 = inputs.addNew() self.input2.setId(2) self.input2.setBBox(Box2I(Point2I(0, 30), Point2I(29, 89))) self.input2.setWcs(wcs) self.input2.set(weightKey, 3.0) # Use the same catalog for visits and CCDs since the algorithm we're # testing only cares about CCDs. self.coadd.getInfo().setCoaddInputs(CoaddInputs(inputs, inputs)) # Set up a catalog with centroids and a FilterFraction plugin. # We have one record in each region (first input only, both inputs, # second input only) schema = SourceCatalog.Table.makeMinimalSchema() centroidKey = Point2DKey.addFields(schema, "centroid", doc="position", unit="pixel") schema.getAliasMap().set("slot_Centroid", "centroid") self.plugin = FilterFractionPlugin( config=FilterFractionPlugin.ConfigClass(), schema=schema, name="subaru_FilterFraction", metadata=PropertyList()) catalog = SourceCatalog(schema) self.record1 = catalog.addNew() self.record1.set(centroidKey, Point2D(14.0, 14.0)) self.record12 = catalog.addNew() self.record12.set(centroidKey, Point2D(14.0, 44.0)) self.record2 = catalog.addNew() self.record2.set(centroidKey, Point2D(14.0, 74.0))
def plotStars(self, refCat, bbox=None): """Plot the centroids of reference objects, and the bounding box (if specified) """ import matplotlib.pyplot as plt if bbox is not None: cornerList = list(afwGeom.Box2D(bbox).getCorners()) cornerList.append(cornerList[0]) # show 4 sides of the box by going back to the beginning xc, yc = list(zip(*cornerList)) plt.plot(xc, yc, '-') centroidKey = Point2DKey(refCat.schema["centroid"]) centroidList = [rec.get(centroidKey) for rec in refCat] xp, yp = list(zip(*centroidList)) plt.plot(xp, yp, '.') plt.show()
def assertObjInBBox(self, refCat, bbox, wcs): """Assert that all reference objects are inside the specified pixel bounding box plus a margin @param[in] refCat reference object catalog, an lsst.afw.table.SimpleCatalog or compatible; the only fields read are "centroid_x/y" and "coord_ra/dec" @param[in] bbox pixel bounding box coordinates, an lsst.afw.geom.Box2I or Box2D; the supplied box is grown by self.config.pixelMargin before testing the stars @param[in] wcs WCS, an lsst.afw.image.Wcs """ bbox = afwGeom.Box2D(bbox) bbox.grow(self.config.pixelMargin) centroidKey = Point2DKey(refCat.schema["centroid"]) coordKey = CoordKey(refCat.schema["coord"]) for refObj in refCat: point = refObj.get(centroidKey) if not bbox.contains(point): coord = refObj.get(coordKey) self.fail("refObj at RA, Dec %0.3f, %0.3f point %s is not in bbox %s" % (coord[0].asDegrees(), coord[1].asDegrees(), point, bbox))
def displayAstrometry(refCat=None, sourceCat=None, distortedCentroidKey=None, bbox=None, exposure=None, matches=None, frame=1, title="", pause=True): """Show an astrometry debug image. Parameters ---------- refCat : `lsst.afw.table.SimpleCatalog` reference object catalog; must have fields "centroid_x" an "centroid_y" sourceCat : `lsst.afw.table.SourceCatalg` source catalog; must have field "slot_Centroid_x" and "slot_Centroid_y" distortedCentroidKey : `lsst.afw.table.Key` key for sourceCat with field to use for distorted positions exposure : `lsst.afw.image.Exposure` exposure to display bbox : `lsst.geom.Box2I` bounding box of exposure; Used if the exposure is `None` matches : `list` of `lsst.afw.table.ReferenceMatch` List of matched objects frame : `int` frame number for display title : `str` title for display pause : `bool` pause for inspection of display? This is done by dropping into pdb. Notes ----- - reference objects in refCat are shown as red X - sources in sourceCat are shown as green + - distorted sources in sourceCat (position given by distortedCentroidKey) are shown as green o - matches are shown as a yellow circle around the source and a yellow line connecting the reference object and source - if both exposure and bbox are `None`, no image is displayed """ disp = afwDisplay.getDisplay(frame=frame) if exposure is not None: disp.mtv(exposure, title=title) elif bbox is not None: disp.mtv(exposure=ExposureF(bbox), title=title) with disp.Buffering(): if refCat is not None: refCentroidKey = Point2DKey(refCat.schema["centroid"]) for refObj in refCat: rx, ry = refObj.get(refCentroidKey) disp.dot("x", rx, ry, size=10, ctype=afwDisplay.RED) if sourceCat is not None: sourceCentroidKey = Point2DKey(sourceCat.schema["slot_Centroid"]) for source in sourceCat: sx, sy = source.get(sourceCentroidKey) disp.dot("+", sx, sy, size=10, ctype=afwDisplay.GREEN) if distortedCentroidKey is not None: dx, dy = source.get(distortedCentroidKey) disp.dot("o", dx, dy, size=10, ctype=afwDisplay.GREEN) disp.line([(sx, sy), (dx, dy)], ctype=afwDisplay.GREEN) if matches is not None: refCentroidKey = Point2DKey(matches[0].first.schema["centroid"]) sourceCentroidKey = Point2DKey( matches[0].second.schema["slot_Centroid"]) radArr = np.ndarray(len(matches)) for i, m in enumerate(matches): refCentroid = m.first.get(refCentroidKey) sourceCentroid = m.second.get(sourceCentroidKey) radArr[i] = math.hypot(*(refCentroid - sourceCentroid)) sx, sy = sourceCentroid disp.dot("o", sx, sy, size=10, ctype=afwDisplay.YELLOW) disp.line([refCentroid, sourceCentroid], ctype=afwDisplay.YELLOW) print("<match radius> = %.4g +- %.4g [%d matches]" % (radArr.mean(), radArr.std(), len(matches))) if pause: print( "Dropping into debugger to allow inspection of display. Type 'continue' when done." ) import pdb pdb.set_trace()
def plotAstrometry(matches, refCat=None, sourceCat=None, refMarker="x", refColor="r", sourceMarker="+", sourceColor="g", matchColor="y"): """Plot reference objects, sources and matches Parameters ---------- matches : `list` of `lsst.afw.table.ReferenceMatch` list of matches refCat : `lsst.afw.table.SimpleCatalog` reference object catalog, or None to not plot reference objects sourceCat : `lsst.afw.table.SourceCatalog` source catalog, or None to not plot sources refMarker : `str` pyplot marker for reference objects refColor : `str` pyplot color for reference objects sourceMarker : `str` pyplot marker for sources sourceColor : `str` pyplot color for sources matchColor : `str` color for matches; can be a constant or a function taking one match and returning a string Notes ----- By default: - reference objects in refCat are shown as red X - sources in sourceCat are shown as green + - matches are shown as a yellow circle around the source and a line connecting the reference object to the source """ # delay importing plt to give users a chance to set the backend before calling this function import matplotlib.pyplot as plt refSchema = matches[0][0].schema refCentroidKey = Point2DKey(refSchema["centroid"]) srcSchema = matches[0][1].schema srcCentroidKey = Point2DKey(srcSchema["slot_Centroid"]) if refCat is not None: refXArr, refYArr = list( zip(*[ref.get(refCentroidKey) for ref in refCat])) plt.plot(refXArr, refYArr, marker=refMarker, color=refColor, linestyle="") if sourceCat is not None: srcXArr, srcYArr = list( zip(*[src.get(srcCentroidKey) for src in sourceCat])) plt.plot(srcXArr, srcYArr, marker=sourceMarker, color=sourceColor, linestyle="") def makeLineSegmentData(refXYArr, srcXYArr, colorArr): """Make a list of line segement data This is used to draw line segements between ref and src positions in the specified color Notes ----- The returned data has the format: [(refX0, srcX0), (refY0, srcY0), color0, (refX1, srcX1), (refY1, srcY1), color1,...] """ if len(refXYArr) != len(srcXYArr): raise RuntimeError("len(refXYArr) = %d != %d = len(srcXYArr)" % (len(refXYArr), len(srcXYArr))) if len(refXYArr) != len(colorArr): raise RuntimeError("len(refXYArr) = %d != %d = len(colorArr)" % (len(refXYArr), len(colorArr))) refXArr, refYArr = list(zip(*refXYArr)) srcXArr, srcYArr = list(zip(*srcXYArr)) refSrcXArr = list(zip(refXArr, srcXArr)) refSrcYArr = list(zip(refYArr, srcYArr)) dataList = [] for xycolor in zip(refSrcXArr, refSrcYArr, colorArr): for val in xycolor: dataList.append(val) return dataList refXYArr, srcXYArr = \ list(zip(*[(match[0].get(refCentroidKey), match[1].get(srcCentroidKey)) for match in matches])) def plotSourceCircles(matches, color): srcXYArr = [match[1].get(srcCentroidKey) for match in matches] srcXArr, srcYArr = list(zip(*srcXYArr)) plt.plot( srcXArr, srcYArr, "o", mec=color, mfc="none", ms=10, ) if callable(matchColor): # different matches have different colors matchColorArr = [matchColor(match) for match in matches] # plot circles for each color separately matchColorSet = set(matchColorArr) for color in matchColorSet: subMatches = [ match for match in matches if matchColor(match) == color ] plotSourceCircles(subMatches, color=color) else: matchColorArr = [matchColor] * len(refXYArr) plotSourceCircles(matches, color=matchColor) lineSegData = makeLineSegmentData(refXYArr, srcXYArr, matchColorArr) plt.plot(*lineSegData) plt.show()
def displayAstrometry(refCat=None, sourceCat=None, distortedCentroidKey=None, bbox=None, exposure=None, matches=None, frame=1, title="", pause=True): """Show an astrometry debug image - reference objects in refCat are shown as red X - sources in sourceCat are shown as green + - distorted sources in sourceCat (position given by distortedCentroidKey) are shown as green o - matches are shown as a yellow circle around the source and a yellow line connecting the reference object and source - if both exposure and bbox are None, no image is displayed @param[in] refCat reference object catalog; must have fields "centroid_x" and "centroid_y" @param[in] sourceCat source catalog; must have field "slot_Centroid_x" and "slot_Centroid_y" @param[in] distortedCentroidKey key for sourceCat with field to use for distorted positions, or None @param[in] exposure exposure to display, or None for a blank exposure @param[in] bbox bounding box of exposure; used if exposure is None for a blank image @param[in] matches a list of lsst.afw.table.ReferenceMatch, or None @param[in] frame frame number for ds9 display @param[in] title title for ds9 display @param[in] pause pause for inspection of display? This is done by dropping into pdb. """ disp = afwDisplay.getDisplay(frame) if exposure is not None: disp.mtv(exposure, title=title) elif bbox is not None: disp.mtv(exposure=ExposureF(bbox), title=title) with disp.Buffering(): if refCat is not None: refCentroidKey = Point2DKey(refCat.schema["centroid"]) for refObj in refCat: rx, ry = refObj.get(refCentroidKey) disp.dot("x", rx, ry, size=10, ctype=afwDisplay.RED) if sourceCat is not None: sourceCentroidKey = Point2DKey(sourceCat.schema["slot_Centroid"]) for source in sourceCat: sx, sy = source.get(sourceCentroidKey) disp.dot("+", sx, sy, size=10, ctype=afwDisplay.GREEN) if distortedCentroidKey is not None: dx, dy = source.get(distortedCentroidKey) disp.dot("o", dx, dy, size=10, ctype=afwDisplay.GREEN) disp.line([(sx, sy), (dx, dy)], ctype=afwDisplay.GREEN) if matches is not None: refCentroidKey = Point2DKey(matches[0].first.schema["centroid"]) sourceCentroidKey = Point2DKey( matches[0].second.schema["slot_Centroid"]) radArr = np.ndarray(len(matches)) for i, m in enumerate(matches): refCentroid = m.first.get(refCentroidKey) sourceCentroid = m.second.get(sourceCentroidKey) radArr[i] = math.hypot(*(refCentroid - sourceCentroid)) sx, sy = sourceCentroid disp.dot("o", sx, sy, size=10, ctype=afwDisplay.YELLOW) disp.line([refCentroid, sourceCentroid], ctype=afwDisplay.YELLOW) print("<match radius> = %.4g +- %.4g [%d matches]" % (radArr.mean(), radArr.std(), len(matches))) if pause: print( "Dropping into debugger to allow inspection of display. Type 'continue' when done." ) import pdb pdb.set_trace()
def run(self, ccdExposure): """Mask negative pixels""" ccd = ccdExposure.getDetector() ccdExposure = self.convertIntToFloat(ccdExposure) self.updateVariance(ccdExposure, ccd[0]) # Treating as having only a single amplifier image = ccdExposure.getMaskedImage().getImage() mask = ccdExposure.getMaskedImage().getMask() bad = mask.getPlaneBitMask("BAD") if False: mask.getArray()[:] = numpy.where(image <= 0, bad, 0) # XXX this causes bad things to happen #from lsst.afw.image.utils import clipImage #clipImage(image,0,10) #exit() """ transfer wcs system to TAN """ matches = ReferenceMatchVector() md = ccdExposure.getMetadata() wcs = ccdExposure.getWcs() refSchema = SimpleTable.makeMinimalSchema() Point2DKey.addFields(refSchema, "centroid", "centroid position", "pixel") refCatalog = SimpleCatalog(refSchema) schema = SourceTable.makeMinimalSchema() centroidKey = Point2DKey.addFields(schema, "centroid", "centroid position", "pixel") imgCatalog = SourceCatalog(schema) imgCatalog.defineCentroid("centroid") # for i in numpy.linspace(10, md.get("ZNAXIS1")-10, 20): # for j in numpy.linspace(10, md.get("ZNAXIS2")-10, 20): for i in numpy.linspace(10, 4000, 10): for j in numpy.linspace(10, 4000, 10): imgcrd = Point2D(i,j) skycrd = wcs.pixelToSky(afwGeom.Point2D(i, j)) # Create the reference catalog (with coordinates on the sky) refSrc = refCatalog.addNew() refSrc.setCoord(skycrd) # Create the position catalog (with positions on the image) imgSrc = imgCatalog.addNew() imgSrc.set(centroidKey, imgcrd) # matches matches.push_back(ReferenceMatch(refSrc, imgSrc, float("NaN"))) # make initial wcs refPix = afwGeom.Point2D(0.5*ccdExposure.getWidth(), 0.5*ccdExposure.getHeight()) refSky = wcs.pixelToSky(refPix) xPixelScale = yPixelScale = (0.2*afwGeom.arcseconds).asDegrees() initanWcs = afwImage.makeWcs(refSky, refPix, xPixelScale, 0.0, 0.0, yPixelScale) # obtain modified wcs with matched catalogs fitter = FitTanSipWcsTask() fitRes = fitter.fitWcs( matches = matches, initWcs = initanWcs, refCat = refCatalog, sourceCat = imgCatalog, ) ccdExposure.setWcs(fitRes.wcs) """ Set zero point, ZP error, exptime """ zp = md.get("MAGZPT") ccdExposure.getCalib().setFluxMag0(10.0**(0.4*zp)) return lsst.pipe.base.Struct(exposure=ccdExposure)