24__all__ = [
"getRefFluxField",
"getRefFluxKeys",
"LoadReferenceObjectsTask",
"LoadReferenceObjectsConfig",
25 "ReferenceObjectLoader"]
37import lsst.pipe.base
as pipeBase
39from lsst
import sphgeom
44 """Return True if this name/units combination corresponds to an
45 "old-style" reference catalog flux field.
47 unitsCheck = units != 'nJy'
48 isFlux = name.endswith(
'_flux')
49 isFluxSigma = name.endswith(
'_fluxSigma')
50 isFluxErr = name.endswith(
'_fluxErr')
51 return (isFlux
or isFluxSigma
or isFluxErr)
and unitsCheck
55 """Return True if the units of all flux and fluxErr are correct (nJy).
64 """"Return the format version stored in a reference catalog header.
69 Reference catalog to inspect.
74 Format verison integer. Returns `0` if the catalog has no metadata
75 or the metadata does
not include a
"REFCAT_FORMAT_VERSION" key.
77 md = refCat.getMetadata()
81 return md.getScalar(
"REFCAT_FORMAT_VERSION")
87 """Convert fluxes in a catalog from jansky to nanojansky.
92 The catalog to convert.
94 Log to send messages to.
95 doConvert : `bool`, optional
96 Return a converted catalog, or just identify the fields that need to be converted?
97 This supports the
"write=False" mode of `bin/convert_to_nJy.py`.
102 The converted catalog,
or None if ``doConvert``
is False.
106 Support
for old units
in reference catalogs will be removed after the
107 release of late calendar year 2019.
108 Use `meas_algorithms/bin/convert_to_nJy.py` to update your reference catalog.
116 for field
in catalog.schema:
117 oldName = field.field.getName()
118 oldUnits = field.field.getUnits()
122 if oldName.endswith(
'_fluxSigma'):
123 name = oldName.replace(
'_fluxSigma',
'_fluxErr')
127 mapper.addMapping(field.getKey(), newField)
128 input_fields.append(field.field)
129 output_fields.append(newField)
131 mapper.addMapping(field.getKey())
133 fluxFieldsStr =
'; '.join(
"(%s, '%s')" % (field.getName(), field.getUnits())
for field
in input_fields)
136 newSchema = mapper.getOutputSchema()
138 output.extend(catalog, mapper=mapper)
139 for field
in output_fields:
140 output[field.getName()] *= 1e9
141 log.info(
"Converted refcat flux fields to nJy (name, units): %s", fluxFieldsStr)
144 log.info(
"Found old-style refcat flux fields (name, units): %s", fluxFieldsStr)
149 """This is a private helper class which filters catalogs by
150 row based on the row being inside the region used to initialize
156 The spatial region which all objects should lie within
162 """This call method on an instance of this class takes in a reference
163 catalog, and the region
from which the catalog was generated.
165 If the catalog region
is entirely contained within the region used to
166 initialize this
class, then all the entries
in the catalog must be
167 within the region
and so the whole catalog
is returned.
169 If the catalog region
is not entirely contained, then the location
for
170 each record
is tested against the region used to initialize the
class.
171 Records which fall inside this region are added to a new catalog,
and
172 this catalog
is then returned.
177 SourceCatalog to be filtered.
179 Region
in which the catalog was created
181 if catRegion.isWithin(self.
region):
185 filteredRefCat = type(refCat)(refCat.table)
186 for record
in refCat:
187 if self.
region.contains(record.getCoord().getVector()):
188 filteredRefCat.append(record)
189 return filteredRefCat
193 """Base class for reference object loaders, to facilitate gen2/gen3 code
197 """Apply proper motion correction to a reference catalog.
199 Adjust position and position error
in the ``catalog``
200 for proper motion to the specified ``epoch``,
201 modifying the catalog
in place.
206 Catalog of positions, containing at least these fields:
208 - Coordinates, retrieved by the table
's coordinate key.
209 - ``coord_raErr`` : Error in Right Ascension (rad).
210 - ``coord_decErr`` : Error
in Declination (rad).
211 - ``pm_ra`` : Proper motion
in Right Ascension (rad/yr,
213 - ``pm_raErr`` : Error
in ``pm_ra`` (rad/yr), optional.
214 - ``pm_dec`` : Proper motion
in Declination (rad/yr,
216 - ``pm_decErr`` : Error
in ``pm_dec`` (rad/yr), optional.
217 - ``epoch`` : Mean epoch of object (an astropy.time.Time)
218 epoch : `astropy.time.Time`
219 Epoch to which to correct proper motion.
220 If
None, do
not apply PM corrections
or raise if
221 ``config.requireProperMotion``
is True.
226 Raised
if ``config.requireProperMotion``
is set but we cannot
227 apply the proper motion correction
for some reason.
230 if self.config.requireProperMotion:
231 raise RuntimeError(
"requireProperMotion=True but epoch not provided to loader.")
233 self.log.debug(
"No epoch provided: not applying proper motion corrections to refcat.")
237 if (
"pm_ra" in catalog.schema
238 and not isinstance(catalog.schema[
"pm_ra"].asKey(), lsst.afw.table.KeyAngle)):
239 if self.config.requireProperMotion:
240 raise RuntimeError(
"requireProperMotion=True but refcat pm_ra field is not an Angle.")
242 self.log.warning(
"Reference catalog pm_ra field is not an Angle; cannot apply proper motion.")
245 if (
"epoch" not in catalog.schema
or "pm_ra" not in catalog.schema):
246 if self.config.requireProperMotion:
247 raise RuntimeError(
"requireProperMotion=True but PM data not available from catalog.")
249 self.log.warning(
"Proper motion correction not available for this reference catalog.")
256 """This class facilitates loading reference catalogs with gen 3 middleware
258 The middleware preflight solver will create a list of datarefs that may
259 possibly overlap a given region. These datarefs are then used to construct
260 and instance of this
class. The
class instance should then be passed into
261 a task which needs reference catalogs. These tasks should then determine
262 the exact region of the sky reference catalogs will be loaded
for,
and
263 call a corresponding method to load the reference objects.
265 def __init__(self, dataIds, refCats, config, log=None):
266 """ Constructs an instance of ReferenceObjectLoader
270 dataIds : iterable of `lsst.daf.butler.DataIds`
271 An iterable object of DataSetRefs which point to reference catalogs
272 in a gen 3 repository.
273 refCats : iterable of `lsst.daf.butler.DeferedDatasetHandle`
274 Handles to load refCats on demand
276 Configuration
for the loader.
278 Logger object used to write out messages. If `
None` the default
279 lsst logger will be used.
287 def _makeBoxRegion(BBox, wcs, BBoxPadding):
300 outerLocalBBox.grow(BBoxPadding)
301 innerLocalBBox.grow(-1*BBoxPadding)
313 innerBoxCorners = innerLocalBBox.getCorners()
314 innerSphCorners = [wcs.pixelToSky(corner).getVector()
for corner
in innerBoxCorners]
317 outerBoxCorners = outerLocalBBox.getCorners()
318 outerSphCorners = [wcs.pixelToSky(corner).getVector()
for corner
in outerBoxCorners]
321 return innerSkyRegion, outerSkyRegion, innerSphCorners, outerSphCorners
323 def loadPixelBox(self, bbox, wcs, filterName=None, epoch=None, photoCalib=None,
324 bboxToSpherePadding=100):
325 """Load reference objects that are within a pixel-based rectangular
328 This algorithm works by creating a spherical box whose corners
329 correspond to the WCS converted corners of the input bounding box
330 (possibly padded). It then defines a filtering function which looks at
331 the pixel position of the reference objects and accepts only those that
332 lie within the specified bounding box.
334 The spherical box region
and filtering function are passed to the
335 generic loadRegion method which loads
and filters the reference objects
336 from the datastore
and returns a single catalog containing the filtered
337 set of reference objects.
342 Box which bounds a region
in pixel space.
344 Wcs object defining the pixel to sky (
and inverse) transform
for
345 the supplied ``bbox``.
346 filterName : `str`
or `
None`, optional
347 Name of camera filter,
or `
None`
or blank
for the default filter.
348 epoch : `astropy.time.Time`
or `
None`, optional
349 Epoch to which to correct proper motion
and parallax,
or `
None`
350 to
not apply such corrections.
352 Deprecated
and ignored, only included
for api compatibility.
353 bboxToSpherePadding : `int`, optional
354 Padding to account
for translating a set of corners into a
355 spherical (convex) boundary that
is certain to encompase the
356 enitre area covered by the box.
361 Catalog containing reference objects inside the specified bounding
362 box (padded by self.
config.pixelMargin).
367 Raised
if no reference catalogs could be found
for the specified
370 Raised
if the loaded reference catalogs do
not have matching
374 paddedBbox.grow(self.config.pixelMargin)
375 innerSkyRegion, outerSkyRegion, _, _ = self._makeBoxRegion(paddedBbox, wcs, bboxToSpherePadding)
377 def _filterFunction(refCat, region):
388 refCat = preFiltFunc(refCat, region)
394 afwTable.updateRefCentroids(wcs, refCat)
397 if innerSkyRegion.contains(region):
401 filteredRefCat = type(refCat)(refCat.table)
402 centroidKey = afwTable.Point2DKey(refCat.schema[
'centroid'])
403 for record
in refCat:
404 pixCoords = record[centroidKey]
406 filteredRefCat.append(record)
407 return filteredRefCat
408 return self.
loadRegion(outerSkyRegion, filtFunc=_filterFunction, epoch=epoch, filterName=filterName)
410 def loadRegion(self, region, filtFunc=None, filterName=None, epoch=None):
411 """Load reference objects within a specified region.
413 This function loads the DataIds used to construct an instance of this
414 class which intersect or are contained within the specified region. The
415 reference catalogs which intersect but are
not fully contained within
416 the input region are further filtered by the specified filter function.
417 This function returns a single source catalog containing all reference
418 objects inside the specified region.
424 should define the spatial region
for which reference objects are to
426 filtFunc : callable
or `
None`, optional
427 This optional parameter should be a callable object that takes a
428 reference catalog
and its corresponding region
as parameters,
429 filters the catalog by some criteria
and returns the filtered
430 reference catalog. If `
None`, an internal filter function
is used
431 which filters according to
if a reference object falls within the
433 filterName : `str`
or `
None`, optional
434 Name of camera filter,
or `
None`
or blank
for the default filter.
435 epoch : `astropy.time.Time`
or `
None`, optional
436 Epoch to which to correct proper motion
and parallax,
or `
None` to
437 not apply such corrections.
442 Catalog containing reference objects which intersect the input region,
443 filtered by the specified filter function.
448 Raised
if no reference catalogs could be found
for the specified
451 Raised
if the loaded reference catalogs do
not have matching
454 regionLat = region.getBoundingBox().getLat()
455 regionLon = region.getBoundingBox().getLon()
456 self.log.info("Loading reference objects from region bounded by "
457 "[%.8f, %.8f], [%.8f, %.8f] RA Dec",
458 regionLon.getA().asDegrees(), regionLon.getB().asDegrees(),
459 regionLat.getA().asDegrees(), regionLat.getB().asDegrees())
468 intersects = dataId.region.intersects(region)
470 intersects = region.intersects(dataId.region)
473 overlapList.append((dataId, refCat))
475 if len(overlapList) == 0:
476 raise RuntimeError(
"No reference tables could be found for input region")
478 firstCat = overlapList[0][1].get()
479 refCat = filtFunc(firstCat, overlapList[0][0].region)
480 trimmedAmount = len(firstCat) - len(refCat)
483 for dataId, inputRefCat
in overlapList[1:]:
484 tmpCat = inputRefCat.get()
486 if tmpCat.schema != firstCat.schema:
487 raise TypeError(
"Reference catalogs have mismatching schemas")
489 filteredCat = filtFunc(tmpCat, dataId.region)
490 refCat.extend(filteredCat)
491 trimmedAmount += len(tmpCat) - len(filteredCat)
493 self.
log.debug(
"Trimmed %d refCat objects lying outside padded region, leaving %d",
494 trimmedAmount, len(refCat))
495 self.
log.info(
"Loaded %d reference objects", len(refCat))
498 if not refCat.isContiguous():
499 refCat = refCat.copy(deep=
True)
506 self.
log.warning(
"Found version 0 reference catalog with old style units in schema.")
507 self.
log.warning(
"run `meas_algorithms/bin/convert_refcat_to_nJy.py` to convert fluxes to nJy.")
508 self.
log.warning(
"See RFC-575 for more details.")
517 if not expandedCat.isContiguous():
518 expandedCat = expandedCat.copy(deep=
True)
520 fluxField =
getRefFluxField(schema=expandedCat.schema, filterName=filterName)
521 return pipeBase.Struct(refCat=expandedCat, fluxField=fluxField)
524 """Load reference objects that lie within a circular region on the sky.
526 This method constructs a circular region from an input center
and
527 angular radius, loads reference catalogs which are contained
in or
528 intersect the circle,
and filters reference catalogs which intersect
529 down to objects which lie within the defined circle.
534 Point defining the center of the circular region.
536 Defines the angular radius of the circular region.
537 filterName : `str`
or `
None`, optional
538 Name of camera filter,
or `
None`
or blank
for the default filter.
539 epoch : `astropy.time.Time`
or `
None`, optional
540 Epoch to which to correct proper motion
and parallax,
or `
None` to
541 not apply such corrections.
546 Catalog containing reference objects inside the specified search
549 centerVector = ctrCoord.getVector()
552 return self.
loadRegion(circularRegion, filterName=filterName, epoch=epoch)
555 """Relink an unpersisted match list to sources and reference objects.
557 A match list is persisted
and unpersisted
as a catalog of IDs
558 produced by afw.table.packMatches(),
with match metadata
559 (
as returned by the astrometry tasks)
in the catalog
's metadata
560 attribute. This method converts such a match catalog into a match
561 list, with links to source records
and reference object records.
566 Unpersisted packed match list.
567 ``matchCat.table.getMetadata()`` must contain match metadata,
568 as returned by the astrometry tasks.
570 Source catalog. As a side effect, the catalog will be sorted
580 def getMetadataBox(self, bbox, wcs, filterName=None, photoCalib=None, epoch=None,
581 bboxToSpherePadding=100):
582 """Return metadata about the load
584 This metadata is used
for reloading the catalog (e.g.,
for
585 reconstituting a normalised match list.)
590 Bounding box
for the pixels.
592 The WCS object associated
with ``bbox``.
593 filterName : `str`
or `
None`, optional
594 Name of the camera filter,
or `
None`
or blank
for the default
597 Deprecated, only included
for api compatibility.
598 epoch : `astropy.time.Time`
or `
None`, optional
599 Epoch to which to correct proper motion
and parallax,
or `
None` to
600 not apply such corrections.
601 bboxToSpherePadding : `int`, optional
602 Padding to account
for translating a set of corners into a
603 spherical (convex) boundary that
is certain to encompase the
604 enitre area covered by the box.
609 The metadata detailing the search parameters used
for this
613 paddedBbox.grow(self.config.pixelMargin)
614 _, _, innerCorners, outerCorners = self._makeBoxRegion(paddedBbox, wcs, bboxToSpherePadding)
616 for box, corners
in zip((
"INNER",
"OUTER"), (innerCorners, outerCorners)):
617 for (name, corner)
in zip((
"UPPER_LEFT",
"UPPER_RIGHT",
"LOWER_LEFT",
"LOWER_RIGHT"),
619 md.add(f
"{box}_{name}_RA",
geom.SpherePoint(corner).getRa().asDegrees(), f
"{box}_corner")
620 md.add(f
"{box}_{name}_DEC",
geom.SpherePoint(corner).getDec().asDegrees(), f
"{box}_corner")
621 md.add(
"SMATCHV", 1,
'SourceMatchVector version number')
622 filterName =
"UNKNOWN" if filterName
is None else str(filterName)
623 md.add(
'FILTER', filterName,
'filter name for photometric data')
624 md.add(
'EPOCH',
"NONE" if epoch
is None else epoch.mjd,
'Epoch (TAI MJD) for catalog')
629 """Return metadata about the load.
631 This metadata is used
for reloading the catalog (e.g.
for
632 reconstituting a normalized match list.)
637 ICRS center of the search region.
639 Radius of the search region.
640 filterName : `str`
or `
None`
641 Name of the camera filter,
or `
None`
or blank
for the default
644 Deprecated, only included
for api compatibility.
645 epoch : `astropy.time.Time`
or `
None`, optional
646 Epoch to which to correct proper motion
and parallax,
or `
None` to
647 not apply such corrections.
654 md.add('RA', coord.getRa().asDegrees(),
'field center in degrees')
655 md.add(
'DEC', coord.getDec().asDegrees(),
'field center in degrees')
656 md.add(
'RADIUS', radius.asDegrees(),
'field radius in degrees, minimum')
657 md.add(
'SMATCHV', 1,
'SourceMatchVector version number')
658 filterName =
"UNKNOWN" if filterName
is None else str(filterName)
659 md.add(
'FILTER', filterName,
'filter name for photometric data')
660 md.add(
'EPOCH',
"NONE" if epoch
is None else epoch.mjd,
'Epoch (TAI MJD) for catalog')
665 """Add flux columns and aliases for camera to reference mapping.
667 Creates a new catalog containing the information of the input refCat
668 as well
as added flux columns
and aliases between camera
and reference
674 Catalog of reference objects
675 defaultFilter : `str`
676 Name of the default reference filter
677 filterReferenceMap : `dict` of `str`
678 Dictionary
with keys corresponding to a filter name
and values
679 which correspond to the name of the reference filter.
684 Reference catalog
with columns added to track reference filters.
689 If the specified reference filter name
is not specifed
as a
690 key
in the reference filter map.
692 refCat = ReferenceObjectLoader.remapReferenceCatalogSchema(refCat,
693 filterNameList=filterReferenceMap.keys())
694 aliasMap = refCat.schema.getAliasMap()
695 if filterReferenceMap
is None:
696 filterReferenceMap = {}
697 for filterName, refFilterName
in itertools.chain([(
None, defaultFilter)],
698 filterReferenceMap.items()):
700 camFluxName = filterName +
"_camFlux" if filterName
is not None else "camFlux"
701 refFluxName = refFilterName +
"_flux"
702 if refFluxName
not in refCat.schema:
703 raise RuntimeError(
"Unknown reference filter %s" % (refFluxName,))
704 aliasMap.set(camFluxName, refFluxName)
706 refFluxErrName = refFluxName +
"Err"
707 camFluxErrName = camFluxName +
"Err"
708 aliasMap.set(camFluxErrName, refFluxErrName)
714 """This function takes in a reference catalog and creates a new catalog with additional
715 columns defined the remaining function arguments.
720 Reference catalog to map to new catalog
725 Deep copy of input reference catalog with additional columns added
727 mapper = afwTable.SchemaMapper(refCat.schema, True)
728 mapper.addMinimalSchema(refCat.schema,
True)
729 mapper.editOutputSchema().disconnectAliases()
731 for filterName
in filterNameList:
732 mapper.editOutputSchema().addField(f
"{filterName}_flux",
734 doc=f
"flux in filter {filterName}",
737 mapper.editOutputSchema().addField(f
"{filterName}_fluxErr",
739 doc=f
"flux uncertanty in filter {filterName}",
744 mapper.editOutputSchema().addField(
"centroid_x", type=float, doReplace=
True)
745 mapper.editOutputSchema().addField(
"centroid_y", type=float, doReplace=
True)
746 mapper.editOutputSchema().addField(
"hasCentroid", type=
"Flag", doReplace=
True)
747 mapper.editOutputSchema().getAliasMap().set(
"slot_Centroid",
"centroid")
750 mapper.editOutputSchema().addField(
"photometric",
752 doc=
"set if the object can be used for photometric"
755 mapper.editOutputSchema().addField(
"resolved",
757 doc=
"set if the object is spatially resolved"
759 mapper.editOutputSchema().addField(
"variable",
761 doc=
"set if the object has variable brightness"
764 expandedCat = afwTable.SimpleCatalog(mapper.getOutputSchema())
765 expandedCat.setMetadata(refCat.getMetadata())
766 expandedCat.extend(refCat, mapper=mapper)
772 """Get the name of a flux field from a schema.
774 return the alias of
"anyFilterMapsToThis",
if present
775 else if filterName
is specified:
776 return "*filterName*_camFlux" if present
777 else return "*filterName*_flux" if present (camera filter name
778 matches reference filter name)
779 else throw RuntimeError
781 return "camFlux",
if present,
782 else throw RuntimeError
787 Reference catalog schema.
788 filterName : `str`, optional
789 Name of camera filter. If
not specified, ``defaultFilter`` needs to be
790 set
in the refcat loader config.
794 fluxFieldName : `str`
800 If an appropriate field
is not found.
802 if not isinstance(schema, afwTable.Schema):
803 raise RuntimeError(
"schema=%s is not a schema" % (schema,))
805 return schema.getAliasMap().get(
"anyFilterMapsToThis")
810 fluxFieldList = [filterName +
"_camFlux", filterName +
"_flux"]
812 fluxFieldList = [
"camFlux"]
813 for fluxField
in fluxFieldList:
814 if fluxField
in schema:
817 raise RuntimeError(
"Could not find flux field(s) %s" % (
", ".join(fluxFieldList)))
821 """Return keys for flux and flux error.
826 Reference catalog schema.
828 Name of camera filter.
836 - flux error key, if present,
else None
841 If flux field
not found.
844 fluxErrField = fluxField + "Err"
845 fluxKey = schema[fluxField].asKey()
847 fluxErrKey = schema[fluxErrField].asKey()
850 return (fluxKey, fluxErrKey)
854 pixelMargin = pexConfig.RangeField(
855 doc=
"Padding to add to 4 all edges of the bounding box (pixels)",
860 defaultFilter = pexConfig.Field(
861 doc=(
"Default reference catalog filter to use if filter not specified in exposure;"
862 " if blank then filter must be specified in exposure."),
865 deprecated=
"defaultFilter is deprecated by RFC-716. Will be removed after v22."
867 anyFilterMapsToThis = pexConfig.Field(
868 doc=(
"Always use this reference catalog filter, no matter whether or what filter name is "
869 "supplied to the loader. Effectively a trivial filterMap: map all filter names to this filter."
870 " This can be set for purely-astrometric catalogs (e.g. Gaia DR2) where there is only one "
871 "reasonable choice for every camera filter->refcat mapping, but not for refcats used for "
872 "photometry, which need a filterMap and/or colorterms/transmission corrections."),
877 filterMap = pexConfig.DictField(
878 doc=(
"Mapping of camera filter name: reference catalog filter name; "
879 "each reference filter must exist in the refcat."
880 " Note that this does not perform any bandpass corrections: it is just a lookup."),
885 requireProperMotion = pexConfig.Field(
886 doc=
"Require that the fields needed to correct proper motion "
887 "(epoch, pm_ra and pm_dec) are present?",
895 msg =
"`filterMap` and `anyFilterMapsToThis` are mutually exclusive"
896 raise pexConfig.FieldValidationError(LoadReferenceObjectsConfig.anyFilterMapsToThis,
901 """Abstract base class to load objects from reference catalogs.
903 ConfigClass = LoadReferenceObjectsConfig
904 _DefaultName = "LoadReferenceObjects"
907 """Construct a LoadReferenceObjectsTask
911 butler : `lsst.daf.persistence.Butler`
912 Data butler, for access reference catalogs.
914 pipeBase.Task.__init__(self, *args, **kwargs)
918 def loadPixelBox(self, bbox, wcs, filterName=None, photoCalib=None, epoch=None):
919 """Load reference objects that overlap a rectangular pixel region.
924 Bounding box
for pixels.
926 WCS; used to convert pixel positions to sky coordinates
928 filterName : `str`
or `
None`, optional
929 Name of filter,
or `
None`
or `
""`
for the default filter.
930 This
is used
for flux values
in case we have flux limits
931 (which are
not yet implemented).
933 Deprecated, only included
for api compatibility.
934 epoch : `astropy.time.Time`
or `
None`, optional
935 Epoch to which to correct proper motion
and parallax,
or `
None` to
936 not apply such corrections.
940 results : `lsst.pipe.base.Struct`
941 A Struct containing the following fields:
942 refCat : `lsst.afw.catalog.SimpleCatalog`
943 A catalog of reference objects
with the standard
944 schema,
as documented
in the main doc string
for
945 `LoadReferenceObjects`.
946 The catalog
is guaranteed to be contiguous.
948 Name of flux field
for specified `filterName`.
952 The search algorithm works by searching
in a region
in sky
953 coordinates whose center
is the center of the bbox
and radius
954 is large enough to just include all 4 corners of the bbox.
955 Stars that lie outside the bbox are then trimmed
from the list.
960 self.log.info(
"Loading reference objects using center %s and radius %s deg",
961 circle.coord, circle.radius.asDegrees())
962 loadRes = self.
loadSkyCircle(circle.coord, circle.radius, filterName=filterName, epoch=epoch,
964 refCat = loadRes.refCat
965 numFound = len(refCat)
968 refCat = self.
_trimToBBox(refCat=refCat, bbox=circle.bbox, wcs=wcs)
969 numTrimmed = numFound - len(refCat)
970 self.log.debug(
"trimmed %d out-of-bbox objects, leaving %d", numTrimmed, len(refCat))
971 self.log.info(
"Loaded %d reference objects", len(refCat))
974 if not refCat.isContiguous():
975 loadRes.refCat = refCat.copy(deep=
True)
980 def loadSkyCircle(self, ctrCoord, radius, filterName=None, epoch=None, centroids=False):
981 """Load reference objects that overlap a circular sky region.
986 ICRS center of search region.
988 Radius of search region.
989 filterName : `str` or `
None`, optional
990 Name of filter,
or `
None`
or `
""`
for the default filter.
991 This
is used
for flux values
in case we have flux limits
992 (which are
not yet implemented).
993 epoch : `astropy.time.Time`
or `
None`, optional
994 Epoch to which to correct proper motion
and parallax,
or `
None` to
995 not apply such corrections.
996 centroids : `bool`, optional
997 Add centroid fields to the loaded Schema. ``loadPixelBox`` expects
998 these fields to exist.
1002 results : `lsst.pipe.base.Struct`
1003 A Struct containing the following fields:
1004 refCat : `lsst.afw.catalog.SimpleCatalog`
1005 A catalog of reference objects
with the standard
1006 schema,
as documented
in the main doc string
for
1007 `LoadReferenceObjects`.
1008 The catalog
is guaranteed to be contiguous.
1010 Name of flux field
for specified `filterName`.
1014 Note that subclasses are responsible
for performing the proper motion
1015 correction, since this
is the lowest-level interface
for retrieving
1021 def _trimToBBox(refCat, bbox, wcs):
1022 """Remove objects outside a given pixel bounding box and set
1023 centroid and hasCentroid fields.
1028 A catalog of objects. The schema must include fields
1029 "coord",
"centroid" and "hasCentroid".
1030 The
"coord" field
is read.
1031 The
"centroid" and "hasCentroid" fields are set.
1035 WCS; used to convert sky coordinates to pixel positions.
1040 Reference objects
in the bbox,
with centroid
and
1041 hasCentroid fields set.
1043 afwTable.updateRefCentroids(wcs, refCat)
1044 centroidKey = afwTable.Point2DKey(refCat.schema["centroid"])
1045 retStarCat = type(refCat)(refCat.table)
1047 point = star.get(centroidKey)
1048 if bbox.contains(point):
1049 retStarCat.append(star)
1052 def _addFluxAliases(self, schema):
1053 """Add aliases for camera filter fluxes to the schema.
1055 If self.config.defaultFilter then adds these aliases:
1056 camFlux: <defaultFilter>_flux
1057 camFluxErr: <defaultFilter>_fluxErr, if the latter exists
1059 For each camFilter: refFilter
in self.config.filterMap adds these aliases:
1060 <camFilter>_camFlux: <refFilter>_flux
1061 <camFilter>_camFluxErr: <refFilter>_fluxErr,
if the latter exists
1066 Schema
for reference catalog.
1071 If any reference flux field
is missing
from the schema.
1073 aliasMap = schema.getAliasMap()
1075 if self.config.anyFilterMapsToThis
is not None:
1076 refFluxName = self.config.anyFilterMapsToThis +
"_flux"
1077 if refFluxName
not in schema:
1078 msg = f
"Unknown reference filter for anyFilterMapsToThis='{refFluxName}'"
1079 raise RuntimeError(msg)
1080 aliasMap.set(
"anyFilterMapsToThis", refFluxName)
1083 def addAliasesForOneFilter(filterName, refFilterName):
1084 """Add aliases for a single filter
1088 filterName : `str` (optional)
1089 Camera filter name. The resulting alias name is
1090 <filterName>_camFlux,
or simply
"camFlux" if `filterName`
1092 refFilterName : `str`
1093 Reference catalog filter name; the field
1094 <refFilterName>_flux must exist.
1096 camFluxName = filterName + "_camFlux" if filterName
is not None else "camFlux"
1097 refFluxName = refFilterName +
"_flux"
1098 if refFluxName
not in schema:
1099 raise RuntimeError(
"Unknown reference filter %s" % (refFluxName,))
1100 aliasMap.set(camFluxName, refFluxName)
1101 refFluxErrName = refFluxName +
"Err"
1102 if refFluxErrName
in schema:
1103 camFluxErrName = camFluxName +
"Err"
1104 aliasMap.set(camFluxErrName, refFluxErrName)
1106 if self.config.defaultFilter:
1107 addAliasesForOneFilter(
None, self.config.defaultFilter)
1109 for filterName, refFilterName
in self.config.filterMap.items():
1110 addAliasesForOneFilter(filterName, refFilterName)
1114 addIsPhotometric=False, addIsResolved=False,
1115 addIsVariable=False, coordErrDim=2,
1116 addProperMotion=False, properMotionErrDim=2,
1118 """Make a standard schema for reference object catalogs.
1122 filterNameList : `list` of `str`
1123 List of filter names. Used to create <filterName>_flux fields.
1124 addIsPhotometric : `bool`
1125 If True then add field
"photometric".
1126 addIsResolved : `bool`
1127 If
True then add field
"resolved".
1128 addIsVariable : `bool`
1129 If
True then add field
"variable".
1131 Number of coord error fields; must be one of 0, 2, 3:
1133 - If 2
or 3: add fields
"coord_raErr" and "coord_decErr".
1134 - If 3: also add field
"coord_radecErr".
1135 addProperMotion : `bool`
1136 If
True add fields
"epoch",
"pm_ra",
"pm_dec" and "pm_flag".
1137 properMotionErrDim : `int`
1138 Number of proper motion error fields; must be one of 0, 2, 3;
1139 ignored
if addProperMotion false:
1140 - If 2
or 3: add fields
"pm_raErr" and "pm_decErr".
1141 - If 3: also add field
"pm_radecErr".
1142 addParallax : `bool`
1143 If
True add fields
"epoch",
"parallax",
"parallaxErr"
1144 and "parallax_flag".
1149 Schema
for reference catalog, an
1154 Reference catalogs support additional covariances, such
as
1155 covariance between RA
and proper motion
in declination,
1156 that are
not supported by this method, but can be added after
1157 calling this method.
1159 schema = afwTable.SimpleTable.makeMinimalSchema()
1161 afwTable.Point2DKey.addFields(
1164 "centroid on an exposure, if relevant",
1168 field=
"hasCentroid",
1170 doc=
"is position known?",
1172 for filterName
in filterNameList:
1174 field=
"%s_flux" % (filterName,),
1176 doc=
"flux in filter %s" % (filterName,),
1179 for filterName
in filterNameList:
1181 field=
"%s_fluxErr" % (filterName,),
1183 doc=
"flux uncertainty in filter %s" % (filterName,),
1186 if addIsPhotometric:
1188 field=
"photometric",
1190 doc=
"set if the object can be used for photometric calibration",
1196 doc=
"set if the object is spatially resolved",
1202 doc=
"set if the object has variable brightness",
1204 if coordErrDim
not in (0, 2, 3):
1205 raise ValueError(
"coordErrDim={}; must be (0, 2, 3)".format(coordErrDim))
1207 afwTable.CovarianceMatrix2fKey.addFields(
1210 names=[
"ra",
"dec"],
1211 units=[
"rad",
"rad"],
1212 diagonalOnly=(coordErrDim == 2),
1215 if addProperMotion
or addParallax:
1219 doc=
"date of observation (TAI, MJD)",
1227 doc=
"proper motion in the right ascension direction = dra/dt * cos(dec)",
1233 doc=
"proper motion in the declination direction",
1236 if properMotionErrDim
not in (0, 2, 3):
1237 raise ValueError(
"properMotionErrDim={}; must be (0, 2, 3)".format(properMotionErrDim))
1238 if properMotionErrDim > 0:
1239 afwTable.CovarianceMatrix2fKey.addFields(
1242 names=[
"ra",
"dec"],
1243 units=[
"rad/year",
"rad/year"],
1244 diagonalOnly=(properMotionErrDim == 2),
1249 doc=
"Set if proper motion or proper motion error is bad",
1260 field=
"parallaxErr",
1262 doc=
"uncertainty in parallax",
1266 field=
"parallax_flag",
1268 doc=
"Set if parallax or parallax error is bad",
1272 def _calculateCircle(self, bbox, wcs):
1273 """Compute on-sky center and radius of search region.
1280 WCS; used to convert pixel positions to sky coordinates.
1284 results : `lsst.pipe.base.Struct`
1285 A Struct containing:
1288 ICRS center of the search region.
1290 Radius of the search region.
1292 Bounding box used to compute the circle.
1295 bbox.grow(self.config.pixelMargin)
1296 coord = wcs.pixelToSky(bbox.getCenter())
1297 radius = max(coord.separation(wcs.pixelToSky(pp))
for pp
in bbox.getCorners())
1298 return pipeBase.Struct(coord=coord, radius=radius, bbox=bbox)
1301 """Return metadata about the load.
1303 This metadata is used
for reloading the catalog (e.g.,
for
1304 reconstituting a normalised match list.
1311 WCS; used to convert pixel positions to sky coordinates.
1312 filterName : `str`
or `
None`, optional
1313 Name of camera filter,
or `
None`
or `
""`
for the default
1316 Deprecated, only included
for api compatibility.
1317 epoch : `astropy.time.Time`
or `
None`, optional
1318 Epoch to which to correct proper motion
and parallax,
1319 or None to
not apply such corrections.
1324 Metadata about the load.
1327 return self.
getMetadataCircle(circle.coord, circle.radius, filterName, epoch=epoch)
1330 """Return metadata about the load.
1332 This metadata is used
for reloading the catalog (e.g.,
for
1333 reconstituting a normalised match list.
1338 ICRS center of the search region.
1340 Radius of the search region.
1342 Name of camera filter,
or `
None`
or `
""`
for the default
1345 Deprecated, only included
for api compatibility.
1346 epoch : `astropy.time.Time` (optional)
1347 Epoch to which to correct proper motion
and parallax,
or `
None` to
1348 not apply such corrections.
1353 Metadata about the load
1356 md.add('RA', coord.getRa().asDegrees(),
'field center in degrees')
1357 md.add(
'DEC', coord.getDec().asDegrees(),
'field center in degrees')
1358 md.add(
'RADIUS', radius.asDegrees(),
'field radius in degrees, minimum')
1359 md.add(
'SMATCHV', 1,
'SourceMatchVector version number')
1360 filterName =
"UNKNOWN" if filterName
is None else str(filterName)
1361 md.add(
'FILTER', filterName,
'filter name for photometric data')
1362 md.add(
'EPOCH',
"NONE" if epoch
is None else epoch.mjd,
'Epoch (TAI MJD) for catalog')
1366 """Relink an unpersisted match list to sources and reference
1369 A match list is persisted
and unpersisted
as a catalog of IDs
1370 produced by afw.table.packMatches(),
with match metadata
1371 (
as returned by the astrometry tasks)
in the catalog
's metadata
1372 attribute. This method converts such a match catalog into a match
1373 list, with links to source records
and reference object records.
1378 Unperisted packed match list.
1379 ``matchCat.table.getMetadata()`` must contain match metadata,
1380 as returned by the astrometry tasks.
1382 Source catalog. As a side effect, the catalog will be sorted
1394 """Relink an unpersisted match list to sources and reference
1397 A match list is persisted
and unpersisted
as a catalog of IDs
1398 produced by afw.table.packMatches(),
with match metadata
1399 (
as returned by the astrometry tasks)
in the catalog
's metadata
1400 attribute. This method converts such a match catalog into a match
1401 list, with links to source records
and reference object records.
1406 Reference object loader to use
in getting reference objects
1408 Unperisted packed match list.
1409 ``matchCat.table.getMetadata()`` must contain match metadata,
1410 as returned by the astrometry tasks.
1412 Source catalog. As a side effect, the catalog will be sorted
1420 matchmeta = matchCat.table.getMetadata()
1421 version = matchmeta.getInt('SMATCHV')
1423 raise ValueError(
'SourceMatchVector version number is %i, not 1.' % version)
1424 filterName = matchmeta.getString(
'FILTER').strip()
1426 epoch = matchmeta.getDouble(
'EPOCH')
1427 except (LookupError, TypeError):
1429 if 'RADIUS' in matchmeta:
1432 matchmeta.getDouble(
'DEC'), geom.degrees)
1433 rad = matchmeta.getDouble(
'RADIUS')*geom.degrees
1434 refCat = refObjLoader.loadSkyCircle(ctrCoord, rad, filterName, epoch=epoch).refCat
1435 elif "INNER_UPPER_LEFT_RA" in matchmeta:
1441 for place
in (
"UPPER_LEFT",
"UPPER_RIGHT",
"LOWER_LEFT",
"LOWER_RIGHT"):
1443 matchmeta.getDouble(f
"OUTER_{place}_DEC"),
1444 geom.degrees).getVector()
1447 refCat = refObjLoader.loadRegion(outerBox, filterName=filterName, epoch=epoch).refCat
1451 return afwTable.unpackMatches(matchCat, refCat, sourceCat)
1455 """Apply proper motion correction to a reference catalog.
1457 Adjust position and position error
in the ``catalog``
1458 for proper motion to the specified ``epoch``,
1459 modifying the catalog
in place.
1464 Log object to write to.
1466 Catalog of positions, containing:
1468 - Coordinates, retrieved by the table
's coordinate key.
1469 - ``coord_raErr`` : Error in Right Ascension (rad).
1470 - ``coord_decErr`` : Error
in Declination (rad).
1471 - ``pm_ra`` : Proper motion
in Right Ascension (rad/yr,
1473 - ``pm_raErr`` : Error
in ``pm_ra`` (rad/yr), optional.
1474 - ``pm_dec`` : Proper motion
in Declination (rad/yr,
1476 - ``pm_decErr`` : Error
in ``pm_dec`` (rad/yr), optional.
1477 - ``epoch`` : Mean epoch of object (an astropy.time.Time)
1478 epoch : `astropy.time.Time`
1479 Epoch to which to correct proper motion.
1481 if "epoch" not in catalog.schema
or "pm_ra" not in catalog.schema
or "pm_dec" not in catalog.schema:
1482 log.warning(
"Proper motion correction not available from catalog")
1484 if not catalog.isContiguous():
1485 raise RuntimeError(
"Catalog must be contiguous")
1486 catEpoch = astropy.time.Time(catalog[
"epoch"], scale=
"tai", format=
"mjd")
1487 log.info(
"Correcting reference catalog for proper motion to %r", epoch)
1489 timeDiffsYears = (epoch.tai - catEpoch).to(astropy.units.yr).value
1490 coordKey = catalog.table.getCoordKey()
1493 pmRaRad = catalog[
"pm_ra"]
1494 pmDecRad = catalog[
"pm_dec"]
1495 offsetsRaRad = pmRaRad*timeDiffsYears
1496 offsetsDecRad = pmDecRad*timeDiffsYears
1504 offsetBearingsRad = numpy.arctan2(pmDecRad*1e6, pmRaRad*1e6)
1505 offsetAmountsRad = numpy.hypot(offsetsRaRad, offsetsDecRad)
1506 for record, bearingRad, amountRad
in zip(catalog, offsetBearingsRad, offsetAmountsRad):
1507 record.set(coordKey,
1508 record.get(coordKey).offset(bearing=bearingRad*geom.radians,
1509 amount=amountRad*geom.radians))
1511 if "coord_raErr" in catalog.schema:
1512 catalog[
"coord_raErr"] = numpy.hypot(catalog[
"coord_raErr"],
1513 catalog[
"pm_raErr"]*timeDiffsYears)
1514 if "coord_decErr" in catalog.schema:
1515 catalog[
"coord_decErr"] = numpy.hypot(catalog[
"coord_decErr"],
1516 catalog[
"pm_decErr"]*timeDiffsYears)
static Schema makeMinimalSchema()
static Log getDefaultLogger()
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)