24__all__ = [
"getRefFluxField",
"getRefFluxKeys",
"LoadReferenceObjectsTask",
"LoadReferenceObjectsConfig",
25 "ReferenceObjectLoader",
"ReferenceObjectLoaderBase"]
37import lsst.pipe.base
as pipeBase
38from lsst
import sphgeom
40from lsst.utils.timer
import timeMethod
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.
112 mapper = afwTable.SchemaMapper(catalog.schema, shareAliasMap=
False)
113 mapper.addMinimalSchema(afwTable.SimpleTable.makeMinimalSchema())
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')
126 newField = afwTable.Field[field.dtype](name, field.field.getDoc(), units)
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()
137 output = afwTable.SimpleCatalog(newSchema)
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.
regionregion):
185 filteredRefCat = type(refCat)(refCat.table)
186 for record
in refCat:
187 if self.
regionregion.contains(record.getCoord().getVector()):
188 filteredRefCat.append(record)
189 return filteredRefCat
193 pixelMargin = pexConfig.RangeField(
194 doc=
"Padding to add to 4 all edges of the bounding box (pixels)",
199 anyFilterMapsToThis = pexConfig.Field(
200 doc=(
"Always use this reference catalog filter, no matter whether or what filter name is "
201 "supplied to the loader. Effectively a trivial filterMap: map all filter names to this filter."
202 " This can be set for purely-astrometric catalogs (e.g. Gaia DR2) where there is only one "
203 "reasonable choice for every camera filter->refcat mapping, but not for refcats used for "
204 "photometry, which need a filterMap and/or colorterms/transmission corrections."),
209 filterMap = pexConfig.DictField(
210 doc=(
"Mapping of camera filter name: reference catalog filter name; "
211 "each reference filter must exist in the refcat."
212 " Note that this does not perform any bandpass corrections: it is just a lookup."),
217 requireProperMotion = pexConfig.Field(
218 doc=
"Require that the fields needed to correct proper motion "
219 "(epoch, pm_ra and pm_dec) are present?",
227 msg =
"`filterMap` and `anyFilterMapsToThis` are mutually exclusive"
228 raise pexConfig.FieldValidationError(LoadReferenceObjectsConfig.anyFilterMapsToThis,
233 """Base class for reference object loaders, to facilitate gen2/gen3 code
238 config : `lsst.pex.config.Config`
239 Configuration for the loader.
241 ConfigClass = LoadReferenceObjectsConfig
247 """Apply proper motion correction to a reference catalog.
249 Adjust position and position error
in the ``catalog``
250 for proper motion to the specified ``epoch``,
251 modifying the catalog
in place.
256 Catalog of positions, containing at least these fields:
258 - Coordinates, retrieved by the table
's coordinate key.
259 - ``coord_raErr`` : Error in Right Ascension (rad).
260 - ``coord_decErr`` : Error
in Declination (rad).
261 - ``pm_ra`` : Proper motion
in Right Ascension (rad/yr,
263 - ``pm_raErr`` : Error
in ``pm_ra`` (rad/yr), optional.
264 - ``pm_dec`` : Proper motion
in Declination (rad/yr,
266 - ``pm_decErr`` : Error
in ``pm_dec`` (rad/yr), optional.
267 - ``epoch`` : Mean epoch of object (an astropy.time.Time)
268 epoch : `astropy.time.Time`
269 Epoch to which to correct proper motion.
270 If
None, do
not apply PM corrections
or raise if
271 ``config.requireProperMotion``
is True.
276 Raised
if ``config.requireProperMotion``
is set but we cannot
277 apply the proper motion correction
for some reason.
280 if self.
configconfig.requireProperMotion:
281 raise RuntimeError(
"requireProperMotion=True but epoch not provided to loader.")
283 self.log.debug(
"No epoch provided: not applying proper motion corrections to refcat.")
287 if (
"pm_ra" in catalog.schema
288 and not isinstance(catalog.schema[
"pm_ra"].asKey(), afwTable.KeyAngle)):
289 if self.
configconfig.requireProperMotion:
290 raise RuntimeError(
"requireProperMotion=True but refcat pm_ra field is not an Angle.")
292 self.log.warning(
"Reference catalog pm_ra field is not an Angle; cannot apply proper motion.")
295 if (
"epoch" not in catalog.schema
or "pm_ra" not in catalog.schema):
296 if self.
configconfig.requireProperMotion:
297 raise RuntimeError(
"requireProperMotion=True but PM data not available from catalog.")
299 self.log.warning(
"Proper motion correction not available for this reference catalog.")
306 addIsPhotometric=False, addIsResolved=False,
307 addIsVariable=False, coordErrDim=2,
308 addProperMotion=False, properMotionErrDim=2,
310 """Make a standard schema for reference object catalogs.
314 filterNameList : `list` of `str`
315 List of filter names. Used to create <filterName>_flux fields.
316 addIsPhotometric : `bool`
317 If True then add field
"photometric".
318 addIsResolved : `bool`
319 If
True then add field
"resolved".
320 addIsVariable : `bool`
321 If
True then add field
"variable".
323 Number of coord error fields; must be one of 0, 2, 3:
325 - If 2
or 3: add fields
"coord_raErr" and "coord_decErr".
326 - If 3: also add field
"coord_radecErr".
327 addProperMotion : `bool`
328 If
True add fields
"epoch",
"pm_ra",
"pm_dec" and "pm_flag".
329 properMotionErrDim : `int`
330 Number of proper motion error fields; must be one of 0, 2, 3;
331 ignored
if addProperMotion false:
332 - If 2
or 3: add fields
"pm_raErr" and "pm_decErr".
333 - If 3: also add field
"pm_radecErr".
335 If
True add fields
"epoch",
"parallax",
"parallaxErr"
341 Schema
for reference catalog, an
346 Reference catalogs support additional covariances, such
as
347 covariance between RA
and proper motion
in declination,
348 that are
not supported by this method, but can be added after
351 schema = afwTable.SimpleTable.makeMinimalSchema()
353 afwTable.Point2DKey.addFields(
356 "centroid on an exposure, if relevant",
362 doc=
"is position known?",
364 for filterName
in filterNameList:
366 field=
"%s_flux" % (filterName,),
368 doc=
"flux in filter %s" % (filterName,),
371 for filterName
in filterNameList:
373 field=
"%s_fluxErr" % (filterName,),
375 doc=
"flux uncertainty in filter %s" % (filterName,),
382 doc=
"set if the object can be used for photometric calibration",
388 doc=
"set if the object is spatially resolved",
394 doc=
"set if the object has variable brightness",
396 if coordErrDim
not in (0, 2, 3):
397 raise ValueError(
"coordErrDim={}; must be (0, 2, 3)".format(coordErrDim))
399 afwTable.CovarianceMatrix2fKey.addFields(
403 units=[
"rad",
"rad"],
404 diagonalOnly=(coordErrDim == 2),
407 if addProperMotion
or addParallax:
411 doc=
"date of observation (TAI, MJD)",
419 doc=
"proper motion in the right ascension direction = dra/dt * cos(dec)",
425 doc=
"proper motion in the declination direction",
428 if properMotionErrDim
not in (0, 2, 3):
429 raise ValueError(
"properMotionErrDim={}; must be (0, 2, 3)".format(properMotionErrDim))
430 if properMotionErrDim > 0:
431 afwTable.CovarianceMatrix2fKey.addFields(
435 units=[
"rad/year",
"rad/year"],
436 diagonalOnly=(properMotionErrDim == 2),
441 doc=
"Set if proper motion or proper motion error is bad",
454 doc=
"uncertainty in parallax",
458 field=
"parallax_flag",
460 doc=
"Set if parallax or parallax error is bad",
465 def _remapReferenceCatalogSchema(refCat, *, anyFilterMapsToThis=None,
466 filterMap=None, centroids=False):
467 """This function takes in a reference catalog and returns a new catalog
468 with additional columns defined
from the remaining function arguments.
473 Reference catalog to map to new catalog
474 anyFilterMapsToThis : `str`, optional
475 Always use this reference catalog filter.
476 Mutually exclusive
with `filterMap`
477 filterMap : `dict` [`str`,`str`], optional
478 Mapping of camera filter name: reference catalog filter name.
479 centroids : `bool`, optional
480 Add centroid fields to the loaded Schema. ``loadPixelBox`` expects
481 these fields to exist.
486 Deep copy of input reference catalog
with additional columns added
488 if anyFilterMapsToThis
or filterMap:
489 ReferenceObjectLoaderBase._addFluxAliases(refCat.schema, anyFilterMapsToThis, filterMap)
491 mapper = afwTable.SchemaMapper(refCat.schema,
True)
492 mapper.addMinimalSchema(refCat.schema,
True)
493 mapper.editOutputSchema().disconnectAliases()
500 mapper.editOutputSchema().addField(
"centroid_x", type=float, doReplace=
True)
501 mapper.editOutputSchema().addField(
"centroid_y", type=float, doReplace=
True)
502 mapper.editOutputSchema().addField(
"hasCentroid", type=
"Flag", doReplace=
True)
503 mapper.editOutputSchema().getAliasMap().set(
"slot_Centroid",
"centroid")
505 expandedCat = afwTable.SimpleCatalog(mapper.getOutputSchema())
506 expandedCat.setMetadata(refCat.getMetadata())
507 expandedCat.extend(refCat, mapper=mapper)
512 def _addFluxAliases(schema, anyFilterMapsToThis=None, filterMap=None):
513 """Add aliases for camera filter fluxes to the schema.
515 For each camFilter: refFilter in filterMap, adds these aliases:
516 <camFilter>_camFlux: <refFilter>_flux
517 <camFilter>_camFluxErr: <refFilter>_fluxErr,
if the latter exists
518 or sets `anyFilterMapsToThis`
in the schema.
523 Schema
for reference catalog.
524 anyFilterMapsToThis : `str`, optional
525 Always use this reference catalog filter.
526 Mutually exclusive
with `filterMap`.
527 filterMap : `dict` [`str`,`str`], optional
528 Mapping of camera filter name: reference catalog filter name.
529 Mutually exclusive
with `anyFilterMapsToThis`.
534 Raised
if any required reference flux field
is missing
from the
538 if anyFilterMapsToThis
and filterMap:
539 raise ValueError(
"anyFilterMapsToThis and filterMap are mutually exclusive!")
541 aliasMap = schema.getAliasMap()
543 if anyFilterMapsToThis
is not None:
544 refFluxName = anyFilterMapsToThis +
"_flux"
545 if refFluxName
not in schema:
546 msg = f
"Unknown reference filter for anyFilterMapsToThis='{refFluxName}'"
547 raise RuntimeError(msg)
548 aliasMap.set(
"anyFilterMapsToThis", refFluxName)
551 def addAliasesForOneFilter(filterName, refFilterName):
552 """Add aliases for a single filter
556 filterName : `str` (optional)
557 Camera filter name. The resulting alias name is
559 refFilterName : `str`
560 Reference catalog filter name; the field
561 <refFilterName>_flux must exist.
563 camFluxName = filterName + "_camFlux"
564 refFluxName = refFilterName +
"_flux"
565 if refFluxName
not in schema:
566 raise RuntimeError(
"Unknown reference filter %s" % (refFluxName,))
567 aliasMap.set(camFluxName, refFluxName)
568 refFluxErrName = refFluxName +
"Err"
569 if refFluxErrName
in schema:
570 camFluxErrName = camFluxName +
"Err"
571 aliasMap.set(camFluxErrName, refFluxErrName)
573 if filterMap
is not None:
574 for filterName, refFilterName
in filterMap.items():
575 addAliasesForOneFilter(filterName, refFilterName)
578 def _makeBoxRegion(BBox, wcs, BBoxPadding):
591 outerLocalBBox.grow(BBoxPadding)
592 innerLocalBBox.grow(-1*BBoxPadding)
604 innerBoxCorners = innerLocalBBox.getCorners()
605 innerSphCorners = [wcs.pixelToSky(corner).getVector()
for corner
in innerBoxCorners]
608 outerBoxCorners = outerLocalBBox.getCorners()
609 outerSphCorners = [wcs.pixelToSky(corner).getVector()
for corner
in outerBoxCorners]
612 return innerSkyRegion, outerSkyRegion, innerSphCorners, outerSphCorners
615 def _calculateCircle(bbox, wcs, pixelMargin):
616 """Compute on-sky center and radius of search region.
623 WCS; used to convert pixel positions to sky coordinates.
625 Padding to add to 4 all edges of the bounding box (pixels).
629 results : `lsst.pipe.base.Struct`
633 ICRS center of the search region.
635 Radius of the search region.
637 Bounding box used to compute the circle.
640 bbox.grow(pixelMargin)
641 coord = wcs.pixelToSky(bbox.getCenter())
642 radius = max(coord.separation(wcs.pixelToSky(pp))
for pp
in bbox.getCorners())
643 return pipeBase.Struct(coord=coord, radius=radius, bbox=bbox)
647 """Return metadata about the reference catalog being loaded.
649 This metadata is used
for reloading the catalog (e.g.
for
650 reconstituting a normalized match list).
655 ICRS center of the search region.
657 Radius of the search region.
659 Name of the camera filter.
660 epoch : `astropy.time.Time`
or `
None`, optional
661 Epoch to which to correct proper motion
and parallax,
or `
None` to
662 not apply such corrections.
667 Metadata about the catalog.
670 md.add('RA', coord.getRa().asDegrees(),
'field center in degrees')
671 md.add(
'DEC', coord.getDec().asDegrees(),
'field center in degrees')
672 md.add(
'RADIUS', radius.asDegrees(),
'field radius in degrees, minimum')
673 md.add(
'SMATCHV', 1,
'SourceMatchVector version number')
674 md.add(
'FILTER', filterName,
'filter name for photometric data')
675 md.add(
'EPOCH',
"NONE" if epoch
is None else epoch.mjd,
'Epoch (TAI MJD) for catalog')
679 bboxToSpherePadding=100):
680 """Return metadata about the load
682 This metadata is used
for reloading the catalog (e.g.,
for
683 reconstituting a normalised match list).
688 Bounding box
for the pixels.
690 The WCS object associated
with ``bbox``.
692 Name of the camera filter.
693 epoch : `astropy.time.Time`
or `
None`, optional
694 Epoch to which to correct proper motion
and parallax,
or `
None` to
695 not apply such corrections.
696 bboxToSpherePadding : `int`, optional
697 Padding
in pixels to account
for translating a set of corners into
698 a spherical (convex) boundary that
is certain to encompass the
699 enitre area covered by the box.
704 The metadata detailing the search parameters used
for this
708 md = self.getMetadataCirclegetMetadataCircle(circle.coord, circle.radius, filterName, epoch=epoch)
710 paddedBbox = circle.bbox
711 _, _, innerCorners, outerCorners = self._makeBoxRegion_makeBoxRegion(paddedBbox, wcs, bboxToSpherePadding)
712 for box, corners
in zip((
"INNER",
"OUTER"), (innerCorners, outerCorners)):
713 for (name, corner)
in zip((
"UPPER_LEFT",
"UPPER_RIGHT",
"LOWER_LEFT",
"LOWER_RIGHT"),
715 md.add(f
"{box}_{name}_RA",
geom.SpherePoint(corner).getRa().asDegrees(), f
"{box}_corner")
716 md.add(f
"{box}_{name}_DEC",
geom.SpherePoint(corner).getDec().asDegrees(), f
"{box}_corner")
720 """Relink an unpersisted match list to sources and reference objects.
722 A match list is persisted
and unpersisted
as a catalog of IDs
723 produced by afw.table.packMatches(),
with match metadata
724 (
as returned by the astrometry tasks)
in the catalog
's metadata
725 attribute. This method converts such a match catalog into a match
726 list, with links to source records
and reference object records.
731 Unpersisted packed match list.
732 ``matchCat.table.getMetadata()`` must contain match metadata,
733 as returned by the astrometry tasks.
735 Source catalog. As a side effect, the catalog will be sorted
747 """This class facilitates loading reference catalogs with gen 3 middleware.
749 The QuantumGraph generation will create a list of datasets that may
750 possibly overlap a given region. These datasets are then used to construct
751 and instance of this
class. The
class instance should then be passed into
752 a task which needs reference catalogs. These tasks should then determine
753 the exact region of the sky reference catalogs will be loaded
for,
and
754 call a corresponding method to load the reference objects.
758 dataIds : iterable of `lsst.daf.butler.DataCoordinate`
759 An iterable object of data IDs that point to reference catalogs
760 in a gen 3 repository.
761 refCats : iterable of `lsst.daf.butler.DeferedDatasetHandle`
762 Handles to load refCats on demand
763 log : `
lsst.log.Log`, `logging.Logger`
or `
None`, optional
764 Logger object used to write out messages. If `
None` a default
767 def __init__(self, dataIds, refCats, log=None, **kwargs):
771 self.
loglog = log
or logging.getLogger(__name__).getChild(
"ReferenceObjectLoader")
774 bboxToSpherePadding=100):
775 """Load reference objects that are within a pixel-based rectangular
778 This algorithm works by creating a spherical box whose corners
779 correspond to the WCS converted corners of the input bounding box
780 (possibly padded). It then defines a filtering function which looks at
781 the pixel position of the reference objects and accepts only those that
782 lie within the specified bounding box.
784 The spherical box region
and filtering function are passed to the
785 generic loadRegion method which loads
and filters the reference objects
786 from the datastore
and returns a single catalog containing the filtered
787 set of reference objects.
792 Box which bounds a region
in pixel space.
794 Wcs object defining the pixel to sky (
and inverse) transform
for
795 the supplied ``bbox``.
797 Name of camera filter.
798 epoch : `astropy.time.Time`
or `
None`, optional
799 Epoch to which to correct proper motion
and parallax,
or `
None`
800 to
not apply such corrections.
801 bboxToSpherePadding : `int`, optional
802 Padding to account
for translating a set of corners into a
803 spherical (convex) boundary that
is certain to encompase the
804 enitre area covered by the box.
809 Catalog containing reference objects inside the specified bounding
810 box (padded by self.
configconfig.pixelMargin).
815 Raised
if no reference catalogs could be found
for the specified
818 Raised
if the loaded reference catalogs do
not have matching
822 paddedBbox.grow(self.configconfig.pixelMargin)
823 innerSkyRegion, outerSkyRegion, _, _ = self._makeBoxRegion_makeBoxRegion(paddedBbox, wcs, bboxToSpherePadding)
825 def _filterFunction(refCat, region):
836 refCat = preFiltFunc(refCat, region)
842 afwTable.updateRefCentroids(wcs, refCat)
845 if innerSkyRegion.contains(region):
849 filteredRefCat = type(refCat)(refCat.table)
850 centroidKey = afwTable.Point2DKey(refCat.schema[
'centroid'])
851 for record
in refCat:
852 pixCoords = record[centroidKey]
854 filteredRefCat.append(record)
855 return filteredRefCat
856 return self.
loadRegionloadRegion(outerSkyRegion, filterName, filtFunc=_filterFunction, epoch=epoch)
858 def loadRegion(self, region, filterName, filtFunc=None, epoch=None):
859 """Load reference objects within a specified region.
861 This function loads the DataIds used to construct an instance of this
862 class which intersect or are contained within the specified region. The
863 reference catalogs which intersect but are
not fully contained within
864 the input region are further filtered by the specified filter function.
865 This function returns a single source catalog containing all reference
866 objects inside the specified region.
872 should define the spatial region
for which reference objects are to
874 filtFunc : callable
or `
None`, optional
875 This optional parameter should be a callable object that takes a
876 reference catalog
and its corresponding region
as parameters,
877 filters the catalog by some criteria
and returns the filtered
878 reference catalog. If `
None`, an internal filter function
is used
879 which filters according to
if a reference object falls within the
882 Name of camera filter.
883 epoch : `astropy.time.Time`
or `
None`, optional
884 Epoch to which to correct proper motion
and parallax,
or `
None` to
885 not apply such corrections.
890 Catalog containing reference objects which intersect the input region,
891 filtered by the specified filter function.
896 Raised
if no reference catalogs could be found
for the specified
899 Raised
if the loaded reference catalogs do
not have matching
902 regionLat = region.getBoundingBox().getLat()
903 regionLon = region.getBoundingBox().getLon()
904 self.loglog.info("Loading reference objects from %s in region bounded by "
905 "[%.8f, %.8f], [%.8f, %.8f] RA Dec",
907 self.
refCatsrefCats[0].ref.datasetType.name,
908 regionLon.getA().asDegrees(), regionLon.getB().asDegrees(),
909 regionLat.getA().asDegrees(), regionLat.getB().asDegrees())
914 for dataId, refCat
in zip(self.
dataIdsdataIds, self.
refCatsrefCats):
918 intersects = dataId.region.intersects(region)
920 intersects = region.intersects(dataId.region)
923 overlapList.append((dataId, refCat))
925 if len(overlapList) == 0:
926 raise RuntimeError(
"No reference tables could be found for input region")
928 firstCat = overlapList[0][1].get()
929 refCat = filtFunc(firstCat, overlapList[0][0].region)
930 trimmedAmount = len(firstCat) - len(refCat)
933 for dataId, inputRefCat
in overlapList[1:]:
934 tmpCat = inputRefCat.get()
936 if tmpCat.schema != firstCat.schema:
937 raise TypeError(
"Reference catalogs have mismatching schemas")
939 filteredCat = filtFunc(tmpCat, dataId.region)
940 refCat.extend(filteredCat)
941 trimmedAmount += len(tmpCat) - len(filteredCat)
943 self.
loglog.debug(
"Trimmed %d refCat objects lying outside padded region, leaving %d",
944 trimmedAmount, len(refCat))
945 self.
loglog.info(
"Loaded %d reference objects", len(refCat))
948 if not refCat.isContiguous():
949 refCat = refCat.copy(deep=
True)
956 self.
loglog.warning(
"Found version 0 reference catalog with old style units in schema.")
957 self.
loglog.warning(
"run `meas_algorithms/bin/convert_refcat_to_nJy.py` to convert fluxes to nJy.")
958 self.
loglog.warning(
"See RFC-575 for more details.")
962 anyFilterMapsToThis=self.
configconfig.anyFilterMapsToThis,
963 filterMap=self.
configconfig.filterMap)
966 if not expandedCat.isContiguous():
967 expandedCat = expandedCat.copy(deep=
True)
970 return pipeBase.Struct(refCat=expandedCat, fluxField=fluxField)
973 """Load reference objects that lie within a circular region on the sky.
975 This method constructs a circular region from an input center
and
976 angular radius, loads reference catalogs which are contained
in or
977 intersect the circle,
and filters reference catalogs which intersect
978 down to objects which lie within the defined circle.
983 Point defining the center of the circular region.
985 Defines the angular radius of the circular region.
987 Name of camera filter.
988 epoch : `astropy.time.Time`
or `
None`, optional
989 Epoch to which to correct proper motion
and parallax,
or `
None` to
990 not apply such corrections.
995 Catalog containing reference objects inside the specified search
998 centerVector = ctrCoord.getVector()
1001 return self.
loadRegionloadRegion(circularRegion, filterName, epoch=epoch)
1005 """Get the name of a flux field from a schema.
1007 return the alias of
"anyFilterMapsToThis",
if present
1009 return "*filterName*_camFlux" if present
1010 else return "*filterName*_flux" if present (camera filter name
1011 matches reference filter name)
1012 else throw RuntimeError
1017 Reference catalog schema.
1019 Name of camera filter.
1023 fluxFieldName : `str`
1029 If an appropriate field
is not found.
1031 if not isinstance(schema, afwTable.Schema):
1032 raise RuntimeError(
"schema=%s is not a schema" % (schema,))
1034 return schema.getAliasMap().get(
"anyFilterMapsToThis")
1038 fluxFieldList = [filterName +
"_camFlux", filterName +
"_flux"]
1039 for fluxField
in fluxFieldList:
1040 if fluxField
in schema:
1043 raise RuntimeError(
"Could not find flux field(s) %s" % (
", ".join(fluxFieldList)))
1047 """Return keys for flux and flux error.
1052 Reference catalog schema.
1054 Name of camera filter.
1062 - flux error key, if present,
else None
1067 If flux field
not found.
1070 fluxErrField = fluxField + "Err"
1071 fluxKey = schema[fluxField].asKey()
1073 fluxErrKey = schema[fluxErrField].asKey()
1076 return (fluxKey, fluxErrKey)
1080 """Abstract gen2 base class to load objects from reference catalogs.
1082 _DefaultName = "LoadReferenceObjects"
1085 """Construct a LoadReferenceObjectsTask
1089 butler : `lsst.daf.persistence.Butler`
1090 Data butler, for access reference catalogs.
1092 pipeBase.Task.__init__(self, *args, **kwargs)
1096 def loadPixelBox(self, bbox, wcs, filterName, photoCalib=None, epoch=None):
1097 """Load reference objects that overlap a rectangular pixel region.
1102 Bounding box
for pixels.
1104 WCS; used to convert pixel positions to sky coordinates
1107 Name of filter. This can be used
for flux limit comparisons.
1109 Deprecated, only included
for api compatibility.
1110 epoch : `astropy.time.Time`
or `
None`, optional
1111 Epoch to which to correct proper motion
and parallax,
or `
None` to
1112 not apply such corrections.
1116 results : `lsst.pipe.base.Struct`
1117 A Struct containing the following fields:
1118 refCat : `lsst.afw.catalog.SimpleCatalog`
1119 A catalog of reference objects
with the standard
1120 schema,
as documented
in the main doc string
for
1121 `LoadReferenceObjects`.
1122 The catalog
is guaranteed to be contiguous.
1124 Name of flux field
for specified `filterName`.
1128 The search algorithm works by searching
in a region
in sky
1129 coordinates whose center
is the center of the bbox
and radius
1130 is large enough to just include all 4 corners of the bbox.
1131 Stars that lie outside the bbox are then trimmed
from the list.
1136 self.log.info(
"Loading reference objects from %s using center %s and radius %s deg",
1137 self.
configconfig.ref_dataset_name, circle.coord, circle.radius.asDegrees())
1138 loadRes = self.
loadSkyCircleloadSkyCircle(circle.coord, circle.radius, filterName, epoch=epoch,
1140 refCat = loadRes.refCat
1141 numFound = len(refCat)
1144 refCat = self.
_trimToBBox_trimToBBox(refCat=refCat, bbox=circle.bbox, wcs=wcs)
1145 numTrimmed = numFound - len(refCat)
1146 self.log.debug(
"trimmed %d out-of-bbox objects, leaving %d", numTrimmed, len(refCat))
1147 self.log.info(
"Loaded %d reference objects", len(refCat))
1150 if not refCat.isContiguous():
1151 loadRes.refCat = refCat.copy(deep=
True)
1156 def loadSkyCircle(self, ctrCoord, radius, filterName, epoch=None, centroids=False):
1157 """Load reference objects that overlap a circular sky region.
1162 ICRS center of search region.
1164 Radius of search region.
1166 Name of filter. This can be used for flux limit comparisons.
1167 epoch : `astropy.time.Time`
or `
None`, optional
1168 Epoch to which to correct proper motion
and parallax,
or `
None` to
1169 not apply such corrections.
1170 centroids : `bool`, optional
1171 Add centroid fields to the loaded Schema. ``loadPixelBox`` expects
1172 these fields to exist.
1176 results : `lsst.pipe.base.Struct`
1177 A Struct containing the following fields:
1178 refCat : `lsst.afw.catalog.SimpleCatalog`
1179 A catalog of reference objects
with the standard
1180 schema,
as documented
in the main doc string
for
1181 `LoadReferenceObjects`.
1182 The catalog
is guaranteed to be contiguous.
1184 Name of flux field
for specified `filterName`.
1188 Note that subclasses are responsible
for performing the proper motion
1189 correction, since this
is the lowest-level interface
for retrieving
1195 def _trimToBBox(refCat, bbox, wcs):
1196 """Remove objects outside a given pixel bounding box and set
1197 centroid and hasCentroid fields.
1202 A catalog of objects. The schema must include fields
1203 "coord",
"centroid" and "hasCentroid".
1204 The
"coord" field
is read.
1205 The
"centroid" and "hasCentroid" fields are set.
1209 WCS; used to convert sky coordinates to pixel positions.
1214 Reference objects
in the bbox,
with centroid
and
1215 hasCentroid fields set.
1217 afwTable.updateRefCentroids(wcs, refCat)
1218 centroidKey = afwTable.Point2DKey(refCat.schema["centroid"])
1219 retStarCat = type(refCat)(refCat.table)
1221 point = star.get(centroidKey)
1222 if bbox.contains(point):
1223 retStarCat.append(star)
1228 """Relink an unpersisted match list to sources and reference
1231 A match list is persisted
and unpersisted
as a catalog of IDs
1232 produced by afw.table.packMatches(),
with match metadata
1233 (
as returned by the astrometry tasks)
in the catalog
's metadata
1234 attribute. This method converts such a match catalog into a match
1235 list, with links to source records
and reference object records.
1240 Reference object loader to use
in getting reference objects
1242 Unperisted packed match list.
1243 ``matchCat.table.getMetadata()`` must contain match metadata,
1244 as returned by the astrometry tasks.
1246 Source catalog. As a side effect, the catalog will be sorted
1254 matchmeta = matchCat.table.getMetadata()
1255 version = matchmeta.getInt('SMATCHV')
1257 raise ValueError(
'SourceMatchVector version number is %i, not 1.' % version)
1258 filterName = matchmeta.getString(
'FILTER').strip()
1260 epoch = matchmeta.getDouble(
'EPOCH')
1261 except (LookupError, TypeError):
1263 if 'RADIUS' in matchmeta:
1266 matchmeta.getDouble(
'DEC'), geom.degrees)
1267 rad = matchmeta.getDouble(
'RADIUS')*geom.degrees
1268 refCat = refObjLoader.loadSkyCircle(ctrCoord, rad, filterName, epoch=epoch).refCat
1269 elif "INNER_UPPER_LEFT_RA" in matchmeta:
1275 for place
in (
"UPPER_LEFT",
"UPPER_RIGHT",
"LOWER_LEFT",
"LOWER_RIGHT"):
1277 matchmeta.getDouble(f
"OUTER_{place}_DEC"),
1278 geom.degrees).getVector()
1281 refCat = refObjLoader.loadRegion(outerBox, filterName, epoch=epoch).refCat
1285 return afwTable.unpackMatches(matchCat, refCat, sourceCat)
1289 """Apply proper motion correction to a reference catalog.
1291 Adjust position and position error
in the ``catalog``
1292 for proper motion to the specified ``epoch``,
1293 modifying the catalog
in place.
1298 Log object to write to.
1300 Catalog of positions, containing:
1302 - Coordinates, retrieved by the table
's coordinate key.
1303 - ``coord_raErr`` : Error in Right Ascension (rad).
1304 - ``coord_decErr`` : Error
in Declination (rad).
1305 - ``pm_ra`` : Proper motion
in Right Ascension (rad/yr,
1307 - ``pm_raErr`` : Error
in ``pm_ra`` (rad/yr), optional.
1308 - ``pm_dec`` : Proper motion
in Declination (rad/yr,
1310 - ``pm_decErr`` : Error
in ``pm_dec`` (rad/yr), optional.
1311 - ``epoch`` : Mean epoch of object (an astropy.time.Time)
1312 epoch : `astropy.time.Time`
1313 Epoch to which to correct proper motion.
1315 if "epoch" not in catalog.schema
or "pm_ra" not in catalog.schema
or "pm_dec" not in catalog.schema:
1316 log.warning(
"Proper motion correction not available from catalog")
1318 if not catalog.isContiguous():
1319 raise RuntimeError(
"Catalog must be contiguous")
1320 catEpoch = astropy.time.Time(catalog[
"epoch"], scale=
"tai", format=
"mjd")
1321 log.info(
"Correcting reference catalog for proper motion to %r", epoch)
1323 timeDiffsYears = (epoch.tai - catEpoch).to(astropy.units.yr).value
1324 coordKey = catalog.table.getCoordKey()
1327 pmRaRad = catalog[
"pm_ra"]
1328 pmDecRad = catalog[
"pm_dec"]
1329 offsetsRaRad = pmRaRad*timeDiffsYears
1330 offsetsDecRad = pmDecRad*timeDiffsYears
1338 offsetBearingsRad = numpy.arctan2(pmDecRad*1e6, pmRaRad*1e6)
1339 offsetAmountsRad = numpy.hypot(offsetsRaRad, offsetsDecRad)
1340 for record, bearingRad, amountRad
in zip(catalog, offsetBearingsRad, offsetAmountsRad):
1341 record.set(coordKey,
1342 record.get(coordKey).offset(bearing=bearingRad*geom.radians,
1343 amount=amountRad*geom.radians))
1345 if "coord_raErr" in catalog.schema:
1346 catalog[
"coord_raErr"] = numpy.hypot(catalog[
"coord_raErr"],
1347 catalog[
"pm_raErr"]*timeDiffsYears)
1348 if "coord_decErr" in catalog.schema:
1349 catalog[
"coord_decErr"] = numpy.hypot(catalog[
"coord_decErr"],
1350 catalog[
"pm_decErr"]*timeDiffsYears)
def __call__(self, refCat, catRegion)
def __init__(self, region)
def _trimToBBox(refCat, bbox, wcs)
def loadSkyCircle(self, ctrCoord, radius, filterName, epoch=None, centroids=False)
def loadPixelBox(self, bbox, wcs, filterName, photoCalib=None, epoch=None)
def __init__(self, butler=None, *args, **kwargs)
def getMetadataBox(self, bbox, wcs, filterName, epoch=None, bboxToSpherePadding=100)
def applyProperMotions(self, catalog, epoch)
def joinMatchListWithCatalog(self, matchCat, sourceCat)
def _makeBoxRegion(BBox, wcs, BBoxPadding)
def makeMinimalSchema(filterNameList, *addCentroid=False, addIsPhotometric=False, addIsResolved=False, addIsVariable=False, coordErrDim=2, addProperMotion=False, properMotionErrDim=2, addParallax=False)
def _calculateCircle(bbox, wcs, pixelMargin)
def getMetadataCircle(coord, radius, filterName, epoch=None)
def __init__(self, config=None, *args, **kwargs)
def _remapReferenceCatalogSchema(refCat, *anyFilterMapsToThis=None, filterMap=None, centroids=False)
def loadSkyCircle(self, ctrCoord, radius, filterName, epoch=None)
def loadRegion(self, region, filterName, filtFunc=None, epoch=None)
def loadPixelBox(self, bbox, wcs, filterName, epoch=None, bboxToSpherePadding=100)
def __init__(self, dataIds, refCats, log=None, **kwargs)
def hasNanojanskyFluxUnits(schema)
def getRefFluxKeys(schema, filterName)
def convertToNanojansky(catalog, log, doConvert=True)
def joinMatchListWithCatalogImpl(refObjLoader, matchCat, sourceCat)
def isOldFluxField(name, units)
def getRefFluxField(schema, filterName)
def getFormatVersionFromRefCat(refCat)
def applyProperMotionsImpl(log, catalog, epoch)