24 __all__ = [
"getRefFluxField",
"getRefFluxKeys",
"LoadReferenceObjectsTask",
"LoadReferenceObjectsConfig",
25 "ReferenceObjectLoader"]
39 from 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.
68 refCat : `lsst.afw.table.SimpleCatalog`
69 Reference catalog to inspect.
73 version : `int` or `None`
74 Format version integer, or `None` 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.
91 catalog : `lsst.afw.table.SimpleCatalog`
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`.
101 catalog : `lsst.afw.table.SimpleCatalog` or None
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(f
"Converted refcat flux fields to nJy (name, units): {fluxFieldsStr}")
144 log.info(f
"Found old-style refcat flux fields (name, units): {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
155 region : `lsst.sphgeom.Region`
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.
176 refCat : `lsst.afw.table.SourceCatalog`
177 SourceCatalog to be filtered.
178 catRegion : `lsst.sphgeom.Region`
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:
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.
205 catalog : `lsst.afw.table.SimpleCatalog`
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.
warn(
"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.
warn(
"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
275 config : `lsst.pex.config.configurableField`
276 Configuration for the loader.
277 log : `lsst.log.Log` or `None`, optional
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.
341 bbox : `lsst.geom.Box2I` or `lsst.geom.Box2D`
342 Box which bounds a region in pixel space.
343 wcs : `lsst.afw.geom.SkyWcs`
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.
360 referenceCatalog : `lsst.afw.table.SimpleCatalog`
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.
configconfig.pixelMargin)
375 innerSkyRegion, outerSkyRegion, _, _ = self.
_makeBoxRegion_makeBoxRegion(paddedBbox, wcs, bboxToSpherePadding)
377 def _filterFunction(refCat, region):
385 if innerSkyRegion.contains(region):
389 filteredRefCat =
type(refCat)(refCat.table)
391 for record
in refCat:
392 pixCoords = record[centroidKey]
394 filteredRefCat.append(record)
395 return filteredRefCat
396 return self.
loadRegionloadRegion(outerSkyRegion, filtFunc=_filterFunction, epoch=epoch, filterName=filterName)
398 def loadRegion(self, region, filtFunc=None, filterName=None, epoch=None):
399 """Load reference objects within a specified region.
401 This function loads the DataIds used to construct an instance of this
402 class which intersect or are contained within the specified region. The
403 reference catalogs which intersect but are not fully contained within
404 the input region are further filtered by the specified filter function.
405 This function returns a single source catalog containing all reference
406 objects inside the specified region.
410 region : `lsst.sphgeom.Region`
411 This can be any type that is derived from `lsst.sphgeom.Region` and
412 should define the spatial region for which reference objects are to
414 filtFunc : callable or `None`, optional
415 This optional parameter should be a callable object that takes a
416 reference catalog and its corresponding region as parameters,
417 filters the catalog by some criteria and returns the filtered
418 reference catalog. If `None`, an internal filter function is used
419 which filters according to if a reference object falls within the
421 filterName : `str` or `None`, optional
422 Name of camera filter, or `None` or blank for the default filter.
423 epoch : `astropy.time.Time` or `None`, optional
424 Epoch to which to correct proper motion and parallax, or `None` to
425 not apply such corrections.
429 referenceCatalog : `lsst.afw.table.SourceCatalog`
430 Catalog containing reference objects which intersect the input region,
431 filtered by the specified filter function.
436 Raised if no reference catalogs could be found for the specified
439 Raised if the loaded reference catalogs do not have matching
442 regionLat = region.getBoundingBox().getLat()
443 regionLon = region.getBoundingBox().getLon()
444 self.
loglog.
info(
"Loading reference objects from region bounded by "
445 "[{:.8f}, {:.8f}], [{:.8f}, {:.8f}] RA Dec".
446 format(regionLon.getA().asDegrees(), regionLon.getB().asDegrees(),
447 regionLat.getA().asDegrees(), regionLat.getB().asDegrees()))
452 for dataId, refCat
in zip(self.
dataIdsdataIds, self.
refCatsrefCats):
456 intersects = dataId.region.intersects(region)
458 intersects = region.intersects(dataId.region)
461 overlapList.append((dataId, refCat))
463 if len(overlapList) == 0:
464 raise RuntimeError(
"No reference tables could be found for input region")
466 firstCat = overlapList[0][1].get()
467 refCat = filtFunc(firstCat, overlapList[0][0].region)
468 trimmedAmount = len(firstCat) - len(refCat)
471 for dataId, inputRefCat
in overlapList[1:]:
472 tmpCat = inputRefCat.get()
474 if tmpCat.schema != firstCat.schema:
475 raise TypeError(
"Reference catalogs have mismatching schemas")
477 filteredCat = filtFunc(tmpCat, dataId.region)
478 refCat.extend(filteredCat)
479 trimmedAmount += len(tmpCat) - len(filteredCat)
481 self.
loglog.
debug(f
"Trimmed {trimmedAmount} refCat objects lying outside padded region, "
482 "leaving {len(refCat)}")
483 self.
loglog.
info(f
"Loaded {len(refCat)} reference objects")
486 if not refCat.isContiguous():
487 refCat = refCat.copy(deep=
True)
494 self.
loglog.
warn(
"Found version 0 reference catalog with old style units in schema.")
495 self.
loglog.
warn(
"run `meas_algorithms/bin/convert_refcat_to_nJy.py` to convert fluxes to nJy.")
496 self.
loglog.
warn(
"See RFC-575 for more details.")
505 if not expandedCat.isContiguous():
506 expandedCat = expandedCat.copy(deep=
True)
508 fluxField =
getRefFluxField(schema=expandedCat.schema, filterName=filterName)
509 return pipeBase.Struct(refCat=expandedCat, fluxField=fluxField)
512 """Load reference objects that lie within a circular region on the sky.
514 This method constructs a circular region from an input center and
515 angular radius, loads reference catalogs which are contained in or
516 intersect the circle, and filters reference catalogs which intersect
517 down to objects which lie within the defined circle.
521 ctrCoord : `lsst.geom.SpherePoint`
522 Point defining the center of the circular region.
523 radius : `lsst.geom.Angle`
524 Defines the angular radius of the circular region.
525 filterName : `str` or `None`, optional
526 Name of camera filter, or `None` or blank for the default filter.
527 epoch : `astropy.time.Time` or `None`, optional
528 Epoch to which to correct proper motion and parallax, or `None` to
529 not apply such corrections.
533 referenceCatalog : `lsst.afw.table.SourceCatalog`
534 Catalog containing reference objects inside the specified search
537 centerVector = ctrCoord.getVector()
540 return self.
loadRegionloadRegion(circularRegion, filterName=filterName, epoch=epoch)
543 """Relink an unpersisted match list to sources and reference objects.
545 A match list is persisted and unpersisted as a catalog of IDs
546 produced by afw.table.packMatches(), with match metadata
547 (as returned by the astrometry tasks) in the catalog's metadata
548 attribute. This method converts such a match catalog into a match
549 list, with links to source records and reference object records.
553 matchCat : `lsst.afw.table.BaseCatalog`
554 Unpersisted packed match list.
555 ``matchCat.table.getMetadata()`` must contain match metadata,
556 as returned by the astrometry tasks.
557 sourceCat : `lsst.afw.table.SourceCatalog`
558 Source catalog. As a side effect, the catalog will be sorted
563 matchList : `lsst.afw.table.ReferenceMatchVector`
568 def getMetadataBox(self, bbox, wcs, filterName=None, photoCalib=None, epoch=None,
569 bboxToSpherePadding=100):
570 """Return metadata about the load
572 This metadata is used for reloading the catalog (e.g., for
573 reconstituting a normalised match list.)
577 bbox : `lsst.geom.Box2I` or `lsst.geom.Box2D`
578 Bounding box for the pixels.
579 wcs : `lsst.afw.geom.SkyWcs`
580 The WCS object associated with ``bbox``.
581 filterName : `str` or `None`, optional
582 Name of the camera filter, or `None` or blank for the default
585 Deprecated, only included for api compatibility.
586 epoch : `astropy.time.Time` or `None`, optional
587 Epoch to which to correct proper motion and parallax, or `None` to
588 not apply such corrections.
589 bboxToSpherePadding : `int`, optional
590 Padding to account for translating a set of corners into a
591 spherical (convex) boundary that is certain to encompase the
592 enitre area covered by the box.
596 md : `lsst.daf.base.PropertyList`
597 The metadata detailing the search parameters used for this
601 paddedBbox.grow(self.
configconfig.pixelMargin)
602 _, _, innerCorners, outerCorners = self.
_makeBoxRegion_makeBoxRegion(paddedBbox, wcs, bboxToSpherePadding)
604 for box, corners
in zip((
"INNER",
"OUTER"), (innerCorners, outerCorners)):
605 for (name, corner)
in zip((
"UPPER_LEFT",
"UPPER_RIGHT",
"LOWER_LEFT",
"LOWER_RIGHT"),
607 md.add(f
"{box}_{name}_RA",
geom.SpherePoint(corner).getRa().asDegrees(), f
"{box}_corner")
608 md.add(f
"{box}_{name}_DEC",
geom.SpherePoint(corner).getDec().asDegrees(), f
"{box}_corner")
609 md.add(
"SMATCHV", 1,
'SourceMatchVector version number')
610 filterName =
"UNKNOWN" if filterName
is None else str(filterName)
611 md.add(
'FILTER', filterName,
'filter name for photometric data')
612 md.add(
'EPOCH',
"NONE" if epoch
is None else epoch.mjd,
'Epoch (TAI MJD) for catalog')
617 """Return metadata about the load.
619 This metadata is used for reloading the catalog (e.g. for
620 reconstituting a normalized match list.)
624 coord : `lsst.geom.SpherePoint`
625 ICRS center of the search region.
626 radius : `lsst.geom.Angle`
627 Radius of the search region.
628 filterName : `str` or `None`
629 Name of the camera filter, or `None` or blank for the default
632 Deprecated, only included for api compatibility.
633 epoch : `astropy.time.Time` or `None`, optional
634 Epoch to which to correct proper motion and parallax, or `None` to
635 not apply such corrections.
639 md : `lsst.daf.base.PropertyList`
642 md.add(
'RA', coord.getRa().asDegrees(),
'field center in degrees')
643 md.add(
'DEC', coord.getDec().asDegrees(),
'field center in degrees')
644 md.add(
'RADIUS', radius.asDegrees(),
'field radius in degrees, minimum')
645 md.add(
'SMATCHV', 1,
'SourceMatchVector version number')
646 filterName =
"UNKNOWN" if filterName
is None else str(filterName)
647 md.add(
'FILTER', filterName,
'filter name for photometric data')
648 md.add(
'EPOCH',
"NONE" if epoch
is None else epoch.mjd,
'Epoch (TAI MJD) for catalog')
653 """Add flux columns and aliases for camera to reference mapping.
655 Creates a new catalog containing the information of the input refCat
656 as well as added flux columns and aliases between camera and reference
661 refCat : `lsst.afw.table.SimpleCatalog`
662 Catalog of reference objects
663 defaultFilter : `str`
664 Name of the default reference filter
665 filterReferenceMap : `dict` of `str`
666 Dictionary with keys corresponding to a filter name and values
667 which correspond to the name of the reference filter.
671 refCat : `lsst.afw.table.SimpleCatalog`
672 Reference catalog with columns added to track reference filters.
677 If the specified reference filter name is not specifed as a
678 key in the reference filter map.
680 refCat = ReferenceObjectLoader.remapReferenceCatalogSchema(refCat,
681 filterNameList=filterReferenceMap.keys())
682 aliasMap = refCat.schema.getAliasMap()
683 if filterReferenceMap
is None:
684 filterReferenceMap = {}
685 for filterName, refFilterName
in itertools.chain([(
None, defaultFilter)],
686 filterReferenceMap.items()):
688 camFluxName = filterName +
"_camFlux" if filterName
is not None else "camFlux"
689 refFluxName = refFilterName +
"_flux"
690 if refFluxName
not in refCat.schema:
691 raise RuntimeError(
"Unknown reference filter %s" % (refFluxName,))
692 aliasMap.set(camFluxName, refFluxName)
694 refFluxErrName = refFluxName +
"Err"
695 camFluxErrName = camFluxName +
"Err"
696 aliasMap.set(camFluxErrName, refFluxErrName)
702 """This function takes in a reference catalog and creates a new catalog with additional
703 columns defined the remaining function arguments.
707 refCat : `lsst.afw.table.SimpleCatalog`
708 Reference catalog to map to new catalog
712 expandedCat : `lsst.afw.table.SimpleCatalog`
713 Deep copy of input reference catalog with additional columns added
716 mapper.addMinimalSchema(refCat.schema,
True)
717 mapper.editOutputSchema().disconnectAliases()
719 for filterName
in filterNameList:
720 mapper.editOutputSchema().addField(f
"{filterName}_flux",
722 doc=f
"flux in filter {filterName}",
725 mapper.editOutputSchema().addField(f
"{filterName}_fluxErr",
727 doc=f
"flux uncertanty in filter {filterName}",
732 mapper.editOutputSchema().addField(
"centroid_x", type=float, doReplace=
True)
733 mapper.editOutputSchema().addField(
"centroid_y", type=float, doReplace=
True)
734 mapper.editOutputSchema().addField(
"hasCentroid", type=
"Flag", doReplace=
True)
735 mapper.editOutputSchema().getAliasMap().
set(
"slot_Centroid",
"centroid")
738 mapper.editOutputSchema().addField(
"photometric",
740 doc=
"set if the object can be used for photometric"
743 mapper.editOutputSchema().addField(
"resolved",
745 doc=
"set if the object is spatially resolved"
747 mapper.editOutputSchema().addField(
"variable",
749 doc=
"set if the object has variable brightness"
753 expandedCat.setMetadata(refCat.getMetadata())
754 expandedCat.extend(refCat, mapper=mapper)
760 """Get the name of a flux field from a schema.
762 return the alias of "anyFilterMapsToThis", if present
763 else if filterName is specified:
764 return "*filterName*_camFlux" if present
765 else return "*filterName*_flux" if present (camera filter name
766 matches reference filter name)
767 else throw RuntimeError
769 return "camFlux", if present,
770 else throw RuntimeError
774 schema : `lsst.afw.table.Schema`
775 Reference catalog schema.
776 filterName : `str`, optional
777 Name of camera filter. If not specified, ``defaultFilter`` needs to be
778 set in the refcat loader config.
782 fluxFieldName : `str`
788 If an appropriate field is not found.
791 raise RuntimeError(
"schema=%s is not a schema" % (schema,))
793 return schema.getAliasMap().get(
"anyFilterMapsToThis")
798 fluxFieldList = [filterName +
"_camFlux", filterName +
"_flux"]
800 fluxFieldList = [
"camFlux"]
801 for fluxField
in fluxFieldList:
802 if fluxField
in schema:
805 raise RuntimeError(
"Could not find flux field(s) %s" % (
", ".join(fluxFieldList)))
809 """Return keys for flux and flux error.
813 schema : `lsst.afw.table.Schema`
814 Reference catalog schema.
816 Name of camera filter.
820 keys : `tuple` of (`lsst.afw.table.Key`, `lsst.afw.table.Key`)
824 - flux error key, if present, else None
829 If flux field not found.
832 fluxErrField = fluxField +
"Err"
833 fluxKey = schema[fluxField].asKey()
835 fluxErrKey = schema[fluxErrField].asKey()
838 return (fluxKey, fluxErrKey)
842 pixelMargin = pexConfig.RangeField(
843 doc=
"Padding to add to 4 all edges of the bounding box (pixels)",
848 defaultFilter = pexConfig.Field(
849 doc=(
"Default reference catalog filter to use if filter not specified in exposure;"
850 " if blank then filter must be specified in exposure."),
853 deprecated=
"defaultFilter is deprecated by RFC-716. Will be removed after v22."
855 anyFilterMapsToThis = pexConfig.Field(
856 doc=(
"Always use this reference catalog filter, no matter whether or what filter name is "
857 "supplied to the loader. Effectively a trivial filterMap: map all filter names to this filter."
858 " This can be set for purely-astrometric catalogs (e.g. Gaia DR2) where there is only one "
859 "reasonable choice for every camera filter->refcat mapping, but not for refcats used for "
860 "photometry, which need a filterMap and/or colorterms/transmission corrections."),
865 filterMap = pexConfig.DictField(
866 doc=(
"Mapping of camera filter name: reference catalog filter name; "
867 "each reference filter must exist in the refcat."
868 " Note that this does not perform any bandpass corrections: it is just a lookup."),
873 requireProperMotion = pexConfig.Field(
874 doc=
"Require that the fields needed to correct proper motion "
875 "(epoch, pm_ra and pm_dec) are present?",
883 msg =
"`filterMap` and `anyFilterMapsToThis` are mutually exclusive"
884 raise pexConfig.FieldValidationError(LoadReferenceObjectsConfig.anyFilterMapsToThis,
889 """Abstract base class to load objects from reference catalogs.
891 ConfigClass = LoadReferenceObjectsConfig
892 _DefaultName =
"LoadReferenceObjects"
895 """Construct a LoadReferenceObjectsTask
899 butler : `lsst.daf.persistence.Butler`
900 Data butler, for access reference catalogs.
902 pipeBase.Task.__init__(self, *args, **kwargs)
906 def loadPixelBox(self, bbox, wcs, filterName=None, photoCalib=None, epoch=None):
907 """Load reference objects that overlap a rectangular pixel region.
911 bbox : `lsst.geom.Box2I` or `lsst.geom.Box2D`
912 Bounding box for pixels.
913 wcs : `lsst.afw.geom.SkyWcs`
914 WCS; used to convert pixel positions to sky coordinates
916 filterName : `str` or `None`, optional
917 Name of filter, or `None` or `""` for the default filter.
918 This is used for flux values in case we have flux limits
919 (which are not yet implemented).
921 Deprecated, only included for api compatibility.
922 epoch : `astropy.time.Time` or `None`, optional
923 Epoch to which to correct proper motion and parallax, or `None` to
924 not apply such corrections.
928 results : `lsst.pipe.base.Struct`
929 A Struct containing the following fields:
930 refCat : `lsst.afw.catalog.SimpleCatalog`
931 A catalog of reference objects with the standard
932 schema, as documented in the main doc string for
933 `LoadReferenceObjects`.
934 The catalog is guaranteed to be contiguous.
936 Name of flux field for specified `filterName`.
940 The search algorithm works by searching in a region in sky
941 coordinates whose center is the center of the bbox and radius
942 is large enough to just include all 4 corners of the bbox.
943 Stars that lie outside the bbox are then trimmed from the list.
948 self.log.
info(
"Loading reference objects using center %s and radius %s deg" %
949 (circle.coord, circle.radius.asDegrees()))
950 loadRes = self.
loadSkyCircleloadSkyCircle(circle.coord, circle.radius, filterName=filterName, epoch=epoch,
952 refCat = loadRes.refCat
953 numFound = len(refCat)
956 refCat = self.
_trimToBBox_trimToBBox(refCat=refCat, bbox=circle.bbox, wcs=wcs)
957 numTrimmed = numFound - len(refCat)
958 self.log.
debug(
"trimmed %d out-of-bbox objects, leaving %d", numTrimmed, len(refCat))
959 self.log.
info(
"Loaded %d reference objects", len(refCat))
962 if not refCat.isContiguous():
963 loadRes.refCat = refCat.copy(deep=
True)
968 def loadSkyCircle(self, ctrCoord, radius, filterName=None, epoch=None, centroids=False):
969 """Load reference objects that overlap a circular sky region.
973 ctrCoord : `lsst.geom.SpherePoint`
974 ICRS center of search region.
975 radius : `lsst.geom.Angle`
976 Radius of search region.
977 filterName : `str` or `None`, optional
978 Name of filter, or `None` or `""` for the default filter.
979 This is used for flux values in case we have flux limits
980 (which are not yet implemented).
981 epoch : `astropy.time.Time` or `None`, optional
982 Epoch to which to correct proper motion and parallax, or `None` to
983 not apply such corrections.
984 centroids : `bool`, optional
985 Add centroid fields to the loaded Schema. ``loadPixelBox`` expects
986 these fields to exist.
990 results : `lsst.pipe.base.Struct`
991 A Struct containing the following fields:
992 refCat : `lsst.afw.catalog.SimpleCatalog`
993 A catalog of reference objects with the standard
994 schema, as documented in the main doc string for
995 `LoadReferenceObjects`.
996 The catalog is guaranteed to be contiguous.
998 Name of flux field for specified `filterName`.
1002 Note that subclasses are responsible for performing the proper motion
1003 correction, since this is the lowest-level interface for retrieving
1009 def _trimToBBox(refCat, bbox, wcs):
1010 """Remove objects outside a given pixel bounding box and set
1011 centroid and hasCentroid fields.
1015 refCat : `lsst.afw.table.SimpleCatalog`
1016 A catalog of objects. The schema must include fields
1017 "coord", "centroid" and "hasCentroid".
1018 The "coord" field is read.
1019 The "centroid" and "hasCentroid" fields are set.
1020 bbox : `lsst.geom.Box2D`
1022 wcs : `lsst.afw.geom.SkyWcs`
1023 WCS; used to convert sky coordinates to pixel positions.
1027 catalog : `lsst.afw.table.SimpleCatalog`
1028 Reference objects in the bbox, with centroid and
1029 hasCentroid fields set.
1033 retStarCat =
type(refCat)(refCat.table)
1035 point = star.get(centroidKey)
1036 if bbox.contains(point):
1037 retStarCat.append(star)
1040 def _addFluxAliases(self, schema):
1041 """Add aliases for camera filter fluxes to the schema.
1043 If self.config.defaultFilter then adds these aliases:
1044 camFlux: <defaultFilter>_flux
1045 camFluxErr: <defaultFilter>_fluxErr, if the latter exists
1047 For each camFilter: refFilter in self.config.filterMap adds these aliases:
1048 <camFilter>_camFlux: <refFilter>_flux
1049 <camFilter>_camFluxErr: <refFilter>_fluxErr, if the latter exists
1053 schema : `lsst.afw.table.Schema`
1054 Schema for reference catalog.
1059 If any reference flux field is missing from the schema.
1061 aliasMap = schema.getAliasMap()
1063 if self.config.anyFilterMapsToThis
is not None:
1064 refFluxName = self.config.anyFilterMapsToThis +
"_flux"
1065 if refFluxName
not in schema:
1066 msg = f
"Unknown reference filter for anyFilterMapsToThis='{refFluxName}'"
1067 raise RuntimeError(msg)
1068 aliasMap.set(
"anyFilterMapsToThis", refFluxName)
1071 def addAliasesForOneFilter(filterName, refFilterName):
1072 """Add aliases for a single filter
1076 filterName : `str` (optional)
1077 Camera filter name. The resulting alias name is
1078 <filterName>_camFlux, or simply "camFlux" if `filterName`
1080 refFilterName : `str`
1081 Reference catalog filter name; the field
1082 <refFilterName>_flux must exist.
1084 camFluxName = filterName +
"_camFlux" if filterName
is not None else "camFlux"
1085 refFluxName = refFilterName +
"_flux"
1086 if refFluxName
not in schema:
1087 raise RuntimeError(
"Unknown reference filter %s" % (refFluxName,))
1088 aliasMap.set(camFluxName, refFluxName)
1089 refFluxErrName = refFluxName +
"Err"
1090 if refFluxErrName
in schema:
1091 camFluxErrName = camFluxName +
"Err"
1092 aliasMap.set(camFluxErrName, refFluxErrName)
1094 if self.config.defaultFilter:
1095 addAliasesForOneFilter(
None, self.config.defaultFilter)
1097 for filterName, refFilterName
in self.config.filterMap.items():
1098 addAliasesForOneFilter(filterName, refFilterName)
1102 addIsPhotometric=False, addIsResolved=False,
1103 addIsVariable=False, coordErrDim=2,
1104 addProperMotion=False, properMotionErrDim=2,
1106 """Make a standard schema for reference object catalogs.
1110 filterNameList : `list` of `str`
1111 List of filter names. Used to create <filterName>_flux fields.
1112 addIsPhotometric : `bool`
1113 If True then add field "photometric".
1114 addIsResolved : `bool`
1115 If True then add field "resolved".
1116 addIsVariable : `bool`
1117 If True then add field "variable".
1119 Number of coord error fields; must be one of 0, 2, 3:
1121 - If 2 or 3: add fields "coord_raErr" and "coord_decErr".
1122 - If 3: also add field "coord_radecErr".
1123 addProperMotion : `bool`
1124 If True add fields "epoch", "pm_ra", "pm_dec" and "pm_flag".
1125 properMotionErrDim : `int`
1126 Number of proper motion error fields; must be one of 0, 2, 3;
1127 ignored if addProperMotion false:
1128 - If 2 or 3: add fields "pm_raErr" and "pm_decErr".
1129 - If 3: also add field "pm_radecErr".
1130 addParallax : `bool`
1131 If True add fields "epoch", "parallax", "parallaxErr"
1132 and "parallax_flag".
1136 schema : `lsst.afw.table.Schema`
1137 Schema for reference catalog, an
1138 `lsst.afw.table.SimpleCatalog`.
1142 Reference catalogs support additional covariances, such as
1143 covariance between RA and proper motion in declination,
1144 that are not supported by this method, but can be added after
1145 calling this method.
1147 schema = afwTable.SimpleTable.makeMinimalSchema()
1149 afwTable.Point2DKey.addFields(
1152 "centroid on an exposure, if relevant",
1156 field=
"hasCentroid",
1158 doc=
"is position known?",
1160 for filterName
in filterNameList:
1162 field=
"%s_flux" % (filterName,),
1164 doc=
"flux in filter %s" % (filterName,),
1167 for filterName
in filterNameList:
1169 field=
"%s_fluxErr" % (filterName,),
1171 doc=
"flux uncertainty in filter %s" % (filterName,),
1174 if addIsPhotometric:
1176 field=
"photometric",
1178 doc=
"set if the object can be used for photometric calibration",
1184 doc=
"set if the object is spatially resolved",
1190 doc=
"set if the object has variable brightness",
1192 if coordErrDim
not in (0, 2, 3):
1193 raise ValueError(
"coordErrDim={}; must be (0, 2, 3)".
format(coordErrDim))
1195 afwTable.CovarianceMatrix2fKey.addFields(
1198 names=[
"ra",
"dec"],
1199 units=[
"rad",
"rad"],
1200 diagonalOnly=(coordErrDim == 2),
1203 if addProperMotion
or addParallax:
1207 doc=
"date of observation (TAI, MJD)",
1215 doc=
"proper motion in the right ascension direction = dra/dt * cos(dec)",
1221 doc=
"proper motion in the declination direction",
1224 if properMotionErrDim
not in (0, 2, 3):
1225 raise ValueError(
"properMotionErrDim={}; must be (0, 2, 3)".
format(properMotionErrDim))
1226 if properMotionErrDim > 0:
1227 afwTable.CovarianceMatrix2fKey.addFields(
1230 names=[
"ra",
"dec"],
1231 units=[
"rad/year",
"rad/year"],
1232 diagonalOnly=(properMotionErrDim == 2),
1237 doc=
"Set if proper motion or proper motion error is bad",
1248 field=
"parallaxErr",
1250 doc=
"uncertainty in parallax",
1254 field=
"parallax_flag",
1256 doc=
"Set if parallax or parallax error is bad",
1260 def _calculateCircle(self, bbox, wcs):
1261 """Compute on-sky center and radius of search region.
1265 bbox : `lsst.geom.Box2I` or `lsst.geom.Box2D`
1267 wcs : `lsst.afw.geom.SkyWcs`
1268 WCS; used to convert pixel positions to sky coordinates.
1272 results : `lsst.pipe.base.Struct`
1273 A Struct containing:
1275 - coord : `lsst.geom.SpherePoint`
1276 ICRS center of the search region.
1277 - radius : `lsst.geom.Angle`
1278 Radius of the search region.
1279 - bbox : `lsst.geom.Box2D`
1280 Bounding box used to compute the circle.
1283 bbox.grow(self.config.pixelMargin)
1284 coord = wcs.pixelToSky(bbox.getCenter())
1285 radius =
max(coord.separation(wcs.pixelToSky(pp))
for pp
in bbox.getCorners())
1286 return pipeBase.Struct(coord=coord, radius=radius, bbox=bbox)
1289 """Return metadata about the load.
1291 This metadata is used for reloading the catalog (e.g., for
1292 reconstituting a normalised match list.
1296 bbox : `lsst.geom.Box2I` or `lsst.geom.Box2D`
1298 wcs : `lsst.afw.geom.SkyWcs`
1299 WCS; used to convert pixel positions to sky coordinates.
1300 filterName : `str` or `None`, optional
1301 Name of camera filter, or `None` or `""` for the default
1304 Deprecated, only included for api compatibility.
1305 epoch : `astropy.time.Time` or `None`, optional
1306 Epoch to which to correct proper motion and parallax,
1307 or None to not apply such corrections.
1311 metadata : `lsst.daf.base.PropertyList`
1312 Metadata about the load.
1315 return self.
getMetadataCirclegetMetadataCircle(circle.coord, circle.radius, filterName, epoch=epoch)
1318 """Return metadata about the load.
1320 This metadata is used for reloading the catalog (e.g., for
1321 reconstituting a normalised match list.
1325 coord : `lsst.geom.SpherePoint`
1326 ICRS center of the search region.
1327 radius : `lsst.geom.Angle`
1328 Radius of the search region.
1330 Name of camera filter, or `None` or `""` for the default
1333 Deprecated, only included for api compatibility.
1334 epoch : `astropy.time.Time` (optional)
1335 Epoch to which to correct proper motion and parallax, or `None` to
1336 not apply such corrections.
1340 metadata : lsst.daf.base.PropertyList
1341 Metadata about the load
1344 md.add(
'RA', coord.getRa().asDegrees(),
'field center in degrees')
1345 md.add(
'DEC', coord.getDec().asDegrees(),
'field center in degrees')
1346 md.add(
'RADIUS', radius.asDegrees(),
'field radius in degrees, minimum')
1347 md.add(
'SMATCHV', 1,
'SourceMatchVector version number')
1348 filterName =
"UNKNOWN" if filterName
is None else str(filterName)
1349 md.add(
'FILTER', filterName,
'filter name for photometric data')
1350 md.add(
'EPOCH',
"NONE" if epoch
is None else epoch.mjd,
'Epoch (TAI MJD) for catalog')
1354 """Relink an unpersisted match list to sources and reference
1357 A match list is persisted and unpersisted as a catalog of IDs
1358 produced by afw.table.packMatches(), with match metadata
1359 (as returned by the astrometry tasks) in the catalog's metadata
1360 attribute. This method converts such a match catalog into a match
1361 list, with links to source records and reference object records.
1365 matchCat : `lsst.afw.table.BaseCatalog`
1366 Unperisted packed match list.
1367 ``matchCat.table.getMetadata()`` must contain match metadata,
1368 as returned by the astrometry tasks.
1369 sourceCat : `lsst.afw.table.SourceCatalog`
1370 Source catalog. As a side effect, the catalog will be sorted
1375 matchList : `lsst.afw.table.ReferenceMatchVector`
1382 """Relink an unpersisted match list to sources and reference
1385 A match list is persisted and unpersisted as a catalog of IDs
1386 produced by afw.table.packMatches(), with match metadata
1387 (as returned by the astrometry tasks) in the catalog's metadata
1388 attribute. This method converts such a match catalog into a match
1389 list, with links to source records and reference object records.
1394 Reference object loader to use in getting reference objects
1395 matchCat : `lsst.afw.table.BaseCatalog`
1396 Unperisted packed match list.
1397 ``matchCat.table.getMetadata()`` must contain match metadata,
1398 as returned by the astrometry tasks.
1399 sourceCat : `lsst.afw.table.SourceCatalog`
1400 Source catalog. As a side effect, the catalog will be sorted
1405 matchList : `lsst.afw.table.ReferenceMatchVector`
1408 matchmeta = matchCat.table.getMetadata()
1409 version = matchmeta.getInt(
'SMATCHV')
1411 raise ValueError(
'SourceMatchVector version number is %i, not 1.' % version)
1412 filterName = matchmeta.getString(
'FILTER').
strip()
1414 epoch = matchmeta.getDouble(
'EPOCH')
1415 except (LookupError, TypeError):
1417 if 'RADIUS' in matchmeta:
1420 matchmeta.getDouble(
'DEC'), geom.degrees)
1421 rad = matchmeta.getDouble(
'RADIUS')*geom.degrees
1422 refCat = refObjLoader.loadSkyCircle(ctrCoord, rad, filterName, epoch=epoch).refCat
1423 elif "INNER_UPPER_LEFT_RA" in matchmeta:
1429 for place
in (
"UPPER_LEFT",
"UPPER_RIGHT",
"LOWER_LEFT",
"LOWER_RIGHT"):
1431 matchmeta.getDouble(f
"OUTER_{place}_DEC"),
1432 geom.degrees).getVector()
1435 refCat = refObjLoader.loadRegion(outerBox, filterName=filterName, epoch=epoch).refCat
1443 """Apply proper motion correction to a reference catalog.
1445 Adjust position and position error in the ``catalog``
1446 for proper motion to the specified ``epoch``,
1447 modifying the catalog in place.
1451 log : `lsst.log.Log`
1452 Log object to write to.
1453 catalog : `lsst.afw.table.SimpleCatalog`
1454 Catalog of positions, containing:
1456 - Coordinates, retrieved by the table's coordinate key.
1457 - ``coord_raErr`` : Error in Right Ascension (rad).
1458 - ``coord_decErr`` : Error in Declination (rad).
1459 - ``pm_ra`` : Proper motion in Right Ascension (rad/yr,
1461 - ``pm_raErr`` : Error in ``pm_ra`` (rad/yr), optional.
1462 - ``pm_dec`` : Proper motion in Declination (rad/yr,
1464 - ``pm_decErr`` : Error in ``pm_dec`` (rad/yr), optional.
1465 - ``epoch`` : Mean epoch of object (an astropy.time.Time)
1466 epoch : `astropy.time.Time`
1467 Epoch to which to correct proper motion.
1469 if "epoch" not in catalog.schema
or "pm_ra" not in catalog.schema
or "pm_dec" not in catalog.schema:
1470 log.warn(
"Proper motion correction not available from catalog")
1472 if not catalog.isContiguous():
1473 raise RuntimeError(
"Catalog must be contiguous")
1474 catEpoch = astropy.time.Time(catalog[
"epoch"], scale=
"tai", format=
"mjd")
1475 log.info(
"Correcting reference catalog for proper motion to %r", epoch)
1477 timeDiffsYears = (epoch.tai - catEpoch).
to(astropy.units.yr).value
1478 coordKey = catalog.table.getCoordKey()
1481 pmRaRad = catalog[
"pm_ra"]
1482 pmDecRad = catalog[
"pm_dec"]
1483 offsetsRaRad = pmRaRad*timeDiffsYears
1484 offsetsDecRad = pmDecRad*timeDiffsYears
1492 offsetBearingsRad = numpy.arctan2(pmDecRad*1e6, pmRaRad*1e6)
1493 offsetAmountsRad = numpy.hypot(offsetsRaRad, offsetsDecRad)
1494 for record, bearingRad, amountRad
in zip(catalog, offsetBearingsRad, offsetAmountsRad):
1495 record.set(coordKey,
1496 record.get(coordKey).offset(bearing=bearingRad*geom.radians,
1497 amount=amountRad*geom.radians))
1499 if "coord_raErr" in catalog.schema:
1500 catalog[
"coord_raErr"] = numpy.hypot(catalog[
"coord_raErr"],
1501 catalog[
"pm_raErr"]*timeDiffsYears)
1502 if "coord_decErr" in catalog.schema:
1503 catalog[
"coord_decErr"] = numpy.hypot(catalog[
"coord_decErr"],
1504 catalog[
"pm_decErr"]*timeDiffsYears)
Defines the fields and offsets for a table.
A mapping between the keys of two Schemas, used to copy data between them.
static Schema makeMinimalSchema()
Return a minimal schema for Simple tables and records.
Custom catalog class for record/table subclasses that are guaranteed to have an ID,...
Class for storing ordered metadata with comments.
A floating-point coordinate rectangle geometry.
Point in an unspecified spherical coordinate system.
static Log getDefaultLogger()
Return default logger instance, same as default constructor.
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)
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.
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 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)
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
A description of a field in a table.