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):
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.
657 output : `lsst.pipe.base.Struct`
658 Results struct with attributes:
661 Catalog containing reference objects which intersect the
662 input region, filtered by the specified filter function.
664 Name of the field containing the flux associated with
670 Raised if no reference catalogs could be found for the specified
673 Raised if the loaded reference catalogs do not have matching
676 regionLat = region.getBoundingBox().getLat()
677 regionLon = region.getBoundingBox().getLon()
678 self.
log.info(
"Loading reference objects from %s in region bounded by "
679 "[%.8f, %.8f], [%.8f, %.8f] RA Dec",
681 regionLon.getA().asDegrees(), regionLon.getB().asDegrees(),
682 regionLat.getA().asDegrees(), regionLat.getB().asDegrees())
692 intersects = dataId.region.intersects(region)
694 intersects = region.intersects(dataId.region)
697 overlapList.append((dataId, refCat))
699 nOverlap = len(overlapList)
701 raise RuntimeError(
"No reference tables could be found for input region")
703 if self.
config.maxRefObjects
is not None:
704 maxRefObjectsPerInput = int(self.
config.maxRefObjects/nOverlap)
706 maxRefObjectsPerInput =
None
708 if self.
config.anyFilterMapsToThis
is not None:
709 refFluxField = self.
config.anyFilterMapsToThis +
"_flux"
713 firstCat = overlapList[0][1].get()
716 if (refFluxField
is not None
717 and (maxRefObjectsPerInput
is not None or self.
config.minRefMag
is not None)):
718 firstCat =
filterRefCat(firstCat, refFluxField, maxRefObjectsPerInput,
719 minRefMag=self.
config.minRefMag, log=self.
log)
720 refCat = filtFunc(firstCat, overlapList[0][0].region)
721 trimmedAmount = len(firstCat) - len(refCat)
724 for dataId, inputRefCat
in overlapList[1:]:
725 tmpCat = inputRefCat.get()
727 if tmpCat.schema != firstCat.schema:
728 raise TypeError(
"Reference catalogs have mismatching schemas")
730 if maxRefObjectsPerInput
is not None or self.
config.minRefMag
is not None:
731 filteredCat =
filterRefCat(tmpCat, refFluxField, maxRefObjectsPerInput,
732 minRefMag=self.
config.minRefMag, log=self.
log)
735 filteredCat = filtFunc(filteredCat, dataId.region)
737 refCat.extend(filteredCat)
738 trimmedAmount += len(tmpCat) - len(filteredCat)
740 self.
log.debug(
"Trimmed %d refCat objects lying outside padded region, leaving %d",
741 trimmedAmount, len(refCat))
742 self.
log.info(
"Loaded %d reference objects", len(refCat))
745 if not refCat.isContiguous():
746 refCat = refCat.copy(deep=
True)
751 anyFilterMapsToThis=self.
config.anyFilterMapsToThis,
752 filterMap=self.
config.filterMap)
755 if not expandedCat.isContiguous():
756 expandedCat = expandedCat.copy(deep=
True)
760 if expandedCat.schema[fluxField].asField().getUnits() !=
"nJy":
763 if version > LATEST_FORMAT_VERSION:
764 raise ValueError(f
"Unsupported refcat format version: {version} > {LATEST_FORMAT_VERSION}.")
766 return pipeBase.Struct(refCat=expandedCat, fluxField=fluxField)
769 """Load reference objects that lie within a circular region on the sky.
771 This method constructs a circular region from an input center and
772 angular radius, loads reference catalogs which are contained in or
773 intersect the circle, and filters reference catalogs which intersect
774 down to objects which lie within the defined circle.
778 ctrCoord : `lsst.geom.SpherePoint`
779 Point defining the center of the circular region.
780 radius : `lsst.geom.Angle`
781 Defines the angular radius of the circular region.
783 Name of camera filter.
784 epoch : `astropy.time.Time` or `None`, optional
785 Epoch to which to correct proper motion and parallax, or `None` to
786 not apply such corrections.
790 output : `lsst.pipe.base.Struct`
791 Results struct with attributes:
794 Catalog containing reference objects inside the specified
797 Name of the field containing the flux associated with
800 centerVector = ctrCoord.getVector()
803 return self.
loadRegion(circularRegion, filterName, epoch=epoch)
806 """Load the schema for the reference catalog.
811 Name of camera filter.
815 output : `lsst.pipe.base.Struct`
816 Results struct with attributes:
819 Schema of the reference catalogs returned by other 'load'
822 Name of the field containing the flux associated with
826 raise RuntimeError(
"No reference tables could be found.")
832 self.
refCats[0] = pipeBase.InMemoryDatasetHandle(cat, dataId=self.
refCats[0].dataId, copy=
False)
833 emptyCat = type(cat)(cat.table.clone())
836 anyFilterMapsToThis=self.
config.anyFilterMapsToThis,
837 filterMap=self.
config.filterMap,
840 if expandedEmptyCat.schema[fluxField].asField().getUnits() !=
"nJy":
843 if version > LATEST_FORMAT_VERSION:
844 raise ValueError(f
"Unsupported refcat format version: {version} > {LATEST_FORMAT_VERSION}.")
845 return pipeBase.Struct(schema=expandedEmptyCat.schema, fluxField=fluxField)