def getCornerCoords(wcs, bbox): """Return the coords of the four corners of a bounding box """ cornerPosList = afwGeom.Box2D(bbox).getCorners() return wcs.pixelToSky(cornerPosList)
def setUp(self): p, e = afwGeom.Point2D(1.0, 1.0), afwGeom.Extent2D(0.5, 0.5) self.data = afwGeom.Box2D(p, e)
def test(self): """Check that we can create and use a coadd ApCorrMap""" coaddBox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(100, 100)) scale = 5.0e-5 # deg/pix; for CD matrix coord = afwCoord.Coord(0.0 * afwGeom.degrees, 0.0 * afwGeom.degrees) center = afwGeom.Point2D( afwGeom.Extent2D(coaddBox.getDimensions()) * 0.5) coaddWcs = afwImage.makeWcs(coord, afwGeom.Point2D(0, 0), scale, 0.0, 0.0, scale) schema = afwTable.ExposureTable.makeMinimalSchema() weightKey = schema.addField("customweightname", type="D", doc="Coadd weight") catalog = afwTable.ExposureCatalog(schema) # Non-overlapping num = 5 inputBox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(10, 10)) validBox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(7, 7)) pointList = [] pointListValid = [] overlapping = [] for i in range(num): value = numpy.array([[1]], dtype=float) # Constant with value = i+1 apCorrMap = afwImage.ApCorrMap() bf = afwMath.ChebyshevBoundedField(inputBox, value * (i + 1)) apCorrMap.set("only", bf) point = afwGeom.Point2D(0, 0) - afwGeom.Extent2D( coaddBox.getDimensions()) * (i + 0.5) / num wcs = afwImage.makeWcs(coord, point, scale, 0.0, 0.0, scale) center = afwGeom.Box2D(inputBox).getCenter() pointList.append(coaddWcs.skyToPixel(wcs.pixelToSky(center))) # This point will only be valid for the second overlapping record pointValid = center + afwGeom.Extent2D(4, 4) pointListValid.append( coaddWcs.skyToPixel(wcs.pixelToSky(pointValid))) # A record with the valid polygon defining a limited region record = catalog.getTable().makeRecord() record.setWcs(wcs) record.setBBox(inputBox) record.setApCorrMap(apCorrMap) record.set(weightKey, i + 1) record['id'] = i record.setValidPolygon(Polygon(afwGeom.Box2D(validBox))) catalog.append(record) # An overlapping record with the whole region as valid record = catalog.getTable().makeRecord() record.setWcs(wcs) record.setBBox(inputBox) apCorrMap = afwImage.ApCorrMap() bf = afwMath.ChebyshevBoundedField(inputBox, value * (i + 2)) apCorrMap.set("only", bf) record.setApCorrMap(apCorrMap) record.set(weightKey, i + 2) record['id'] = i + num record.setValidPolygon(Polygon(afwGeom.Box2D(inputBox))) catalog.append(record) apCorrMap = measAlg.makeCoaddApCorrMap(catalog, coaddBox, coaddWcs, "customweightname") # This will test a point where both records contribute self.assertApCorrMap(apCorrMap, pointList) # Only the second record will be valid for this point self.assertApCorrMapValid(apCorrMap, pointListValid) filename = "tests/coaddApCorrMap.fits" exposure = afwImage.ExposureF(1, 1) exposure.getInfo().setApCorrMap(apCorrMap) exposure.writeFits(filename) exposure = afwImage.ExposureF(filename) self.assertApCorrMap(exposure.getInfo().getApCorrMap(), pointList) self.assertApCorrMapValid(exposure.getInfo().getApCorrMap(), pointListValid) os.unlink(filename)
def run(self, exposure, sensorRef, templateIdList=None): """Retrieve and mosaic a template coadd exposure that overlaps the exposure Parameters ---------- exposure: `lsst.afw.image.Exposure` an exposure for which to generate an overlapping template sensorRef : TYPE a Butler data reference that can be used to obtain coadd data templateIdList : TYPE, optional list of data ids (unused) Returns ------- result : `struct` return a pipeBase.Struct: - ``exposure`` : a template coadd exposure assembled out of patches - ``sources`` : None for this subtask """ skyMap = sensorRef.get(datasetType=self.config.coaddName + "Coadd_skyMap") expWcs = exposure.getWcs() expBoxD = afwGeom.Box2D(exposure.getBBox()) expBoxD.grow(self.config.templateBorderSize) ctrSkyPos = expWcs.pixelToSky(expBoxD.getCenter()) tractInfo = skyMap.findTract(ctrSkyPos) self.log.info("Using skyMap tract %s" % (tractInfo.getId(),)) skyCorners = [expWcs.pixelToSky(pixPos) for pixPos in expBoxD.getCorners()] patchList = tractInfo.findPatchList(skyCorners) if not patchList: raise RuntimeError("No suitable tract found") self.log.info("Assembling %s coadd patches" % (len(patchList),)) # compute coadd bbox coaddWcs = tractInfo.getWcs() coaddBBox = afwGeom.Box2D() for skyPos in skyCorners: coaddBBox.include(coaddWcs.skyToPixel(skyPos)) coaddBBox = afwGeom.Box2I(coaddBBox) self.log.info("exposure dimensions=%s; coadd dimensions=%s" % (exposure.getDimensions(), coaddBBox.getDimensions())) # assemble coadd exposure from subregions of patches coaddExposure = afwImage.ExposureF(coaddBBox, coaddWcs) coaddExposure.maskedImage.set(np.nan, afwImage.Mask.getPlaneBitMask("NO_DATA"), np.nan) nPatchesFound = 0 coaddFilter = None coaddPsf = None for patchInfo in patchList: patchSubBBox = patchInfo.getOuterBBox() patchSubBBox.clip(coaddBBox) patchArgDict = dict( datasetType=self.getCoaddDatasetName() + "_sub", bbox=patchSubBBox, tract=tractInfo.getId(), patch="%s,%s" % (patchInfo.getIndex()[0], patchInfo.getIndex()[1]), numSubfilters=self.config.numSubfilters, ) if patchSubBBox.isEmpty(): self.log.info("skip tract=%(tract)s, patch=%(patch)s; no overlapping pixels" % patchArgDict) continue if self.config.coaddName == 'dcr': if not sensorRef.datasetExists(subfilter=0, **patchArgDict): self.log.warn("%(datasetType)s, tract=%(tract)s, patch=%(patch)s," " numSubfilters=%(numSubfilters)s, subfilter=0 does not exist" % patchArgDict) continue self.log.info("Constructing DCR-matched template for patch %s" % patchArgDict) dcrModel = DcrModel.fromDataRef(sensorRef, **patchArgDict) # The edge pixels of the DcrCoadd may contain artifacts due to missing data. # Each patch has significant overlap, and the contaminated edge pixels in # a new patch will overwrite good pixels in the overlap region from # previous patches. # Shrink the BBox to remove the contaminated pixels, # but make sure it is only the overlap region that is reduced. patchInnerBBox = patchInfo.getInnerBBox() patchInnerBBox.clip(coaddBBox) dcrBBox = afwGeom.Box2I(patchSubBBox) dcrBBox.grow(-self.config.templateBorderSize) dcrBBox.include(patchInnerBBox) coaddPatch = dcrModel.buildMatchedExposure(bbox=dcrBBox, wcs=coaddWcs, visitInfo=exposure.getInfo().getVisitInfo()) else: if not sensorRef.datasetExists(**patchArgDict): self.log.warn("%(datasetType)s, tract=%(tract)s, patch=%(patch)s does not exist" % patchArgDict) continue self.log.info("Reading patch %s" % patchArgDict) coaddPatch = sensorRef.get(**patchArgDict) nPatchesFound += 1 coaddExposure.maskedImage.assign(coaddPatch.maskedImage, coaddPatch.getBBox()) if coaddFilter is None: coaddFilter = coaddPatch.getFilter() # Retrieve the PSF for this coadd tract, if not already retrieved if coaddPsf is None and coaddPatch.hasPsf(): coaddPsf = coaddPatch.getPsf() if nPatchesFound == 0: raise RuntimeError("No patches found!") if coaddPsf is None: raise RuntimeError("No coadd Psf found!") coaddExposure.setPsf(coaddPsf) coaddExposure.setFilter(coaddFilter) return pipeBase.Struct(exposure=coaddExposure, sources=None)
def showCamera(camera, imageSource=FakeImageDataSource(), imageFactory=afwImage.ImageF, detectorNameList=None, binSize=10, bufferSize=10, frame=None, overlay=True, title="", showWcs=None, ctype=afwDisplay.GREEN, textSize=1.25, originAtCenter=True, display=None, **kwargs): """!Show a Camera on display, with the specified display The rotation of the sensors is snapped to the nearest multiple of 90 deg. Also note that the pixel size is constant over the image array. The lower left corner (LLC) of each sensor amp is snapped to the LLC of the pixel containing the LLC of the image. if overlay show the IDs and detector boundaries @param[in] camera Camera to show @param[in] imageSource Source to get Ccd images from. Must have a getCcdImage method. @param[in] imageFactory Type of image to make @param[in] detectorNameList List of names of Detectors to use. If None use all @param[in] binSize bin factor @param[in] bufferSize size of border in binned pixels to make around camera image. @param[in] frame specify image display (@deprecated; new code should use display) @param[in] overlay Overlay Detector IDs and boundaries? @param[in] title Title in display @param[in] showWcs whether to include a WCS in the display @param[in] ctype Color to use when drawing Detector boundaries @param[in] textSize Size of detector labels @param[in] originAtCenter Put origin of the camera WCS at the center of the image? Else it will be LL @param[in] display image display on which to display @param[in] **kwargs all remaining keyword arguments are passed to makeImageFromCamera @return the mosaic image """ display = _getDisplayFromDisplayOrFrame(display, frame) if binSize < 1: binSize = 1 cameraImage = makeImageFromCamera(camera, detectorNameList=detectorNameList, bufferSize=bufferSize, imageSource=imageSource, imageFactory=imageFactory, binSize=binSize, **kwargs) if detectorNameList is None: ccdList = [camera[name] for name in camera.getNameIter()] else: ccdList = [camera[name] for name in detectorNameList] if detectorNameList is None: camBbox = camera.getFpBBox() else: camBbox = afwGeom.Box2D() for detName in detectorNameList: for corner in camera[detName].getCorners(FOCAL_PLANE): camBbox.include(corner) pixelSize = ccdList[0].getPixelSize() if showWcs: if originAtCenter: wcsReferencePixel = afwGeom.Box2D( cameraImage.getBBox()).getCenter() else: wcsReferencePixel = afwGeom.Point2I(0, 0) wcs = makeFocalPlaneWcs(pixelSize * binSize, wcsReferencePixel) else: wcs = None if display: if title == "": title = camera.getName() display.mtv(cameraImage, title=title, wcs=wcs) if overlay: with display.Buffering(): camBbox = getCameraImageBBox(camBbox, pixelSize, bufferSize * binSize) bboxList = getCcdInCamBBoxList(ccdList, binSize, pixelSize, camBbox.getMin()) for bbox, ccd in zip(bboxList, ccdList): nQuarter = ccd.getOrientation().getNQuarter() # borderWidth to 0.5 to align with the outside edge of the # pixel displayUtils.drawBBox(bbox, borderWidth=0.5, ctype=ctype, display=display) dims = bbox.getDimensions() display.dot(ccd.getName(), bbox.getMinX() + dims.getX() / 2, bbox.getMinY() + dims.getY() / 2, ctype=ctype, size=textSize, textAngle=nQuarter * 90) return cameraImage
def cutout_from_pos(self, params: dict): """ Get cutout of source image by supported SODA shapes: POS: CIRCLE, RANGE, POLYGON LSST extension: BRECT Parameters ---------- params: `dict` the POS parameter. Returns ------- cutout: `afwImage.Exposure` """ _pos = params["POS"] _id = params["ID"] db, ds, filt = _id.split(".") pos_items = _pos.split() shape = pos_items[0] if shape == "BRECT": if len(pos_items) < 6: raise Exception("BRECT: invalid parameters") ra, dec = float(pos_items[1]), float(pos_items[2]) w, h = float(pos_items[3]), float(pos_items[4]) unit_size = pos_items[5] cutout = self.cutout_from_nearest(ra, dec, w, h, unit_size, filt) return cutout elif shape == "CIRCLE": if len(pos_items) < 4: raise Exception("CIRCLE: invalid parameters") ra, dec = float(pos_items[1]), float(pos_items[2]) radius = float(pos_items[3]) # convert from deg to pixels by wcs (ICRS) q_result = self._metaservget.nearest_image_containing(ra, dec, filt) data_id = self._data_id_from_qr(q_result) metadata = self._metadata_from_data_id(data_id) wcs = afwGeom.makeSkyWcs(metadata, strip=False) pix_r = int(radius / wcs.getPixelScale().asArcseconds()) ss = SpanSet.fromShape(pix_r) ss_width = ss.getBBox().getWidth() src_image = self.cutout_from_nearest(ra, dec, ss_width, ss_width, "pixel", filt) src_cutout = src_image.getMaskedImage() circle_cutout = afwImage.MaskedImageF(src_cutout.getBBox()) spanset = SpanSet.fromShape(pix_r, Stencil.CIRCLE, offset=src_cutout.getXY0() + afwGeom.Extent2I(pix_r, pix_r)) spanset.copyMaskedImage(src_cutout, circle_cutout) # make an Exposure cutout with WCS info cutout = afwImage.ExposureF(circle_cutout, afwImage.ExposureInfo(wcs)) return cutout elif shape == "RANGE": if len(pos_items) < 5: raise Exception("RANGE: invalid parameters") # convert the pair of (ra,dec) to bbox ra1, ra2 = float(pos_items[1]), float(pos_items[2]) dec1, dec2 = float(pos_items[3]), float(pos_items[4]) box = afwGeom.Box2D(afwGeom.Point2D(ra1, dec1), afwGeom.Point2D(ra2, dec2)) # convert from deg to arcsec w = box.getWidth()*3600 h = box.getHeight()*3600 # compute the arithmetic center (ra, dec) of the range ra = (ra1 + ra2) / 2 dec = (dec1 + dec2) / 2 cutout = self.cutout_from_nearest(ra, dec, w, h, "arcsec", filt) return cutout elif shape == "POLYGON": if len(pos_items) < 7: raise Exception("POLYGON: invalid parameters") vertices = [] pos_items.pop(0) for long, lat in zip(pos_items[::2], pos_items[1::2]): pt = afwGeom.Point2D(float(long), float(lat)) vertices.append(pt) polygon = afwGeom.Polygon(vertices) center = polygon.calculateCenter() ra, dec = center.getX(), center.getY() # afw limitation: can only return the bbox of the polygon bbox = polygon.getBBox() # convert from 'deg' to 'arcsec' w = bbox.getWidth()*3600 h = bbox.getHeight()*3600 cutout = self.cutout_from_nearest(ra, dec, w, h, "arcsec", filt) return cutout
def approximateWcs(wcs, camera_wrapper=None, detector_name=None, obs_metadata=None, order=3, nx=20, ny=20, iterations=3, skyTolerance=0.001 * afwGeom.arcseconds, pixelTolerance=0.02): """Approximate an existing WCS as a TAN-SIP WCS The fit is performed by evaluating the WCS at a uniform grid of points within a bounding box. @param[in] wcs wcs to approximate @param[in] camera_wrapper is an instantiation of GalSimCameraWrapper @param[in] detector_name is the name of the detector @param[in] obs_metadata is an ObservationMetaData characterizing the telescope pointing @param[in] order order of SIP fit @param[in] nx number of grid points along x @param[in] ny number of grid points along y @param[in] iterations number of times to iterate over fitting @param[in] skyTolerance maximum allowed difference in world coordinates between input wcs and approximate wcs (default is 0.001 arcsec) @param[in] pixelTolerance maximum allowed difference in pixel coordinates between input wcs and approximate wcs (default is 0.02 pixels) @return the fit TAN-SIP WCS """ tanWcs = wcs # create a matchList consisting of a grid of points covering the bbox refSchema = afwTable.SimpleTable.makeMinimalSchema() refCoordKey = afwTable.CoordKey(refSchema["coord"]) refCat = afwTable.SimpleCatalog(refSchema) sourceSchema = afwTable.SourceTable.makeMinimalSchema() SingleFrameMeasurementTask(schema=sourceSchema) # expand the schema sourceCentroidKey = afwTable.Point2DKey(sourceSchema["slot_Centroid"]) sourceCat = afwTable.SourceCatalog(sourceSchema) # 20 March 2017 # the 'try' block is how it works in swig; # the 'except' block is how it works in pybind11 try: matchList = afwTable.ReferenceMatchVector() except AttributeError: matchList = [] bbox = camera_wrapper.getBBox(detector_name) bboxd = afwGeom.Box2D(bbox) for x in np.linspace(bboxd.getMinX(), bboxd.getMaxX(), nx): for y in np.linspace(bboxd.getMinY(), bboxd.getMaxY(), ny): pixelPos = afwGeom.Point2D(x, y) ra, dec = camera_wrapper.raDecFromPixelCoords( np.array([x]), np.array([y]), detector_name, obs_metadata=obs_metadata, epoch=2000.0, includeDistortion=True) skyCoord = afwCoord.Coord(afwGeom.Point2D(ra[0], dec[0])) refObj = refCat.addNew() refObj.set(refCoordKey, skyCoord) source = sourceCat.addNew() source.set(sourceCentroidKey, pixelPos) matchList.append(afwTable.ReferenceMatch(refObj, source, 0.0)) # The TAN-SIP fitter is fitting x and y separately, so we have to iterate to make it converge for indx in range(iterations): sipObject = makeCreateWcsWithSip(matchList, tanWcs, order, bbox) tanWcs = sipObject.getNewWcs() fitWcs = sipObject.getNewWcs() return fitWcs
def _compareWcsOverBBox(wcs0, wcs1, bbox, maxDiffSky=0.01 * afwGeom.arcseconds, maxDiffPix=0.01, nx=5, ny=5, doShortCircuit=True): """!Compare two WCS over a rectangular grid of pixel positions @param[in] wcs0 WCS 0 (an lsst.afw.image.Wcs) @param[in] wcs1 WCS 1 (an lsst.afw.image.Wcs) @param[in] bbox boundaries of pixel grid over which to compare the WCSs (an lsst.afw.geom.Box2I or Box2D) @param[in] maxDiffSky maximum separation between sky positions computed using Wcs.pixelToSky (an lsst.afw.geom.Angle) @param[in] maxDiffPix maximum separation between pixel positions computed using Wcs.skyToPixel @param[in] nx number of points in x for the grid of pixel positions @param[in] ny number of points in y for the grid of pixel positions @param[in] doShortCircuit if True then stop at the first error, else test all values in the grid and return information about the worst violations found @return return an empty string if the WCS are sufficiently close; else return a string describing the largest error measured in pixel coordinates (if sky to pixel error was excessive) and sky coordinates (if pixel to sky error was excessive). If doShortCircuit is true then the reported error is likely to be much less than the maximum error across the whole pixel grid. """ if nx < 1 or ny < 1: raise RuntimeError("nx = %s and ny = %s must both be positive" % (nx, ny)) if maxDiffSky <= 0 * afwGeom.arcseconds: raise RuntimeError("maxDiffSky = %s must be positive" % (maxDiffSky, )) if maxDiffPix <= 0: raise RuntimeError("maxDiffPix = %s must be positive" % (maxDiffPix, )) bboxd = afwGeom.Box2D(bbox) xList = numpy.linspace(bboxd.getMinX(), bboxd.getMaxX(), nx) yList = numpy.linspace(bboxd.getMinY(), bboxd.getMaxY(), ny) # we don't care about measured error unless it is too large, so initialize to max allowed measDiffSky = (maxDiffSky, "?") # (sky diff, pix pos) measDiffPix = (maxDiffPix, "?") # (pix diff, sky pos) for x, y in itertools.product(xList, yList): fromPixPos = afwGeom.Point2D(x, y) sky0 = wcs0.pixelToSky(fromPixPos) sky1 = wcs1.pixelToSky(fromPixPos) diffSky = sky0.angularSeparation(sky1) if diffSky > measDiffSky[0]: measDiffSky = (diffSky, fromPixPos) if doShortCircuit: break toPixPos0 = wcs0.skyToPixel(sky0) toPixPos1 = wcs1.skyToPixel(sky0) diffPix = math.hypot(*(toPixPos0 - toPixPos1)) if diffPix > measDiffPix[0]: measDiffPix = (diffPix, sky0) if doShortCircuit: break msgList = [] if measDiffSky[0] > maxDiffSky: msgList.append( "%s arcsec max measured sky error > %s arcsec max allowed sky error at pix pos=%s" % (measDiffSky[0].asArcseconds(), maxDiffSky.asArcseconds(), measDiffSky[1])) if measDiffPix[0] > maxDiffPix: msgList.append( "%s max measured pix error > %s max allowed pix error at sky pos=%s" % (measDiffPix[0], maxDiffPix, measDiffPix[1])) return "; ".join(msgList)
def makePolygon(wcs, bbox): """Return a polygon for the image, given Wcs and bounding box""" boxPixelCorners = afwGeom.Box2D(bbox).getCorners() boxSkyCorners = wcs.pixelToSky(boxPixelCorners) return lsst.sphgeom.ConvexPolygonconvexHull([coord.getVector() for coord in boxSkyCorners])
def _findDetectorsListLSST(cameraPointList, detectorList, allow_multiple_chips=False): """!Find the detectors that cover a list of points specified by x and y coordinates in any system This is based one afw.camerGeom.camera.findDetectorsList. It has been optimized for the LSST camera in the following way: - it accepts a limited list of detectors to check in advance (this list should be constructed by comparing the pupil coordinates in question and comparing to the pupil coordinates of the center of each detector) - it will stop looping through detectors one it has found one that is correct (the LSST camera does not allow an object to fall on more than one detector) @param[in] cameraPointList a list of cameraPoints in PUPIL coordinates @param[in] detecorList is a list of lists. Each row contains the detectors that should be searched for the correspdonding cameraPoint @param [in] allow_multiple_chips is a boolean (default False) indicating whether or not this method will allow objects to be visible on more than one chip. If it is 'False' and an object appears on more than one chip, only the first chip will appear in the list of chipNames but NO WARNING WILL BE EMITTED. If it is 'True' and an object falls on more than one chip, a list of chipNames will appear for that object. @return outputNameList is a numpy array of the names of the detectors """ # transform the points to the native coordinate system nativePointList = lsst_camera()._transformSingleSysArray(cameraPointList, PUPIL, lsst_camera()._nativeCameraSys) # initialize output and some caching lists outputNameList = [None]*len(cameraPointList) chip_has_found = np.array([-1]*len(cameraPointList)) checked_detectors = [] # Figure out if any of these (RA, Dec) pairs could be # on more than one chip. This is possible on the # wavefront sensors, since adjoining wavefront sensors # are kept one in focus, one out of focus. # See figure 2 of arXiv:1506.04839v2 # (This might actually be a bug in obs_lsstSim # I opened DM-8075 on 25 October 2016 to investigate) could_be_multiple = [False]*len(cameraPointList) if allow_multiple_chips: for ipt in range(len(cameraPointList)): for det in detectorList[ipt]: if det.getType() == WAVEFRONT: could_be_multiple[ipt] = True # loop over (RA, Dec) pairs for ipt, nativePoint in enumerate(nativePointList): if chip_has_found[ipt] < 0: # i.e. if we have not yet found this (RA, Dec) pair for detector in detectorList[ipt]: # check that we have not already considered this detector if detector.getName() not in checked_detectors: checked_detectors.append(detector.getName()) # in order to avoid constantly re-instantiating the same afwCameraGeom detector, # we will now find all of the (RA, Dec) pairs that could be on the present # chip and test them. unfound_pts = np.where(chip_has_found < 0)[0] if len(unfound_pts) == 0: # we have already found all of the (RA, Dec) pairs for ix, name in enumerate(outputNameList): if isinstance(name, list): outputNameList[ix] = str(name) return np.array(outputNameList) valid_pt_dexes = np.array([ii for ii in unfound_pts if detector in detectorList[ii]]) if len(valid_pt_dexes) > 0: valid_pt_list = [nativePointList[ii] for ii in valid_pt_dexes] coordMap = detector.getTransformMap() cameraSys = detector.makeCameraSys(PIXELS) detectorPointList = coordMap.transform(valid_pt_list, lsst_camera()._nativeCameraSys, cameraSys) box = afwGeom.Box2D(detector.getBBox()) for ix, pt in zip(valid_pt_dexes, detectorPointList): if box.contains(pt): if not could_be_multiple[ix]: # because this (RA, Dec) pair is not marked as could_be_multiple, # the fact that this (RA, Dec) pair is on the current chip # means this (RA, Dec) pair no longer needs to be considered. # You can set chip_has_found[ix] to unity. outputNameList[ix] = detector.getName() chip_has_found[ix] = 1 else: # Since this (RA, Dec) pair has been makred could_be_multiple, # finding this (RA, Dec) pair on the chip does not remove the # (RA, Dec) pair from contention. if outputNameList[ix] is None: outputNameList[ix] = detector.getName() elif isinstance(outputNameList[ix], list): outputNameList[ix].append(detector.getName()) else: outputNameList[ix] = [outputNameList[ix], detector.getName()] # convert entries corresponding to multiple chips into strings # (i.e. [R:2,2 S:0,0, R:2,2 S:0,1] becomes `[R:2,2 S:0,0, R:2,2 S:0,1]`) for ix, name in enumerate(outputNameList): if isinstance(name, list): outputNameList[ix] = str(name) return np.array(outputNameList)
#Need RMS from fit: 2895 will replace this: rms = 0.0 X, Y, Z, dZ = self._gridImage(diffMI, self.config.binSize, statsFlag) x0, y0 = diffMI.getXY0() modelValueArr = numpy.empty(len(Z)) for i in range(len(X)): modelValueArr[i] = bkgdImage.get(int(X[i] - x0), int(Y[i] - y0)) resids = Z - modelValueArr rms = numpy.sqrt(numpy.mean(resids[~numpy.isnan(resids)]**2)) if lsstDebug.Info(__name__).savefits: sciExposure.writeFits( lsstDebug.Info(__name__).figpath + 'sciMatchedExposure.fits') if lsstDebug.Info(__name__).savefig: bbox = afwGeom.Box2D(refExposure.getMaskedImage().getBBox()) try: self._debugPlot(X, Y, Z, dZ, bkgdImage, bbox, modelValueArr, resids) except Exception, e: self.log.warn('Debug plot not generated: %s' % (e)) meanVar = afwMath.makeStatistics(diffMI.getVariance(), diffMI.getMask(), afwMath.MEANCLIP, self.sctrl).getValue() diffIm = diffMI.getImage() diffIm -= bkgdImage #diffMI should now have a mean ~ 0 del diffIm mse = afwMath.makeStatistics(diffMI, afwMath.MEANSQUARE, self.sctrl).getValue()
def testCountInputs(self): num = 3 # Number of images size = 10 # Size of images (pixels) shift = 4 # Shift to apply between images (pixels) value = 100.0 # Value to give objects offset = 12345 # x0,y0 cdMatrix = (1.0e-5, 0.0, 0.0, 1.0e-5) crval = afwCoord.Coord(0.0 * afwGeom.degrees, 0.0 * afwGeom.degrees) positions = [ afwGeom.Point2D(size // 2 + shift * (i - num // 2) + offset, size // 2 + shift * (i - num // 2) + offset) for i in range(num) ] wcsList = [ afwImage.makeWcs(crval, pos - afwGeom.Extent2D(offset, offset), *cdMatrix) for pos in positions ] imageBox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(size, size)) wcsRef = afwImage.makeWcs(crval, afwGeom.Point2D(offset, offset), *cdMatrix) exp = afwImage.ExposureF(size, size) exp.setXY0(afwGeom.Point2I(offset, offset)) exp.setWcs(wcsRef) exp.setPsf(measAlg.DoubleGaussianPsf(5, 5, 1.0)) exp.getInfo().setCoaddInputs( afwImage.CoaddInputs(afwTable.ExposureTable.makeMinimalSchema(), afwTable.ExposureTable.makeMinimalSchema())) ccds = exp.getInfo().getCoaddInputs().ccds for wcs in wcsList: record = ccds.addNew() record.setWcs(wcs) record.setBBox(imageBox) record.setValidPolygon(Polygon(afwGeom.Box2D(imageBox))) exp.getMaskedImage().getImage().set(0) exp.getMaskedImage().getMask().set(0) exp.getMaskedImage().getVariance().set(1.0) for pp in positions: x, y = map(int, pp) exp.getMaskedImage().getImage().set0(x, y, value) exp.getMaskedImage().getMask().set(x - offset, y - offset, value) measureSourcesConfig = measAlg.SourceMeasurementConfig() measureSourcesConfig.algorithms.names = [ "centroid.naive", "countInputs" ] measureSourcesConfig.slots.centroid = "centroid.naive" measureSourcesConfig.slots.psfFlux = None measureSourcesConfig.slots.apFlux = None measureSourcesConfig.slots.modelFlux = None measureSourcesConfig.slots.instFlux = None measureSourcesConfig.slots.calibFlux = None measureSourcesConfig.slots.shape = None measureSourcesConfig.validate() schema = afwTable.SourceTable.makeMinimalSchema() ms = measureSourcesConfig.makeMeasureSources(schema) catalog = afwTable.SourceCatalog(schema) measureSourcesConfig.slots.setupTable(catalog.getTable()) for pp in positions: foot = afwDetection.Footprint(afwGeom.Point2I(pp), 1.0) peak = foot.getPeaks().addNew() peak.setIx(int(pp[0])) peak.setIy(int(pp[1])) peak.setFx(pp[0]) peak.setFy(pp[1]) peak.setPeakValue(value) source = catalog.addNew() source.setFootprint(foot) ms.applyWithPeak(source, exp) number = sum( afwGeom.Box2D(imageBox).contains( wcs.skyToPixel(wcsRef.pixelToSky(pp))) for wcs in wcsList) self.assertEqual(source.get("countInputs"), number)
def testMutators(self): box = geom.Box2D(geom.Point2D(-2, -3), geom.Point2D(2, 1), True) box.grow(1) self.assertEqual( box, geom.Box2D(geom.Point2D(-3, -4), geom.Point2D(3, 2), True)) box.grow(geom.Extent2D(2, 3)) self.assertEqual( box, geom.Box2D(geom.Point2D(-5, -7), geom.Point2D(5, 5), True)) box.shift(geom.Extent2D(3, 2)) self.assertEqual( box, geom.Box2D(geom.Point2D(-2, -5), geom.Point2D(8, 7), True)) box.include(geom.Point2D(-4, 2)) self.assertEqual( box, geom.Box2D(geom.Point2D(-4, -5), geom.Point2D(8, 7), True)) self.assertTrue(box.contains(geom.Point2D(-4, 2))) box.include(geom.Point2D(0, -6)) self.assertEqual( box, geom.Box2D(geom.Point2D(-4, -6), geom.Point2D(8, 7), True)) box.include(geom.Box2D(geom.Point2D(0, 0), geom.Point2D(10, 11), True)) self.assertEqual( box, geom.Box2D(geom.Point2D(-4, -6), geom.Point2D(10, 11), True)) box.clip(geom.Box2D(geom.Point2D(0, 0), geom.Point2D(11, 12), True)) self.assertEqual( box, geom.Box2D(geom.Point2D(0, 0), geom.Point2D(10, 11), True)) box.clip(geom.Box2D(geom.Point2D(-1, -2), geom.Point2D(5, 4), True)) self.assertEqual( box, geom.Box2D(geom.Point2D(0, 0), geom.Point2D(5, 4), True))
def testRelations(self): box = geom.Box2D(geom.Point2D(-2, -3), geom.Point2D(2, 1), True) self.assertTrue(box.contains(geom.Point2D(0, 0))) self.assertTrue(box.contains(geom.Point2D(-2, -3))) self.assertFalse(box.contains(geom.Point2D(2, -3))) self.assertFalse(box.contains(geom.Point2D(2, 1))) self.assertFalse(box.contains(geom.Point2D(-2, 1))) self.assertTrue( box.contains(geom.Box2D(geom.Point2D(-1, -2), geom.Point2D(1, 0)))) self.assertTrue(box.contains(box)) self.assertFalse( box.contains(geom.Box2D(geom.Point2D(-2, -3), geom.Point2D(2, 2)))) self.assertFalse( box.contains(geom.Box2D(geom.Point2D(-2, -3), geom.Point2D(3, 1)))) self.assertFalse( box.contains(geom.Box2D(geom.Point2D(-3, -3), geom.Point2D(2, 1)))) self.assertFalse( box.contains(geom.Box2D(geom.Point2D(-3, -4), geom.Point2D(2, 1)))) self.assertTrue( box.overlaps(geom.Box2D(geom.Point2D(-2, -3), geom.Point2D(2, 2)))) self.assertTrue( box.overlaps(geom.Box2D(geom.Point2D(-2, -3), geom.Point2D(3, 1)))) self.assertTrue( box.overlaps(geom.Box2D(geom.Point2D(-3, -3), geom.Point2D(2, 1)))) self.assertTrue( box.overlaps(geom.Box2D(geom.Point2D(-3, -4), geom.Point2D(2, 1)))) self.assertTrue( box.overlaps(geom.Box2D(geom.Point2D(-1, -2), geom.Point2D(1, 0)))) self.assertTrue(box.overlaps(box)) self.assertFalse( box.overlaps(geom.Box2D(geom.Point2D(-5, -3), geom.Point2D(-3, 1)))) self.assertFalse( box.overlaps(geom.Box2D(geom.Point2D(-2, -6), geom.Point2D(2, -4)))) self.assertFalse( box.overlaps(geom.Box2D(geom.Point2D(3, -3), geom.Point2D(4, 1)))) self.assertFalse( box.overlaps(geom.Box2D(geom.Point2D(-2, 2), geom.Point2D(2, 2)))) self.assertFalse( box.overlaps(geom.Box2D(geom.Point2D(-2, -5), geom.Point2D(2, -3)))) self.assertFalse( box.overlaps(geom.Box2D(geom.Point2D(-4, -3), geom.Point2D(-2, 1)))) self.assertFalse( box.contains(geom.Box2D(geom.Point2D(-2, 1), geom.Point2D(2, 3)))) self.assertFalse( box.contains(geom.Box2D(geom.Point2D(2, -3), geom.Point2D(4, 1))))
def run(self, butler, dataRefList): """!Make a skymap from the bounds of the given set of calexps. @param[in] butler data butler used to save the SkyMap @param[in] dataRefList dataRefs of calexps used to determine the size and pointing of the SkyMap @return a pipeBase Struct containing: - skyMap: the constructed SkyMap """ self.log.info("Extracting bounding boxes of %d images" % len(dataRefList)) points = [] for dataRef in dataRefList: if not dataRef.datasetExists("calexp"): self.log.warn("CalExp for %s does not exist: ignoring" % (dataRef.dataId, )) continue md = dataRef.get("calexp_md", immediate=True) wcs = afwImage.makeWcs(md) # nb: don't need to worry about xy0 because Exposure saves Wcs with CRPIX shifted by (-x0, -y0). boxI = afwGeom.Box2I( afwGeom.Point2I(0, 0), afwGeom.Extent2I(md.get("NAXIS1"), md.get("NAXIS2"))) boxD = afwGeom.Box2D(boxI) points.extend( tuple(wcs.pixelToSky(corner).getVector()) for corner in boxD.getCorners()) if len(points) == 0: raise RuntimeError( "No data found from which to compute convex hull") self.log.info("Computing spherical convex hull") polygon = lsst.geom.convexHull(points) if polygon is None: raise RuntimeError( "Failed to compute convex hull of the vertices of all calexp bounding boxes; " "they may not be hemispherical.") circle = polygon.getBoundingCircle() datasetName = self.config.coaddName + "Coadd_skyMap" skyMapConfig = DiscreteSkyMap.ConfigClass() if self.config.doAppend and butler.datasetExists(datasetName): oldSkyMap = butler.get(datasetName, immediate=True) if not isinstance(oldSkyMap.config, DiscreteSkyMap.ConfigClass): raise TypeError( "Cannot append to existing non-discrete skymap") compareLog = [] if not self.config.skyMap.compare(oldSkyMap.config, output=compareLog.append): raise ValueError( "Cannot append to existing skymap - configurations differ:", *compareLog) skyMapConfig.raList.extend(oldSkyMap.config.raList) skyMapConfig.decList.extend(oldSkyMap.config.decList) skyMapConfig.radiusList.extend(oldSkyMap.config.radiusList) skyMapConfig.update(**self.config.skyMap.toDict()) skyMapConfig.raList.append(circle.center[0]) skyMapConfig.decList.append(circle.center[1]) skyMapConfig.radiusList.append(circle.radius + self.config.borderSize) skyMap = DiscreteSkyMap(skyMapConfig) for tractInfo in skyMap: wcs = tractInfo.getWcs() posBox = afwGeom.Box2D(tractInfo.getBBox()) pixelPosList = ( posBox.getMin(), afwGeom.Point2D(posBox.getMaxX(), posBox.getMinY()), posBox.getMax(), afwGeom.Point2D(posBox.getMinX(), posBox.getMaxY()), ) skyPosList = [ wcs.pixelToSky(pos).getPosition(afwGeom.degrees) for pos in pixelPosList ] posStrList = [ "(%0.3f, %0.3f)" % tuple(skyPos) for skyPos in skyPosList ] self.log.info("tract %s has corners %s (RA, Dec deg) and %s x %s patches" % \ (tractInfo.getId(), ", ".join(posStrList), \ tractInfo.getNumPatches()[0], tractInfo.getNumPatches()[1])) if self.config.doWrite: butler.put(skyMap, datasetName) return pipeBase.Struct(skyMap=skyMap)
def testChebyshev1Function2D(self): errMsg = ("{}: {} != {} for x={}, xMin={}, xMax={}, xNorm={}, " "yMin={}, yMax={}, yNorm={}, params={}; {}") maxOrder = 6 deltaParam = 0.3 ranges = ((-1, 1), (-1, 0), (0, 1), (-17, -2), (-65.3, 2.132)) xRangeIter = itertools.cycle(ranges) yRangeIter = itertools.cycle(ranges) next(yRangeIter) # make x and y ranges off from each other nPoints = 7 # number of points in x and y at which to test the functions for order in range(maxOrder + 1): xMin, xMax = next(xRangeIter) xMean = (xMin + xMax) / 2.0 xDelta = (xMax - xMin) / float(nPoints - 1) yMin, yMax = next(yRangeIter) yMean = (yMin + yMax) / 2.0 yDelta = (yMax - yMin) / float(nPoints - 1) xyRange = afwGeom.Box2D(afwGeom.Point2D(xMin, yMin), afwGeom.Point2D(xMax, yMax)) f = afwMath.Chebyshev1Function2D(order, xyRange) numParams = f.getNParameters() params = nrange(numParams, deltaParam, deltaParam) f.setParameters(params) g = afwMath.Chebyshev1Function2D(params, xyRange) h = f.clone() self.assertEqual(f.getNParameters(), g.getNParameters()) self.assertEqual(f.getNParameters(), h.getNParameters()) self.assertEqual(f.getXYRange(), xyRange) self.assertEqual(f.getOrder(), order) self.assertEqual(g.getXYRange(), xyRange) self.assertEqual(g.getOrder(), order) # vary x in the inner loop to exercise the caching minYNorm = None maxYNorm = None for y in np.arange(yMin, yMax + yDelta / 2.0, yDelta): yNorm = 2.0 * (y - yMean) / float(yMax - yMin) if minYNorm is None or yNorm < minYNorm: minYNorm = yNorm if maxYNorm is None or yNorm > maxYNorm: maxYNorm = yNorm minXNorm = None maxXNorm = None for x in np.arange(xMin, xMax + xDelta / 2.0, xDelta): xNorm = 2.0 * (x - xMean) / float(xMax - xMin) if minXNorm is None or xNorm < minXNorm: minXNorm = xNorm if maxXNorm is None or xNorm > maxXNorm: maxXNorm = xNorm predVal = referenceChebyshev1Polynomial2( xNorm, yNorm, params) msg = errMsg.format( type(f).__name__, f(x, y), predVal, x, xMin, xMax, xNorm, yMin, yMax, yNorm, params, "order constructor") self.assertFloatsAlmostEqual(f(x, y), predVal, msg=msg, atol=self.atol, rtol=None) msg = errMsg.format( type(g).__name__, g(x, y), predVal, x, xMin, xMax, xNorm, yMin, yMax, yNorm, params, "params constructor") self.assertFloatsAlmostEqual(g(x, y), predVal, msg=msg, atol=self.atol, rtol=None) msg = errMsg.format( type(h).__name__, h(x, y), predVal, x, xMin, xMax, xNorm, yMin, yMax, yNorm, params, "order") self.assertFloatsAlmostEqual(h(x, y), predVal, msg=msg, atol=self.atol, rtol=None) msg = self.normErr.format("x", xMin, xMax, minXNorm, maxXNorm) self.assertFloatsAlmostEqual(minXNorm, -1., msg=msg, atol=self.atol) self.assertFloatsAlmostEqual(maxXNorm, 1., msg=msg, atol=self.atol) msg = self.normErr.format("y", yMin, yMax, minYNorm, maxYNorm) self.assertFloatsAlmostEqual(minYNorm, -1., msg=msg, atol=self.atol) self.assertFloatsAlmostEqual(maxYNorm, 1., msg=msg, atol=self.atol) # test that the number of parameters is correct for the given order def numParamsFromOrder(order): return (order + 1) * (order + 2) // 2 MaxOrder = 13 for order in range(MaxOrder + 1): f = afwMath.Chebyshev1Function2D(order) predNParams = numParamsFromOrder(order) self.assertEqual(f.getNParameters(), predNParams) afwMath.Chebyshev1Function2D(np.zeros(predNParams, dtype=float)) # test that the wrong number of parameters raises an exception validNumParams = set() for order in range(MaxOrder + 1): validNumParams.add(numParamsFromOrder(order)) for numParams in range(numParamsFromOrder(MaxOrder)): if numParams in validNumParams: continue with self.assertRaises(pexExceptions.InvalidParameterError): afwMath.Chebyshev1Function2D(np.zeros(numParams, dtype=float)) # test that changing parameters clears the cache # for simplicity use the xyRange that requires no normalization order = 3 numParams = numParamsFromOrder(order) f = afwMath.Chebyshev1Function2D(order) xyRange = afwGeom.Box2D(afwGeom.Point2D(-1.0, -1.0), afwGeom.Point2D(1.0, 1.0)) x = 0.5 y = -0.24 for addValue in (0.0, 0.2): params = nrange(numParams, deltaParam + addValue, deltaParam) f.setParameters(params) predVal = referenceChebyshev1Polynomial2(x, y, params) msg = "%s != %s for x=%s, y=%s, params=%s" % (f( x, y), predVal, x, y, params) self.assertFloatsAlmostEqual(f(x, y), predVal, msg=msg, atol=self.atol, rtol=None)
def matchBackgrounds(self, refExposure, sciExposure): """ Match science exposure's background level to that of reference exposure. Process creates a difference image of the reference exposure minus the science exposure, and then generates an afw.math.Background object. It assumes (but does not require/check) that the mask plane already has detections set. If detections have not been set/masked, sources will bias the background estimation. The 'background' of the difference image is smoothed by spline interpolation (by the Background class) or by polynomial interpolation by the Approximate class. This model of difference image is added to the science exposure in memory. Fit diagnostics are also calculated and returned. @param[in] refExposure: reference exposure @param[in,out] sciExposure: science exposure; modified by changing the background level to match that of the reference exposure @returns a pipBase.Struct with fields: - backgroundModel: an afw.math.Approximate or an afw.math.Background. - fitRMS: rms of the fit. This is the sqrt(mean(residuals**2)). - matchedMSE: the MSE of the reference and matched images: mean((refImage - matchedSciImage)**2); should be comparable to difference image's mean variance. - diffImVar: the mean variance of the difference image. """ if lsstDebug.Info(__name__).savefits: refExposure.writeFits( lsstDebug.Info(__name__).figpath + 'refExposure.fits') sciExposure.writeFits( lsstDebug.Info(__name__).figpath + 'sciExposure.fits') # Check Configs for polynomials: if self.config.usePolynomial: x, y = sciExposure.getDimensions() shortSideLength = min(x, y) if shortSideLength < self.config.binSize: raise ValueError( "%d = config.binSize > shorter dimension = %d" % (self.config.binSize, shortSideLength)) npoints = shortSideLength // self.config.binSize if shortSideLength % self.config.binSize != 0: npoints += 1 if self.config.order > npoints - 1: raise ValueError("%d = config.order > npoints - 1 = %d" % (self.config.order, npoints - 1)) # Check that exposures are same shape if (sciExposure.getDimensions() != refExposure.getDimensions()): wSci, hSci = sciExposure.getDimensions() wRef, hRef = refExposure.getDimensions() raise RuntimeError( "Exposures are different dimensions. sci:(%i, %i) vs. ref:(%i, %i)" % (wSci, hSci, wRef, hRef)) statsFlag = getattr(afwMath, self.config.gridStatistic) self.sctrl.setNumSigmaClip(self.config.numSigmaClip) self.sctrl.setNumIter(self.config.numIter) im = refExposure.getMaskedImage() diffMI = im.Factory(im, True) diffMI -= sciExposure.getMaskedImage() width = diffMI.getWidth() height = diffMI.getHeight() nx = width // self.config.binSize if width % self.config.binSize != 0: nx += 1 ny = height // self.config.binSize if height % self.config.binSize != 0: ny += 1 bctrl = afwMath.BackgroundControl(nx, ny, self.sctrl, statsFlag) bctrl.setUndersampleStyle(self.config.undersampleStyle) bctrl.setInterpStyle(self.config.interpStyle) bkgd = afwMath.makeBackground(diffMI, bctrl) # Some config and input checks if config.usePolynomial: # 1) Check that order/bin size make sense: # 2) Change binsize or order if underconstrained. if self.config.usePolynomial: order = self.config.order bgX, bgY, bgZ, bgdZ = self._gridImage(diffMI, self.config.binSize, statsFlag) minNumberGridPoints = min(len(set(bgX)), len(set(bgY))) if len(bgZ) == 0: raise ValueError("No overlap with reference. Nothing to match") elif minNumberGridPoints <= self.config.order: # must either lower order or raise number of bins or throw exception if self.config.undersampleStyle == "THROW_EXCEPTION": raise ValueError( "Image does not cover enough of ref image for order and binsize" ) elif self.config.undersampleStyle == "REDUCE_INTERP_ORDER": self.log.warn("Reducing order to %d" % (minNumberGridPoints - 1)) order = minNumberGridPoints - 1 elif self.config.undersampleStyle == "INCREASE_NXNYSAMPLE": newBinSize = (minNumberGridPoints * self.config.binSize ) // (self.config.order + 1) bctrl.setNxSample(newBinSize) bctrl.setNySample(newBinSize) bkgd = afwMath.makeBackground(diffMI, bctrl) # do over self.log.warn("Decreasing binsize to %d" % (newBinSize)) # If there is no variance in any image pixels, do not weight bins by inverse variance isUniformImageDiff = not numpy.any( bgdZ > self.config.gridStdevEpsilon) weightByInverseVariance = False if isUniformImageDiff else self.config.approxWeighting # Add offset to sciExposure try: if self.config.usePolynomial: actrl = afwMath.ApproximateControl( afwMath.ApproximateControl.CHEBYSHEV, order, order, weightByInverseVariance) undersampleStyle = getattr(afwMath, self.config.undersampleStyle) approx = bkgd.getApproximate(actrl, undersampleStyle) bkgdImage = approx.getImage() else: bkgdImage = bkgd.getImageF() except Exception as e: raise RuntimeError( "Background/Approximation failed to interp image %s: %s" % (self.debugDataIdString, e)) sciMI = sciExposure.getMaskedImage() sciMI += bkgdImage del sciMI # Need RMS from fit: 2895 will replace this: rms = 0.0 X, Y, Z, dZ = self._gridImage(diffMI, self.config.binSize, statsFlag) x0, y0 = diffMI.getXY0() modelValueArr = numpy.empty(len(Z)) for i in range(len(X)): modelValueArr[i] = bkgdImage.get(int(X[i] - x0), int(Y[i] - y0)) resids = Z - modelValueArr rms = numpy.sqrt(numpy.mean(resids[~numpy.isnan(resids)]**2)) if lsstDebug.Info(__name__).savefits: sciExposure.writeFits( lsstDebug.Info(__name__).figpath + 'sciMatchedExposure.fits') if lsstDebug.Info(__name__).savefig: bbox = afwGeom.Box2D(refExposure.getMaskedImage().getBBox()) try: self._debugPlot(X, Y, Z, dZ, bkgdImage, bbox, modelValueArr, resids) except Exception as e: self.log.warn('Debug plot not generated: %s' % (e)) meanVar = afwMath.makeStatistics(diffMI.getVariance(), diffMI.getMask(), afwMath.MEANCLIP, self.sctrl).getValue() diffIm = diffMI.getImage() diffIm -= bkgdImage # diffMI should now have a mean ~ 0 del diffIm mse = afwMath.makeStatistics(diffMI, afwMath.MEANSQUARE, self.sctrl).getValue() outBkgd = approx if self.config.usePolynomial else bkgd return pipeBase.Struct(backgroundModel=outBkgd, fitRMS=rms, matchedMSE=mse, diffImVar=meanVar)
def testChebyshev1Function2DTruncate(self): errMsg = ( "{} != {} = {} for x={}, xMin={}, xMax={}, xNorm={}," " yMin={}, yMax={}, yNorm={}, truncParams={}; order constructor") maxOrder = 6 deltaParam = 0.3 ranges = ((-1, 1), (-17, -2), (-65.3, 2.132)) xRangeIter = itertools.cycle(ranges) yRangeIter = itertools.cycle(ranges) next(yRangeIter) # make x and y ranges off from each other nPoints = 7 # number of points in x and y at which to test the functions for order in range(maxOrder + 1): xMin, xMax = next(xRangeIter) xMean = (xMin + xMax) / 2.0 xDelta = (xMax - xMin) / float(nPoints - 1) yMin, yMax = next(yRangeIter) yMean = (yMin + yMax) / 2.0 yDelta = (yMax - yMin) / float(nPoints - 1) xyRange = afwGeom.Box2D(afwGeom.Point2D(xMin, yMin), afwGeom.Point2D(xMax, yMax)) fullNParams = afwMath.Chebyshev1Function2D.nParametersFromOrder( order) fullParams = nrange(fullNParams, deltaParam, deltaParam) fullPoly = afwMath.Chebyshev1Function2D(fullParams, xyRange) for tooBigTruncOrder in range(order + 1, order + 3): with self.assertRaises(pexExceptions.InvalidParameterError): fullPoly.truncate(tooBigTruncOrder) for truncOrder in range(order + 1): truncNParams = fullPoly.nParametersFromOrder(truncOrder) truncParams = fullParams[0:truncNParams] f = fullPoly.truncate(truncOrder) self.assertEqual(f.getNParameters(), truncNParams) g = afwMath.Chebyshev1Function2D(fullParams[0:truncNParams], xyRange) self.assertEqual(f.getNParameters(), g.getNParameters()) self.assertEqual(f.getOrder(), truncOrder) self.assertEqual(f.getXYRange(), xyRange) self.assertEqual(g.getOrder(), truncOrder) self.assertEqual(g.getXYRange(), xyRange) minXNorm = None maxXNorm = None for x in np.arange(xMin, xMax + xDelta / 2.0, xDelta): xNorm = 2.0 * (x - xMean) / float(xMax - xMin) if minXNorm is None or xNorm < minXNorm: minXNorm = xNorm if maxXNorm is None or xNorm > maxXNorm: maxXNorm = xNorm minYNorm = None maxYNorm = None for y in np.arange(yMin, yMax + yDelta / 2.0, yDelta): yNorm = 2.0 * (y - yMean) / float(yMax - yMin) if minYNorm is None or yNorm < minYNorm: minYNorm = yNorm if maxYNorm is None or yNorm > maxYNorm: maxYNorm = yNorm msg = errMsg.format( type(f).__name__, f(x, y), g(x, y), type(g).__name__, x, xMin, xMax, xNorm, yMin, yMax, yNorm, truncParams) self.assertFloatsAlmostEqual(f(x, y), g(x, y), msg=msg) msg = self.normErr.format("y", yMin, yMax, minYNorm, maxYNorm) self.assertFloatsAlmostEqual(minYNorm, -1.0, msg=msg, atol=self.atol, rtol=None) self.assertFloatsAlmostEqual(maxYNorm, 1.0, msg=msg, atol=self.atol, rtol=None) msg = self.normErr.format("x", xMin, xMax, minXNorm, maxXNorm) self.assertFloatsAlmostEqual(minXNorm, -1.0, msg=msg, atol=self.atol, rtol=None) self.assertFloatsAlmostEqual(maxXNorm, 1.0, msg=msg, atol=self.atol, rtol=None)
def run(self, patchRef): """Select images for a region and report how many are in each tract and patch Also report quartiles of FWHM @param patchRef: data reference for coadd patch. @return: a pipeBase.Struct with fields: - exposureInfoList: a list of exposure info objects, as returned by the select subtask """ skyMap = patchRef.get(self.config.coaddName + "Coadd_skyMap") tractId = patchRef.dataId["tract"] patchIndex = [int(val) for val in patchRef.dataId["patch"].split(",")] tractInfo = skyMap[tractId] wcs = tractInfo.getWcs() patchInfo = tractInfo.getPatchInfo(patchIndex) posBox = afwGeom.Box2D(patchInfo.getOuterBBox()) coordList = [wcs.pixelToSky(pos) for pos in _getBox2DCorners(posBox)] skyPosList = [ coord.getPosition(afwGeom.degrees) for coord in coordList ] skyPosStrList = [ "(%0.3f, %0.3f)" % tuple(skyPos) for skyPos in skyPosList ] skyPosStr = ", ".join(skyPosStrList) print("PatchId=%s; corner RA/Dec (deg)=%s" % (patchRef.dataId, skyPosStr)) exposureInfoList = self.select.runDataRef( dataRef=patchRef, coordList=coordList, makeDataRefList=False, ).exposureInfoList print("Found %d suitable exposures" % (len(exposureInfoList), )) if len(exposureInfoList) < 1: return fwhmList = [exposureInfo.fwhm for exposureInfo in exposureInfoList] fwhmList = numpy.array(fwhmList, dtype=float) print("FWHM Q1=%0.2f Q2=%0.2f Q3=%0.2f" % ( numpy.percentile(fwhmList, 25.0), numpy.percentile(fwhmList, 50.0), numpy.percentile(fwhmList, 75.0), )) print("Image IDs:") if len(exposureInfoList) > 0: idKeys = sorted(exposureInfoList[0].dataId.keys()) for exposureInfo in exposureInfoList: idStr = " ".join("%s=%s" % (key, exposureInfo.dataId[key]) for key in idKeys) skyPosList = [ coord.getPosition(afwGeom.degrees) for coord in exposureInfo.coordList ] skyPosStrList = [ "(%0.3f, %0.3f)" % tuple(skyPos) for skyPos in skyPosList ] skyPosStr = ", ".join(skyPosStrList) print("dataId=%s; corner RA/Dec (deg)=%s" % (idStr, skyPosStr)) return pipeBase.Struct(exposureInfoList=exposureInfoList)
def testInputCounts(self, showPlot=False): # Generate a simulated coadd of four overlapping-but-offset CCDs. # Populate it with three sources. # Demonstrate that we can correctly recover the number of images which # contribute to each source. size = 20 # Size of images (pixels) value = 100.0 # Source flux ccdPositions = [ afwGeom.Point2D(8, 0), afwGeom.Point2D(10, 10), afwGeom.Point2D(-8, -8), afwGeom.Point2D(-8, 8) ] # Represent sources by a tuple of position and expected number of # contributing CCDs (based on the size/positions given above). Source = namedtuple("Source", ["pos", "count"]) sources = [ Source(pos=afwGeom.Point2D(6, 6), count=2), Source(pos=afwGeom.Point2D(10, 10), count=3), Source(pos=afwGeom.Point2D(14, 14), count=1) ] # These lines are used in the creation of WCS information cdMatrix = (1.0e-5, 0.0, 0.0, 1.0e-5) crval = afwCoord.Coord(0.0 * afwGeom.degrees, 0.0 * afwGeom.degrees) # Construct the info needed to set the exposure object imageBox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(size, size)) wcsRef = afwImage.makeWcs(crval, afwGeom.Point2D(0, 0), *cdMatrix) # Create the exposure object, and set it up to be the output of a coadd exp = afwImage.ExposureF(size, size) exp.setWcs(wcsRef) exp.getInfo().setCoaddInputs( afwImage.CoaddInputs(afwTable.ExposureTable.makeMinimalSchema(), afwTable.ExposureTable.makeMinimalSchema())) # Set the fake CCDs that "went into" making this coadd, using the # differing wcs objects created above. ccds = exp.getInfo().getCoaddInputs().ccds for pos in ccdPositions: record = ccds.addNew() record.setWcs(afwImage.makeWcs(crval, pos, *cdMatrix)) record.setBBox(imageBox) record.setValidPolygon(Polygon(afwGeom.Box2D(imageBox))) # Configure a SingleFrameMeasurementTask to run InputCounts. measureSourcesConfig = measBase.SingleFrameMeasurementConfig() measureSourcesConfig.plugins.names = [ "base_PeakCentroid", "base_InputCount" ] measureSourcesConfig.slots.centroid = "base_PeakCentroid" measureSourcesConfig.slots.psfFlux = None measureSourcesConfig.slots.apFlux = None measureSourcesConfig.slots.modelFlux = None measureSourcesConfig.slots.instFlux = None measureSourcesConfig.slots.calibFlux = None measureSourcesConfig.slots.shape = None measureSourcesConfig.validate() schema = afwTable.SourceTable.makeMinimalSchema() task = measBase.SingleFrameMeasurementTask(schema, config=measureSourcesConfig) catalog = afwTable.SourceCatalog(schema) # Add simulated sources to the measurement catalog. for src in sources: spans = afwGeom.SpanSet.fromShape(1) spans = spans.shiftedBy(int(src.pos.getX()), int(src.pos.getY())) foot = afwDetection.Footprint(spans) peak = foot.getPeaks().addNew() peak.setFx(src.pos[0]) peak.setFy(src.pos[1]) peak.setPeakValue(value) catalog.addNew().setFootprint(foot) task.run(catalog, exp) for src, rec in zip(sources, catalog): self.assertEqual(rec.get("base_InputCount_value"), src.count) if display: ccdVennDiagram(exp)
def get(self, center_coord, width, height, filt, units): """Merge multiple patches from a SkyMap into a single image. This function takes advantage of the fact that all tracts in a SkyMap share the same WCS and should have identical pixels where they overlap. @center_coord: base coordinate of the box RA and Dec (minimum values, lower left corner) @width: in arcsec or pixel @height: in arcsec or pixel @filt: valid filter for the data set such as 'i', 'r', 'g' @units: 'pixel' or 'arcsecond' (defaults to 'pixel') """ dest_tract_info = self._skymap.findTract(center_coord) dest_wcs = dest_tract_info.getWcs() # Determine target area. if units != "arcsec" and units != "arcsecond" and units != "pixel": units = "pixel" dest_bbox = self._bbox_for_coords(dest_wcs, center_coord, width, height, units) dest_corner_coords = [ dest_wcs.pixelToSky(pixPos) for pixPos in afw_geom.Box2D(dest_bbox).getCorners() ] # Collect patches of the SkyMap that are in the target region. # Create source exposures from the patches within each tract # as all patches from a tract share a WCS. exposure_list = [] tract_patch_list = self._skymap.findTractPatchList(dest_corner_coords) for j, tract_patch in enumerate(tract_patch_list): tract_info = tract_patch[0] patch_list = tract_patch[1] self._log.info("tract_info[{}]={}".format(j, tract_info)) self._log.info("patch_list[{}]={}".format(j, patch_list)) src_wcs = tract_info.getWcs() src_bbox = afw_geom.Box2I() for patch_info in patch_list: src_bbox.include(patch_info.getOuterBBox()) src_exposure = afw_image.ExposureF(src_bbox, src_wcs) # blank, so far exposure_list.append(src_exposure) # load srcExposures with patches tract_id = tract_info.getId() for patch_info in patch_list: patch_index = patch_info.getIndex() patch_index_str = ','.join(str(i) for i in patch_index) self._log.info("butler.get dataId=filter:{}, tract:{}, " "patch:{}".format(filt, tract_id, patch_index_str)) patch_exposure = self._butler.get("deepCoadd", dataId={ "filter": filt, "tract": tract_id, "patch": patch_index_str }) src_view = afw_image.ExposureF(src_exposure, patch_exposure.getBBox()) src_view_img = src_view.getMaskedImage() patch_img = patch_exposure.getMaskedImage() src_view_img[:] = patch_img # Copy the pixels from the source exposures to the destination exposures. dest_exposure_list = [] for j, src_exposure in enumerate(exposure_list): src_image = src_exposure.getMaskedImage() src_wcs = src_exposure.getWcs() if j == 0: expo_bbox = dest_bbox # dest_bbox only correct for first image else: # Determine the correct BBox (in pixels) for the current src_wcs ll_corner = afw_geom.Point2I( src_wcs.skyToPixel(dest_corner_coords[0])) ur_corner = afw_geom.Point2I( src_wcs.skyToPixel(dest_corner_coords[2])) # Handle negative values for in expo_bbox. if ll_corner.getX() < 0: ll_corner.setX(0) if ll_corner.getY() < 0: ll_corner.setY(0) if ur_corner.getX() < 0: ur_corner.setX(0) self._log.warn("getSkyMap negative X for ur_corner") if ur_corner.getY() < 0: ur_corner.setY(0) self._log.warn("getSkyMap negative Y for ur_corner") expo_bbox = afw_geom.Box2I(ll_corner, ur_corner) self._log.info("j={} expo_bbox={} sBBox={}".format( j, expo_bbox, src_exposure.getBBox())) dest_exposure = afw_image.ExposureF(expo_bbox, src_wcs) dest_img = dest_exposure.getMaskedImage() begin_x = expo_bbox.getBeginX() - src_image.getX0() end_x = expo_bbox.getEndX() - src_image.getX0() begin_y = expo_bbox.getBeginY() - src_image.getY0() end_y = expo_bbox.getEndY() - src_image.getY0() new_width = src_exposure.getBBox().getEndX() - expo_bbox.getBeginX( ) new_height = src_exposure.getBBox().getEndY( ) - expo_bbox.getBeginY() # Do a final check to make sure that the we're not going past the end of src_image. src_img_len_x = src_image.getWidth() if end_x > src_img_len_x: new_width = src_img_len_x - begin_x end_x = src_img_len_x s_img_len_y = src_image.getHeight() if end_y > s_img_len_y: new_width = s_img_len_y - begin_y end_y = s_img_len_y self._log.debug("begin_x={} end_x={}".format(begin_x, end_x)) self._log.debug( "new_width{} = sBBox.EndX{} - sBBox.BeginX{}".format( new_width, src_exposure.getBBox().getEndX(), expo_bbox.getBeginX())) self._log.debug("begin_y={} end_y={}".format(begin_y, end_y)) self._log.debug( "new_height{} = sBBox.EndY{} - sBBox.BeginY{}".format( new_height, src_exposure.getBBox().getEndY(), expo_bbox.getBeginY())) dest_img[0:new_width, 0:new_height] = src_image[begin_x:end_x, begin_y:end_y] dest_exposure_list.append(dest_exposure) # If there's only one exposure in the list (and there usually is) just return it. if len(dest_exposure_list) == 1: return dest_exposure_list[0] # Need to stitch together the multiple destination exposures. self._log.debug( "SkymapImage: stitching together multiple destExposures") warper_config = afw_math.WarperConfig() warper = afw_math.Warper.fromConfig(warper_config) stitched_exposure = self._stitch_exposures_good_pixel_copy( dest_wcs, dest_bbox, dest_exposure_list, warper) return stitched_exposure
def ccdVennDiagram(exp, showImage=True, legendLocation='best'): ''' Create a figure with the bounding boxes for each of the images which go into a coadd, over-plotting the given exposure object. @param[in] exp (Exposure) The exposure object to plot, must be the product of a coadd Optional: @param[in] showImage (Bool) Plot image data in addition to it's bounding box, default True @param[in] legendLocation (String) Matplotlib legend location code, can be: 'best', 'upper right', 'upper left', 'lower left', 'lower right', 'right', center left', 'center right', 'lower center', 'upper center', 'center' ''' # Create the figure object fig = plt.figure() # Use all the built in matplotib line style attributes to create a list of the possible styles linestyles = ['solid', 'dashed', 'dashdot', 'dotted'] colors = ['b', 'g', 'r', 'c', 'm', 'y', 'k'] # Calculate the cartisian product of the styles, and randomize the order, to help each CCD get # it's own color pcomb = np.random.permutation(list(itertools.product(colors, linestyles))) # Filter out a black solid box, as that will be the style of the given exp object pcomb = pcomb[((pcomb[:, 0] == 'k') * (pcomb[:, 1] == 'solid')) is False] # Get the image properties origin = afwGeom.PointD(exp.getXY0()) mainBox = exp.getBBox().getCorners() # Plot the exposure plt.gca().add_patch( patches.Rectangle((0, 0), *list(mainBox[2] - mainBox[0]), fill=False, label="exposure")) # Grab all of the CCDs that went into creating the exposure ccds = exp.getInfo().getCoaddInputs().ccds # Loop over and plot the extents of each ccd for i, ccd in enumerate(ccds): ccdBox = afwGeom.Box2D(ccd.getBBox()) ccdCorners = ccdBox.getCorners() coaddCorners = [ exp.getWcs().skyToPixel(ccd.getWcs().pixelToSky(point)) + (afwGeom.PointD() - origin) for point in ccdCorners ] plt.gca().add_patch( patches.Rectangle(coaddCorners[0], *list(coaddCorners[2] - coaddCorners[0]), fill=False, color=pcomb[i][0], ls=pcomb[i][1], label="CCD{}".format(i))) # If showImage is true, plot the data contained in exp as well as the boundrys if showImage: plt.imshow(exp.getMaskedImage().getArrays()[0], cmap='Greys', origin='lower') plt.colorbar() # Adjust plot parameters and plot plt.gca().relim() plt.gca().autoscale_view() ylim = plt.gca().get_ylim() xlim = plt.gca().get_xlim() plt.gca().set_ylim(1.5 * ylim[0], 1.5 * ylim[1]) plt.gca().set_xlim(1.5 * xlim[0], 1.5 * xlim[1]) plt.legend(loc=legendLocation) fig.canvas.draw() plt.show()
def main(rootDir, tract, visits, ccds=None, ccdKey='ccd', showPatch=False, saveFile=None, showCcds=False): butler = dafPersist.Butler(rootDir) camera = butler.get("camera") # draw the CCDs ras, decs = [], [] bboxesPlotted = [] for i_v, visit in enumerate(visits): print("%r visit=%r" % (i_v, visit)) for ccd in camera: bbox = ccd.getBBox() ccdId = int(ccd.getSerial()) if (ccds is None or ccdId in ccds) and ccd.getType() is cameraGeom.SCIENCE: dataId = {'visit': visit, ccdKey: ccdId} try: md = butler.get("calexp_md", dataId) wcs = afwImage.makeWcs(md) ra, dec = bboxToRaDec(bbox, wcs) ras += ra decs += dec color = ('r', 'b', 'c', 'g', 'm')[i_v % 5] pyplot.fill(ra, dec, fill=True, alpha=0.2, color=color, edgecolor=color) # add CCD serial numbers if showCcds: minPoint = afwGeom.Point2D(min(ra), min(dec)) maxPoint = afwGeom.Point2D(max(ra), max(dec)) # Use doubles in Box2D to check overlap bboxDouble = afwGeom.Box2D(minPoint, maxPoint) overlaps = [ not bboxDouble.overlaps(otherBbox) for otherBbox in bboxesPlotted ] if all(overlaps): pyplot.text(percent(ra), percent(dec), str(ccdId), fontsize=6, horizontalalignment='center', verticalalignment='center', color=color) pyplot.fill(ra, dec, fill=False, alpha=0.5, color=color, edgecolor=color) bboxesPlotted.append(bboxDouble) except: pass buff = 0.1 xlim = max(ras) + buff, min(ras) - buff ylim = min(decs) - buff, max(decs) + buff # draw the skymap if showPatch: skymap = butler.get('deepCoadd_skyMap', {'tract': 0}) for tract in skymap: for patch in tract: ra, dec = bboxToRaDec(patch.getInnerBBox(), tract.getWcs()) pyplot.fill(ra, dec, fill=False, edgecolor='k', lw=1, linestyle='dashed') if xlim[1] < percent(ra) < xlim[0] and ylim[0] < percent( dec) < ylim[1]: pyplot.text(percent(ra), percent(dec, 0.9), str(patch.getIndex()), fontsize=6, horizontalalignment='center', verticalalignment='top') # add labels and save ax = pyplot.gca() ax.set_xlabel("R.A. (deg)") ax.set_ylabel("Decl. (deg)") ax.set_xlim(xlim) ax.set_ylim(ylim) fig = pyplot.gcf() if saveFile is not None: fig.savefig(saveFile) else: fig.show()
def run(self, exposure, sensorRef, templateIdList=None): """!Retrieve and mosaic a template coadd exposure that overlaps the exposure \param[in] exposure -- an exposure for which to generate an overlapping template \param[in] sensorRef -- a Butler data reference that can be used to obtain coadd data \param[in] templateIdList -- list of data ids (unused) \return a pipeBase.Struct - exposure: a template coadd exposure assembled out of patches - sources: None for this subtask """ skyMap = sensorRef.get(datasetType=self.config.coaddName + "Coadd_skyMap") expWcs = exposure.getWcs() expBoxD = afwGeom.Box2D(exposure.getBBox()) expBoxD.grow(self.config.templateBorderSize) ctrSkyPos = expWcs.pixelToSky(expBoxD.getCenter()) tractInfo = skyMap.findTract(ctrSkyPos) self.log.info("Using skyMap tract %s" % (tractInfo.getId(), )) skyCorners = [ expWcs.pixelToSky(pixPos) for pixPos in expBoxD.getCorners() ] patchList = tractInfo.findPatchList(skyCorners) if not patchList: raise RuntimeError("No suitable tract found") self.log.info("Assembling %s coadd patches" % (len(patchList), )) # compute coadd bbox coaddWcs = tractInfo.getWcs() coaddBBox = afwGeom.Box2D() for skyPos in skyCorners: coaddBBox.include(coaddWcs.skyToPixel(skyPos)) coaddBBox = afwGeom.Box2I(coaddBBox) self.log.info("exposure dimensions=%s; coadd dimensions=%s" % (exposure.getDimensions(), coaddBBox.getDimensions())) # assemble coadd exposure from subregions of patches coaddExposure = afwImage.ExposureF(coaddBBox, coaddWcs) coaddExposure.getMaskedImage().set(np.nan, afwImage.Mask\ .getPlaneBitMask("NO_DATA"), np.nan) nPatchesFound = 0 coaddFilter = None coaddPsf = None for patchInfo in patchList: patchSubBBox = patchInfo.getOuterBBox() patchSubBBox.clip(coaddBBox) patchArgDict = dict( datasetType=self.config.coaddName + "Coadd_sub", bbox=patchSubBBox, tract=tractInfo.getId(), patch="%s,%s" % (patchInfo.getIndex()[0], patchInfo.getIndex()[1]), ) if patchSubBBox.isEmpty(): self.log.info( "skip tract=%(tract)s, patch=%(patch)s; no overlapping pixels" % patchArgDict) continue if not sensorRef.datasetExists(**patchArgDict): self.log.warn( "%(datasetType)s, tract=%(tract)s, patch=%(patch)s does not exist" % patchArgDict) continue nPatchesFound += 1 self.log.info("Reading patch %s" % patchArgDict) coaddPatch = sensorRef.get(**patchArgDict) coaddExposure.getMaskedImage().assign(coaddPatch.getMaskedImage(), coaddPatch.getBBox()) if coaddFilter is None: coaddFilter = coaddPatch.getFilter() # Retrieve the PSF for this coadd tract, if not already retrieved if coaddPsf is None and coaddPatch.hasPsf(): coaddPsf = coaddPatch.getPsf() if nPatchesFound == 0: raise RuntimeError("No patches found!") if coaddPsf is None: raise RuntimeError("No coadd Psf found!") coaddExposure.setPsf(coaddPsf) coaddExposure.setFilter(coaddFilter) return pipeBase.Struct(exposure=coaddExposure, sources=None)
def plot_skymap_tract(skyMap, tract=0, title=None, ax=None, nvisits_tab=None): """ Plot a tract from a skyMap. Parameters ---------- skyMap: lsst.skyMap.SkyMap The SkyMap object containing the tract and patch information. tract: int [0] The tract id of the desired tract to plot. title: str [None] Title of the tract plot. If None, the use `tract <id>`. ax: matplotlib.axes._subplots.AxesSubplot [None] The subplot object to contain the tract plot. If None, then make a new one. Returns ------- matplotlib.axes._subplots.AxesSubplot: The subplot containing the tract plot. """ if title is None: title = 'tract {}'.format(tract) tractInfo = skyMap[tract] tractBox = afwGeom.Box2D(tractInfo.getBBox()) tractPosList = tractBox.getCorners() wcs = tractInfo.getWcs() xNum, yNum = tractInfo.getNumPatches() if ax is None: fig = plt.figure(figsize=(12, 8)) ax = fig.add_subplot(111) tract_center = wcs.pixelToSky(tractBox.getCenter())\ .getPosition(afwGeom.degrees) ax.text(tract_center[0], tract_center[1], '%d' % tract, size=16, ha="center", va="center", color='blue') for x in range(xNum): for y in range(yNum): patchInfo = tractInfo.getPatchInfo([x, y]) patchBox = afwGeom.Box2D(patchInfo.getOuterBBox()) pixelPatchList = patchBox.getCorners() path = make_patch(pixelPatchList, wcs) patch = patches.PathPatch(path, alpha=0.1, lw=1) ax.add_patch(patch) center = wcs.pixelToSky(patchBox.getCenter())\ .getPosition(afwGeom.degrees) if nvisits_tab is not None: nvisits = nvisits_tab[(nvisits_tab.tract == tract) & (nvisits_tab.patch_x == x) & (nvisits_tab.patch_y == y)].nvisits ax.text(center[0], center[1], '%d' % nvisits, size=6, ha="center", va="center") else: ax.text(center[0], center[1], '%d,%d' % (x, y), size=6, ha="center", va="center") skyPosList = [ wcs.pixelToSky(pos).getPosition(afwGeom.degrees) for pos in tractPosList ] ax.set_xlim( max(coord[0] for coord in skyPosList) + 1, min(coord[0] for coord in skyPosList) - 1) ax.set_ylim( min(coord[1] for coord in skyPosList) - 1, max(coord[1] for coord in skyPosList) + 1) ax.grid(ls=':', color='gray') ax.set_xlabel("RA (deg.)") ax.set_ylabel("Dec (deg.)") ax.set_title(title) return ax
def plotKernelCoefficients(spatialKernel, kernelCellSet, showBadCandidates=False, keepPlots=True): """Plot the individual kernel candidate and the spatial kernel solution coefficients. Parameters ---------- spatialKernel : `lsst.afw.math.LinearCombinationKernel` The spatial spatialKernel solution model which is a spatially varying linear combination of the spatialKernel basis functions. Typically returned by `lsst.ip.diffim.SpatialKernelSolution.getSolutionPair()`. kernelCellSet : `lsst.afw.math.SpatialCellSet` The spatial cells that was used for solution for the spatialKernel. They contain the local solutions of the AL kernel for the selected sources. showBadCandidates : `bool`, optional If True, plot the coefficient values for kernel candidates where the solution was marked bad by the numerical algorithm. Defaults to False. keepPlots: `bool`, optional If True, sets ``plt.show()`` to be called before the task terminates, so that the plots can be explored interactively. Defaults to True. Notes ----- This function produces 3 figures per image subtraction operation. * A grid plot of the local solutions. Each grid cell corresponds to a proportional area in the image. In each cell, local kernel solution coefficients are plotted of kernel candidates (color) that fall into this area as a function of the kernel basis function number. * A grid plot of the spatial solution. Each grid cell corresponds to a proportional area in the image. In each cell, the spatial solution coefficients are evaluated for the center of the cell. * Histogram of the local solution coefficients. Red line marks the spatial solution value at center of the image. This function is called if ``lsst.ip.diffim.psfMatch.plotKernelCoefficients==True`` in lsstDebug. This function was implemented as part of DM-17825. """ try: import matplotlib.pyplot as plt except ImportError as e: print("Unable to import matplotlib: %s" % e) return # Image dimensions imgBBox = kernelCellSet.getBBox() x0 = imgBBox.getBeginX() y0 = imgBBox.getBeginY() wImage = imgBBox.getWidth() hImage = imgBBox.getHeight() imgCenterX = imgBBox.getCenterX() imgCenterY = imgBBox.getCenterY() # Plot the local solutions # ---- # Grid size nX = 8 nY = 8 wCell = wImage / nX hCell = hImage / nY fig = plt.figure() fig.suptitle("Kernel candidate parameters on an image grid") arrAx = fig.subplots(nrows=nY, ncols=nX, sharex=True, sharey=True, gridspec_kw=dict(wspace=0, hspace=0)) # Bottom left panel is for bottom left part of the image arrAx = arrAx[::-1, :] allParams = [] for cell in kernelCellSet.getCellList(): cellBBox = afwGeom.Box2D(cell.getBBox()) # Determine which panel this spatial cell belongs to iX = int((cellBBox.getCenterX() - x0) // wCell) iY = int((cellBBox.getCenterY() - y0) // hCell) for cand in cell.begin(False): try: kernel = cand.getKernel(cand.ORIG) except Exception: continue if not showBadCandidates and cand.isBad(): continue nKernelParams = kernel.getNKernelParameters() kernelParams = np.array(kernel.getKernelParameters()) allParams.append(kernelParams) if cand.isBad(): color = 'red' else: color = None arrAx[iY, iX].plot(np.arange(nKernelParams), kernelParams, '.-', color=color, drawstyle='steps-mid', linewidth=0.1) for ax in arrAx.ravel(): ax.grid(True, axis='y') # Plot histogram of the local parameters and the global solution at the image center # ---- spatialFuncs = spatialKernel.getSpatialFunctionList() nKernelParams = spatialKernel.getNKernelParameters() nX = 8 fig = plt.figure() fig.suptitle( "Hist. of parameters marked with spatial solution at img center") arrAx = fig.subplots(nrows=int(nKernelParams // nX) + 1, ncols=nX) arrAx = arrAx[::-1, :] allParams = np.array(allParams) for k in range(nKernelParams): ax = arrAx.ravel()[k] ax.hist(allParams[:, k], bins=20, edgecolor='black') ax.set_xlabel('P{}'.format(k)) valueParam = spatialFuncs[k](imgCenterX, imgCenterY) ax.axvline(x=valueParam, color='red') ax.text(0.1, 0.9, '{:.1f}'.format(valueParam), transform=ax.transAxes, backgroundcolor='lightsteelblue') # Plot grid of the spatial solution # ---- nX = 8 nY = 8 wCell = wImage / nX hCell = hImage / nY x0 += wCell / 2 y0 += hCell / 2 fig = plt.figure() fig.suptitle("Spatial solution of kernel parameters on an image grid") arrAx = fig.subplots(nrows=nY, ncols=nX, sharex=True, sharey=True, gridspec_kw=dict(wspace=0, hspace=0)) arrAx = arrAx[::-1, :] kernelParams = np.zeros(nKernelParams, dtype=float) for iX in range(nX): for iY in range(nY): x = x0 + iX * wCell y = y0 + iY * hCell # Evaluate the spatial solution functions for this x,y location kernelParams = [f(x, y) for f in spatialFuncs] arrAx[iY, iX].plot(np.arange(nKernelParams), kernelParams, '.-', drawstyle='steps-mid') arrAx[iY, iX].grid(True, axis='y') global keptPlots if keepPlots and not keptPlots: # Keep plots open when done def show(): print("%s: Please close plots when done." % __name__) try: plt.show() except Exception: pass print("Plots closed, exiting...") import atexit atexit.register(show) keptPlots = True
def main(rootDir, tracts, visits, ccds=None, ccdKey='ccd', showPatch=False, saveFile=None, showCcds=False): butler = dafPersist.Butler(rootDir) camera = butler.get("camera") # draw the CCDs ras, decs = [], [] bboxesPlotted = [] print('Number of visits = ', len(visits)) cmap = get_cmap(len(visits)) for i_v, visit in enumerate(visits): print("%r visit=%r" % (i_v + 1, visit)) inLegend = False color = cmap(i_v) for ccd in camera: bbox = ccd.getBBox() ccdId = int(ccd.getId()) if (ccds is None or ccdId in ccds) and ccd.getType() == cameraGeom.SCIENCE: dataId = {'visit': visit, ccdKey: ccdId} try: md = butler.get("calexp_md", dataId) wcs = afwGeom.makeSkyWcs(md) ra, dec = bboxToRaDec(bbox, wcs) ras += ra decs += dec if not inLegend: pyplot.fill(ra, dec, fill=True, alpha=0.2, color=color, edgecolor=color, label=str(visit)) inLegend = True else: pyplot.fill(ra, dec, fill=True, alpha=0.2, color=color, edgecolor=color) # add CCD serial numbers if showCcds: minPoint = afwGeom.Point2D(min(ra), min(dec)) maxPoint = afwGeom.Point2D(max(ra), max(dec)) # Use doubles in Box2D to check overlap bboxDouble = afwGeom.Box2D(minPoint, maxPoint) overlaps = [ not bboxDouble.overlaps(otherBbox) for otherBbox in bboxesPlotted ] if all(overlaps): pyplot.text(percent(ra), percent(dec), str(ccdId), fontsize=6, horizontalalignment='center', verticalalignment='center', color=color) pyplot.fill(ra, dec, fill=False, alpha=0.5, color=color, edgecolor=color) bboxesPlotted.append(bboxDouble) except Exception: pass buff = 0.1 xlim = max(ras) + buff, min(ras) - buff ylim = min(decs) - buff, max(decs) + buff skymap = butler.get("deepCoadd_skyMap") # draw the skymap if showPatch: alpha0 = 1.0 for i_t, tract in enumerate(tracts): alpha = max(0.1, alpha0 - i_t * 1.0 / len(tracts)) inLegend = False for tractInfo in skymap: if tractInfo.getId() == tract: for patch in tractInfo: ra, dec = bboxToRaDec(patch.getInnerBBox(), tractInfo.getWcs()) if not inLegend: pyplot.fill(ra, dec, fill=False, edgecolor='k', lw=1, linestyle='dashed', alpha=alpha, label=str(tract)) inLegend = True else: pyplot.fill(ra, dec, fill=False, edgecolor='k', lw=1, linestyle='dashed', alpha=alpha) if xlim[1] < percent(ra) < xlim[0] and ylim[ 0] < percent(dec) < ylim[1]: pyplot.text(percent(ra), percent(dec), ((str(patch.getIndex()))[1:-1].replace( " ", "")), fontsize=6, horizontalalignment='center', verticalalignment='center', alpha=alpha) # add labels and save ax = pyplot.gca() ax.set_xlabel("R.A. (deg)") ax.set_ylabel("Decl. (deg)") ax.set_xlim(xlim) ax.set_ylim(ylim) ax.legend(loc='center left', bbox_to_anchor=(1.0, 0.5), fancybox=True, shadow=True, fontsize=6) fig = pyplot.gcf() if saveFile is not None: fig.savefig(saveFile) else: fig.show()
def runDataRef(self, dataRef, coordList, makeDataRefList=True, selectDataList=[]): """Select images in the selectDataList that overlap the patch We use the "convexHull" function in the geom package to define polygons on the celestial sphere, and test the polygon of the patch for overlap with the polygon of the image. We use "convexHull" instead of generating a SphericalConvexPolygon directly because the standard for the inputs to SphericalConvexPolygon are pretty high and we don't want to be responsible for reaching them. If "convexHull" is found to be too slow, we can revise this. @param dataRef: Data reference for coadd/tempExp (with tract, patch) @param coordList: List of Coord specifying boundary of patch @param makeDataRefList: Construct a list of data references? @param selectDataList: List of SelectStruct, to consider for selection """ from lsst.geom import convexHull dataRefList = [] exposureInfoList = [] patchVertices = [coord.getVector() for coord in coordList] patchPoly = convexHull(patchVertices) for data in selectDataList: dataRef = data.dataRef imageWcs = data.wcs nx, ny = data.dims imageBox = afwGeom.Box2D(afwGeom.Point2D(0, 0), afwGeom.Extent2D(nx, ny)) try: imageCorners = [ imageWcs.pixelToSky(pix) for pix in imageBox.getCorners() ] except (pexExceptions.DomainError, pexExceptions.RuntimeError) as e: # Protecting ourselves from awful Wcs solutions in input images self.log.debug( "WCS error in testing calexp %s (%s): deselecting", dataRef.dataId, e) continue imagePoly = convexHull( [coord.getVector() for coord in imageCorners]) if imagePoly is None: self.log.debug( "Unable to create polygon from image %s: deselecting", dataRef.dataId) continue if patchPoly.intersects( imagePoly ): # "intersects" also covers "contains" or "is contained by" self.log.info("Selecting calexp %s" % dataRef.dataId) dataRefList.append(dataRef) exposureInfoList.append( BaseExposureInfo(dataRef.dataId, imageCorners)) return pipeBase.Struct( dataRefList=dataRefList if makeDataRefList else None, exposureInfoList=exposureInfoList, )
for tractOverlap in (0.0, 0.33, 1.0, 3.5): print("tractOverlap = %s degrees" % (tractOverlap,)) config = DodecaSkyMap.ConfigClass() config.tractOverlap = tractOverlap skyMap = DodecaSkyMap(config) totNumPix = 0 print("Tract Ctr RA Ctr Dec Rows Cols # Pix Width Height") print(" ID (deg) (deg) (pix) (pix) (deg) (deg)") for tractInfo in skyMap: bbox = tractInfo.getBBox() dimensions = bbox.getDimensions() numPix = dimensions[0] * dimensions[1] totNumPix += numPix wcs = tractInfo.getWcs() posBBox = afwGeom.Box2D(bbox) ctrPixPos = posBBox.getCenter() ctrCoord = wcs.pixelToSky(ctrPixPos) ctrSkyPosDeg = ctrCoord.getPosition(afwGeom.degrees) leftCoord = wcs.pixelToSky(afwGeom.Point2D(posBBox.getMinX(), ctrPixPos[1])) rightCoord = wcs.pixelToSky(afwGeom.Point2D(posBBox.getMaxX(), ctrPixPos[1])) topCoord = wcs.pixelToSky(afwGeom.Point2D(ctrPixPos[0], posBBox.getMinY())) bottomCoord = wcs.pixelToSky(afwGeom.Point2D(ctrPixPos[0], posBBox.getMaxY())) xSpan = leftCoord.separation(rightCoord).asDegrees() ySpan = bottomCoord.separation(topCoord).asDegrees() print("%3d %7.1f %7.1f %10.2e %10.2e %10.1e %6.1f %6.1f" % (tractInfo.getId(), ctrSkyPosDeg[0], ctrSkyPosDeg[1], dimensions[0], dimensions[1], numPix, xSpan, ySpan)) pixelScaleRad = afwGeom.Angle(skyMap.config.pixelScale, afwGeom.arcseconds).asRadians() nomPixelAreaRad2 = pixelScaleRad**2 # nominal area of a pixel in rad^2
def run(self, sensorRef, templateIdList=None): """Subtract an image from a template coadd and measure the result Steps include: - warp template coadd to match WCS of image - PSF match image to warped template - subtract image from PSF-matched, warped template - persist difference image - detect sources - measure sources @param sensorRef: sensor-level butler data reference, used for the following data products: Input only: - calexp - psf - ccdExposureId - ccdExposureId_bits - self.config.coaddName + "Coadd_skyMap" - self.config.coaddName + "Coadd" Input or output, depending on config: - self.config.coaddName + "Diff_subtractedExp" Output, depending on config: - self.config.coaddName + "Diff_matchedExp" - self.config.coaddName + "Diff_src" @return pipe_base Struct containing these fields: - subtractedExposure: exposure after subtracting template; the unpersisted version if subtraction not run but detection run None if neither subtraction nor detection run (i.e. nothing useful done) - subtractRes: results of subtraction task; None if subtraction not run - sources: detected and possibly measured sources; None if detection not run """ self.log.info("Processing %s" % (sensorRef.dataId)) # initialize outputs and some intermediate products subtractedExposure = None subtractRes = None selectSources = None kernelSources = None controlSources = None diaSources = None # We make one IdFactory that will be used by both icSrc and src datasets; # I don't know if this is the way we ultimately want to do things, but at least # this ensures the source IDs are fully unique. expBits = sensorRef.get("ccdExposureId_bits") expId = long(sensorRef.get("ccdExposureId")) idFactory = afwTable.IdFactory.makeSource(expId, 64 - expBits) # Retrieve the science image we wish to analyze exposure = sensorRef.get("calexp", immediate=True) if self.config.doAddCalexpBackground: mi = exposure.getMaskedImage() mi += sensorRef.get("calexpBackground").getImage() if not exposure.hasPsf(): raise pipeBase.TaskError("Exposure has no psf") sciencePsf = exposure.getPsf() subtractedExposureName = self.config.coaddName + "Diff_differenceExp" templateExposure = None # Stitched coadd exposure templateSources = None # Sources on the template image if self.config.doSubtract: print templateIdList template = self.getTemplate.run(exposure, sensorRef, templateIdList=templateIdList) templateExposure = template.exposure templateSources = template.sources # compute scienceSigmaOrig: sigma of PSF of science image before pre-convolution ctr = afwGeom.Box2D(exposure.getBBox()).getCenter() psfAttr = PsfAttributes(sciencePsf, afwGeom.Point2I(ctr)) scienceSigmaOrig = psfAttr.computeGaussianWidth( psfAttr.ADAPTIVE_MOMENT) # sigma of PSF of template image before warping ctr = afwGeom.Box2D(templateExposure.getBBox()).getCenter() psfAttr = PsfAttributes(templateExposure.getPsf(), afwGeom.Point2I(ctr)) templateSigma = psfAttr.computeGaussianWidth( psfAttr.ADAPTIVE_MOMENT) # if requested, convolve the science exposure with its PSF # (properly, this should be a cross-correlation, but our code does not yet support that) # compute scienceSigmaPost: sigma of science exposure with pre-convolution, if done, # else sigma of original science exposure if self.config.doPreConvolve: convControl = afwMath.ConvolutionControl() # cannot convolve in place, so make a new MI to receive convolved image srcMI = exposure.getMaskedImage() destMI = srcMI.Factory(srcMI.getDimensions()) srcPsf = sciencePsf if self.config.useGaussianForPreConvolution: # convolve with a simplified PSF model: a double Gaussian kWidth, kHeight = sciencePsf.getLocalKernel( ).getDimensions() preConvPsf = SingleGaussianPsf(kWidth, kHeight, scienceSigmaOrig) else: # convolve with science exposure's PSF model preConvPsf = srcPsf afwMath.convolve(destMI, srcMI, preConvPsf.getLocalKernel(), convControl) exposure.setMaskedImage(destMI) scienceSigmaPost = scienceSigmaOrig * math.sqrt(2) else: scienceSigmaPost = scienceSigmaOrig # If requested, find sources in the image if self.config.doSelectSources: if not sensorRef.datasetExists("src"): self.log.warn( "Src product does not exist; running detection, measurement, selection" ) # Run own detection and measurement; necessary in nightly processing selectSources = self.subtract.getSelectSources( exposure, sigma=scienceSigmaPost, doSmooth=not self.doPreConvolve, idFactory=idFactory, ) else: self.log.info("Source selection via src product") # Sources already exist; for data release processing selectSources = sensorRef.get("src") # Number of basis functions nparam = len( makeKernelBasisList( self.subtract.config.kernel.active, referenceFwhmPix=scienceSigmaPost * FwhmPerSigma, targetFwhmPix=templateSigma * FwhmPerSigma)) if self.config.doAddMetrics: # Modify the schema of all Sources kcQa = KernelCandidateQa(nparam) selectSources = kcQa.addToSchema(selectSources) if self.config.kernelSourcesFromRef: # match exposure sources to reference catalog astromRet = self.astrometer.loadAndMatch( exposure=exposure, sourceCat=selectSources) matches = astromRet.matches elif templateSources: # match exposure sources to template sources matches = afwTable.matchRaDec(templateSources, selectSources, 1.0 * afwGeom.arcseconds, False) else: raise RuntimeError( "doSelectSources=True and kernelSourcesFromRef=False," + "but template sources not available. Cannot match science " + "sources with template sources. Run process* on data from " + "which templates are built.") kernelSources = self.sourceSelector.selectStars( exposure, selectSources, matches=matches).starCat random.shuffle(kernelSources, random.random) controlSources = kernelSources[::self.config.controlStepSize] kernelSources = [ k for i, k in enumerate(kernelSources) if i % self.config.controlStepSize ] if self.config.doSelectDcrCatalog: redSelector = DiaCatalogSourceSelectorTask( DiaCatalogSourceSelectorConfig( grMin=self.sourceSelector.config.grMax, grMax=99.999)) redSources = redSelector.selectStars( exposure, selectSources, matches=matches).starCat controlSources.extend(redSources) blueSelector = DiaCatalogSourceSelectorTask( DiaCatalogSourceSelectorConfig( grMin=-99.999, grMax=self.sourceSelector.config.grMin)) blueSources = blueSelector.selectStars( exposure, selectSources, matches=matches).starCat controlSources.extend(blueSources) if self.config.doSelectVariableCatalog: varSelector = DiaCatalogSourceSelectorTask( DiaCatalogSourceSelectorConfig(includeVariable=True)) varSources = varSelector.selectStars( exposure, selectSources, matches=matches).starCat controlSources.extend(varSources) self.log.info( "Selected %d / %d sources for Psf matching (%d for control sample)" % (len(kernelSources), len(selectSources), len(controlSources))) allresids = {} if self.config.doUseRegister: self.log.info("Registering images") if templateSources is None: # Run detection on the template, which is # temporarily background-subtracted templateSources = self.subtract.getSelectSources( templateExposure, sigma=templateSigma, doSmooth=True, idFactory=idFactory) # Third step: we need to fit the relative astrometry. # wcsResults = self.fitAstrometry(templateSources, templateExposure, selectSources) warpedExp = self.register.warpExposure(templateExposure, wcsResults.wcs, exposure.getWcs(), exposure.getBBox()) templateExposure = warpedExp # Create debugging outputs on the astrometric # residuals as a function of position. Persistence # not yet implemented; expected on (I believe) #2636. if self.config.doDebugRegister: # Grab matches to reference catalog srcToMatch = {x.second.getId(): x.first for x in matches} refCoordKey = wcsResults.matches[0].first.getTable( ).getCoordKey() inCentroidKey = wcsResults.matches[0].second.getTable( ).getCentroidKey() sids = [m.first.getId() for m in wcsResults.matches] positions = [ m.first.get(refCoordKey) for m in wcsResults.matches ] residuals = [ m.first.get(refCoordKey).getOffsetFrom( wcsResults.wcs.pixelToSky( m.second.get(inCentroidKey))) for m in wcsResults.matches ] allresids = dict(zip(sids, zip(positions, residuals))) cresiduals = [ m.first.get(refCoordKey).getTangentPlaneOffset( wcsResults.wcs.pixelToSky( m.second.get(inCentroidKey))) for m in wcsResults.matches ] colors = numpy.array([ -2.5 * numpy.log10(srcToMatch[x].get("g")) + 2.5 * numpy.log10(srcToMatch[x].get("r")) for x in sids if x in srcToMatch.keys() ]) dlong = numpy.array([ r[0].asArcseconds() for s, r in zip(sids, cresiduals) if s in srcToMatch.keys() ]) dlat = numpy.array([ r[1].asArcseconds() for s, r in zip(sids, cresiduals) if s in srcToMatch.keys() ]) idx1 = numpy.where( colors < self.sourceSelector.config.grMin) idx2 = numpy.where( (colors >= self.sourceSelector.config.grMin) & (colors <= self.sourceSelector.config.grMax)) idx3 = numpy.where( colors > self.sourceSelector.config.grMax) rms1Long = IqrToSigma * (numpy.percentile( dlong[idx1], 75) - numpy.percentile(dlong[idx1], 25)) rms1Lat = IqrToSigma * (numpy.percentile(dlat[idx1], 75) - numpy.percentile(dlat[idx1], 25)) rms2Long = IqrToSigma * (numpy.percentile( dlong[idx2], 75) - numpy.percentile(dlong[idx2], 25)) rms2Lat = IqrToSigma * (numpy.percentile(dlat[idx2], 75) - numpy.percentile(dlat[idx2], 25)) rms3Long = IqrToSigma * (numpy.percentile( dlong[idx3], 75) - numpy.percentile(dlong[idx3], 25)) rms3Lat = IqrToSigma * (numpy.percentile(dlat[idx3], 75) - numpy.percentile(dlat[idx3], 25)) self.log.info("Blue star offsets'': %.3f %.3f, %.3f %.3f" % (numpy.median(dlong[idx1]), rms1Long, numpy.median(dlat[idx1]), rms1Lat)) self.log.info( "Green star offsets'': %.3f %.3f, %.3f %.3f" % (numpy.median(dlong[idx2]), rms2Long, numpy.median(dlat[idx2]), rms2Lat)) self.log.info("Red star offsets'': %.3f %.3f, %.3f %.3f" % (numpy.median(dlong[idx3]), rms3Long, numpy.median(dlat[idx3]), rms3Lat)) self.metadata.add("RegisterBlueLongOffsetMedian", numpy.median(dlong[idx1])) self.metadata.add("RegisterGreenLongOffsetMedian", numpy.median(dlong[idx2])) self.metadata.add("RegisterRedLongOffsetMedian", numpy.median(dlong[idx3])) self.metadata.add("RegisterBlueLongOffsetStd", rms1Long) self.metadata.add("RegisterGreenLongOffsetStd", rms2Long) self.metadata.add("RegisterRedLongOffsetStd", rms3Long) self.metadata.add("RegisterBlueLatOffsetMedian", numpy.median(dlat[idx1])) self.metadata.add("RegisterGreenLatOffsetMedian", numpy.median(dlat[idx2])) self.metadata.add("RegisterRedLatOffsetMedian", numpy.median(dlat[idx3])) self.metadata.add("RegisterBlueLatOffsetStd", rms1Lat) self.metadata.add("RegisterGreenLatOffsetStd", rms2Lat) self.metadata.add("RegisterRedLatOffsetStd", rms3Lat) # warp template exposure to match exposure, # PSF match template exposure to exposure, # then return the difference #Return warped template... Construct sourceKernelCand list after subtract self.log.info("Subtracting images") subtractRes = self.subtract.subtractExposures( templateExposure=templateExposure, scienceExposure=exposure, candidateList=kernelSources, convolveTemplate=self.config.convolveTemplate, doWarping=not self.config.doUseRegister) subtractedExposure = subtractRes.subtractedExposure if self.config.doWriteMatchedExp: sensorRef.put(subtractRes.matchedExposure, self.config.coaddName + "Diff_matchedExp") if self.config.doDetection: self.log.info("Running diaSource detection") if subtractedExposure is None: subtractedExposure = sensorRef.get(subtractedExposureName) # Get Psf from the appropriate input image if it doesn't exist if not subtractedExposure.hasPsf(): if self.config.convolveTemplate: subtractedExposure.setPsf(exposure.getPsf()) else: if templateExposure is None: template = self.getTemplate.run( exposure, sensorRef, templateIdList=templateIdList) subtractedExposure.setPsf(template.exposure.getPsf()) # Erase existing detection mask planes mask = subtractedExposure.getMaskedImage().getMask() mask &= ~(mask.getPlaneBitMask("DETECTED") | mask.getPlaneBitMask("DETECTED_NEGATIVE")) table = afwTable.SourceTable.make(self.schema, idFactory) table.setMetadata(self.algMetadata) results = self.detection.makeSourceCatalog( table=table, exposure=subtractedExposure, doSmooth=not self.config.doPreConvolve) if self.config.doMerge: fpSet = results.fpSets.positive fpSet.merge(results.fpSets.negative, self.config.growFootprint, self.config.growFootprint, False) diaSources = afwTable.SourceCatalog(table) fpSet.makeSources(diaSources) self.log.info("Merging detections into %d sources" % (len(diaSources))) else: diaSources = results.sources if self.config.doMeasurement: self.log.info("Running diaSource measurement") self.measurement.run(diaSources, subtractedExposure) # Match with the calexp sources if possible if self.config.doMatchSources: if sensorRef.datasetExists("src"): # Create key,val pair where key=diaSourceId and val=sourceId matchRadAsec = self.config.diaSourceMatchRadius matchRadPixel = matchRadAsec / exposure.getWcs( ).pixelScale().asArcseconds() srcMatches = afwTable.matchXy(sensorRef.get("src"), diaSources, matchRadPixel, True) srcMatchDict = dict([(srcMatch.second.getId(), srcMatch.first.getId()) for srcMatch in srcMatches]) self.log.info("Matched %d / %d diaSources to sources" % (len(srcMatchDict), len(diaSources))) else: self.log.warn( "Src product does not exist; cannot match with diaSources" ) srcMatchDict = {} # Create key,val pair where key=diaSourceId and val=refId refAstromConfig = measAstrom.AstrometryConfig() refAstromConfig.matcher.maxMatchDistArcSec = matchRadAsec refAstrometer = measAstrom.AstrometryTask(refAstromConfig) astromRet = refAstrometer.run(exposure=exposure, sourceCat=diaSources) refMatches = astromRet.matches if refMatches is None: self.log.warn( "No diaSource matches with reference catalog") refMatchDict = {} else: self.log.info( "Matched %d / %d diaSources to reference catalog" % (len(refMatches), len(diaSources))) refMatchDict = dict([(refMatch.second.getId(), refMatch.first.getId()) for \ refMatch in refMatches]) # Assign source Ids for diaSource in diaSources: sid = diaSource.getId() if srcMatchDict.has_key(sid): diaSource.set("srcMatchId", srcMatchDict[sid]) if refMatchDict.has_key(sid): diaSource.set("refMatchId", refMatchDict[sid]) if diaSources is not None and self.config.doWriteSources: sensorRef.put(diaSources, self.config.coaddName + "Diff_diaSrc") if self.config.doAddMetrics and self.config.doSelectSources: self.log.info("Evaluating metrics and control sample") kernelCandList = [] for cell in subtractRes.kernelCellSet.getCellList(): for cand in cell.begin(False): # include bad candidates kernelCandList.append(cast_KernelCandidateF(cand)) # Get basis list to build control sample kernels basisList = afwMath.cast_LinearCombinationKernel( kernelCandList[0].getKernel( KernelCandidateF.ORIG)).getKernelList() controlCandList = \ diffimTools.sourceTableToCandidateList(controlSources, subtractRes.warpedExposure, exposure, self.config.subtract.kernel.active, self.config.subtract.kernel.active.detectionConfig, self.log, doBuild=True, basisList=basisList) kcQa.apply(kernelCandList, subtractRes.psfMatchingKernel, subtractRes.backgroundModel, dof=nparam) kcQa.apply(controlCandList, subtractRes.psfMatchingKernel, subtractRes.backgroundModel) if self.config.doDetection: kcQa.aggregate(selectSources, self.metadata, allresids, diaSources) else: kcQa.aggregate(selectSources, self.metadata, allresids) sensorRef.put(selectSources, self.config.coaddName + "Diff_kernelSrc") if self.config.doWriteSubtractedExp: sensorRef.put(subtractedExposure, subtractedExposureName) self.runDebug(exposure, subtractRes, selectSources, kernelSources, diaSources) return pipeBase.Struct( subtractedExposure=subtractedExposure, subtractRes=subtractRes, sources=diaSources, )