280 filterMap=None, centroids=False):
281 """This function takes in a reference catalog and returns a new catalog
282 with additional columns defined from the remaining function arguments.
286 refCat : `lsst.afw.table.SimpleCatalog`
287 Reference catalog to map to new catalog
288 anyFilterMapsToThis : `str`, optional
289 Always use this reference catalog filter.
290 Mutually exclusive with `filterMap`
291 filterMap : `dict` [`str`,`str`], optional
292 Mapping of camera filter name: reference catalog filter name.
293 centroids : `bool`, optional
294 Add centroid fields to the loaded Schema. ``loadPixelBox`` expects
295 these fields to exist.
299 expandedCat : `lsst.afw.table.SimpleCatalog`
300 Deep copy of input reference catalog with additional columns added
302 if anyFilterMapsToThis
or filterMap:
303 ReferenceObjectLoader._addFluxAliases(refCat.schema, anyFilterMapsToThis, filterMap)
305 mapper = afwTable.SchemaMapper(refCat.schema,
True)
306 mapper.addMinimalSchema(refCat.schema,
True)
307 mapper.editOutputSchema().disconnectAliases()
314 mapper.editOutputSchema().addField(
"centroid_x", type=float, doReplace=
True)
315 mapper.editOutputSchema().addField(
"centroid_y", type=float, doReplace=
True)
316 mapper.editOutputSchema().addField(
"hasCentroid", type=
"Flag", doReplace=
True)
317 mapper.editOutputSchema().getAliasMap().set(
"slot_Centroid",
"centroid")
319 expandedCat = afwTable.SimpleCatalog(mapper.getOutputSchema())
320 expandedCat.setMetadata(refCat.getMetadata())
321 expandedCat.extend(refCat, mapper=mapper)
327 """Add aliases for camera filter fluxes to the schema.
329 For each camFilter: refFilter in filterMap, adds these aliases:
330 <camFilter>_camFlux: <refFilter>_flux
331 <camFilter>_camFluxErr: <refFilter>_fluxErr, if the latter exists
332 or sets `anyFilterMapsToThis` in the schema.
336 schema : `lsst.afw.table.Schema`
337 Schema for reference catalog.
338 anyFilterMapsToThis : `str`, optional
339 Always use this reference catalog filter.
340 Mutually exclusive with `filterMap`.
341 filterMap : `dict` [`str`,`str`], optional
342 Mapping of camera filter name: reference catalog filter name.
343 Mutually exclusive with `anyFilterMapsToThis`.
348 Raised if any required reference flux field is missing from the
352 if anyFilterMapsToThis
and filterMap:
353 raise ValueError(
"anyFilterMapsToThis and filterMap are mutually exclusive!")
355 aliasMap = schema.getAliasMap()
357 if anyFilterMapsToThis
is not None:
358 refFluxName = anyFilterMapsToThis +
"_flux"
359 if refFluxName
not in schema:
360 msg = f
"Unknown reference filter for anyFilterMapsToThis='{refFluxName}'"
361 raise RuntimeError(msg)
362 aliasMap.set(
"anyFilterMapsToThis", refFluxName)
365 def addAliasesForOneFilter(filterName, refFilterName):
366 """Add aliases for a single filter
370 filterName : `str` (optional)
371 Camera filter name. The resulting alias name is
373 refFilterName : `str`
374 Reference catalog filter name; the field
375 <refFilterName>_flux must exist.
377 camFluxName = filterName +
"_camFlux"
378 refFluxName = refFilterName +
"_flux"
379 if refFluxName
not in schema:
380 raise RuntimeError(
"Unknown reference filter %s" % (refFluxName,))
381 aliasMap.set(camFluxName, refFluxName)
382 refFluxErrName = refFluxName +
"Err"
383 if refFluxErrName
in schema:
384 camFluxErrName = camFluxName +
"Err"
385 aliasMap.set(camFluxErrName, refFluxErrName)
387 if filterMap
is not None:
388 for filterName, refFilterName
in filterMap.items():
389 addAliasesForOneFilter(filterName, refFilterName)
405 outerLocalBBox.grow(BBoxPadding)
406 innerLocalBBox.grow(-1*BBoxPadding)
418 innerBoxCorners = innerLocalBBox.getCorners()
419 innerSphCorners = [wcs.pixelToSky(corner).getVector()
for corner
in innerBoxCorners]
422 outerBoxCorners = outerLocalBBox.getCorners()
423 outerSphCorners = [wcs.pixelToSky(corner).getVector()
for corner
in outerBoxCorners]
426 return innerSkyRegion, outerSkyRegion, innerSphCorners, outerSphCorners
430 """Compute on-sky center and radius of search region.
434 bbox : `lsst.geom.Box2I` or `lsst.geom.Box2D`
436 wcs : `lsst.afw.geom.SkyWcs`
437 WCS; used to convert pixel positions to sky coordinates.
439 Padding to add to 4 all edges of the bounding box (pixels).
443 results : `lsst.pipe.base.Struct`
446 - coord : `lsst.geom.SpherePoint`
447 ICRS center of the search region.
448 - radius : `lsst.geom.Angle`
449 Radius of the search region.
450 - bbox : `lsst.geom.Box2D`
451 Bounding box used to compute the circle.
454 bbox.grow(pixelMargin)
455 coord = wcs.pixelToSky(bbox.getCenter())
456 radius = max(coord.separation(wcs.pixelToSky(pp))
for pp
in bbox.getCorners())
457 return pipeBase.Struct(coord=coord, radius=radius, bbox=bbox)
461 """Return metadata about the loaded reference catalog, in an on-sky
464 This metadata is used for reloading the catalog (e.g. for
465 reconstituting a normalized match list).
469 coord : `lsst.geom.SpherePoint`
470 ICRS center of the search region.
471 radius : `lsst.geom.Angle`
472 Radius of the search region.
474 Name of the camera filter.
475 epoch : `astropy.time.Time` or `None`, optional
476 Epoch that proper motion and parallax were corrected to, or `None`
477 if no such corrections were applied.
481 md : `lsst.daf.base.PropertyList`
482 Metadata about the catalog.
485 md.add(
'RA', coord.getRa().asDegrees(),
'field center in degrees')
486 md.add(
'DEC', coord.getDec().asDegrees(),
'field center in degrees')
487 md.add(
'RADIUS', radius.asDegrees(),
'field radius in degrees, minimum')
490 md.add(
'SMATCHV', 2,
'SourceMatchVector version number')
491 md.add(
'FILTER', filterName,
'camera filter name for photometric data')
492 md.add(
'TIMESYS',
"TAI",
"time scale of time keywords")
493 md.add(
'JEPOCH',
None if epoch
is None else epoch.tai.jyear,
494 'Julian epoch (TAI Julian Epoch year) for catalog')
498 bboxToSpherePadding=100):
499 """Return metadata about the loaded reference catalog, in an
502 This metadata is used for reloading the catalog (e.g., for
503 reconstituting a normalised match list).
507 bbox : `lsst.geom.Box2I` or `lsst.geom.Box2D`
508 Bounding box for the pixels.
509 wcs : `lsst.afw.geom.SkyWcs`
510 The WCS object associated with ``bbox``.
512 Name of the camera filter.
513 epoch : `astropy.time.Time` or `None`, optional
514 Epoch that proper motion and parallax were corrected to, or `None`
515 if no such corrections were applied.
516 bboxToSpherePadding : `int`, optional
517 Padding in pixels to account for translating a set of corners into
518 a spherical (convex) boundary that is certain to encompass the
519 enitre area covered by the box.
523 md : `lsst.daf.base.PropertyList`
524 The metadata detailing the search parameters used for this
528 md = self.
getMetadataCircle(circle.coord, circle.radius, filterName, epoch=epoch)
530 paddedBbox = circle.bbox
531 _, _, innerCorners, outerCorners = self.
_makeBoxRegion(paddedBbox, wcs, bboxToSpherePadding)
532 for box, corners
in zip((
"INNER",
"OUTER"), (innerCorners, outerCorners)):
533 for (name, corner)
in zip((
"UPPER_LEFT",
"UPPER_RIGHT",
"LOWER_LEFT",
"LOWER_RIGHT"),
535 md.add(f
"{box}_{name}_RA",
geom.SpherePoint(corner).getRa().asDegrees(), f
"{box}_corner")
536 md.add(f
"{box}_{name}_DEC",
geom.SpherePoint(corner).getDec().asDegrees(), f
"{box}_corner")
540 bboxToSpherePadding=100):
541 """Load reference objects that are within a pixel-based rectangular
544 This algorithm works by creating a spherical box whose corners
545 correspond to the WCS converted corners of the input bounding box
546 (possibly padded). It then defines a filtering function which looks at
547 the pixel position of the reference objects and accepts only those that
548 lie within the specified bounding box.
550 The spherical box region and filtering function are passed to the
551 generic loadRegion method which loads and filters the reference objects
552 from the datastore and returns a single catalog containing the filtered
553 set of reference objects.
557 bbox : `lsst.geom.Box2I` or `lsst.geom.Box2D`
558 Box which bounds a region in pixel space.
559 wcs : `lsst.afw.geom.SkyWcs`
560 Wcs object defining the pixel to sky (and inverse) transform for
561 the supplied ``bbox``.
563 Name of camera filter.
564 epoch : `astropy.time.Time` or `None`, optional
565 Epoch to which to correct proper motion and parallax, or `None`
566 to not apply such corrections.
567 bboxToSpherePadding : `int`, optional
568 Padding to account for translating a set of corners into a
569 spherical (convex) boundary that is certain to encompase the
570 enitre area covered by the box.
574 output : `lsst.pipe.base.Struct`
575 Results struct with attributes:
578 Catalog containing reference objects inside the specified
579 bounding box (padded by self.config.pixelMargin).
581 Name of the field containing the flux associated with
587 Raised if no reference catalogs could be found for the specified
590 Raised if the loaded reference catalogs do not have matching
594 paddedBbox.grow(self.
config.pixelMargin)
595 innerSkyRegion, outerSkyRegion, _, _ = self.
_makeBoxRegion(paddedBbox, wcs, bboxToSpherePadding)
597 def _filterFunction(refCat, region):
608 refCat = preFiltFunc(refCat, region)
614 afwTable.updateRefCentroids(wcs, refCat)
617 if innerSkyRegion.contains(region):
620 inside = paddedBbox.contains(x=refCat[
"slot_Centroid_x"], y=refCat[
"slot_Centroid_y"])
621 filteredRefCat = refCat[inside]
623 return filteredRefCat
624 return self.
loadRegion(outerSkyRegion, filterName, filtFunc=_filterFunction, epoch=epoch)
626 def loadRegion(self, region, filterName, filtFunc=None, epoch=None, wcsForCentroids=None):
627 """Load reference objects within a specified region.
629 This function loads the DataIds used to construct an instance of this
630 class which intersect or are contained within the specified region. The
631 reference catalogs which intersect but are not fully contained within
632 the input region are further filtered by the specified filter function.
633 This function returns a single source catalog containing all reference
634 objects inside the specified region.
638 region : `lsst.sphgeom.Region`
639 This can be any type that is derived from `lsst.sphgeom.Region` and
640 should define the spatial region for which reference objects are to
642 filtFunc : callable or `None`, optional
643 This optional parameter should be a callable object that takes a
644 reference catalog and its corresponding region as parameters,
645 filters the catalog by some criteria and returns the filtered
646 reference catalog. If `None`, an internal filter function is used
647 which filters according to if a reference object falls within the
650 Name of camera filter.
651 epoch : `astropy.time.Time` or `None`, optional
652 Epoch to which to correct proper motion and parallax, or `None` to
653 not apply such corrections.
654 wcsForCentroids : `lsst.afw.geom.SkyWcs`, optional
655 If provided, this WCS will be used to convert the reference catalog
656 sky coordinates to pixel positions.
660 output : `lsst.pipe.base.Struct`
661 Results struct with attributes:
664 Catalog containing reference objects which intersect the
665 input region, filtered by the specified filter function.
667 Name of the field containing the flux associated with
673 Raised if no reference catalogs could be found for the specified
676 Raised if the loaded reference catalogs do not have matching
679 regionLat = region.getBoundingBox().getLat()
680 regionLon = region.getBoundingBox().getLon()
681 self.
log.info(
"Loading reference objects from %s in region bounded by "
682 "[%.8f, %.8f], [%.8f, %.8f] RA Dec",
684 regionLon.getA().asDegrees(), regionLon.getB().asDegrees(),
685 regionLat.getA().asDegrees(), regionLat.getB().asDegrees())
695 intersects = dataId.region.intersects(region)
697 intersects = region.intersects(dataId.region)
700 overlapList.append((dataId, refCat))
702 nOverlap = len(overlapList)
704 raise RuntimeError(
"No reference tables could be found for input region")
706 if self.
config.maxRefObjects
is not None:
707 maxRefObjectsPerInput = int(self.
config.maxRefObjects/nOverlap)
709 maxRefObjectsPerInput =
None
711 if self.
config.anyFilterMapsToThis
is not None:
712 refFluxField = self.
config.anyFilterMapsToThis +
"_flux"
716 firstCat = overlapList[0][1].get()
719 if (refFluxField
is not None
720 and (maxRefObjectsPerInput
is not None or self.
config.minRefMag
is not None)):
721 firstCat =
filterRefCat(firstCat, refFluxField, maxRefObjectsPerInput,
722 minRefMag=self.
config.minRefMag, log=self.
log)
723 refCat = filtFunc(firstCat, overlapList[0][0].region)
724 trimmedAmount = len(firstCat) - len(refCat)
727 for dataId, inputRefCat
in overlapList[1:]:
728 tmpCat = inputRefCat.get()
730 if tmpCat.schema != firstCat.schema:
731 raise TypeError(
"Reference catalogs have mismatching schemas")
733 if maxRefObjectsPerInput
is not None or self.
config.minRefMag
is not None:
734 filteredCat =
filterRefCat(tmpCat, refFluxField, maxRefObjectsPerInput,
735 minRefMag=self.
config.minRefMag, log=self.
log)
738 filteredCat = filtFunc(filteredCat, dataId.region)
740 refCat.extend(filteredCat)
741 trimmedAmount += len(tmpCat) - len(filteredCat)
743 self.
log.debug(
"Trimmed %d refCat objects lying outside padded region, leaving %d",
744 trimmedAmount, len(refCat))
745 self.
log.info(
"Loaded %d reference objects", len(refCat))
748 if not refCat.isContiguous():
749 refCat = refCat.copy(deep=
True)
753 if wcsForCentroids
is not None:
756 anyFilterMapsToThis=self.
config.anyFilterMapsToThis,
757 filterMap=self.
config.filterMap,
760 afwTable.updateRefCentroids(wcsForCentroids, expandedCat)
764 anyFilterMapsToThis=self.
config.anyFilterMapsToThis,
765 filterMap=self.
config.filterMap,
769 if not expandedCat.isContiguous():
770 expandedCat = expandedCat.copy(deep=
True)
774 if expandedCat.schema[fluxField].asField().getUnits() !=
"nJy":
777 if version > LATEST_FORMAT_VERSION:
778 raise ValueError(f
"Unsupported refcat format version: {version} > {LATEST_FORMAT_VERSION}.")
780 return pipeBase.Struct(refCat=expandedCat, fluxField=fluxField)
783 """Load reference objects that lie within a circular region on the sky.
785 This method constructs a circular region from an input center and
786 angular radius, loads reference catalogs which are contained in or
787 intersect the circle, and filters reference catalogs which intersect
788 down to objects which lie within the defined circle.
792 ctrCoord : `lsst.geom.SpherePoint`
793 Point defining the center of the circular region.
794 radius : `lsst.geom.Angle`
795 Defines the angular radius of the circular region.
797 Name of camera filter.
798 epoch : `astropy.time.Time` or `None`, optional
799 Epoch to which to correct proper motion and parallax, or `None` to
800 not apply such corrections.
804 output : `lsst.pipe.base.Struct`
805 Results struct with attributes:
808 Catalog containing reference objects inside the specified
811 Name of the field containing the flux associated with
814 centerVector = ctrCoord.getVector()
817 return self.
loadRegion(circularRegion, filterName, epoch=epoch)
820 """Load the schema for the reference catalog.
825 Name of camera filter.
829 output : `lsst.pipe.base.Struct`
830 Results struct with attributes:
833 Schema of the reference catalogs returned by other 'load'
836 Name of the field containing the flux associated with
840 raise RuntimeError(
"No reference tables could be found.")
846 self.
refCats[0] = pipeBase.InMemoryDatasetHandle(cat, dataId=self.
refCats[0].dataId, copy=
False)
847 emptyCat = type(cat)(cat.table.clone())
850 anyFilterMapsToThis=self.
config.anyFilterMapsToThis,
851 filterMap=self.
config.filterMap,
854 if expandedEmptyCat.schema[fluxField].asField().getUnits() !=
"nJy":
857 if version > LATEST_FORMAT_VERSION:
858 raise ValueError(f
"Unsupported refcat format version: {version} > {LATEST_FORMAT_VERSION}.")
859 return pipeBase.Struct(schema=expandedEmptyCat.schema, fluxField=fluxField)