def test_Box2D_repr(self): from lsst.geom import Box2D, Point2D, Extent2D print(repr(Box2D())) self.assertEqual(eval(repr(Box2D())), Box2D()) self.assertEqual( eval(repr(Box2D(Point2D(1.0, 2.0), Extent2D(3.0, 4.0)))), Box2D(Point2D(1.0, 2.0), Extent2D(3.0, 4.0)))
def setUp(self): # Test geometry: # # -100,99 99,99 # +--------------------+ # |AAAAAAAAAACCCCCDDDDD| A == only in epoch A # |AAAAAAAAAACCCCCDDDDD| B == only in epoch B # |AAAAAAAAAACCCCCDDDDD| C == in both epoch A and epoch B # |AAAAAAAAAACCCCCDDDDD| D == in epoch A; in B's bbox but outside its ValidPolygon # |AAAAAAAAAACCCCCDDDDD| # | BBBBBBBBBB| All WCSs have the same CRVAL and CD. # | BBBBBBBBBB| # | BBBBBBBBBB| Coadd has CRPIX=(0, 0) # | BBBBBBBBBB| Epoch A has CRPIX=(0, -50) # | BBBBBBBBBB| Epoch B has CRPIX=(-50, 0) # +--------------------+ # -100,-100 99,-100 # self.rng = np.random.RandomState(50) crval = SpherePoint(45.0, 45.0, degrees) cdMatrix = makeCdMatrix(scale=5E-5 * degrees, flipX=True) self.wcsCoadd = makeSkyWcs(crpix=Point2D(0.0, 0.0), crval=crval, cdMatrix=cdMatrix) self.wcsA = makeSkyWcs(crpix=Point2D(0.0, -50.0), crval=crval, cdMatrix=cdMatrix) self.wcsB = makeSkyWcs(crpix=Point2D(-50.0, 0.0), crval=crval, cdMatrix=cdMatrix) self.bboxCoadd = Box2I(Point2I(-100, -100), Point2I(99, 99)) self.bboxA = Box2I(Point2I(-100, -50), Point2I(99, 49)) self.bboxB = Box2I(Point2I(-50, -100), Point2I(49, 99)) self.polygonA = None polygonD = Polygon(Box2D(Box2I(Point2I(0, 0), Point2I(49, 99)))) self.polygonB, = polygonD.symDifference(Polygon(Box2D(self.bboxB))) self.curveA = makeRandomTransmissionCurve(self.rng) self.curveB = makeRandomTransmissionCurve(self.rng) self.weightA = 0.6 self.weightB = 0.2 schema = ExposureTable.makeMinimalSchema() weightKey = schema.addField("weight", type=float, doc="relative weight of image in Coadd") catalog = ExposureCatalog(schema) recordA = catalog.addNew() recordA[weightKey] = self.weightA recordA.setWcs(self.wcsA) recordA.setValidPolygon(self.polygonA) recordA.setBBox(self.bboxA) recordA.setTransmissionCurve(self.curveA) recordB = catalog.addNew() recordB[weightKey] = self.weightB recordB.setWcs(self.wcsB) recordB.setValidPolygon(self.polygonB) recordB.setBBox(self.bboxB) recordB.setTransmissionCurve(self.curveB) self.curveCoadd = makeCoaddTransmissionCurve(self.wcsCoadd, catalog)
def generateSummaryStats(cat, colName, skymap, plotInfo): """Generate a summary statistic in each patch Parameters ---------- cat : `pandas.core.frame.DataFrame` colName : `str` skymap : `lsst.skymap.ringsSkyMap.RingsSkyMap` plotInfo : `dict` Returns ------- patchInfoDict : `dict` """ # TODO: what is the more generic type of skymap? tractInfo = skymap.generateTract(plotInfo["tract"]) tractWcs = tractInfo.getWcs() # For now also convert the gen 2 patchIds to gen 3 patchInfoDict = {} for patch in cat.patchId.unique(): if patch is None: continue # Once the objectTable_tract catalogues are using gen 3 patches # this will go away onPatch = (cat["patchId"] == patch) stat = np.nanmedian(cat[colName].values[onPatch]) try: patchTuple = (int(patch.split(",")[0]), int(patch.split(",")[-1])) patchInfo = tractInfo.getPatchInfo(patchTuple) gen3PatchId = tractInfo.getSequentialPatchIndex(patchInfo) except AttributeError: # For native gen 3 tables the patches don't need converting # When we are no longer looking at the gen 2 -> gen 3 # converted repos we can tidy this up gen3PatchId = patch patchInfo = tractInfo.getPatchInfo(patch) corners = Box2D(patchInfo.getInnerBBox()).getCorners() skyCoords = tractWcs.pixelToSky(corners) patchInfoDict[gen3PatchId] = (skyCoords, stat) tractCorners = Box2D(tractInfo.getBBox()).getCorners() skyCoords = tractWcs.pixelToSky(tractCorners) patchInfoDict["tract"] = (skyCoords, np.nan) return patchInfoDict
def testMultiPlaneFitsReaders(self): """Run tests for MaskedImageFitsReader and ExposureFitsReader. """ metadata = PropertyList() metadata.add("FIVE", 5) metadata.add("SIX", 6.0) wcs = makeSkyWcs(Point2D(2.5, 3.75), SpherePoint(40.0 * degrees, 50.0 * degrees), np.array([[1E-5, 0.0], [0.0, -1E-5]])) defineFilter("test_readers_filter", lambdaEff=470.0) calib = PhotoCalib(2.5E4) psf = GaussianPsf(21, 21, 8.0) polygon = Polygon(Box2D(self.bbox)) apCorrMap = ApCorrMap() visitInfo = VisitInfo(exposureTime=5.0) transmissionCurve = TransmissionCurve.makeIdentity() coaddInputs = CoaddInputs(ExposureTable.makeMinimalSchema(), ExposureTable.makeMinimalSchema()) detector = DetectorWrapper().detector record = coaddInputs.ccds.addNew() record.setWcs(wcs) record.setPhotoCalib(calib) record.setPsf(psf) record.setValidPolygon(polygon) record.setApCorrMap(apCorrMap) record.setVisitInfo(visitInfo) record.setTransmissionCurve(transmissionCurve) record.setDetector(detector) for n, dtypeIn in enumerate(self.dtypes): with self.subTest(dtypeIn=dtypeIn): exposureIn = Exposure(self.bbox, dtype=dtypeIn) shape = exposureIn.image.array.shape exposureIn.image.array[:, :] = np.random.randint(low=1, high=5, size=shape) exposureIn.mask.array[:, :] = np.random.randint(low=1, high=5, size=shape) exposureIn.variance.array[:, :] = np.random.randint(low=1, high=5, size=shape) exposureIn.setMetadata(metadata) exposureIn.setWcs(wcs) exposureIn.setFilter(Filter("test_readers_filter")) exposureIn.setFilterLabel( FilterLabel(physical="test_readers_filter")) exposureIn.setPhotoCalib(calib) exposureIn.setPsf(psf) exposureIn.getInfo().setValidPolygon(polygon) exposureIn.getInfo().setApCorrMap(apCorrMap) exposureIn.getInfo().setVisitInfo(visitInfo) exposureIn.getInfo().setTransmissionCurve(transmissionCurve) exposureIn.getInfo().setCoaddInputs(coaddInputs) exposureIn.setDetector(detector) with lsst.utils.tests.getTempFilePath(".fits") as fileName: exposureIn.writeFits(fileName) self.checkMaskedImageFitsReader(exposureIn, fileName, self.dtypes[n:]) self.checkExposureFitsReader(exposureIn, fileName, self.dtypes[n:])
def trimFakeCat(self, fakeCat, image, wcs): """Trim the fake cat to about the size of the input image. Parameters ---------- fakeCat : `pandas.core.frame.DataFrame` The catalog of fake sources to be input image : `lsst.afw.image.exposure.exposure.ExposureF` The image into which the fake sources should be added wcs : `lsst.afw.geom.skyWcs.skyWcs.SkyWcs` WCS to use to add fake sources Returns ------- fakeCat : `pandas.core.frame.DataFrame` The original fakeCat trimmed to the area of the image """ bbox = Box2D(image.getBBox()) corners = bbox.getCorners() skyCorners = wcs.pixelToSky(corners) region = ConvexPolygon([s.getVector() for s in skyCorners]) def trim(row): coord = SpherePoint(row[self.config.raColName], row[self.config.decColName], radians) return region.contains(coord.getVector()) return fakeCat[fakeCat.apply(trim, axis=1)]
def trimFakeCat(self, fakeCat, image, wcs): """Trim the fake cat to about the size of the input image. `fakeCat` must be processed with addPixCoords before using this method. Parameters ---------- fakeCat : `pandas.core.frame.DataFrame` The catalog of fake sources to be input image : `lsst.afw.image.exposure.exposure.ExposureF` The image into which the fake sources should be added wcs : `lsst.afw.geom.SkyWcs` WCS to use to add fake sources Returns ------- fakeCat : `pandas.core.frame.DataFrame` The original fakeCat trimmed to the area of the image """ bbox = Box2D(image.getBBox()) def trim(row): return bbox.contains(row["x"], row["y"]) return fakeCat[fakeCat.apply(trim, axis=1)]
def _trimFakeCat(self, fakeCat, image): """Trim the fake cat to about the size of the input image. Parameters ---------- fakeCat : `pandas.core.frame.DataFrame` The catalog of fake sources to be input image : `lsst.afw.image.exposure.exposure.ExposureF` The image into which the fake sources should be added Returns ------- fakeCat : `pandas.core.frame.DataFrame` The original fakeCat trimmed to the area of the image """ wcs = image.getWcs() bbox = Box2D(image.getBBox()) def trim(row): coord = SpherePoint(row[self.config.raColName], row[self.config.decColName], radians) cent = wcs.skyToPixel(coord) return bbox.contains(cent) return fakeCat[fakeCat.apply(trim, axis=1)]
def _calculate_region_from_dataset_metadata(self, obsInfo, header, FormatterClass): """Calculate the sky region covered by the supplied observation information. Parameters ---------- obsInfo : `~astro_metadata_translator.ObservationInfo` Summary information of this dataset. header : `Mapping` Header from the dataset. FormatterClass: `type` as subclass of `FitsRawFormatterBase` Formatter class that should be used to compute the spatial region. Returns ------- region : `lsst.sphgeom.ConvexPolygon` Region of sky covered by this observation. """ if obsInfo.visit_id is not None and obsInfo.tracking_radec is not None: formatter = FormatterClass.fromMetadata(metadata=header, obsInfo=obsInfo) visitInfo = formatter.makeVisitInfo() detector = self.camera[obsInfo.detector_num] wcs = formatter.makeWcs(visitInfo, detector) pixBox = Box2D(detector.getBBox()) if self.config.padRegionAmount > 0: pixBox.grow(self.config.padRegionAmount) pixCorners = pixBox.getCorners() sphCorners = [wcs.pixelToSky(point).getVector() for point in pixCorners] region = ConvexPolygon(sphCorners) else: region = None return region
def makeRandomPoint(self, *args, **kwds): """Draw a random Point2D within a Box2I. All arguments are forwarded directly to the Box2I constructor, allowing the caller to pass a fully-constructed Box2I, a (Point2I, Point2I) pair, or a (Point2I, Extent2I) pair. """ bboxD = Box2D(Box2I(*args, **kwds)) return bboxD.getMin() + Extent2D(bboxD.getWidth() * self.rng.rand(), bboxD.getHeight() * self.rng.rand())
def testBasics(self): """Test construction of a discrete sky map """ butler = Butler(inputs=self.inPath, outputs={ 'root': self.outPath, 'mode': 'rw' }) coordList = [] # list of sky coords of all corners of all calexp for dataId in ( dict(visit=1, filter="g"), dict(visit=2, filter="g"), dict(visit=3, filter="r"), ): rawImage = butler.get("raw", dataId) # fake calexp by simply copying raw data; the task just cares about its bounding box # (which is slightly larger for raw, but that doesn't matter for this test) calexp = rawImage butler.put(calexp, "calexp", dataId) calexpWcs = calexp.getWcs() calexpBoxD = Box2D(calexp.getBBox()) coordList += [ calexpWcs.pixelToSky(corner) for corner in calexpBoxD.getCorners() ] # use the calexp to make a sky map retVal = MakeDiscreteSkyMapTask.parseAndRun( args=[self.inPath, "--output", self.outPath, "--id", "filter=g^r"], config=self.config, doReturnResults=True, ) self.assertEqual(len(retVal.resultList), 1) skyMap = retVal.resultList[0].result.skyMap self.assertEqual(type(skyMap), DiscreteSkyMap) self.assertEqual(len(skyMap), 1) tractInfo = skyMap[0] self.assertEqual(tractInfo.getId(), 0) self.assertEqual(tractInfo.getNumPatches(), Extent2I(3, 3)) tractWcs = tractInfo.getWcs() tractBoxD = Box2D(tractInfo.getBBox()) for skyPoint in coordList: self.assertTrue(tractBoxD.contains(tractWcs.skyToPixel(skyPoint)))
def extractCtorArgs(md): wcs = makeSkyWcs(makePropertyListFromDict(md)) kwds = { "pixelToIwc": getPixelToIntermediateWorldCoords(wcs), "bbox": Box2D(Box2I(Point2I(0, 0), Extent2I(md["NAXES1"], md["NAXES2"]))), "crpix": Point2D(md["CRPIX1"] - 1.0, md["CRPIX2"] - 1.0), # -1 for LSST vs. FITS conventions "cd": np.array([[md["CD1_1"], md["CD1_2"]], [md["CD2_1"], md["CD2_2"]]]), } return kwds
def insertObservationRegions(self, registry, datastore): """Add spatial regions for visit-detector combinations. """ sql = ( "SELECT wcs.instrument AS instrument, wcs.visit AS visit, wcs.detector AS detector, " " wcs.dataset_id AS wcs, metadata.dataset_id AS metadata " " FROM dataset wcs " " INNER JOIN dataset_collection wcs_collection " " ON (wcs.dataset_id = wcs_collection.dataset_id) " " INNER JOIN dataset metadata " " ON (wcs.instrument = metadata.instrument " " AND wcs.visit = metadata.visit " " AND wcs.detector = metadata.detector) " " INNER JOIN dataset_collection metadata_collection " " ON (metadata.dataset_id = metadata_collection.dataset_id) " " WHERE wcs_collection.collection = :collection " " AND metadata_collection.collection = :collection " " AND wcs.dataset_type_name = :wcs_name" " AND metadata.dataset_type_name = :metadata_name") log = Log.getLogger("lsst.daf.butler.gen2convert") for config in self.config["regions"]: log.info("Adding observation regions using %s from %s.", config["DatasetType"], config["collection"]) visits = {} for row in registry.query( sql, collection=config["collection"], wcs_name="{}.wcs".format(config["DatasetType"]), metadata_name="{}.metadata".format(config["DatasetType"])): wcsRef = registry.getDataset(row["wcs"]) metadataRef = registry.getDataset(row["metadata"]) wcs = datastore.get(wcsRef) metadata = datastore.get(metadataRef) bbox = Box2D(bboxFromMetadata(metadata)) bbox.grow(config["padding"]) region = ConvexPolygon([ sp.getVector() for sp in wcs.pixelToSky(bbox.getCorners()) ]) registry.setDimensionRegion( {k: row[k] for k in ("instrument", "visit", "detector")}, region=region, update=False) visits.setdefault((row["instrument"], row["visit"]), []).extend(region.getVertices()) for (instrument, visit), vertices in visits.items(): region = ConvexPolygon(vertices) registry.setDimensionRegion(instrument=instrument, visit=visit, region=region)
def getPatchInner(sources, patchInfo): """Set a flag for each source if it is in the innerBBox of a patch. Parameters ---------- sources : `lsst.afw.table.SourceCatalog` A sourceCatalog with pre-calculated centroids. patchInfo : `lsst.skymap.PatchInfo` Information about a `SkyMap` `Patch`. Returns -------- isPatchInner : array-like of `bool` `True` for each source that has a centroid in the inner region of a patch. """ # Extract the centroid position for all the sources x = sources["slot_Centroid_x"] y = sources["slot_Centroid_y"] centroidFlag = sources["slot_Centroid_flag"] # set inner flags for each source and set primary flags for # sources with no children (or all sources if deblend info not available) innerFloatBBox = Box2D(patchInfo.getInnerBBox()) inInner = innerFloatBBox.contains(x, y) # When the centroider fails, we can still fall back to the peak, # but we don't trust that quite as much - # so we use a slightly smaller box for the patch comparison. shrunkInnerFloatBBox = Box2D(innerFloatBBox) shrunkInnerFloatBBox.grow(-1) inShrunkInner = shrunkInnerFloatBBox.contains(x, y) # Flag sources contained in the inner region of a patch isPatchInner = (centroidFlag & inShrunkInner) | (~centroidFlag & inInner) return isPatchInner
def calculateSipWcsHeader(wcs, order, bbox, spacing, header=None): """Generate a SIP WCS header approximating a given ``SkyWcs`` Parameters ---------- wcs : `lsst.afw.geom.SkyWcs` World Coordinate System to approximate as SIP. order : `int` SIP order (equal to the maximum sum of the polynomial exponents). bbox : `lsst.geom.Box2I` Bounding box over which to approximate the ``wcs``. spacing : `float` Spacing between sample points. header : `lsst.daf.base.PropertyList`, optional Header to which to add SIP WCS keywords. Returns ------- header : `lsst.daf.base.PropertyList` Header including SIP WCS keywords. Examples -------- >>> header = calculateSipWcsHeader(exposure.getWcs(), 3, exposure.getBBox(), 20) >>> sipWcs = SkyWcs(header) """ transform = getPixelToIntermediateWorldCoords(wcs) crpix = wcs.getPixelOrigin() cdMatrix = wcs.getCdMatrix() crval = wcs.getSkyOrigin() gridNum = Extent2I(int(bbox.getWidth() / spacing + 0.5), int(bbox.getHeight() / spacing + 0.5)) sip = SipApproximation(transform, crpix, cdMatrix, Box2D(bbox), gridNum, order) md = makeTanSipMetadata(sip.getPixelOrigin(), crval, sip.getCdMatrix(), sip.getA(), sip.getB(), sip.getAP(), sip.getBP()) if header is not None: header.combine(md) else: header = md return header
def makeSkyPolygonFromBBox(bbox, wcs): """Make an on-sky polygon from a bbox and a SkyWcs Parameters ---------- bbox : `lsst.geom.Box2I` or `lsst.geom.Box2D` Bounding box of region, in pixel coordinates wcs : `lsst.afw.geom.SkyWcs` Celestial WCS Returns ------- polygon : `lsst.sphgeom.ConvexPolygon` On-sky region """ pixelPoints = Box2D(bbox).getCorners() skyPoints = wcs.pixelToSky(pixelPoints) return ConvexPolygon.convexHull([sp.getVector() for sp in skyPoints])
def testReadV1Catalog(self): testDir = os.path.dirname(__file__) v1CatalogPath = os.path.join(testDir, "data", "exposure_catalog_v1.fits") catV1 = lsst.afw.table.ExposureCatalog.readFits(v1CatalogPath) self.assertEqual(self.cat[0].get(self.ka), catV1[0].get(self.ka)) self.assertEqual(self.cat[0].get(self.kb), catV1[0].get(self.kb)) self.comparePsfs(self.cat[0].getPsf(), catV1[0].getPsf()) bbox = Box2D(Point2D(0, 0), Extent2D(2000, 2000)) self.assertWcsAlmostEqualOverBBox(self.cat[0].getWcs(), catV1[0].getWcs(), bbox) self.assertEqual(self.cat[1].get(self.ka), catV1[1].get(self.ka)) self.assertEqual(self.cat[1].get(self.kb), catV1[1].get(self.kb)) self.assertEqual(self.cat[1].getWcs(), catV1[1].getWcs()) self.assertIsNone(self.cat[1].getPsf()) self.assertIsNone(self.cat[1].getPhotoCalib()) self.assertEqual(self.cat[0].getPhotoCalib(), catV1[0].getPhotoCalib()) self.assertIsNone(catV1[0].getVisitInfo()) self.assertIsNone(catV1[1].getVisitInfo())
def _trimFakeCat(self, fakeCat, image): """Trim the fake cat to the exact size of the input image. Parameters ---------- fakeCat : `pandas.core.frame.DataFrame` The catalog of fake sources that was input image : `lsst.afw.image.exposure.exposure.ExposureF` The image into which the fake sources were added Returns ------- fakeCat : `pandas.core.frame.DataFrame` The original fakeCat trimmed to the area of the image """ # fakeCat must be processed with _addPixCoords before trimming if ('x' not in fakeCat.columns) or ('y' not in fakeCat.columns): fakeCat = self._addPixCoords(fakeCat, image) # Prefilter in ra/dec to avoid cases where the wcs incorrectly maps # input fakes which are really off the chip onto it. ras = fakeCat[self.config.ra_col].values * u.rad decs = fakeCat[self.config.dec_col].values * u.rad isContainedRaDec = image.containsSkyCoords(ras, decs, padding=0) # now use the exact pixel BBox to filter to only fakes that were inserted xs = fakeCat["x"].values ys = fakeCat["y"].values bbox = Box2D(image.getBBox()) isContainedXy = xs >= bbox.minX isContainedXy &= xs <= bbox.maxX isContainedXy &= ys >= bbox.minY isContainedXy &= ys <= bbox.maxY return fakeCat[isContainedRaDec & isContainedXy]
def run(self, sources, skyMap=None, tractInfo=None, patchInfo=None, includeDeblend=True): """Set is-patch-inner, is-tract-inner and is-primary flags on sources. For coadded imaging, the is-primary flag returns True when an object has no children, is in the inner region of a coadd patch, is in the inner region of a coadd trach, and is not detected in a pseudo-filter (e.g., a sky_object). For single frame imaging, the is-primary flag returns True when a source has no children and is not a sky source. Parameters ---------- sources : `lsst.afw.table.SourceCatalog` A sourceTable. Reads in centroid fields and an nChild field. Writes is-patch-inner, is-tract-inner, and is-primary flags. skyMap : `lsst.skymap.BaseSkyMap` Sky tessellation object tractInfo : `lsst.skymap.TractInfo` Tract object patchInfo : `lsst.skymap.PatchInfo` Patch object includeDeblend : `bool` Include deblend information in isPrimary? """ nChildKey = None if includeDeblend: nChildKey = self.schema.find(self.config.nChildKeyName).key # coadd case if not self.isSingleFrame: # set inner flags for each source and set primary flags for sources with no children # (or all sources if deblend info not available) innerFloatBBox = Box2D(patchInfo.getInnerBBox()) # When the centroider fails, we can still fall back to the peak, but we don't trust # that quite as much - so we use a slightly smaller box for the patch comparison. # That's trickier for the tract comparison, so we just use the peak without extra # care there. shrunkInnerFloatBBox = Box2D(innerFloatBBox) shrunkInnerFloatBBox.grow(-1) pseudoFilterKeys = [] for filt in self.config.pseudoFilterList: try: pseudoFilterKeys.append( self.schema.find("merge_peak_%s" % filt).getKey()) except Exception: self.log.warn( "merge_peak is not set for pseudo-filter %s" % filt) tractId = tractInfo.getId() for source in sources: centroidPos = source.getCentroid() if numpy.any(numpy.isnan(centroidPos)): continue if source.getCentroidFlag(): # Use a slightly smaller box to guard against bad centroids (see above) isPatchInner = shrunkInnerFloatBBox.contains(centroidPos) else: isPatchInner = innerFloatBBox.contains(centroidPos) source.setFlag(self.isPatchInnerKey, isPatchInner) skyPos = source.getCoord() sourceInnerTractId = skyMap.findTract(skyPos).getId() isTractInner = sourceInnerTractId == tractId source.setFlag(self.isTractInnerKey, isTractInner) if nChildKey is None or source.get(nChildKey) == 0: for pseudoFilterKey in pseudoFilterKeys: if source.get(pseudoFilterKey): isPseudo = True break else: isPseudo = False source.setFlag( self.isPrimaryKey, isPatchInner and isTractInner and not isPseudo) # single frame case else: hasSkySources = True if "sky_source" in sources.schema else False for source in sources: hasNoChildren = True if nChildKey is None or source.get( nChildKey) == 0 else False isSkySource = False if hasSkySources: if source["sky_source"]: isSkySource = True source.setFlag(self.isPrimaryKey, hasNoChildren and not isSkySource)
def run(self, sources, skyMap, tractInfo, patchInfo, includeDeblend=True): """Set is-primary and related flags on sources @param[in,out] sources a SourceTable - reads centroid fields and an nChild field - writes is-patch-inner, is-tract-inner and is-primary flags @param[in] skyMap sky tessellation object (subclass of lsst.skymap.BaseSkyMap) @param[in] tractInfo tract object (subclass of lsst.skymap.TractInfo) @param[in] patchInfo patch object (subclass of lsst.skymap.PatchInfo) @param[in] includeDeblend include deblend information in isPrimary? """ nChildKey = None if includeDeblend: nChildKey = self.schema.find(self.config.nChildKeyName).key # set inner flags for each source and set primary flags for sources with no children # (or all sources if deblend info not available) innerFloatBBox = Box2D(patchInfo.getInnerBBox()) # When the centroider fails, we can still fall back to the peak, but we don't trust # that quite as much - so we use a slightly smaller box for the patch comparison. # That's trickier for the tract comparison, so we just use the peak without extra # care there. shrunkInnerFloatBBox = Box2D(innerFloatBBox) shrunkInnerFloatBBox.grow(-1) pseudoFilterKeys = [] for filt in self.config.pseudoFilterList: try: pseudoFilterKeys.append( self.schema.find("merge_peak_%s" % filt).getKey()) except Exception: self.log.warn("merge_peak is not set for pseudo-filter %s" % filt) tractId = tractInfo.getId() for source in sources: centroidPos = source.getCentroid() if numpy.any(numpy.isnan(centroidPos)): continue if source.getCentroidFlag(): # Use a slightly smaller box to guard against bad centroids (see above) isPatchInner = shrunkInnerFloatBBox.contains(centroidPos) else: isPatchInner = innerFloatBBox.contains(centroidPos) source.setFlag(self.isPatchInnerKey, isPatchInner) skyPos = source.getCoord() sourceInnerTractId = skyMap.findTract(skyPos).getId() isTractInner = sourceInnerTractId == tractId source.setFlag(self.isTractInnerKey, isTractInner) if nChildKey is None or source.get(nChildKey) == 0: for pseudoFilterKey in pseudoFilterKeys: if source.get(pseudoFilterKey): isPseudo = True break else: isPseudo = False source.setFlag(self.isPrimaryKey, isPatchInner and isTractInner and not isPseudo)
def testSetPupilFieldAngleZero(self): """Test setPupilFieldAngle for zero field angle and various points on the pupil """ for pupilPos in ((0, 5000), (-5000, 0), (5000, -5000)): with self.subTest(pupilPos=pupilPos): self.cco.setPupilFieldAngle(pupilPos=pupilPos) # the telescope should be pointing in the opposite direction of the CBP telDir = self.cco.telAzAltInternal.getVector() cbpDir = self.cco.cbpAzAltInternal.getVector() negativeCbpDir = -np.array(cbpDir, dtype=float) np.testing.assert_allclose(telDir, negativeCbpDir, atol=1e-15) # beam 0 should be pointed to the center of the pupil, normal to the pupil # and land on the center of the focal plane, which is also the center of detector D0 beamInfo0 = self.cco[0] self.assertEqual(beamInfo0.name, "beam0") self.assertPairsAlmostEqual(beamInfo0.holePos, (0, 0)) self.assertTrue(beamInfo0.isOnPupil) self.assertTrue(beamInfo0.isOnFocalPlane) self.assertTrue(beamInfo0.isOnDetector) self.assertTrue(beamInfo0.isVisible) self.assertPairsAlmostEqual(beamInfo0.focalPlanePos, (0, 0), maxDiff=self.maxFocalPlanePosErr) self.assertPairsAlmostEqual(beamInfo0.focalFieldAngle, (0, 0), maxDiff=self.maxFieldAngleErrRad) self.assertPairsAlmostEqual(beamInfo0.pupilFieldAngle, (0, 0), maxDiff=self.maxFieldAngleErrRad) self.assertPairsAlmostEqual(beamInfo0.pupilPos, pupilPos, maxDiff=self.maxPupilPosErr) self.assertEqual(beamInfo0.detectorName, "D0") bboxd = Box2D(self.cco.cameraGeom["D0"].getBBox()) detectorCtrPos = bboxd.getCenter() detector34Pos = bboxd.getMin() + bboxd.getDimensions()*0.75 self.assertPairsAlmostEqual(beamInfo0.detectorPos, detectorCtrPos, maxDiff=self.maxDetectorPosErr) # beam 1 should land on detector D0, 3/4 of the way from LL to UR beamInfo1 = self.cco["beam1"] self.assertEqual(beamInfo1.name, "beam1") self.assertTrue(beamInfo1.isOnDetector) self.assertEqual(beamInfo1.detectorName, "D0") self.assertPairsAlmostEqual(beamInfo1.detectorPos, detector34Pos, maxDiff=self.maxDetectorPosErr) # beam 2 should land on the center of detector D1 beamInfo2 = self.cco["beam2"] self.assertEqual(beamInfo2.name, "beam2") self.assertTrue(beamInfo2.isOnDetector) self.assertEqual(beamInfo2.detectorName, "D1") self.assertPairsAlmostEqual(beamInfo2.detectorPos, detectorCtrPos, maxDiff=self.maxDetectorPosErr) # beam 3 should land on detector D1, 3/4 of the way from LL to UR beamInfo3 = self.cco["beam3"] self.assertEqual(beamInfo3.name, "beam3") self.assertTrue(beamInfo3.isOnDetector) self.assertEqual(beamInfo3.detectorName, "D1") self.assertPairsAlmostEqual(beamInfo3.detectorPos, detector34Pos, maxDiff=self.maxDetectorPosErr) # beam 4 should land on the center of detector D2 beamInfo4 = self.cco["beam4"] self.assertEqual(beamInfo4.name, "beam4") self.assertTrue(beamInfo4.isOnDetector) self.assertEqual(beamInfo4.detectorName, "D2") self.assertPairsAlmostEqual(beamInfo4.detectorPos, detectorCtrPos, maxDiff=self.maxDetectorPosErr) # beam 5 should land on detector D2, 3/4 of the way from LL to UR beamInfo5 = self.cco["beam5"] self.assertEqual(beamInfo5.name, "beam5") self.assertTrue(beamInfo5.isOnDetector) self.assertEqual(beamInfo5.detectorName, "D2") self.assertPairsAlmostEqual(beamInfo5.detectorPos, detector34Pos, maxDiff=self.maxDetectorPosErr)
def testSetPupilFieldAngleTrivial(self): """Test setPupilFieldAngle for the trivial case of hole 0 aimed perpendicular to the center of the pupil """ self.cco.setPupilFieldAngle(pupilPos=(0, 0)) # the telescope should be pointed at the center of the CBP and vice-versa # NOTE: It would be nice to get better than the 0.0028" that I measure self.assertSpherePointsAlmostEqual(self.cco.telAzAltInternal, SpherePoint(Vector3d(*self.cco.config.cbpPosition)), maxSep=0.01*arcseconds) self.assertSpherePointsAlmostEqual(self.cco.cbpAzAltInternal, SpherePoint(Vector3d(*(-self.cco.config.cbpPosition))), maxSep=0.01*arcseconds) self.assertAnglesAlmostEqual(self.cco.telRotInternal, 0*degrees) # beam 0 should be pointed to the center of the pupil, normal to the pupil # and land on the center of the focal plane and the center of detector D0 beamInfo0 = self.cco[0] self.assertEqual(beamInfo0.name, "beam0") self.assertPairsAlmostEqual(beamInfo0.holePos, (0, 0)) self.assertFalse(beamInfo0.isOnPupil) # blocked by the central obscuration self.assertTrue(beamInfo0.isOnFocalPlane) self.assertTrue(beamInfo0.isOnDetector) self.assertFalse(beamInfo0.isVisible) # blocked by the central obscuration self.assertPairsAlmostEqual(beamInfo0.focalPlanePos, (0, 0), maxDiff=self.maxFocalPlanePosErr) self.assertPairsAlmostEqual(beamInfo0.focalFieldAngle, (0, 0), maxDiff=self.maxFieldAngleErrRad) self.assertPairsAlmostEqual(beamInfo0.pupilFieldAngle, (0, 0), maxDiff=self.maxFieldAngleErrRad) self.assertPairsAlmostEqual(beamInfo0.pupilPos, (0, 0), maxDiff=self.maxPupilPosErr) self.assertEqual(beamInfo0.detectorName, "D0") bboxd = Box2D(self.cco.cameraGeom["D0"].getBBox()) detectorCtrPos = bboxd.getCenter() detector34Pos = bboxd.getMin() + bboxd.getDimensions()*0.75 self.assertPairsAlmostEqual(beamInfo0.detectorPos, detectorCtrPos, maxDiff=self.maxDetectorPosErr) # beam 1 should land on detector D0, 3/4 of the way from LL to UR beamInfo1 = self.cco[1] self.assertEqual(beamInfo1.name, "beam1") self.assertTrue(beamInfo1.isOnDetector) self.assertEqual(beamInfo1.detectorName, "D0") self.assertPairsAlmostEqual(beamInfo1.detectorPos, detector34Pos, maxDiff=self.maxDetectorPosErr) # beam 2 should land on the center of detector D1 beamInfo2 = self.cco[2] self.assertEqual(beamInfo2.name, "beam2") self.assertTrue(beamInfo2.isOnDetector) self.assertEqual(beamInfo2.detectorName, "D1") self.assertPairsAlmostEqual(beamInfo2.detectorPos, detectorCtrPos, maxDiff=self.maxDetectorPosErr) # beam 3 should land on detector D1, 3/4 of the way from LL to UR beamInfo3 = self.cco[3] self.assertEqual(beamInfo3.name, "beam3") self.assertTrue(beamInfo3.isOnDetector) self.assertEqual(beamInfo3.detectorName, "D1") self.assertPairsAlmostEqual(beamInfo3.detectorPos, detector34Pos, maxDiff=self.maxDetectorPosErr) # beam 4 should land on the center of detector D2 beamInfo4 = self.cco[4] self.assertEqual(beamInfo4.name, "beam4") self.assertTrue(beamInfo4.isOnDetector) self.assertEqual(beamInfo4.detectorName, "D2") self.assertPairsAlmostEqual(beamInfo4.detectorPos, detectorCtrPos, maxDiff=self.maxDetectorPosErr) # beam 5 should land on detector D2, 3/4 of the way from LL to UR # The measured error is 5e-7 pixels, which is small enough not to worry. # I strongly suspect it is due to inaccuracy in the inverse of the # field angle to focal plane transform. beamInfo5 = self.cco[5] self.assertEqual(beamInfo5.name, "beam5") self.assertTrue(beamInfo5.isOnDetector) self.assertEqual(beamInfo5.detectorName, "D2") self.assertPairsAlmostEqual(beamInfo5.detectorPos, detector34Pos, maxDiff=self.maxDetectorPosErr)
def computeExposureBounds( self, exposure: DimensionRecord, *, collections: Any = None) -> Dict[int, List[UnitVector3d]]: """Compute the lists of unit vectors on the sphere that correspond to the sky positions of detector corners. Parameters ---------- exposure : `DimensionRecord` Dimension record for the exposure. collections : Any, optional Collections to be searched for raws and camera geometry, overriding ``self.butler.collections``. Can be any of the types supported by the ``collections`` argument to butler construction. Returns ------- bounds : `dict` Dictionary mapping detector ID to a list of unit vectors on the sphere representing that detector's corners projected onto the sky. """ if collections is None: collections = self.butler.collections camera, versioned = loadCamera(self.butler, exposure.dataId, collections=collections) if not versioned and self.config.requireVersionedCamera: raise LookupError( f"No versioned camera found for exposure {exposure.dataId}.") # Derive WCS from boresight information -- if available in registry use_registry = True try: orientation = lsst.geom.Angle(exposure.sky_angle, lsst.geom.degrees) radec = lsst.geom.SpherePoint( lsst.geom.Angle(exposure.tracking_ra, lsst.geom.degrees), lsst.geom.Angle(exposure.tracking_dec, lsst.geom.degrees)) except AttributeError: use_registry = False if use_registry: if self.config.detectorId is None: detectorId = next(camera.getIdIter()) else: detectorId = self.config.detectorId wcsDetector = camera[detectorId] # Ask the raw formatter to create the relevant WCS # This allows flips to be taken into account instrument = self.getInstrument(exposure.instrument) rawFormatter = instrument.getRawFormatter({"detector": detectorId}) wcs = rawFormatter.makeRawSkyWcsFromBoresight( radec, orientation, wcsDetector) else: if self.config.detectorId is None: wcsRefsIter = self.butler.registry.queryDatasets( "raw.wcs", dataId=exposure.dataId, collections=collections) if not wcsRefsIter: raise LookupError( f"No raw.wcs datasets found for data ID {exposure.dataId} " f"in collections {collections}.") wcsRef = next(iter(wcsRefsIter)) wcsDetector = camera[wcsRef.dataId["detector"]] wcs = self.butler.getDirect(wcsRef) else: wcsDetector = camera[self.config.detectorId] wcs = self.butler.get("raw.wcs", dataId=exposure.dataId, detector=self.config.detectorId, collections=collections) fpToSky = wcsDetector.getTransform(FOCAL_PLANE, PIXELS).then(wcs.getTransform()) bounds = {} for detector in camera: pixelsToSky = detector.getTransform(PIXELS, FOCAL_PLANE).then(fpToSky) pixCorners = Box2D(detector.getBBox().dilatedBy( self.config.padding)).getCorners() bounds[detector.getId()] = [ skyCorner.getVector() for skyCorner in pixelsToSky.applyForward(pixCorners) ] return bounds
def testSampleAt(self): """Test the behavior of TransmissionCurve.sampleAt on the subclass returned by makeCoaddTransmissionCurve. """ wavelengths = np.linspace(4000, 7000, 200) # Points in coadd coordinates in each of the distinct regions point0 = self.makeRandomPoint(Point2I(-100, -100), Point2I(-1, -1)) pointA = self.makeRandomPoint(Point2I(-100, 0), Point2I(-1, 99)) pointB = self.makeRandomPoint(Point2I(0, -100), Point2I(99, -1)) pointC = self.makeRandomPoint(Point2I(0, 0), Point2I(49, 99)) pointD = self.makeRandomPoint(Point2I(50, 0), Point2I(99, 99)) points = [point0, pointA, pointB, pointC, pointD] # The same points, in sky coordinates coords = [self.wcsCoadd.pixelToSky(point) for point in points] # The same points, in Epoch A's coordinates point0A, pointAA, pointBA, pointCA, pointDA = [ self.wcsA.skyToPixel(coord) for coord in coords ] self.assertFalse(Box2D(self.bboxA).contains(point0A)) self.assertTrue(Box2D(self.bboxA).contains(pointAA)) self.assertFalse(Box2D(self.bboxA).contains(pointBA)) self.assertTrue(Box2D(self.bboxA).contains(pointCA)) self.assertTrue(Box2D(self.bboxA).contains(pointDA)) # The same points, in Epoch B's coordinates point0B, pointAB, pointBB, pointCB, pointDB = [ self.wcsB.skyToPixel(coord) for coord in coords ] self.assertFalse(Box2D(self.bboxB).contains(point0B)) self.assertFalse(Box2D(self.bboxB).contains(pointAB)) self.assertTrue(Box2D(self.bboxB).contains(pointBB)) self.assertTrue(Box2D(self.bboxB).contains(pointCB)) self.assertTrue(Box2D(self.bboxB).contains(pointDB)) self.assertTrue(self.polygonB.contains(pointBB)) self.assertTrue(self.polygonB.contains(pointCB)) self.assertFalse(self.polygonB.contains(pointDB)) # Test that we can't compute throughputs in region 0 (where there are no inputs) self.assertRaises(InvalidParameterError, self.curveCoadd.sampleAt, point0, wavelengths) # Test throughputs in region A (only Epoch A contributes) throughputA1 = self.curveCoadd.sampleAt(pointA, wavelengths) throughputA2 = self.curveA.sampleAt(pointAA, wavelengths) self.assertFloatsAlmostEqual(throughputA1, throughputA2) # Test throughputs in region B (only Epoch B contributes) throughputB1 = self.curveCoadd.sampleAt(pointB, wavelengths) throughputB2 = self.curveB.sampleAt(pointBB, wavelengths) self.assertFloatsAlmostEqual(throughputB1, throughputB2) # Test throughputs in region C (both epochs contribute) throughputC1 = self.curveCoadd.sampleAt(pointC, wavelengths) throughputC2 = self.curveA.sampleAt(pointCA, wavelengths) throughputC3 = self.curveB.sampleAt(pointCB, wavelengths) self.assertFloatsAlmostEqual(throughputC1, throughputC2 * 0.75 + throughputC3 * 0.25) # Test throughputs in region D (only Epoch A contributes) throughputD1 = self.curveCoadd.sampleAt(pointD, wavelengths) throughputD2 = self.curveA.sampleAt(pointDA, wavelengths) self.assertFloatsAlmostEqual(throughputD1, throughputD2)