24__all__ = [
"getRefFluxField",
"getRefFluxKeys",
"LoadReferenceObjectsTask",
"LoadReferenceObjectsConfig",
25 "ReferenceObjectLoader"]
38import lsst.pipe.base
as pipeBase
39from lsst
import sphgeom
41from lsst.utils.timer
import timeMethod
45 """Return True if this name/units combination corresponds to an
46 "old-style" reference catalog flux field.
48 unitsCheck = units != 'nJy'
49 isFlux = name.endswith(
'_flux')
50 isFluxSigma = name.endswith(
'_fluxSigma')
51 isFluxErr = name.endswith(
'_fluxErr')
52 return (isFlux
or isFluxSigma
or isFluxErr)
and unitsCheck
56 """Return True if the units of all flux and fluxErr are correct (nJy).
65 """"Return the format version stored in a reference catalog header.
70 Reference catalog to inspect.
75 Format verison integer. Returns `0` if the catalog has no metadata
76 or the metadata does
not include a
"REFCAT_FORMAT_VERSION" key.
78 md = refCat.getMetadata()
82 return md.getScalar(
"REFCAT_FORMAT_VERSION")
88 """Convert fluxes in a catalog from jansky to nanojansky.
93 The catalog to convert.
95 Log to send messages to.
96 doConvert : `bool`, optional
97 Return a converted catalog,
or just identify the fields that need to be converted?
98 This supports the
"write=False" mode of `bin/convert_to_nJy.py`.
103 The converted catalog,
or None if ``doConvert``
is False.
107 Support
for old units
in reference catalogs will be removed after the
108 release of late calendar year 2019.
109 Use `meas_algorithms/bin/convert_to_nJy.py` to update your reference catalog.
113 mapper = afwTable.SchemaMapper(catalog.schema, shareAliasMap=
False)
114 mapper.addMinimalSchema(afwTable.SimpleTable.makeMinimalSchema())
117 for field
in catalog.schema:
118 oldName = field.field.getName()
119 oldUnits = field.field.getUnits()
123 if oldName.endswith(
'_fluxSigma'):
124 name = oldName.replace(
'_fluxSigma',
'_fluxErr')
127 newField = afwTable.Field[field.dtype](name, field.field.getDoc(), units)
128 mapper.addMapping(field.getKey(), newField)
129 input_fields.append(field.field)
130 output_fields.append(newField)
132 mapper.addMapping(field.getKey())
134 fluxFieldsStr =
'; '.join(
"(%s, '%s')" % (field.getName(), field.getUnits())
for field
in input_fields)
137 newSchema = mapper.getOutputSchema()
138 output = afwTable.SimpleCatalog(newSchema)
139 output.extend(catalog, mapper=mapper)
140 for field
in output_fields:
141 output[field.getName()] *= 1e9
142 log.info(
"Converted refcat flux fields to nJy (name, units): %s", fluxFieldsStr)
145 log.info(
"Found old-style refcat flux fields (name, units): %s", fluxFieldsStr)
150 """This is a private helper class which filters catalogs by
151 row based on the row being inside the region used to initialize
157 The spatial region which all objects should lie within
163 """This call method on an instance of this class takes in a reference
164 catalog, and the region
from which the catalog was generated.
166 If the catalog region
is entirely contained within the region used to
167 initialize this
class, then all the entries
in the catalog must be
168 within the region
and so the whole catalog
is returned.
170 If the catalog region
is not entirely contained, then the location
for
171 each record
is tested against the region used to initialize the
class.
172 Records which fall inside this region are added to a new catalog,
and
173 this catalog
is then returned.
178 SourceCatalog to be filtered.
180 Region
in which the catalog was created
182 if catRegion.isWithin(self.
regionregion):
186 filteredRefCat = type(refCat)(refCat.table)
187 for record
in refCat:
188 if self.
regionregion.contains(record.getCoord().getVector()):
189 filteredRefCat.append(record)
190 return filteredRefCat
194 """Base class for reference object loaders, to facilitate gen2/gen3 code
198 """Apply proper motion correction to a reference catalog.
200 Adjust position and position error
in the ``catalog``
201 for proper motion to the specified ``epoch``,
202 modifying the catalog
in place.
207 Catalog of positions, containing at least these fields:
209 - Coordinates, retrieved by the table
's coordinate key.
210 - ``coord_raErr`` : Error in Right Ascension (rad).
211 - ``coord_decErr`` : Error
in Declination (rad).
212 - ``pm_ra`` : Proper motion
in Right Ascension (rad/yr,
214 - ``pm_raErr`` : Error
in ``pm_ra`` (rad/yr), optional.
215 - ``pm_dec`` : Proper motion
in Declination (rad/yr,
217 - ``pm_decErr`` : Error
in ``pm_dec`` (rad/yr), optional.
218 - ``epoch`` : Mean epoch of object (an astropy.time.Time)
219 epoch : `astropy.time.Time`
220 Epoch to which to correct proper motion.
221 If
None, do
not apply PM corrections
or raise if
222 ``config.requireProperMotion``
is True.
227 Raised
if ``config.requireProperMotion``
is set but we cannot
228 apply the proper motion correction
for some reason.
231 if self.config.requireProperMotion:
232 raise RuntimeError(
"requireProperMotion=True but epoch not provided to loader.")
234 self.log.debug(
"No epoch provided: not applying proper motion corrections to refcat.")
238 if (
"pm_ra" in catalog.schema
239 and not isinstance(catalog.schema[
"pm_ra"].asKey(), afwTable.KeyAngle)):
240 if self.config.requireProperMotion:
241 raise RuntimeError(
"requireProperMotion=True but refcat pm_ra field is not an Angle.")
243 self.log.warning(
"Reference catalog pm_ra field is not an Angle; cannot apply proper motion.")
246 if (
"epoch" not in catalog.schema
or "pm_ra" not in catalog.schema):
247 if self.config.requireProperMotion:
248 raise RuntimeError(
"requireProperMotion=True but PM data not available from catalog.")
250 self.log.warning(
"Proper motion correction not available for this reference catalog.")
257 """This class facilitates loading reference catalogs with gen 3 middleware
259 The middleware preflight solver will create a list of datarefs that may
260 possibly overlap a given region. These datarefs are then used to construct
261 and instance of this
class. The
class instance should then be passed into
262 a task which needs reference catalogs. These tasks should then determine
263 the exact region of the sky reference catalogs will be loaded
for,
and
264 call a corresponding method to load the reference objects.
266 def __init__(self, dataIds, refCats, config, log=None):
267 """ Constructs an instance of ReferenceObjectLoader
271 dataIds : iterable of `lsst.daf.butler.DataIds`
272 An iterable object of DataSetRefs which point to reference catalogs
273 in a gen 3 repository.
274 refCats : iterable of `lsst.daf.butler.DeferedDatasetHandle`
275 Handles to load refCats on demand
277 Configuration
for the loader.
278 log : `
lsst.log.Log`, `logging.Logger`
or `
None`, optional
279 Logger object used to write out messages. If `
None` a default
284 self.loglog = log or logging.getLogger(__name__).getChild(
"ReferenceObjectLoader")
288 def _makeBoxRegion(BBox, wcs, BBoxPadding):
301 outerLocalBBox.grow(BBoxPadding)
302 innerLocalBBox.grow(-1*BBoxPadding)
314 innerBoxCorners = innerLocalBBox.getCorners()
315 innerSphCorners = [wcs.pixelToSky(corner).getVector()
for corner
in innerBoxCorners]
318 outerBoxCorners = outerLocalBBox.getCorners()
319 outerSphCorners = [wcs.pixelToSky(corner).getVector()
for corner
in outerBoxCorners]
322 return innerSkyRegion, outerSkyRegion, innerSphCorners, outerSphCorners
324 def loadPixelBox(self, bbox, wcs, filterName=None, epoch=None, photoCalib=None,
325 bboxToSpherePadding=100):
326 """Load reference objects that are within a pixel-based rectangular
329 This algorithm works by creating a spherical box whose corners
330 correspond to the WCS converted corners of the input bounding box
331 (possibly padded). It then defines a filtering function which looks at
332 the pixel position of the reference objects and accepts only those that
333 lie within the specified bounding box.
335 The spherical box region
and filtering function are passed to the
336 generic loadRegion method which loads
and filters the reference objects
337 from the datastore
and returns a single catalog containing the filtered
338 set of reference objects.
343 Box which bounds a region
in pixel space.
345 Wcs object defining the pixel to sky (
and inverse) transform
for
346 the supplied ``bbox``.
347 filterName : `str`
or `
None`, optional
348 Name of camera filter,
or `
None`
or blank
for the default filter.
349 epoch : `astropy.time.Time`
or `
None`, optional
350 Epoch to which to correct proper motion
and parallax,
or `
None`
351 to
not apply such corrections.
353 Deprecated
and ignored, only included
for api compatibility.
354 bboxToSpherePadding : `int`, optional
355 Padding to account
for translating a set of corners into a
356 spherical (convex) boundary that
is certain to encompase the
357 enitre area covered by the box.
362 Catalog containing reference objects inside the specified bounding
363 box (padded by self.
configconfig.pixelMargin).
368 Raised
if no reference catalogs could be found
for the specified
371 Raised
if the loaded reference catalogs do
not have matching
375 paddedBbox.grow(self.configconfig.pixelMargin)
376 innerSkyRegion, outerSkyRegion, _, _ = self._makeBoxRegion_makeBoxRegion(paddedBbox, wcs, bboxToSpherePadding)
378 def _filterFunction(refCat, region):
389 refCat = preFiltFunc(refCat, region)
395 afwTable.updateRefCentroids(wcs, refCat)
398 if innerSkyRegion.contains(region):
402 filteredRefCat = type(refCat)(refCat.table)
403 centroidKey = afwTable.Point2DKey(refCat.schema[
'centroid'])
404 for record
in refCat:
405 pixCoords = record[centroidKey]
407 filteredRefCat.append(record)
408 return filteredRefCat
409 return self.
loadRegionloadRegion(outerSkyRegion, filtFunc=_filterFunction, epoch=epoch, filterName=filterName)
411 def loadRegion(self, region, filtFunc=None, filterName=None, epoch=None):
412 """Load reference objects within a specified region.
414 This function loads the DataIds used to construct an instance of this
415 class which intersect or are contained within the specified region. The
416 reference catalogs which intersect but are
not fully contained within
417 the input region are further filtered by the specified filter function.
418 This function returns a single source catalog containing all reference
419 objects inside the specified region.
425 should define the spatial region
for which reference objects are to
427 filtFunc : callable
or `
None`, optional
428 This optional parameter should be a callable object that takes a
429 reference catalog
and its corresponding region
as parameters,
430 filters the catalog by some criteria
and returns the filtered
431 reference catalog. If `
None`, an internal filter function
is used
432 which filters according to
if a reference object falls within the
434 filterName : `str`
or `
None`, optional
435 Name of camera filter,
or `
None`
or blank
for the default filter.
436 epoch : `astropy.time.Time`
or `
None`, optional
437 Epoch to which to correct proper motion
and parallax,
or `
None` to
438 not apply such corrections.
443 Catalog containing reference objects which intersect the input region,
444 filtered by the specified filter function.
449 Raised
if no reference catalogs could be found
for the specified
452 Raised
if the loaded reference catalogs do
not have matching
455 regionLat = region.getBoundingBox().getLat()
456 regionLon = region.getBoundingBox().getLon()
457 self.loglog.info("Loading reference objects from region bounded by "
458 "[%.8f, %.8f], [%.8f, %.8f] RA Dec",
459 regionLon.getA().asDegrees(), regionLon.getB().asDegrees(),
460 regionLat.getA().asDegrees(), regionLat.getB().asDegrees())
465 for dataId, refCat
in zip(self.
dataIdsdataIds, self.
refCatsrefCats):
469 intersects = dataId.region.intersects(region)
471 intersects = region.intersects(dataId.region)
474 overlapList.append((dataId, refCat))
476 if len(overlapList) == 0:
477 raise RuntimeError(
"No reference tables could be found for input region")
479 firstCat = overlapList[0][1].get()
480 refCat = filtFunc(firstCat, overlapList[0][0].region)
481 trimmedAmount = len(firstCat) - len(refCat)
484 for dataId, inputRefCat
in overlapList[1:]:
485 tmpCat = inputRefCat.get()
487 if tmpCat.schema != firstCat.schema:
488 raise TypeError(
"Reference catalogs have mismatching schemas")
490 filteredCat = filtFunc(tmpCat, dataId.region)
491 refCat.extend(filteredCat)
492 trimmedAmount += len(tmpCat) - len(filteredCat)
494 self.
loglog.debug(
"Trimmed %d refCat objects lying outside padded region, leaving %d",
495 trimmedAmount, len(refCat))
496 self.
loglog.info(
"Loaded %d reference objects", len(refCat))
499 if not refCat.isContiguous():
500 refCat = refCat.copy(deep=
True)
507 self.
loglog.warning(
"Found version 0 reference catalog with old style units in schema.")
508 self.
loglog.warning(
"run `meas_algorithms/bin/convert_refcat_to_nJy.py` to convert fluxes to nJy.")
509 self.
loglog.warning(
"See RFC-575 for more details.")
518 if not expandedCat.isContiguous():
519 expandedCat = expandedCat.copy(deep=
True)
521 fluxField =
getRefFluxField(schema=expandedCat.schema, filterName=filterName)
522 return pipeBase.Struct(refCat=expandedCat, fluxField=fluxField)
525 """Load reference objects that lie within a circular region on the sky.
527 This method constructs a circular region from an input center
and
528 angular radius, loads reference catalogs which are contained
in or
529 intersect the circle,
and filters reference catalogs which intersect
530 down to objects which lie within the defined circle.
535 Point defining the center of the circular region.
537 Defines the angular radius of the circular region.
538 filterName : `str`
or `
None`, optional
539 Name of camera filter,
or `
None`
or blank
for the default filter.
540 epoch : `astropy.time.Time`
or `
None`, optional
541 Epoch to which to correct proper motion
and parallax,
or `
None` to
542 not apply such corrections.
547 Catalog containing reference objects inside the specified search
550 centerVector = ctrCoord.getVector()
553 return self.
loadRegionloadRegion(circularRegion, filterName=filterName, epoch=epoch)
556 """Relink an unpersisted match list to sources and reference objects.
558 A match list is persisted
and unpersisted
as a catalog of IDs
559 produced by afw.table.packMatches(),
with match metadata
560 (
as returned by the astrometry tasks)
in the catalog
's metadata
561 attribute. This method converts such a match catalog into a match
562 list, with links to source records
and reference object records.
567 Unpersisted packed match list.
568 ``matchCat.table.getMetadata()`` must contain match metadata,
569 as returned by the astrometry tasks.
571 Source catalog. As a side effect, the catalog will be sorted
581 def getMetadataBox(self, bbox, wcs, filterName=None, photoCalib=None, epoch=None,
582 bboxToSpherePadding=100):
583 """Return metadata about the load
585 This metadata is used
for reloading the catalog (e.g.,
for
586 reconstituting a normalised match list.)
591 Bounding box
for the pixels.
593 The WCS object associated
with ``bbox``.
594 filterName : `str`
or `
None`, optional
595 Name of the camera filter,
or `
None`
or blank
for the default
598 Deprecated, only included
for api compatibility.
599 epoch : `astropy.time.Time`
or `
None`, optional
600 Epoch to which to correct proper motion
and parallax,
or `
None` to
601 not apply such corrections.
602 bboxToSpherePadding : `int`, optional
603 Padding to account
for translating a set of corners into a
604 spherical (convex) boundary that
is certain to encompase the
605 enitre area covered by the box.
610 The metadata detailing the search parameters used
for this
614 paddedBbox.grow(self.configconfig.pixelMargin)
615 _, _, innerCorners, outerCorners = self._makeBoxRegion_makeBoxRegion(paddedBbox, wcs, bboxToSpherePadding)
617 for box, corners
in zip((
"INNER",
"OUTER"), (innerCorners, outerCorners)):
618 for (name, corner)
in zip((
"UPPER_LEFT",
"UPPER_RIGHT",
"LOWER_LEFT",
"LOWER_RIGHT"),
620 md.add(f
"{box}_{name}_RA",
geom.SpherePoint(corner).getRa().asDegrees(), f
"{box}_corner")
621 md.add(f
"{box}_{name}_DEC",
geom.SpherePoint(corner).getDec().asDegrees(), f
"{box}_corner")
622 md.add(
"SMATCHV", 1,
'SourceMatchVector version number')
623 filterName =
"UNKNOWN" if filterName
is None else str(filterName)
624 md.add(
'FILTER', filterName,
'filter name for photometric data')
625 md.add(
'EPOCH',
"NONE" if epoch
is None else epoch.mjd,
'Epoch (TAI MJD) for catalog')
630 """Return metadata about the load.
632 This metadata is used
for reloading the catalog (e.g.
for
633 reconstituting a normalized match list.)
638 ICRS center of the search region.
640 Radius of the search region.
641 filterName : `str`
or `
None`
642 Name of the camera filter,
or `
None`
or blank
for the default
645 Deprecated, only included
for api compatibility.
646 epoch : `astropy.time.Time`
or `
None`, optional
647 Epoch to which to correct proper motion
and parallax,
or `
None` to
648 not apply such corrections.
655 md.add('RA', coord.getRa().asDegrees(),
'field center in degrees')
656 md.add(
'DEC', coord.getDec().asDegrees(),
'field center in degrees')
657 md.add(
'RADIUS', radius.asDegrees(),
'field radius in degrees, minimum')
658 md.add(
'SMATCHV', 1,
'SourceMatchVector version number')
659 filterName =
"UNKNOWN" if filterName
is None else str(filterName)
660 md.add(
'FILTER', filterName,
'filter name for photometric data')
661 md.add(
'EPOCH',
"NONE" if epoch
is None else epoch.mjd,
'Epoch (TAI MJD) for catalog')
666 """Add flux columns and aliases for camera to reference mapping.
668 Creates a new catalog containing the information of the input refCat
669 as well
as added flux columns
and aliases between camera
and reference
675 Catalog of reference objects
676 defaultFilter : `str`
677 Name of the default reference filter
678 filterReferenceMap : `dict` of `str`
679 Dictionary
with keys corresponding to a filter name
and values
680 which correspond to the name of the reference filter.
685 Reference catalog
with columns added to track reference filters.
690 If the specified reference filter name
is not specifed
as a
691 key
in the reference filter map.
693 refCat = ReferenceObjectLoader.remapReferenceCatalogSchema(refCat,
694 filterNameList=filterReferenceMap.keys())
695 aliasMap = refCat.schema.getAliasMap()
696 if filterReferenceMap
is None:
697 filterReferenceMap = {}
698 for filterName, refFilterName
in itertools.chain([(
None, defaultFilter)],
699 filterReferenceMap.items()):
701 camFluxName = filterName +
"_camFlux" if filterName
is not None else "camFlux"
702 refFluxName = refFilterName +
"_flux"
703 if refFluxName
not in refCat.schema:
704 raise RuntimeError(
"Unknown reference filter %s" % (refFluxName,))
705 aliasMap.set(camFluxName, refFluxName)
707 refFluxErrName = refFluxName +
"Err"
708 camFluxErrName = camFluxName +
"Err"
709 aliasMap.set(camFluxErrName, refFluxErrName)
715 """This function takes in a reference catalog and creates a new catalog with additional
716 columns defined the remaining function arguments.
721 Reference catalog to map to new catalog
726 Deep copy of input reference catalog with additional columns added
728 mapper = afwTable.SchemaMapper(refCat.schema, True)
729 mapper.addMinimalSchema(refCat.schema,
True)
730 mapper.editOutputSchema().disconnectAliases()
732 for filterName
in filterNameList:
733 mapper.editOutputSchema().addField(f
"{filterName}_flux",
735 doc=f
"flux in filter {filterName}",
738 mapper.editOutputSchema().addField(f
"{filterName}_fluxErr",
740 doc=f
"flux uncertanty in filter {filterName}",
745 mapper.editOutputSchema().addField(
"centroid_x", type=float, doReplace=
True)
746 mapper.editOutputSchema().addField(
"centroid_y", type=float, doReplace=
True)
747 mapper.editOutputSchema().addField(
"hasCentroid", type=
"Flag", doReplace=
True)
748 mapper.editOutputSchema().getAliasMap().set(
"slot_Centroid",
"centroid")
751 mapper.editOutputSchema().addField(
"photometric",
753 doc=
"set if the object can be used for photometric"
756 mapper.editOutputSchema().addField(
"resolved",
758 doc=
"set if the object is spatially resolved"
760 mapper.editOutputSchema().addField(
"variable",
762 doc=
"set if the object has variable brightness"
765 expandedCat = afwTable.SimpleCatalog(mapper.getOutputSchema())
766 expandedCat.setMetadata(refCat.getMetadata())
767 expandedCat.extend(refCat, mapper=mapper)
773 """Get the name of a flux field from a schema.
775 return the alias of
"anyFilterMapsToThis",
if present
776 else if filterName
is specified:
777 return "*filterName*_camFlux" if present
778 else return "*filterName*_flux" if present (camera filter name
779 matches reference filter name)
780 else throw RuntimeError
782 return "camFlux",
if present,
783 else throw RuntimeError
788 Reference catalog schema.
789 filterName : `str`, optional
790 Name of camera filter. If
not specified, ``defaultFilter`` needs to be
791 set
in the refcat loader config.
795 fluxFieldName : `str`
801 If an appropriate field
is not found.
803 if not isinstance(schema, afwTable.Schema):
804 raise RuntimeError(
"schema=%s is not a schema" % (schema,))
806 return schema.getAliasMap().get(
"anyFilterMapsToThis")
811 fluxFieldList = [filterName +
"_camFlux", filterName +
"_flux"]
813 fluxFieldList = [
"camFlux"]
814 for fluxField
in fluxFieldList:
815 if fluxField
in schema:
818 raise RuntimeError(
"Could not find flux field(s) %s" % (
", ".join(fluxFieldList)))
822 """Return keys for flux and flux error.
827 Reference catalog schema.
829 Name of camera filter.
837 - flux error key, if present,
else None
842 If flux field
not found.
845 fluxErrField = fluxField + "Err"
846 fluxKey = schema[fluxField].asKey()
848 fluxErrKey = schema[fluxErrField].asKey()
851 return (fluxKey, fluxErrKey)
855 pixelMargin = pexConfig.RangeField(
856 doc=
"Padding to add to 4 all edges of the bounding box (pixels)",
861 defaultFilter = pexConfig.Field(
862 doc=(
"Default reference catalog filter to use if filter not specified in exposure;"
863 " if blank then filter must be specified in exposure."),
866 deprecated=
"defaultFilter is deprecated by RFC-716. Will be removed after v22."
868 anyFilterMapsToThis = pexConfig.Field(
869 doc=(
"Always use this reference catalog filter, no matter whether or what filter name is "
870 "supplied to the loader. Effectively a trivial filterMap: map all filter names to this filter."
871 " This can be set for purely-astrometric catalogs (e.g. Gaia DR2) where there is only one "
872 "reasonable choice for every camera filter->refcat mapping, but not for refcats used for "
873 "photometry, which need a filterMap and/or colorterms/transmission corrections."),
878 filterMap = pexConfig.DictField(
879 doc=(
"Mapping of camera filter name: reference catalog filter name; "
880 "each reference filter must exist in the refcat."
881 " Note that this does not perform any bandpass corrections: it is just a lookup."),
886 requireProperMotion = pexConfig.Field(
887 doc=
"Require that the fields needed to correct proper motion "
888 "(epoch, pm_ra and pm_dec) are present?",
896 msg =
"`filterMap` and `anyFilterMapsToThis` are mutually exclusive"
897 raise pexConfig.FieldValidationError(LoadReferenceObjectsConfig.anyFilterMapsToThis,
902 """Abstract base class to load objects from reference catalogs.
904 ConfigClass = LoadReferenceObjectsConfig
905 _DefaultName = "LoadReferenceObjects"
908 """Construct a LoadReferenceObjectsTask
912 butler : `lsst.daf.persistence.Butler`
913 Data butler, for access reference catalogs.
915 pipeBase.Task.__init__(self, *args, **kwargs)
919 def loadPixelBox(self, bbox, wcs, filterName=None, photoCalib=None, epoch=None):
920 """Load reference objects that overlap a rectangular pixel region.
925 Bounding box
for pixels.
927 WCS; used to convert pixel positions to sky coordinates
929 filterName : `str`
or `
None`, optional
930 Name of filter,
or `
None`
or `
""`
for the default filter.
931 This
is used
for flux values
in case we have flux limits
932 (which are
not yet implemented).
934 Deprecated, only included
for api compatibility.
935 epoch : `astropy.time.Time`
or `
None`, optional
936 Epoch to which to correct proper motion
and parallax,
or `
None` to
937 not apply such corrections.
941 results : `lsst.pipe.base.Struct`
942 A Struct containing the following fields:
943 refCat : `lsst.afw.catalog.SimpleCatalog`
944 A catalog of reference objects
with the standard
945 schema,
as documented
in the main doc string
for
946 `LoadReferenceObjects`.
947 The catalog
is guaranteed to be contiguous.
949 Name of flux field
for specified `filterName`.
953 The search algorithm works by searching
in a region
in sky
954 coordinates whose center
is the center of the bbox
and radius
955 is large enough to just include all 4 corners of the bbox.
956 Stars that lie outside the bbox are then trimmed
from the list.
961 self.log.info(
"Loading reference objects using center %s and radius %s deg",
962 circle.coord, circle.radius.asDegrees())
963 loadRes = self.
loadSkyCircleloadSkyCircle(circle.coord, circle.radius, filterName=filterName, epoch=epoch,
965 refCat = loadRes.refCat
966 numFound = len(refCat)
969 refCat = self.
_trimToBBox_trimToBBox(refCat=refCat, bbox=circle.bbox, wcs=wcs)
970 numTrimmed = numFound - len(refCat)
971 self.log.debug(
"trimmed %d out-of-bbox objects, leaving %d", numTrimmed, len(refCat))
972 self.log.info(
"Loaded %d reference objects", len(refCat))
975 if not refCat.isContiguous():
976 loadRes.refCat = refCat.copy(deep=
True)
981 def loadSkyCircle(self, ctrCoord, radius, filterName=None, epoch=None, centroids=False):
982 """Load reference objects that overlap a circular sky region.
987 ICRS center of search region.
989 Radius of search region.
990 filterName : `str` or `
None`, optional
991 Name of filter,
or `
None`
or `
""`
for the default filter.
992 This
is used
for flux values
in case we have flux limits
993 (which are
not yet implemented).
994 epoch : `astropy.time.Time`
or `
None`, optional
995 Epoch to which to correct proper motion
and parallax,
or `
None` to
996 not apply such corrections.
997 centroids : `bool`, optional
998 Add centroid fields to the loaded Schema. ``loadPixelBox`` expects
999 these fields to exist.
1003 results : `lsst.pipe.base.Struct`
1004 A Struct containing the following fields:
1005 refCat : `lsst.afw.catalog.SimpleCatalog`
1006 A catalog of reference objects
with the standard
1007 schema,
as documented
in the main doc string
for
1008 `LoadReferenceObjects`.
1009 The catalog
is guaranteed to be contiguous.
1011 Name of flux field
for specified `filterName`.
1015 Note that subclasses are responsible
for performing the proper motion
1016 correction, since this
is the lowest-level interface
for retrieving
1022 def _trimToBBox(refCat, bbox, wcs):
1023 """Remove objects outside a given pixel bounding box and set
1024 centroid and hasCentroid fields.
1029 A catalog of objects. The schema must include fields
1030 "coord",
"centroid" and "hasCentroid".
1031 The
"coord" field
is read.
1032 The
"centroid" and "hasCentroid" fields are set.
1036 WCS; used to convert sky coordinates to pixel positions.
1041 Reference objects
in the bbox,
with centroid
and
1042 hasCentroid fields set.
1044 afwTable.updateRefCentroids(wcs, refCat)
1045 centroidKey = afwTable.Point2DKey(refCat.schema["centroid"])
1046 retStarCat = type(refCat)(refCat.table)
1048 point = star.get(centroidKey)
1049 if bbox.contains(point):
1050 retStarCat.append(star)
1053 def _addFluxAliases(self, schema):
1054 """Add aliases for camera filter fluxes to the schema.
1056 If self.config.defaultFilter then adds these aliases:
1057 camFlux: <defaultFilter>_flux
1058 camFluxErr: <defaultFilter>_fluxErr, if the latter exists
1060 For each camFilter: refFilter
in self.config.filterMap adds these aliases:
1061 <camFilter>_camFlux: <refFilter>_flux
1062 <camFilter>_camFluxErr: <refFilter>_fluxErr,
if the latter exists
1067 Schema
for reference catalog.
1072 If any reference flux field
is missing
from the schema.
1074 aliasMap = schema.getAliasMap()
1076 if self.config.anyFilterMapsToThis
is not None:
1077 refFluxName = self.config.anyFilterMapsToThis +
"_flux"
1078 if refFluxName
not in schema:
1079 msg = f
"Unknown reference filter for anyFilterMapsToThis='{refFluxName}'"
1080 raise RuntimeError(msg)
1081 aliasMap.set(
"anyFilterMapsToThis", refFluxName)
1084 def addAliasesForOneFilter(filterName, refFilterName):
1085 """Add aliases for a single filter
1089 filterName : `str` (optional)
1090 Camera filter name. The resulting alias name is
1091 <filterName>_camFlux,
or simply
"camFlux" if `filterName`
1093 refFilterName : `str`
1094 Reference catalog filter name; the field
1095 <refFilterName>_flux must exist.
1097 camFluxName = filterName + "_camFlux" if filterName
is not None else "camFlux"
1098 refFluxName = refFilterName +
"_flux"
1099 if refFluxName
not in schema:
1100 raise RuntimeError(
"Unknown reference filter %s" % (refFluxName,))
1101 aliasMap.set(camFluxName, refFluxName)
1102 refFluxErrName = refFluxName +
"Err"
1103 if refFluxErrName
in schema:
1104 camFluxErrName = camFluxName +
"Err"
1105 aliasMap.set(camFluxErrName, refFluxErrName)
1107 if self.config.defaultFilter:
1108 addAliasesForOneFilter(
None, self.config.defaultFilter)
1110 for filterName, refFilterName
in self.config.filterMap.items():
1111 addAliasesForOneFilter(filterName, refFilterName)
1115 addIsPhotometric=False, addIsResolved=False,
1116 addIsVariable=False, coordErrDim=2,
1117 addProperMotion=False, properMotionErrDim=2,
1119 """Make a standard schema for reference object catalogs.
1123 filterNameList : `list` of `str`
1124 List of filter names. Used to create <filterName>_flux fields.
1125 addIsPhotometric : `bool`
1126 If True then add field
"photometric".
1127 addIsResolved : `bool`
1128 If
True then add field
"resolved".
1129 addIsVariable : `bool`
1130 If
True then add field
"variable".
1132 Number of coord error fields; must be one of 0, 2, 3:
1134 - If 2
or 3: add fields
"coord_raErr" and "coord_decErr".
1135 - If 3: also add field
"coord_radecErr".
1136 addProperMotion : `bool`
1137 If
True add fields
"epoch",
"pm_ra",
"pm_dec" and "pm_flag".
1138 properMotionErrDim : `int`
1139 Number of proper motion error fields; must be one of 0, 2, 3;
1140 ignored
if addProperMotion false:
1141 - If 2
or 3: add fields
"pm_raErr" and "pm_decErr".
1142 - If 3: also add field
"pm_radecErr".
1143 addParallax : `bool`
1144 If
True add fields
"epoch",
"parallax",
"parallaxErr"
1145 and "parallax_flag".
1150 Schema
for reference catalog, an
1155 Reference catalogs support additional covariances, such
as
1156 covariance between RA
and proper motion
in declination,
1157 that are
not supported by this method, but can be added after
1158 calling this method.
1160 schema = afwTable.SimpleTable.makeMinimalSchema()
1162 afwTable.Point2DKey.addFields(
1165 "centroid on an exposure, if relevant",
1169 field=
"hasCentroid",
1171 doc=
"is position known?",
1173 for filterName
in filterNameList:
1175 field=
"%s_flux" % (filterName,),
1177 doc=
"flux in filter %s" % (filterName,),
1180 for filterName
in filterNameList:
1182 field=
"%s_fluxErr" % (filterName,),
1184 doc=
"flux uncertainty in filter %s" % (filterName,),
1187 if addIsPhotometric:
1189 field=
"photometric",
1191 doc=
"set if the object can be used for photometric calibration",
1197 doc=
"set if the object is spatially resolved",
1203 doc=
"set if the object has variable brightness",
1205 if coordErrDim
not in (0, 2, 3):
1206 raise ValueError(
"coordErrDim={}; must be (0, 2, 3)".format(coordErrDim))
1208 afwTable.CovarianceMatrix2fKey.addFields(
1211 names=[
"ra",
"dec"],
1212 units=[
"rad",
"rad"],
1213 diagonalOnly=(coordErrDim == 2),
1216 if addProperMotion
or addParallax:
1220 doc=
"date of observation (TAI, MJD)",
1228 doc=
"proper motion in the right ascension direction = dra/dt * cos(dec)",
1234 doc=
"proper motion in the declination direction",
1237 if properMotionErrDim
not in (0, 2, 3):
1238 raise ValueError(
"properMotionErrDim={}; must be (0, 2, 3)".format(properMotionErrDim))
1239 if properMotionErrDim > 0:
1240 afwTable.CovarianceMatrix2fKey.addFields(
1243 names=[
"ra",
"dec"],
1244 units=[
"rad/year",
"rad/year"],
1245 diagonalOnly=(properMotionErrDim == 2),
1250 doc=
"Set if proper motion or proper motion error is bad",
1261 field=
"parallaxErr",
1263 doc=
"uncertainty in parallax",
1267 field=
"parallax_flag",
1269 doc=
"Set if parallax or parallax error is bad",
1273 def _calculateCircle(self, bbox, wcs):
1274 """Compute on-sky center and radius of search region.
1281 WCS; used to convert pixel positions to sky coordinates.
1285 results : `lsst.pipe.base.Struct`
1286 A Struct containing:
1289 ICRS center of the search region.
1291 Radius of the search region.
1293 Bounding box used to compute the circle.
1296 bbox.grow(self.config.pixelMargin)
1297 coord = wcs.pixelToSky(bbox.getCenter())
1298 radius = max(coord.separation(wcs.pixelToSky(pp))
for pp
in bbox.getCorners())
1299 return pipeBase.Struct(coord=coord, radius=radius, bbox=bbox)
1302 """Return metadata about the load.
1304 This metadata is used
for reloading the catalog (e.g.,
for
1305 reconstituting a normalised match list.
1312 WCS; used to convert pixel positions to sky coordinates.
1313 filterName : `str`
or `
None`, optional
1314 Name of camera filter,
or `
None`
or `
""`
for the default
1317 Deprecated, only included
for api compatibility.
1318 epoch : `astropy.time.Time`
or `
None`, optional
1319 Epoch to which to correct proper motion
and parallax,
1320 or None to
not apply such corrections.
1325 Metadata about the load.
1328 return self.
getMetadataCirclegetMetadataCircle(circle.coord, circle.radius, filterName, epoch=epoch)
1331 """Return metadata about the load.
1333 This metadata is used
for reloading the catalog (e.g.,
for
1334 reconstituting a normalised match list.
1339 ICRS center of the search region.
1341 Radius of the search region.
1343 Name of camera filter,
or `
None`
or `
""`
for the default
1346 Deprecated, only included
for api compatibility.
1347 epoch : `astropy.time.Time` (optional)
1348 Epoch to which to correct proper motion
and parallax,
or `
None` to
1349 not apply such corrections.
1354 Metadata about the load
1357 md.add('RA', coord.getRa().asDegrees(),
'field center in degrees')
1358 md.add(
'DEC', coord.getDec().asDegrees(),
'field center in degrees')
1359 md.add(
'RADIUS', radius.asDegrees(),
'field radius in degrees, minimum')
1360 md.add(
'SMATCHV', 1,
'SourceMatchVector version number')
1361 filterName =
"UNKNOWN" if filterName
is None else str(filterName)
1362 md.add(
'FILTER', filterName,
'filter name for photometric data')
1363 md.add(
'EPOCH',
"NONE" if epoch
is None else epoch.mjd,
'Epoch (TAI MJD) for catalog')
1367 """Relink an unpersisted match list to sources and reference
1370 A match list is persisted
and unpersisted
as a catalog of IDs
1371 produced by afw.table.packMatches(),
with match metadata
1372 (
as returned by the astrometry tasks)
in the catalog
's metadata
1373 attribute. This method converts such a match catalog into a match
1374 list, with links to source records
and reference object records.
1379 Unperisted packed match list.
1380 ``matchCat.table.getMetadata()`` must contain match metadata,
1381 as returned by the astrometry tasks.
1383 Source catalog. As a side effect, the catalog will be sorted
1395 """Relink an unpersisted match list to sources and reference
1398 A match list is persisted
and unpersisted
as a catalog of IDs
1399 produced by afw.table.packMatches(),
with match metadata
1400 (
as returned by the astrometry tasks)
in the catalog
's metadata
1401 attribute. This method converts such a match catalog into a match
1402 list, with links to source records
and reference object records.
1407 Reference object loader to use
in getting reference objects
1409 Unperisted packed match list.
1410 ``matchCat.table.getMetadata()`` must contain match metadata,
1411 as returned by the astrometry tasks.
1413 Source catalog. As a side effect, the catalog will be sorted
1421 matchmeta = matchCat.table.getMetadata()
1422 version = matchmeta.getInt('SMATCHV')
1424 raise ValueError(
'SourceMatchVector version number is %i, not 1.' % version)
1425 filterName = matchmeta.getString(
'FILTER').strip()
1427 epoch = matchmeta.getDouble(
'EPOCH')
1428 except (LookupError, TypeError):
1430 if 'RADIUS' in matchmeta:
1433 matchmeta.getDouble(
'DEC'), geom.degrees)
1434 rad = matchmeta.getDouble(
'RADIUS')*geom.degrees
1435 refCat = refObjLoader.loadSkyCircle(ctrCoord, rad, filterName, epoch=epoch).refCat
1436 elif "INNER_UPPER_LEFT_RA" in matchmeta:
1442 for place
in (
"UPPER_LEFT",
"UPPER_RIGHT",
"LOWER_LEFT",
"LOWER_RIGHT"):
1444 matchmeta.getDouble(f
"OUTER_{place}_DEC"),
1445 geom.degrees).getVector()
1448 refCat = refObjLoader.loadRegion(outerBox, filterName=filterName, epoch=epoch).refCat
1452 return afwTable.unpackMatches(matchCat, refCat, sourceCat)
1456 """Apply proper motion correction to a reference catalog.
1458 Adjust position and position error
in the ``catalog``
1459 for proper motion to the specified ``epoch``,
1460 modifying the catalog
in place.
1465 Log object to write to.
1467 Catalog of positions, containing:
1469 - Coordinates, retrieved by the table
's coordinate key.
1470 - ``coord_raErr`` : Error in Right Ascension (rad).
1471 - ``coord_decErr`` : Error
in Declination (rad).
1472 - ``pm_ra`` : Proper motion
in Right Ascension (rad/yr,
1474 - ``pm_raErr`` : Error
in ``pm_ra`` (rad/yr), optional.
1475 - ``pm_dec`` : Proper motion
in Declination (rad/yr,
1477 - ``pm_decErr`` : Error
in ``pm_dec`` (rad/yr), optional.
1478 - ``epoch`` : Mean epoch of object (an astropy.time.Time)
1479 epoch : `astropy.time.Time`
1480 Epoch to which to correct proper motion.
1482 if "epoch" not in catalog.schema
or "pm_ra" not in catalog.schema
or "pm_dec" not in catalog.schema:
1483 log.warning(
"Proper motion correction not available from catalog")
1485 if not catalog.isContiguous():
1486 raise RuntimeError(
"Catalog must be contiguous")
1487 catEpoch = astropy.time.Time(catalog[
"epoch"], scale=
"tai", format=
"mjd")
1488 log.info(
"Correcting reference catalog for proper motion to %r", epoch)
1490 timeDiffsYears = (epoch.tai - catEpoch).to(astropy.units.yr).value
1491 coordKey = catalog.table.getCoordKey()
1494 pmRaRad = catalog[
"pm_ra"]
1495 pmDecRad = catalog[
"pm_dec"]
1496 offsetsRaRad = pmRaRad*timeDiffsYears
1497 offsetsDecRad = pmDecRad*timeDiffsYears
1505 offsetBearingsRad = numpy.arctan2(pmDecRad*1e6, pmRaRad*1e6)
1506 offsetAmountsRad = numpy.hypot(offsetsRaRad, offsetsDecRad)
1507 for record, bearingRad, amountRad
in zip(catalog, offsetBearingsRad, offsetAmountsRad):
1508 record.set(coordKey,
1509 record.get(coordKey).offset(bearing=bearingRad*geom.radians,
1510 amount=amountRad*geom.radians))
1512 if "coord_raErr" in catalog.schema:
1513 catalog[
"coord_raErr"] = numpy.hypot(catalog[
"coord_raErr"],
1514 catalog[
"pm_raErr"]*timeDiffsYears)
1515 if "coord_decErr" in catalog.schema:
1516 catalog[
"coord_decErr"] = numpy.hypot(catalog[
"coord_decErr"],
1517 catalog[
"pm_decErr"]*timeDiffsYears)
def __call__(self, refCat, catRegion)
def __init__(self, region)
def _trimToBBox(refCat, bbox, wcs)
def joinMatchListWithCatalog(self, matchCat, sourceCat)
def makeMinimalSchema(filterNameList, *addCentroid=False, addIsPhotometric=False, addIsResolved=False, addIsVariable=False, coordErrDim=2, addProperMotion=False, properMotionErrDim=2, addParallax=False)
def getMetadataCircle(self, coord, radius, filterName, photoCalib=None, epoch=None)
def __init__(self, butler=None, *args, **kwargs)
def _calculateCircle(self, bbox, wcs)
def loadPixelBox(self, bbox, wcs, filterName=None, photoCalib=None, epoch=None)
def loadSkyCircle(self, ctrCoord, radius, filterName=None, epoch=None, centroids=False)
def getMetadataBox(self, bbox, wcs, filterName=None, photoCalib=None, epoch=None)
def applyProperMotions(self, catalog, epoch)
def loadRegion(self, region, filtFunc=None, filterName=None, epoch=None)
def getMetadataCircle(coord, radius, filterName, photoCalib=None, epoch=None)
def _makeBoxRegion(BBox, wcs, BBoxPadding)
def remapReferenceCatalogSchema(refCat, *filterNameList=None, position=False, photometric=False)
def addFluxAliases(refCat, defaultFilter, filterReferenceMap)
def getMetadataBox(self, bbox, wcs, filterName=None, photoCalib=None, epoch=None, bboxToSpherePadding=100)
def loadPixelBox(self, bbox, wcs, filterName=None, epoch=None, photoCalib=None, bboxToSpherePadding=100)
def loadSkyCircle(self, ctrCoord, radius, filterName=None, epoch=None)
def joinMatchListWithCatalog(self, matchCat, sourceCat)
def __init__(self, dataIds, refCats, config, log=None)
def getRefFluxKeys(schema, filterName=None)
def hasNanojanskyFluxUnits(schema)
def getRefFluxField(schema, filterName=None)
def convertToNanojansky(catalog, log, doConvert=True)
def joinMatchListWithCatalogImpl(refObjLoader, matchCat, sourceCat)
def isOldFluxField(name, units)
def getFormatVersionFromRefCat(refCat)
def applyProperMotionsImpl(log, catalog, epoch)