24__all__ = [
"getRefFluxField",
"getRefFluxKeys",
"LoadReferenceObjectsTask",
"LoadReferenceObjectsConfig",
25 "ReferenceObjectLoader",
"ReferenceObjectLoaderBase"]
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.
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()
138 output.reserve(len(catalog))
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:
189 filteredRefCat.append(record)
190 return filteredRefCat
194 pixelMargin = pexConfig.RangeField(
195 doc=
"Padding to add to 4 all edges of the bounding box (pixels)",
200 anyFilterMapsToThis = pexConfig.Field(
201 doc=(
"Always use this reference catalog filter, no matter whether or what filter name is "
202 "supplied to the loader. Effectively a trivial filterMap: map all filter names to this filter."
203 " This can be set for purely-astrometric catalogs (e.g. Gaia DR2) where there is only one "
204 "reasonable choice for every camera filter->refcat mapping, but not for refcats used for "
205 "photometry, which need a filterMap and/or colorterms/transmission corrections."),
210 filterMap = pexConfig.DictField(
211 doc=(
"Mapping of camera filter name: reference catalog filter name; "
212 "each reference filter must exist in the refcat."
213 " Note that this does not perform any bandpass corrections: it is just a lookup."),
218 requireProperMotion = pexConfig.Field(
219 doc=
"Require that the fields needed to correct proper motion "
220 "(epoch, pm_ra and pm_dec) are present?",
228 msg =
"`filterMap` and `anyFilterMapsToThis` are mutually exclusive"
229 raise pexConfig.FieldValidationError(LoadReferenceObjectsConfig.anyFilterMapsToThis,
234 """Base class for reference object loaders, to facilitate gen2/gen3 code
240 Configuration for the loader.
242 ConfigClass = LoadReferenceObjectsConfig
248 """Apply proper motion correction to a reference catalog.
250 Adjust position and position error
in the ``catalog``
251 for proper motion to the specified ``epoch``,
252 modifying the catalog
in place.
257 Catalog of positions, containing at least these fields:
259 - Coordinates, retrieved by the table
's coordinate key.
260 - ``coord_raErr`` : Error in Right Ascension (rad).
261 - ``coord_decErr`` : Error
in Declination (rad).
262 - ``pm_ra`` : Proper motion
in Right Ascension (rad/yr,
264 - ``pm_raErr`` : Error
in ``pm_ra`` (rad/yr), optional.
265 - ``pm_dec`` : Proper motion
in Declination (rad/yr,
267 - ``pm_decErr`` : Error
in ``pm_dec`` (rad/yr), optional.
268 - ``epoch`` : Mean epoch of object (an astropy.time.Time)
269 epoch : `astropy.time.Time`
270 Epoch to which to correct proper motion.
271 If
None, do
not apply PM corrections
or raise if
272 ``config.requireProperMotion``
is True.
277 Raised
if ``config.requireProperMotion``
is set but we cannot
278 apply the proper motion correction
for some reason.
281 if self.
configconfig.requireProperMotion:
282 raise RuntimeError(
"requireProperMotion=True but epoch not provided to loader.")
284 self.log.
debug(
"No epoch provided: not applying proper motion corrections to refcat.")
288 if (
"pm_ra" in catalog.schema
289 and not isinstance(catalog.schema[
"pm_ra"].asKey(), afwTable.KeyAngle)):
290 if self.
configconfig.requireProperMotion:
291 raise RuntimeError(
"requireProperMotion=True but refcat pm_ra field is not an Angle.")
293 self.log.
warning(
"Reference catalog pm_ra field is not an Angle; cannot apply proper motion.")
296 if (
"epoch" not in catalog.schema
or "pm_ra" not in catalog.schema):
297 if self.
configconfig.requireProperMotion:
298 raise RuntimeError(
"requireProperMotion=True but PM data not available from catalog.")
300 self.log.
warning(
"Proper motion correction not available for this reference catalog.")
307 addIsPhotometric=False, addIsResolved=False,
308 addIsVariable=False, coordErrDim=2,
309 addProperMotion=False, properMotionErrDim=2,
311 """Make a standard schema for reference object catalogs.
315 filterNameList : `list` of `str`
316 List of filter names. Used to create <filterName>_flux fields.
317 addIsPhotometric : `bool`
318 If True then add field
"photometric".
319 addIsResolved : `bool`
320 If
True then add field
"resolved".
321 addIsVariable : `bool`
322 If
True then add field
"variable".
324 Number of coord error fields; must be one of 0, 2, 3:
326 - If 2
or 3: add fields
"coord_raErr" and "coord_decErr".
327 - If 3: also add field
"coord_radecErr".
328 addProperMotion : `bool`
329 If
True add fields
"epoch",
"pm_ra",
"pm_dec" and "pm_flag".
330 properMotionErrDim : `int`
331 Number of proper motion error fields; must be one of 0, 2, 3;
332 ignored
if addProperMotion false:
333 - If 2
or 3: add fields
"pm_raErr" and "pm_decErr".
334 - If 3: also add field
"pm_radecErr".
336 If
True add fields
"epoch",
"parallax",
"parallaxErr"
342 Schema
for reference catalog, an
347 Reference catalogs support additional covariances, such
as
348 covariance between RA
and proper motion
in declination,
349 that are
not supported by this method, but can be added after
352 schema = afwTable.SimpleTable.makeMinimalSchema()
354 afwTable.Point2DKey.addFields(
357 "centroid on an exposure, if relevant",
363 doc=
"is position known?",
365 for filterName
in filterNameList:
367 field=
"%s_flux" % (filterName,),
369 doc=
"flux in filter %s" % (filterName,),
372 for filterName
in filterNameList:
374 field=
"%s_fluxErr" % (filterName,),
376 doc=
"flux uncertainty in filter %s" % (filterName,),
383 doc=
"set if the object can be used for photometric calibration",
389 doc=
"set if the object is spatially resolved",
395 doc=
"set if the object has variable brightness",
397 if coordErrDim
not in (0, 2, 3):
398 raise ValueError(
"coordErrDim={}; must be (0, 2, 3)".
format(coordErrDim))
400 afwTable.CovarianceMatrix2fKey.addFields(
404 units=[
"rad",
"rad"],
405 diagonalOnly=(coordErrDim == 2),
408 if addProperMotion
or addParallax:
412 doc=
"date of observation (TAI, MJD)",
420 doc=
"proper motion in the right ascension direction = dra/dt * cos(dec)",
426 doc=
"proper motion in the declination direction",
429 if properMotionErrDim
not in (0, 2, 3):
430 raise ValueError(
"properMotionErrDim={}; must be (0, 2, 3)".
format(properMotionErrDim))
431 if properMotionErrDim > 0:
432 afwTable.CovarianceMatrix2fKey.addFields(
436 units=[
"rad/year",
"rad/year"],
437 diagonalOnly=(properMotionErrDim == 2),
442 doc=
"Set if proper motion or proper motion error is bad",
455 doc=
"uncertainty in parallax",
459 field=
"parallax_flag",
461 doc=
"Set if parallax or parallax error is bad",
466 def _remapReferenceCatalogSchema(refCat, *, anyFilterMapsToThis=None,
467 filterMap=None, centroids=False):
468 """This function takes in a reference catalog and returns a new catalog
469 with additional columns defined
from the remaining function arguments.
474 Reference catalog to map to new catalog
475 anyFilterMapsToThis : `str`, optional
476 Always use this reference catalog filter.
477 Mutually exclusive
with `filterMap`
478 filterMap : `dict` [`str`,`str`], optional
479 Mapping of camera filter name: reference catalog filter name.
480 centroids : `bool`, optional
481 Add centroid fields to the loaded Schema. ``loadPixelBox`` expects
482 these fields to exist.
487 Deep copy of input reference catalog
with additional columns added
489 if anyFilterMapsToThis
or filterMap:
490 ReferenceObjectLoaderBase._addFluxAliases(refCat.schema, anyFilterMapsToThis, filterMap)
493 mapper.addMinimalSchema(refCat.schema,
True)
494 mapper.editOutputSchema().disconnectAliases()
501 mapper.editOutputSchema().addField(
"centroid_x", type=float, doReplace=
True)
502 mapper.editOutputSchema().addField(
"centroid_y", type=float, doReplace=
True)
503 mapper.editOutputSchema().addField(
"hasCentroid", type=
"Flag", doReplace=
True)
504 mapper.editOutputSchema().getAliasMap().
set(
"slot_Centroid",
"centroid")
507 expandedCat.setMetadata(refCat.getMetadata())
508 expandedCat.extend(refCat, mapper=mapper)
513 def _addFluxAliases(schema, anyFilterMapsToThis=None, filterMap=None):
514 """Add aliases for camera filter fluxes to the schema.
516 For each camFilter: refFilter in filterMap, adds these aliases:
517 <camFilter>_camFlux: <refFilter>_flux
518 <camFilter>_camFluxErr: <refFilter>_fluxErr,
if the latter exists
519 or sets `anyFilterMapsToThis`
in the schema.
524 Schema
for reference catalog.
525 anyFilterMapsToThis : `str`, optional
526 Always use this reference catalog filter.
527 Mutually exclusive
with `filterMap`.
528 filterMap : `dict` [`str`,`str`], optional
529 Mapping of camera filter name: reference catalog filter name.
530 Mutually exclusive
with `anyFilterMapsToThis`.
535 Raised
if any required reference flux field
is missing
from the
539 if anyFilterMapsToThis
and filterMap:
540 raise ValueError(
"anyFilterMapsToThis and filterMap are mutually exclusive!")
542 aliasMap = schema.getAliasMap()
544 if anyFilterMapsToThis
is not None:
545 refFluxName = anyFilterMapsToThis +
"_flux"
546 if refFluxName
not in schema:
547 msg = f
"Unknown reference filter for anyFilterMapsToThis='{refFluxName}'"
548 raise RuntimeError(msg)
549 aliasMap.set(
"anyFilterMapsToThis", refFluxName)
552 def addAliasesForOneFilter(filterName, refFilterName):
553 """Add aliases for a single filter
557 filterName : `str` (optional)
558 Camera filter name. The resulting alias name is
560 refFilterName : `str`
561 Reference catalog filter name; the field
562 <refFilterName>_flux must exist.
564 camFluxName = filterName + "_camFlux"
565 refFluxName = refFilterName +
"_flux"
566 if refFluxName
not in schema:
567 raise RuntimeError(
"Unknown reference filter %s" % (refFluxName,))
568 aliasMap.set(camFluxName, refFluxName)
569 refFluxErrName = refFluxName +
"Err"
570 if refFluxErrName
in schema:
571 camFluxErrName = camFluxName +
"Err"
572 aliasMap.set(camFluxErrName, refFluxErrName)
574 if filterMap
is not None:
575 for filterName, refFilterName
in filterMap.items():
576 addAliasesForOneFilter(filterName, refFilterName)
579 def _makeBoxRegion(BBox, wcs, BBoxPadding):
592 outerLocalBBox.grow(BBoxPadding)
593 innerLocalBBox.grow(-1*BBoxPadding)
605 innerBoxCorners = innerLocalBBox.getCorners()
606 innerSphCorners = [wcs.pixelToSky(corner).getVector()
for corner
in innerBoxCorners]
609 outerBoxCorners = outerLocalBBox.getCorners()
610 outerSphCorners = [wcs.pixelToSky(corner).getVector()
for corner
in outerBoxCorners]
613 return innerSkyRegion, outerSkyRegion, innerSphCorners, outerSphCorners
616 def _calculateCircle(bbox, wcs, pixelMargin):
617 """Compute on-sky center and radius of search region.
624 WCS; used to convert pixel positions to sky coordinates.
626 Padding to add to 4 all edges of the bounding box (pixels).
630 results : `lsst.pipe.base.Struct`
634 ICRS center of the search region.
636 Radius of the search region.
638 Bounding box used to compute the circle.
641 bbox.grow(pixelMargin)
642 coord = wcs.pixelToSky(bbox.getCenter())
643 radius =
max(coord.separation(wcs.pixelToSky(pp))
for pp
in bbox.getCorners())
644 return pipeBase.Struct(coord=coord, radius=radius, bbox=bbox)
648 """Return metadata about the reference catalog being loaded.
650 This metadata is used
for reloading the catalog (e.g.
for
651 reconstituting a normalized match list).
656 ICRS center of the search region.
658 Radius of the search region.
660 Name of the camera filter.
661 epoch : `astropy.time.Time`
or `
None`, optional
662 Epoch to which to correct proper motion
and parallax,
or `
None` to
663 not apply such corrections.
668 Metadata about the catalog.
671 md.add('RA', coord.getRa().asDegrees(),
'field center in degrees')
672 md.add(
'DEC', coord.getDec().asDegrees(),
'field center in degrees')
673 md.add(
'RADIUS', radius.asDegrees(),
'field radius in degrees, minimum')
674 md.add(
'SMATCHV', 1,
'SourceMatchVector version number')
675 md.add(
'FILTER', filterName,
'filter name for photometric data')
676 md.add(
'EPOCH',
"NONE" if epoch
is None else epoch.mjd,
'Epoch (TAI MJD) for catalog')
680 bboxToSpherePadding=100):
681 """Return metadata about the load
683 This metadata is used
for reloading the catalog (e.g.,
for
684 reconstituting a normalised match list).
689 Bounding box
for the pixels.
691 The WCS object associated
with ``bbox``.
693 Name of the camera filter.
694 epoch : `astropy.time.Time`
or `
None`, optional
695 Epoch to which to correct proper motion
and parallax,
or `
None` to
696 not apply such corrections.
697 bboxToSpherePadding : `int`, optional
698 Padding
in pixels to account
for translating a set of corners into
699 a spherical (convex) boundary that
is certain to encompass the
700 enitre area covered by the box.
705 The metadata detailing the search parameters used
for this
709 md = self.getMetadataCirclegetMetadataCircle(circle.coord, circle.radius, filterName, epoch=epoch)
711 paddedBbox = circle.bbox
712 _, _, innerCorners, outerCorners = self._makeBoxRegion_makeBoxRegion(paddedBbox, wcs, bboxToSpherePadding)
713 for box, corners
in zip((
"INNER",
"OUTER"), (innerCorners, outerCorners)):
714 for (name, corner)
in zip((
"UPPER_LEFT",
"UPPER_RIGHT",
"LOWER_LEFT",
"LOWER_RIGHT"),
716 md.add(f
"{box}_{name}_RA",
geom.SpherePoint(corner).getRa().asDegrees(), f
"{box}_corner")
717 md.add(f
"{box}_{name}_DEC",
geom.SpherePoint(corner).getDec().asDegrees(), f
"{box}_corner")
721 """Relink an unpersisted match list to sources and reference objects.
723 A match list is persisted
and unpersisted
as a catalog of IDs
724 produced by afw.table.packMatches(),
with match metadata
725 (
as returned by the astrometry tasks)
in the catalog
's metadata
726 attribute. This method converts such a match catalog into a match
727 list, with links to source records
and reference object records.
732 Unpersisted packed match list.
733 ``matchCat.table.getMetadata()`` must contain match metadata,
734 as returned by the astrometry tasks.
736 Source catalog. As a side effect, the catalog will be sorted
748 """This class facilitates loading reference catalogs with gen 3 middleware.
750 The QuantumGraph generation will create a list of datasets that may
751 possibly overlap a given region. These datasets are then used to construct
752 and instance of this
class. The
class instance should
then be passed into
753 a task which needs reference catalogs. These tasks should then determine
754 the exact region of the sky reference catalogs will be loaded
for,
and
755 call a corresponding method to load the reference objects.
759 dataIds : iterable of `lsst.daf.butler.DataCoordinate`
760 An iterable object of data IDs that point to reference catalogs
761 in a gen 3 repository.
762 refCats : iterable of `lsst.daf.butler.DeferredDatasetHandle`
763 Handles to load refCats on demand
764 log : `
lsst.log.Log`, `logging.Logger`
or `
None`, optional
765 Logger object used to write out messages. If `
None` a default
768 def __init__(self, dataIds, refCats, log=None, config=None, **kwargs):
771 super().
__init__(config=config, **kwargs)
774 self.
loglog = log
or logging.getLogger(__name__).getChild(
"ReferenceObjectLoader")
777 bboxToSpherePadding=100):
778 """Load reference objects that are within a pixel-based rectangular
781 This algorithm works by creating a spherical box whose corners
782 correspond to the WCS converted corners of the input bounding box
783 (possibly padded). It then defines a filtering function which looks at
784 the pixel position of the reference objects and accepts only those that
785 lie within the specified bounding box.
787 The spherical box region
and filtering function are passed to the
788 generic loadRegion method which loads
and filters the reference objects
789 from the datastore
and returns a single catalog containing the filtered
790 set of reference objects.
795 Box which bounds a region
in pixel space.
797 Wcs object defining the pixel to sky (
and inverse) transform
for
798 the supplied ``bbox``.
800 Name of camera filter.
801 epoch : `astropy.time.Time`
or `
None`, optional
802 Epoch to which to correct proper motion
and parallax,
or `
None`
803 to
not apply such corrections.
804 bboxToSpherePadding : `int`, optional
805 Padding to account
for translating a set of corners into a
806 spherical (convex) boundary that
is certain to encompase the
807 enitre area covered by the box.
811 output : `lsst.pipe.base.Struct`
812 Results struct
with attributes:
815 Catalog containing reference objects inside the specified
816 bounding box (padded by self.
configconfig.pixelMargin).
818 Name of the field containing the flux associated
with
824 Raised
if no reference catalogs could be found
for the specified
827 Raised
if the loaded reference catalogs do
not have matching
831 paddedBbox.grow(self.configconfig.pixelMargin)
832 innerSkyRegion, outerSkyRegion, _, _ = self._makeBoxRegion_makeBoxRegion(paddedBbox, wcs, bboxToSpherePadding)
834 def _filterFunction(refCat, region):
845 refCat = preFiltFunc(refCat, region)
854 if innerSkyRegion.contains(region):
858 filteredRefCat =
type(refCat)(refCat.table)
860 for record
in refCat:
861 pixCoords = record[centroidKey]
863 filteredRefCat.append(record)
864 return filteredRefCat
865 return self.
loadRegionloadRegion(outerSkyRegion, filterName, filtFunc=_filterFunction, epoch=epoch)
867 def loadRegion(self, region, filterName, filtFunc=None, epoch=None):
868 """Load reference objects within a specified region.
870 This function loads the DataIds used to construct an instance of this
871 class which intersect or are contained within the specified region. The
872 reference catalogs which intersect but are
not fully contained within
873 the input region are further filtered by the specified filter function.
874 This function returns a single source catalog containing all reference
875 objects inside the specified region.
881 should define the spatial region
for which reference objects are to
883 filtFunc : callable
or `
None`, optional
884 This optional parameter should be a callable object that takes a
885 reference catalog
and its corresponding region
as parameters,
886 filters the catalog by some criteria
and returns the filtered
887 reference catalog. If `
None`, an internal filter function
is used
888 which filters according to
if a reference object falls within the
891 Name of camera filter.
892 epoch : `astropy.time.Time`
or `
None`, optional
893 Epoch to which to correct proper motion
and parallax,
or `
None` to
894 not apply such corrections.
898 output : `lsst.pipe.base.Struct`
899 Results struct
with attributes:
902 Catalog containing reference objects which intersect the
903 input region, filtered by the specified filter function.
905 Name of the field containing the flux associated
with
911 Raised
if no reference catalogs could be found
for the specified
914 Raised
if the loaded reference catalogs do
not have matching
917 regionLat = region.getBoundingBox().getLat()
918 regionLon = region.getBoundingBox().getLon()
919 self.loglog.info("Loading reference objects from %s in region bounded by "
920 "[%.8f, %.8f], [%.8f, %.8f] RA Dec",
922 self.
refCatsrefCats[0].ref.datasetType.name,
923 regionLon.getA().asDegrees(), regionLon.getB().asDegrees(),
924 regionLat.getA().asDegrees(), regionLat.getB().asDegrees())
929 for dataId, refCat
in zip(self.
dataIdsdataIds, self.
refCatsrefCats):
933 intersects = dataId.region.intersects(region)
935 intersects = region.intersects(dataId.region)
938 overlapList.append((dataId, refCat))
940 if len(overlapList) == 0:
941 raise RuntimeError(
"No reference tables could be found for input region")
943 firstCat = overlapList[0][1].get()
944 refCat = filtFunc(firstCat, overlapList[0][0].region)
945 trimmedAmount = len(firstCat) - len(refCat)
948 for dataId, inputRefCat
in overlapList[1:]:
949 tmpCat = inputRefCat.get()
951 if tmpCat.schema != firstCat.schema:
952 raise TypeError(
"Reference catalogs have mismatching schemas")
954 filteredCat = filtFunc(tmpCat, dataId.region)
955 refCat.extend(filteredCat)
956 trimmedAmount += len(tmpCat) - len(filteredCat)
958 self.
loglog.
debug(
"Trimmed %d refCat objects lying outside padded region, leaving %d",
959 trimmedAmount, len(refCat))
960 self.
loglog.
info(
"Loaded %d reference objects", len(refCat))
963 if not refCat.isContiguous():
964 refCat = refCat.copy(deep=
True)
971 self.
loglog.
warning(
"Found version 0 reference catalog with old style units in schema.")
972 self.
loglog.
warning(
"run `meas_algorithms/bin/convert_refcat_to_nJy.py` to convert fluxes to nJy.")
973 self.
loglog.
warning(
"See RFC-575 for more details.")
977 anyFilterMapsToThis=self.
configconfig.anyFilterMapsToThis,
978 filterMap=self.
configconfig.filterMap)
981 if not expandedCat.isContiguous():
982 expandedCat = expandedCat.copy(deep=
True)
985 return pipeBase.Struct(refCat=expandedCat, fluxField=fluxField)
988 """Load reference objects that lie within a circular region on the sky.
990 This method constructs a circular region from an input center
and
991 angular radius, loads reference catalogs which are contained
in or
992 intersect the circle,
and filters reference catalogs which intersect
993 down to objects which lie within the defined circle.
998 Point defining the center of the circular region.
1000 Defines the angular radius of the circular region.
1002 Name of camera filter.
1003 epoch : `astropy.time.Time`
or `
None`, optional
1004 Epoch to which to correct proper motion
and parallax,
or `
None` to
1005 not apply such corrections.
1009 output : `lsst.pipe.base.Struct`
1010 Results struct
with attributes:
1013 Catalog containing reference objects inside the specified
1016 Name of the field containing the flux associated
with
1019 centerVector = ctrCoord.getVector()
1022 return self.
loadRegionloadRegion(circularRegion, filterName, epoch=epoch)
1026 """Get the name of a flux field from a schema.
1028 return the alias of
"anyFilterMapsToThis",
if present
1030 return "*filterName*_camFlux" if present
1031 else return "*filterName*_flux" if present (camera filter name
1032 matches reference filter name)
1033 else throw RuntimeError
1038 Reference catalog schema.
1040 Name of camera filter.
1044 fluxFieldName : `str`
1050 If an appropriate field
is not found.
1053 raise RuntimeError(
"schema=%s is not a schema" % (schema,))
1055 return schema.getAliasMap().get(
"anyFilterMapsToThis")
1059 fluxFieldList = [filterName +
"_camFlux", filterName +
"_flux"]
1060 for fluxField
in fluxFieldList:
1061 if fluxField
in schema:
1064 raise RuntimeError(
"Could not find flux field(s) %s" % (
", ".join(fluxFieldList)))
1068 """Return keys for flux and flux error.
1073 Reference catalog schema.
1075 Name of camera filter.
1083 - flux error key, if present,
else None
1088 If flux field
not found.
1091 fluxErrField = fluxField + "Err"
1092 fluxKey = schema[fluxField].asKey()
1094 fluxErrKey = schema[fluxErrField].asKey()
1097 return (fluxKey, fluxErrKey)
1101 """Abstract gen2 base class to load objects from reference catalogs.
1103 _DefaultName = "LoadReferenceObjects"
1106 """Construct a LoadReferenceObjectsTask
1111 Data butler, for access reference catalogs.
1113 pipeBase.Task.__init__(self, *args, **kwargs)
1117 def loadPixelBox(self, bbox, wcs, filterName, photoCalib=None, epoch=None):
1118 """Load reference objects that overlap a rectangular pixel region.
1123 Bounding box
for pixels.
1125 WCS; used to convert pixel positions to sky coordinates
1128 Name of filter. This can be used
for flux limit comparisons.
1130 Deprecated, only included
for api compatibility.
1131 epoch : `astropy.time.Time`
or `
None`, optional
1132 Epoch to which to correct proper motion
and parallax,
or `
None` to
1133 not apply such corrections.
1137 results : `lsst.pipe.base.Struct`
1138 A Struct containing the following fields:
1139 refCat : `lsst.afw.catalog.SimpleCatalog`
1140 A catalog of reference objects
with the standard
1141 schema,
as documented
in the main doc string
for
1142 `LoadReferenceObjects`.
1143 The catalog
is guaranteed to be contiguous.
1145 Name of flux field
for specified `filterName`.
1149 The search algorithm works by searching
in a region
in sky
1150 coordinates whose center
is the center of the bbox
and radius
1151 is large enough to just include all 4 corners of the bbox.
1152 Stars that lie outside the bbox are then trimmed
from the list.
1157 self.log.
info(
"Loading reference objects from %s using center %s and radius %s deg",
1158 self.
configconfig.ref_dataset_name, circle.coord, circle.radius.asDegrees())
1159 loadRes = self.
loadSkyCircleloadSkyCircle(circle.coord, circle.radius, filterName, epoch=epoch,
1161 refCat = loadRes.refCat
1162 numFound = len(refCat)
1165 refCat = self.
_trimToBBox_trimToBBox(refCat=refCat, bbox=circle.bbox, wcs=wcs)
1166 numTrimmed = numFound - len(refCat)
1167 self.log.
debug(
"trimmed %d out-of-bbox objects, leaving %d", numTrimmed, len(refCat))
1168 self.log.
info(
"Loaded %d reference objects", len(refCat))
1171 if not refCat.isContiguous():
1172 loadRes.refCat = refCat.copy(deep=
True)
1177 def loadSkyCircle(self, ctrCoord, radius, filterName, epoch=None, centroids=False):
1178 """Load reference objects that overlap a circular sky region.
1183 ICRS center of search region.
1185 Radius of search region.
1187 Name of filter. This can be used for flux limit comparisons.
1188 epoch : `astropy.time.Time`
or `
None`, optional
1189 Epoch to which to correct proper motion
and parallax,
or `
None` to
1190 not apply such corrections.
1191 centroids : `bool`, optional
1192 Add centroid fields to the loaded Schema. ``loadPixelBox`` expects
1193 these fields to exist.
1197 results : `lsst.pipe.base.Struct`
1198 A Struct containing the following fields:
1199 refCat : `lsst.afw.catalog.SimpleCatalog`
1200 A catalog of reference objects
with the standard
1201 schema,
as documented
in the main doc string
for
1202 `LoadReferenceObjects`.
1203 The catalog
is guaranteed to be contiguous.
1205 Name of flux field
for specified `filterName`.
1209 Note that subclasses are responsible
for performing the proper motion
1210 correction, since this
is the lowest-level interface
for retrieving
1216 def _trimToBBox(refCat, bbox, wcs):
1217 """Remove objects outside a given pixel bounding box and set
1218 centroid and hasCentroid fields.
1223 A catalog of objects. The schema must include fields
1224 "coord",
"centroid" and "hasCentroid".
1225 The
"coord" field
is read.
1226 The
"centroid" and "hasCentroid" fields are set.
1230 WCS; used to convert sky coordinates to pixel positions.
1235 Reference objects
in the bbox,
with centroid
and
1236 hasCentroid fields set.
1240 retStarCat =
type(refCat)(refCat.table)
1242 point = star.get(centroidKey)
1243 if bbox.contains(point):
1244 retStarCat.append(star)
1249 """Relink an unpersisted match list to sources and reference
1252 A match list is persisted
and unpersisted
as a catalog of IDs
1253 produced by afw.table.packMatches(),
with match metadata
1254 (
as returned by the astrometry tasks)
in the catalog
's metadata
1255 attribute. This method converts such a match catalog into a match
1256 list, with links to source records
and reference object records.
1261 Reference object loader to use
in getting reference objects
1263 Unperisted packed match list.
1264 ``matchCat.table.getMetadata()`` must contain match metadata,
1265 as returned by the astrometry tasks.
1267 Source catalog. As a side effect, the catalog will be sorted
1275 matchmeta = matchCat.table.getMetadata()
1276 version = matchmeta.getInt('SMATCHV')
1278 raise ValueError(
'SourceMatchVector version number is %i, not 1.' % version)
1279 filterName = matchmeta.getString(
'FILTER').
strip()
1281 epoch = matchmeta.getDouble(
'EPOCH')
1282 except (LookupError, TypeError):
1284 if 'RADIUS' in matchmeta:
1287 matchmeta.getDouble(
'DEC'), geom.degrees)
1288 rad = matchmeta.getDouble(
'RADIUS')*geom.degrees
1289 refCat = refObjLoader.loadSkyCircle(ctrCoord, rad, filterName, epoch=epoch).refCat
1290 elif "INNER_UPPER_LEFT_RA" in matchmeta:
1296 for place
in (
"UPPER_LEFT",
"UPPER_RIGHT",
"LOWER_LEFT",
"LOWER_RIGHT"):
1298 matchmeta.getDouble(f
"OUTER_{place}_DEC"),
1299 geom.degrees).getVector()
1302 refCat = refObjLoader.loadRegion(outerBox, filterName, epoch=epoch).refCat
1310 """Apply proper motion correction to a reference catalog.
1312 Adjust position and position error
in the ``catalog``
1313 for proper motion to the specified ``epoch``,
1314 modifying the catalog
in place.
1319 Log object to write to.
1321 Catalog of positions, containing:
1323 - Coordinates, retrieved by the table
's coordinate key.
1324 - ``coord_raErr`` : Error in Right Ascension (rad).
1325 - ``coord_decErr`` : Error
in Declination (rad).
1326 - ``pm_ra`` : Proper motion
in Right Ascension (rad/yr,
1328 - ``pm_raErr`` : Error
in ``pm_ra`` (rad/yr), optional.
1329 - ``pm_dec`` : Proper motion
in Declination (rad/yr,
1331 - ``pm_decErr`` : Error
in ``pm_dec`` (rad/yr), optional.
1332 - ``epoch`` : Mean epoch of object (an astropy.time.Time)
1333 epoch : `astropy.time.Time`
1334 Epoch to which to correct proper motion.
1336 if "epoch" not in catalog.schema
or "pm_ra" not in catalog.schema
or "pm_dec" not in catalog.schema:
1337 log.warning(
"Proper motion correction not available from catalog")
1339 if not catalog.isContiguous():
1340 raise RuntimeError(
"Catalog must be contiguous")
1341 catEpoch = astropy.time.Time(catalog[
"epoch"], scale=
"tai", format=
"mjd")
1342 log.info(
"Correcting reference catalog for proper motion to %r", epoch)
1344 timeDiffsYears = (epoch.tai - catEpoch).
to(astropy.units.yr).value
1345 coordKey = catalog.table.getCoordKey()
1348 pmRaRad = catalog[
"pm_ra"]
1349 pmDecRad = catalog[
"pm_dec"]
1350 offsetsRaRad = pmRaRad*timeDiffsYears
1351 offsetsDecRad = pmDecRad*timeDiffsYears
1359 offsetBearingsRad = numpy.arctan2(pmDecRad*1e6, pmRaRad*1e6)
1360 offsetAmountsRad = numpy.hypot(offsetsRaRad, offsetsDecRad)
1361 for record, bearingRad, amountRad
in zip(catalog, offsetBearingsRad, offsetAmountsRad):
1362 record.set(coordKey,
1363 record.get(coordKey).offset(bearing=bearingRad*geom.radians,
1364 amount=amountRad*geom.radians))
1366 if "coord_raErr" in catalog.schema:
1367 catalog[
"coord_raErr"] = numpy.hypot(catalog[
"coord_raErr"],
1368 catalog[
"pm_raErr"]*timeDiffsYears)
1369 if "coord_decErr" in catalog.schema:
1370 catalog[
"coord_decErr"] = numpy.hypot(catalog[
"coord_decErr"],
1371 catalog[
"pm_decErr"]*timeDiffsYears)
A 2-dimensional celestial WCS that transform pixels to ICRS RA/Dec, using the LSST standard for pixel...
A class used as a handle to a particular field in a table.
Defines the fields and offsets for a table.
A mapping between the keys of two Schemas, used to copy data between them.
Custom catalog class for record/table subclasses that are guaranteed to have an ID,...
Class for storing ordered metadata with comments.
A class representing an angle.
A floating-point coordinate rectangle geometry.
An integer coordinate rectangle.
Point in an unspecified spherical coordinate system.
This static class includes a variety of methods for interacting with the the logging module.
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 __init__(self, dataIds, refCats, log=None, config=None, **kwargs)
def loadRegion(self, region, filterName, filtFunc=None, epoch=None)
def loadPixelBox(self, bbox, wcs, filterName, epoch=None, bboxToSpherePadding=100)
Angle represents an angle in radians.
Circle is a circular region on the unit sphere that contains its boundary.
ConvexPolygon is a closed convex polygon on the unit sphere.
Region is a minimal interface for 2-dimensional regions on the unit sphere.
daf::base::PropertySet * set
std::vector< Match< typename Cat1::Record, typename Cat2::Record > > unpackMatches(BaseCatalog const &matches, Cat1 const &cat1, Cat2 const &cat2)
Reconstruct a MatchVector from a BaseCatalog representation of the matches and a pair of catalogs.
void updateRefCentroids(geom::SkyWcs const &wcs, ReferenceCollection &refList)
Update centroids in a collection of reference objects.
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)
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
A description of a field in a table.