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.
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.
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(
"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
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.
warning(
"Reference catalog pm_ra field is not an Angle; cannot apply proper motion.")
245 if (
"epoch" not in catalog.schema
or "pm_ra" not in catalog.schema):
246 if self.config.requireProperMotion:
247 raise RuntimeError(
"requireProperMotion=True but PM data not available from catalog.")
249 self.log.
warning(
"Proper motion correction not available for this reference catalog.")
256 """This class facilitates loading reference catalogs with gen 3 middleware
258 The middleware preflight solver will create a list of datarefs that may
259 possibly overlap a given region. These datarefs are then used to construct
260 and instance of this class. The class instance should then be passed into
261 a task which needs reference catalogs. These tasks should then determine
262 the exact region of the sky reference catalogs will be loaded for, and
263 call a corresponding method to load the reference objects.
265 def __init__(self, dataIds, refCats, config, log=None):
266 """ Constructs an instance of ReferenceObjectLoader
270 dataIds : iterable of `lsst.daf.butler.DataIds`
271 An iterable object of DataSetRefs which point to reference catalogs
272 in a gen 3 repository.
273 refCats : iterable of `lsst.daf.butler.DeferedDatasetHandle`
274 Handles to load refCats on demand
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):
388 refCat = preFiltFunc(refCat, region)
397 if innerSkyRegion.contains(region):
401 filteredRefCat =
type(refCat)(refCat.table)
403 for record
in refCat:
404 pixCoords = record[centroidKey]
406 filteredRefCat.append(record)
407 return filteredRefCat
408 return self.
loadRegionloadRegion(outerSkyRegion, filtFunc=_filterFunction, epoch=epoch, filterName=filterName)
410 def loadRegion(self, region, filtFunc=None, filterName=None, epoch=None):
411 """Load reference objects within a specified region.
413 This function loads the DataIds used to construct an instance of this
414 class which intersect or are contained within the specified region. The
415 reference catalogs which intersect but are not fully contained within
416 the input region are further filtered by the specified filter function.
417 This function returns a single source catalog containing all reference
418 objects inside the specified region.
422 region : `lsst.sphgeom.Region`
423 This can be any type that is derived from `lsst.sphgeom.Region` and
424 should define the spatial region for which reference objects are to
426 filtFunc : callable or `None`, optional
427 This optional parameter should be a callable object that takes a
428 reference catalog and its corresponding region as parameters,
429 filters the catalog by some criteria and returns the filtered
430 reference catalog. If `None`, an internal filter function is used
431 which filters according to if a reference object falls within the
433 filterName : `str` or `None`, optional
434 Name of camera filter, or `None` or blank for the default filter.
435 epoch : `astropy.time.Time` or `None`, optional
436 Epoch to which to correct proper motion and parallax, or `None` to
437 not apply such corrections.
441 referenceCatalog : `lsst.afw.table.SourceCatalog`
442 Catalog containing reference objects which intersect the input region,
443 filtered by the specified filter function.
448 Raised if no reference catalogs could be found for the specified
451 Raised if the loaded reference catalogs do not have matching
454 regionLat = region.getBoundingBox().getLat()
455 regionLon = region.getBoundingBox().getLon()
456 self.
loglog.
info(
"Loading reference objects from region bounded by "
457 "[%.8f, %.8f], [%.8f, %.8f] RA Dec",
458 regionLon.getA().asDegrees(), regionLon.getB().asDegrees(),
459 regionLat.getA().asDegrees(), regionLat.getB().asDegrees())
464 for dataId, refCat
in zip(self.
dataIdsdataIds, self.
refCatsrefCats):
468 intersects = dataId.region.intersects(region)
470 intersects = region.intersects(dataId.region)
473 overlapList.append((dataId, refCat))
475 if len(overlapList) == 0:
476 raise RuntimeError(
"No reference tables could be found for input region")
478 firstCat = overlapList[0][1].get()
479 refCat = filtFunc(firstCat, overlapList[0][0].region)
480 trimmedAmount = len(firstCat) - len(refCat)
483 for dataId, inputRefCat
in overlapList[1:]:
484 tmpCat = inputRefCat.get()
486 if tmpCat.schema != firstCat.schema:
487 raise TypeError(
"Reference catalogs have mismatching schemas")
489 filteredCat = filtFunc(tmpCat, dataId.region)
490 refCat.extend(filteredCat)
491 trimmedAmount += len(tmpCat) - len(filteredCat)
493 self.
loglog.
debug(
"Trimmed %d refCat objects lying outside padded region, leaving %d",
494 trimmedAmount, len(refCat))
495 self.
loglog.
info(
"Loaded %d reference objects", len(refCat))
498 if not refCat.isContiguous():
499 refCat = refCat.copy(deep=
True)
506 self.
loglog.
warning(
"Found version 0 reference catalog with old style units in schema.")
507 self.
loglog.
warning(
"run `meas_algorithms/bin/convert_refcat_to_nJy.py` to convert fluxes to nJy.")
508 self.
loglog.
warning(
"See RFC-575 for more details.")
517 if not expandedCat.isContiguous():
518 expandedCat = expandedCat.copy(deep=
True)
520 fluxField =
getRefFluxField(schema=expandedCat.schema, filterName=filterName)
521 return pipeBase.Struct(refCat=expandedCat, fluxField=fluxField)
524 """Load reference objects that lie within a circular region on the sky.
526 This method constructs a circular region from an input center and
527 angular radius, loads reference catalogs which are contained in or
528 intersect the circle, and filters reference catalogs which intersect
529 down to objects which lie within the defined circle.
533 ctrCoord : `lsst.geom.SpherePoint`
534 Point defining the center of the circular region.
535 radius : `lsst.geom.Angle`
536 Defines the angular radius of the circular region.
537 filterName : `str` or `None`, optional
538 Name of camera filter, or `None` or blank for the default filter.
539 epoch : `astropy.time.Time` or `None`, optional
540 Epoch to which to correct proper motion and parallax, or `None` to
541 not apply such corrections.
545 referenceCatalog : `lsst.afw.table.SourceCatalog`
546 Catalog containing reference objects inside the specified search
549 centerVector = ctrCoord.getVector()
552 return self.
loadRegionloadRegion(circularRegion, filterName=filterName, epoch=epoch)
555 """Relink an unpersisted match list to sources and reference objects.
557 A match list is persisted and unpersisted as a catalog of IDs
558 produced by afw.table.packMatches(), with match metadata
559 (as returned by the astrometry tasks) in the catalog's metadata
560 attribute. This method converts such a match catalog into a match
561 list, with links to source records and reference object records.
565 matchCat : `lsst.afw.table.BaseCatalog`
566 Unpersisted packed match list.
567 ``matchCat.table.getMetadata()`` must contain match metadata,
568 as returned by the astrometry tasks.
569 sourceCat : `lsst.afw.table.SourceCatalog`
570 Source catalog. As a side effect, the catalog will be sorted
575 matchList : `lsst.afw.table.ReferenceMatchVector`
580 def getMetadataBox(self, bbox, wcs, filterName=None, photoCalib=None, epoch=None,
581 bboxToSpherePadding=100):
582 """Return metadata about the load
584 This metadata is used for reloading the catalog (e.g., for
585 reconstituting a normalised match list.)
589 bbox : `lsst.geom.Box2I` or `lsst.geom.Box2D`
590 Bounding box for the pixels.
591 wcs : `lsst.afw.geom.SkyWcs`
592 The WCS object associated with ``bbox``.
593 filterName : `str` or `None`, optional
594 Name of the camera filter, or `None` or blank for the default
597 Deprecated, only included for api compatibility.
598 epoch : `astropy.time.Time` or `None`, optional
599 Epoch to which to correct proper motion and parallax, or `None` to
600 not apply such corrections.
601 bboxToSpherePadding : `int`, optional
602 Padding to account for translating a set of corners into a
603 spherical (convex) boundary that is certain to encompase the
604 enitre area covered by the box.
608 md : `lsst.daf.base.PropertyList`
609 The metadata detailing the search parameters used for this
613 paddedBbox.grow(self.
configconfig.pixelMargin)
614 _, _, innerCorners, outerCorners = self.
_makeBoxRegion_makeBoxRegion(paddedBbox, wcs, bboxToSpherePadding)
616 for box, corners
in zip((
"INNER",
"OUTER"), (innerCorners, outerCorners)):
617 for (name, corner)
in zip((
"UPPER_LEFT",
"UPPER_RIGHT",
"LOWER_LEFT",
"LOWER_RIGHT"),
619 md.add(f
"{box}_{name}_RA",
geom.SpherePoint(corner).getRa().asDegrees(), f
"{box}_corner")
620 md.add(f
"{box}_{name}_DEC",
geom.SpherePoint(corner).getDec().asDegrees(), f
"{box}_corner")
621 md.add(
"SMATCHV", 1,
'SourceMatchVector version number')
622 filterName =
"UNKNOWN" if filterName
is None else str(filterName)
623 md.add(
'FILTER', filterName,
'filter name for photometric data')
624 md.add(
'EPOCH',
"NONE" if epoch
is None else epoch.mjd,
'Epoch (TAI MJD) for catalog')
629 """Return metadata about the load.
631 This metadata is used for reloading the catalog (e.g. for
632 reconstituting a normalized match list.)
636 coord : `lsst.geom.SpherePoint`
637 ICRS center of the search region.
638 radius : `lsst.geom.Angle`
639 Radius of the search region.
640 filterName : `str` or `None`
641 Name of the camera filter, or `None` or blank for the default
644 Deprecated, only included for api compatibility.
645 epoch : `astropy.time.Time` or `None`, optional
646 Epoch to which to correct proper motion and parallax, or `None` to
647 not apply such corrections.
651 md : `lsst.daf.base.PropertyList`
654 md.add(
'RA', coord.getRa().asDegrees(),
'field center in degrees')
655 md.add(
'DEC', coord.getDec().asDegrees(),
'field center in degrees')
656 md.add(
'RADIUS', radius.asDegrees(),
'field radius in degrees, minimum')
657 md.add(
'SMATCHV', 1,
'SourceMatchVector version number')
658 filterName =
"UNKNOWN" if filterName
is None else str(filterName)
659 md.add(
'FILTER', filterName,
'filter name for photometric data')
660 md.add(
'EPOCH',
"NONE" if epoch
is None else epoch.mjd,
'Epoch (TAI MJD) for catalog')
665 """Add flux columns and aliases for camera to reference mapping.
667 Creates a new catalog containing the information of the input refCat
668 as well as added flux columns and aliases between camera and reference
673 refCat : `lsst.afw.table.SimpleCatalog`
674 Catalog of reference objects
675 defaultFilter : `str`
676 Name of the default reference filter
677 filterReferenceMap : `dict` of `str`
678 Dictionary with keys corresponding to a filter name and values
679 which correspond to the name of the reference filter.
683 refCat : `lsst.afw.table.SimpleCatalog`
684 Reference catalog with columns added to track reference filters.
689 If the specified reference filter name is not specifed as a
690 key in the reference filter map.
692 refCat = ReferenceObjectLoader.remapReferenceCatalogSchema(refCat,
693 filterNameList=filterReferenceMap.keys())
694 aliasMap = refCat.schema.getAliasMap()
695 if filterReferenceMap
is None:
696 filterReferenceMap = {}
697 for filterName, refFilterName
in itertools.chain([(
None, defaultFilter)],
698 filterReferenceMap.items()):
700 camFluxName = filterName +
"_camFlux" if filterName
is not None else "camFlux"
701 refFluxName = refFilterName +
"_flux"
702 if refFluxName
not in refCat.schema:
703 raise RuntimeError(
"Unknown reference filter %s" % (refFluxName,))
704 aliasMap.set(camFluxName, refFluxName)
706 refFluxErrName = refFluxName +
"Err"
707 camFluxErrName = camFluxName +
"Err"
708 aliasMap.set(camFluxErrName, refFluxErrName)
714 """This function takes in a reference catalog and creates a new catalog with additional
715 columns defined the remaining function arguments.
719 refCat : `lsst.afw.table.SimpleCatalog`
720 Reference catalog to map to new catalog
724 expandedCat : `lsst.afw.table.SimpleCatalog`
725 Deep copy of input reference catalog with additional columns added
728 mapper.addMinimalSchema(refCat.schema,
True)
729 mapper.editOutputSchema().disconnectAliases()
731 for filterName
in filterNameList:
732 mapper.editOutputSchema().addField(f
"{filterName}_flux",
734 doc=f
"flux in filter {filterName}",
737 mapper.editOutputSchema().addField(f
"{filterName}_fluxErr",
739 doc=f
"flux uncertanty in filter {filterName}",
744 mapper.editOutputSchema().addField(
"centroid_x", type=float, doReplace=
True)
745 mapper.editOutputSchema().addField(
"centroid_y", type=float, doReplace=
True)
746 mapper.editOutputSchema().addField(
"hasCentroid", type=
"Flag", doReplace=
True)
747 mapper.editOutputSchema().getAliasMap().
set(
"slot_Centroid",
"centroid")
750 mapper.editOutputSchema().addField(
"photometric",
752 doc=
"set if the object can be used for photometric"
755 mapper.editOutputSchema().addField(
"resolved",
757 doc=
"set if the object is spatially resolved"
759 mapper.editOutputSchema().addField(
"variable",
761 doc=
"set if the object has variable brightness"
765 expandedCat.setMetadata(refCat.getMetadata())
766 expandedCat.extend(refCat, mapper=mapper)
772 """Get the name of a flux field from a schema.
774 return the alias of "anyFilterMapsToThis", if present
775 else if filterName is specified:
776 return "*filterName*_camFlux" if present
777 else return "*filterName*_flux" if present (camera filter name
778 matches reference filter name)
779 else throw RuntimeError
781 return "camFlux", if present,
782 else throw RuntimeError
786 schema : `lsst.afw.table.Schema`
787 Reference catalog schema.
788 filterName : `str`, optional
789 Name of camera filter. If not specified, ``defaultFilter`` needs to be
790 set in the refcat loader config.
794 fluxFieldName : `str`
800 If an appropriate field is not found.
803 raise RuntimeError(
"schema=%s is not a schema" % (schema,))
805 return schema.getAliasMap().get(
"anyFilterMapsToThis")
810 fluxFieldList = [filterName +
"_camFlux", filterName +
"_flux"]
812 fluxFieldList = [
"camFlux"]
813 for fluxField
in fluxFieldList:
814 if fluxField
in schema:
817 raise RuntimeError(
"Could not find flux field(s) %s" % (
", ".join(fluxFieldList)))
821 """Return keys for flux and flux error.
825 schema : `lsst.afw.table.Schema`
826 Reference catalog schema.
828 Name of camera filter.
832 keys : `tuple` of (`lsst.afw.table.Key`, `lsst.afw.table.Key`)
836 - flux error key, if present, else None
841 If flux field not found.
844 fluxErrField = fluxField +
"Err"
845 fluxKey = schema[fluxField].asKey()
847 fluxErrKey = schema[fluxErrField].asKey()
850 return (fluxKey, fluxErrKey)
854 pixelMargin = pexConfig.RangeField(
855 doc=
"Padding to add to 4 all edges of the bounding box (pixels)",
860 defaultFilter = pexConfig.Field(
861 doc=(
"Default reference catalog filter to use if filter not specified in exposure;"
862 " if blank then filter must be specified in exposure."),
865 deprecated=
"defaultFilter is deprecated by RFC-716. Will be removed after v22."
867 anyFilterMapsToThis = pexConfig.Field(
868 doc=(
"Always use this reference catalog filter, no matter whether or what filter name is "
869 "supplied to the loader. Effectively a trivial filterMap: map all filter names to this filter."
870 " This can be set for purely-astrometric catalogs (e.g. Gaia DR2) where there is only one "
871 "reasonable choice for every camera filter->refcat mapping, but not for refcats used for "
872 "photometry, which need a filterMap and/or colorterms/transmission corrections."),
877 filterMap = pexConfig.DictField(
878 doc=(
"Mapping of camera filter name: reference catalog filter name; "
879 "each reference filter must exist in the refcat."
880 " Note that this does not perform any bandpass corrections: it is just a lookup."),
885 requireProperMotion = pexConfig.Field(
886 doc=
"Require that the fields needed to correct proper motion "
887 "(epoch, pm_ra and pm_dec) are present?",
895 msg =
"`filterMap` and `anyFilterMapsToThis` are mutually exclusive"
896 raise pexConfig.FieldValidationError(LoadReferenceObjectsConfig.anyFilterMapsToThis,
901 """Abstract base class to load objects from reference catalogs.
903 ConfigClass = LoadReferenceObjectsConfig
904 _DefaultName =
"LoadReferenceObjects"
907 """Construct a LoadReferenceObjectsTask
911 butler : `lsst.daf.persistence.Butler`
912 Data butler, for access reference catalogs.
914 pipeBase.Task.__init__(self, *args, **kwargs)
918 def loadPixelBox(self, bbox, wcs, filterName=None, photoCalib=None, epoch=None):
919 """Load reference objects that overlap a rectangular pixel region.
923 bbox : `lsst.geom.Box2I` or `lsst.geom.Box2D`
924 Bounding box for pixels.
925 wcs : `lsst.afw.geom.SkyWcs`
926 WCS; used to convert pixel positions to sky coordinates
928 filterName : `str` or `None`, optional
929 Name of filter, or `None` or `""` for the default filter.
930 This is used for flux values in case we have flux limits
931 (which are not yet implemented).
933 Deprecated, only included for api compatibility.
934 epoch : `astropy.time.Time` or `None`, optional
935 Epoch to which to correct proper motion and parallax, or `None` to
936 not apply such corrections.
940 results : `lsst.pipe.base.Struct`
941 A Struct containing the following fields:
942 refCat : `lsst.afw.catalog.SimpleCatalog`
943 A catalog of reference objects with the standard
944 schema, as documented in the main doc string for
945 `LoadReferenceObjects`.
946 The catalog is guaranteed to be contiguous.
948 Name of flux field for specified `filterName`.
952 The search algorithm works by searching in a region in sky
953 coordinates whose center is the center of the bbox and radius
954 is large enough to just include all 4 corners of the bbox.
955 Stars that lie outside the bbox are then trimmed from the list.
960 self.log.
info(
"Loading reference objects using center %s and radius %s deg",
961 circle.coord, circle.radius.asDegrees())
962 loadRes = self.
loadSkyCircleloadSkyCircle(circle.coord, circle.radius, filterName=filterName, epoch=epoch,
964 refCat = loadRes.refCat
965 numFound = len(refCat)
968 refCat = self.
_trimToBBox_trimToBBox(refCat=refCat, bbox=circle.bbox, wcs=wcs)
969 numTrimmed = numFound - len(refCat)
970 self.log.
debug(
"trimmed %d out-of-bbox objects, leaving %d", numTrimmed, len(refCat))
971 self.log.
info(
"Loaded %d reference objects", len(refCat))
974 if not refCat.isContiguous():
975 loadRes.refCat = refCat.copy(deep=
True)
980 def loadSkyCircle(self, ctrCoord, radius, filterName=None, epoch=None, centroids=False):
981 """Load reference objects that overlap a circular sky region.
985 ctrCoord : `lsst.geom.SpherePoint`
986 ICRS center of search region.
987 radius : `lsst.geom.Angle`
988 Radius of search region.
989 filterName : `str` or `None`, optional
990 Name of filter, or `None` or `""` for the default filter.
991 This is used for flux values in case we have flux limits
992 (which are not yet implemented).
993 epoch : `astropy.time.Time` or `None`, optional
994 Epoch to which to correct proper motion and parallax, or `None` to
995 not apply such corrections.
996 centroids : `bool`, optional
997 Add centroid fields to the loaded Schema. ``loadPixelBox`` expects
998 these fields to exist.
1002 results : `lsst.pipe.base.Struct`
1003 A Struct containing the following fields:
1004 refCat : `lsst.afw.catalog.SimpleCatalog`
1005 A catalog of reference objects with the standard
1006 schema, as documented in the main doc string for
1007 `LoadReferenceObjects`.
1008 The catalog is guaranteed to be contiguous.
1010 Name of flux field for specified `filterName`.
1014 Note that subclasses are responsible for performing the proper motion
1015 correction, since this is the lowest-level interface for retrieving
1021 def _trimToBBox(refCat, bbox, wcs):
1022 """Remove objects outside a given pixel bounding box and set
1023 centroid and hasCentroid fields.
1027 refCat : `lsst.afw.table.SimpleCatalog`
1028 A catalog of objects. The schema must include fields
1029 "coord", "centroid" and "hasCentroid".
1030 The "coord" field is read.
1031 The "centroid" and "hasCentroid" fields are set.
1032 bbox : `lsst.geom.Box2D`
1034 wcs : `lsst.afw.geom.SkyWcs`
1035 WCS; used to convert sky coordinates to pixel positions.
1039 catalog : `lsst.afw.table.SimpleCatalog`
1040 Reference objects in the bbox, with centroid and
1041 hasCentroid fields set.
1045 retStarCat =
type(refCat)(refCat.table)
1047 point = star.get(centroidKey)
1048 if bbox.contains(point):
1049 retStarCat.append(star)
1052 def _addFluxAliases(self, schema):
1053 """Add aliases for camera filter fluxes to the schema.
1055 If self.config.defaultFilter then adds these aliases:
1056 camFlux: <defaultFilter>_flux
1057 camFluxErr: <defaultFilter>_fluxErr, if the latter exists
1059 For each camFilter: refFilter in self.config.filterMap adds these aliases:
1060 <camFilter>_camFlux: <refFilter>_flux
1061 <camFilter>_camFluxErr: <refFilter>_fluxErr, if the latter exists
1065 schema : `lsst.afw.table.Schema`
1066 Schema for reference catalog.
1071 If any reference flux field is missing from the schema.
1073 aliasMap = schema.getAliasMap()
1075 if self.config.anyFilterMapsToThis
is not None:
1076 refFluxName = self.config.anyFilterMapsToThis +
"_flux"
1077 if refFluxName
not in schema:
1078 msg = f
"Unknown reference filter for anyFilterMapsToThis='{refFluxName}'"
1079 raise RuntimeError(msg)
1080 aliasMap.set(
"anyFilterMapsToThis", refFluxName)
1083 def addAliasesForOneFilter(filterName, refFilterName):
1084 """Add aliases for a single filter
1088 filterName : `str` (optional)
1089 Camera filter name. The resulting alias name is
1090 <filterName>_camFlux, or simply "camFlux" if `filterName`
1092 refFilterName : `str`
1093 Reference catalog filter name; the field
1094 <refFilterName>_flux must exist.
1096 camFluxName = filterName +
"_camFlux" if filterName
is not None else "camFlux"
1097 refFluxName = refFilterName +
"_flux"
1098 if refFluxName
not in schema:
1099 raise RuntimeError(
"Unknown reference filter %s" % (refFluxName,))
1100 aliasMap.set(camFluxName, refFluxName)
1101 refFluxErrName = refFluxName +
"Err"
1102 if refFluxErrName
in schema:
1103 camFluxErrName = camFluxName +
"Err"
1104 aliasMap.set(camFluxErrName, refFluxErrName)
1106 if self.config.defaultFilter:
1107 addAliasesForOneFilter(
None, self.config.defaultFilter)
1109 for filterName, refFilterName
in self.config.filterMap.items():
1110 addAliasesForOneFilter(filterName, refFilterName)
1114 addIsPhotometric=False, addIsResolved=False,
1115 addIsVariable=False, coordErrDim=2,
1116 addProperMotion=False, properMotionErrDim=2,
1118 """Make a standard schema for reference object catalogs.
1122 filterNameList : `list` of `str`
1123 List of filter names. Used to create <filterName>_flux fields.
1124 addIsPhotometric : `bool`
1125 If True then add field "photometric".
1126 addIsResolved : `bool`
1127 If True then add field "resolved".
1128 addIsVariable : `bool`
1129 If True then add field "variable".
1131 Number of coord error fields; must be one of 0, 2, 3:
1133 - If 2 or 3: add fields "coord_raErr" and "coord_decErr".
1134 - If 3: also add field "coord_radecErr".
1135 addProperMotion : `bool`
1136 If True add fields "epoch", "pm_ra", "pm_dec" and "pm_flag".
1137 properMotionErrDim : `int`
1138 Number of proper motion error fields; must be one of 0, 2, 3;
1139 ignored if addProperMotion false:
1140 - If 2 or 3: add fields "pm_raErr" and "pm_decErr".
1141 - If 3: also add field "pm_radecErr".
1142 addParallax : `bool`
1143 If True add fields "epoch", "parallax", "parallaxErr"
1144 and "parallax_flag".
1148 schema : `lsst.afw.table.Schema`
1149 Schema for reference catalog, an
1150 `lsst.afw.table.SimpleCatalog`.
1154 Reference catalogs support additional covariances, such as
1155 covariance between RA and proper motion in declination,
1156 that are not supported by this method, but can be added after
1157 calling this method.
1159 schema = afwTable.SimpleTable.makeMinimalSchema()
1161 afwTable.Point2DKey.addFields(
1164 "centroid on an exposure, if relevant",
1168 field=
"hasCentroid",
1170 doc=
"is position known?",
1172 for filterName
in filterNameList:
1174 field=
"%s_flux" % (filterName,),
1176 doc=
"flux in filter %s" % (filterName,),
1179 for filterName
in filterNameList:
1181 field=
"%s_fluxErr" % (filterName,),
1183 doc=
"flux uncertainty in filter %s" % (filterName,),
1186 if addIsPhotometric:
1188 field=
"photometric",
1190 doc=
"set if the object can be used for photometric calibration",
1196 doc=
"set if the object is spatially resolved",
1202 doc=
"set if the object has variable brightness",
1204 if coordErrDim
not in (0, 2, 3):
1205 raise ValueError(
"coordErrDim={}; must be (0, 2, 3)".
format(coordErrDim))
1207 afwTable.CovarianceMatrix2fKey.addFields(
1210 names=[
"ra",
"dec"],
1211 units=[
"rad",
"rad"],
1212 diagonalOnly=(coordErrDim == 2),
1215 if addProperMotion
or addParallax:
1219 doc=
"date of observation (TAI, MJD)",
1227 doc=
"proper motion in the right ascension direction = dra/dt * cos(dec)",
1233 doc=
"proper motion in the declination direction",
1236 if properMotionErrDim
not in (0, 2, 3):
1237 raise ValueError(
"properMotionErrDim={}; must be (0, 2, 3)".
format(properMotionErrDim))
1238 if properMotionErrDim > 0:
1239 afwTable.CovarianceMatrix2fKey.addFields(
1242 names=[
"ra",
"dec"],
1243 units=[
"rad/year",
"rad/year"],
1244 diagonalOnly=(properMotionErrDim == 2),
1249 doc=
"Set if proper motion or proper motion error is bad",
1260 field=
"parallaxErr",
1262 doc=
"uncertainty in parallax",
1266 field=
"parallax_flag",
1268 doc=
"Set if parallax or parallax error is bad",
1272 def _calculateCircle(self, bbox, wcs):
1273 """Compute on-sky center and radius of search region.
1277 bbox : `lsst.geom.Box2I` or `lsst.geom.Box2D`
1279 wcs : `lsst.afw.geom.SkyWcs`
1280 WCS; used to convert pixel positions to sky coordinates.
1284 results : `lsst.pipe.base.Struct`
1285 A Struct containing:
1287 - coord : `lsst.geom.SpherePoint`
1288 ICRS center of the search region.
1289 - radius : `lsst.geom.Angle`
1290 Radius of the search region.
1291 - bbox : `lsst.geom.Box2D`
1292 Bounding box used to compute the circle.
1295 bbox.grow(self.config.pixelMargin)
1296 coord = wcs.pixelToSky(bbox.getCenter())
1297 radius =
max(coord.separation(wcs.pixelToSky(pp))
for pp
in bbox.getCorners())
1298 return pipeBase.Struct(coord=coord, radius=radius, bbox=bbox)
1301 """Return metadata about the load.
1303 This metadata is used for reloading the catalog (e.g., for
1304 reconstituting a normalised match list.
1308 bbox : `lsst.geom.Box2I` or `lsst.geom.Box2D`
1310 wcs : `lsst.afw.geom.SkyWcs`
1311 WCS; used to convert pixel positions to sky coordinates.
1312 filterName : `str` or `None`, optional
1313 Name of camera filter, or `None` or `""` for the default
1316 Deprecated, only included for api compatibility.
1317 epoch : `astropy.time.Time` or `None`, optional
1318 Epoch to which to correct proper motion and parallax,
1319 or None to not apply such corrections.
1323 metadata : `lsst.daf.base.PropertyList`
1324 Metadata about the load.
1327 return self.
getMetadataCirclegetMetadataCircle(circle.coord, circle.radius, filterName, epoch=epoch)
1330 """Return metadata about the load.
1332 This metadata is used for reloading the catalog (e.g., for
1333 reconstituting a normalised match list.
1337 coord : `lsst.geom.SpherePoint`
1338 ICRS center of the search region.
1339 radius : `lsst.geom.Angle`
1340 Radius of the search region.
1342 Name of camera filter, or `None` or `""` for the default
1345 Deprecated, only included for api compatibility.
1346 epoch : `astropy.time.Time` (optional)
1347 Epoch to which to correct proper motion and parallax, or `None` to
1348 not apply such corrections.
1352 metadata : lsst.daf.base.PropertyList
1353 Metadata about the load
1356 md.add(
'RA', coord.getRa().asDegrees(),
'field center in degrees')
1357 md.add(
'DEC', coord.getDec().asDegrees(),
'field center in degrees')
1358 md.add(
'RADIUS', radius.asDegrees(),
'field radius in degrees, minimum')
1359 md.add(
'SMATCHV', 1,
'SourceMatchVector version number')
1360 filterName =
"UNKNOWN" if filterName
is None else str(filterName)
1361 md.add(
'FILTER', filterName,
'filter name for photometric data')
1362 md.add(
'EPOCH',
"NONE" if epoch
is None else epoch.mjd,
'Epoch (TAI MJD) for catalog')
1366 """Relink an unpersisted match list to sources and reference
1369 A match list is persisted and unpersisted as a catalog of IDs
1370 produced by afw.table.packMatches(), with match metadata
1371 (as returned by the astrometry tasks) in the catalog's metadata
1372 attribute. This method converts such a match catalog into a match
1373 list, with links to source records and reference object records.
1377 matchCat : `lsst.afw.table.BaseCatalog`
1378 Unperisted packed match list.
1379 ``matchCat.table.getMetadata()`` must contain match metadata,
1380 as returned by the astrometry tasks.
1381 sourceCat : `lsst.afw.table.SourceCatalog`
1382 Source catalog. As a side effect, the catalog will be sorted
1387 matchList : `lsst.afw.table.ReferenceMatchVector`
1394 """Relink an unpersisted match list to sources and reference
1397 A match list is persisted and unpersisted as a catalog of IDs
1398 produced by afw.table.packMatches(), with match metadata
1399 (as returned by the astrometry tasks) in the catalog's metadata
1400 attribute. This method converts such a match catalog into a match
1401 list, with links to source records and reference object records.
1406 Reference object loader to use in getting reference objects
1407 matchCat : `lsst.afw.table.BaseCatalog`
1408 Unperisted packed match list.
1409 ``matchCat.table.getMetadata()`` must contain match metadata,
1410 as returned by the astrometry tasks.
1411 sourceCat : `lsst.afw.table.SourceCatalog`
1412 Source catalog. As a side effect, the catalog will be sorted
1417 matchList : `lsst.afw.table.ReferenceMatchVector`
1420 matchmeta = matchCat.table.getMetadata()
1421 version = matchmeta.getInt(
'SMATCHV')
1423 raise ValueError(
'SourceMatchVector version number is %i, not 1.' % version)
1424 filterName = matchmeta.getString(
'FILTER').
strip()
1426 epoch = matchmeta.getDouble(
'EPOCH')
1427 except (LookupError, TypeError):
1429 if 'RADIUS' in matchmeta:
1432 matchmeta.getDouble(
'DEC'), geom.degrees)
1433 rad = matchmeta.getDouble(
'RADIUS')*geom.degrees
1434 refCat = refObjLoader.loadSkyCircle(ctrCoord, rad, filterName, epoch=epoch).refCat
1435 elif "INNER_UPPER_LEFT_RA" in matchmeta:
1441 for place
in (
"UPPER_LEFT",
"UPPER_RIGHT",
"LOWER_LEFT",
"LOWER_RIGHT"):
1443 matchmeta.getDouble(f
"OUTER_{place}_DEC"),
1444 geom.degrees).getVector()
1447 refCat = refObjLoader.loadRegion(outerBox, filterName=filterName, epoch=epoch).refCat
1455 """Apply proper motion correction to a reference catalog.
1457 Adjust position and position error in the ``catalog``
1458 for proper motion to the specified ``epoch``,
1459 modifying the catalog in place.
1463 log : `lsst.log.Log`
1464 Log object to write to.
1465 catalog : `lsst.afw.table.SimpleCatalog`
1466 Catalog of positions, containing:
1468 - Coordinates, retrieved by the table's coordinate key.
1469 - ``coord_raErr`` : Error in Right Ascension (rad).
1470 - ``coord_decErr`` : Error in Declination (rad).
1471 - ``pm_ra`` : Proper motion in Right Ascension (rad/yr,
1473 - ``pm_raErr`` : Error in ``pm_ra`` (rad/yr), optional.
1474 - ``pm_dec`` : Proper motion in Declination (rad/yr,
1476 - ``pm_decErr`` : Error in ``pm_dec`` (rad/yr), optional.
1477 - ``epoch`` : Mean epoch of object (an astropy.time.Time)
1478 epoch : `astropy.time.Time`
1479 Epoch to which to correct proper motion.
1481 if "epoch" not in catalog.schema
or "pm_ra" not in catalog.schema
or "pm_dec" not in catalog.schema:
1482 log.warning(
"Proper motion correction not available from catalog")
1484 if not catalog.isContiguous():
1485 raise RuntimeError(
"Catalog must be contiguous")
1486 catEpoch = astropy.time.Time(catalog[
"epoch"], scale=
"tai", format=
"mjd")
1487 log.info(
"Correcting reference catalog for proper motion to %r", epoch)
1489 timeDiffsYears = (epoch.tai - catEpoch).
to(astropy.units.yr).value
1490 coordKey = catalog.table.getCoordKey()
1493 pmRaRad = catalog[
"pm_ra"]
1494 pmDecRad = catalog[
"pm_dec"]
1495 offsetsRaRad = pmRaRad*timeDiffsYears
1496 offsetsDecRad = pmDecRad*timeDiffsYears
1504 offsetBearingsRad = numpy.arctan2(pmDecRad*1e6, pmRaRad*1e6)
1505 offsetAmountsRad = numpy.hypot(offsetsRaRad, offsetsDecRad)
1506 for record, bearingRad, amountRad
in zip(catalog, offsetBearingsRad, offsetAmountsRad):
1507 record.set(coordKey,
1508 record.get(coordKey).offset(bearing=bearingRad*geom.radians,
1509 amount=amountRad*geom.radians))
1511 if "coord_raErr" in catalog.schema:
1512 catalog[
"coord_raErr"] = numpy.hypot(catalog[
"coord_raErr"],
1513 catalog[
"pm_raErr"]*timeDiffsYears)
1514 if "coord_decErr" in catalog.schema:
1515 catalog[
"coord_decErr"] = numpy.hypot(catalog[
"coord_decErr"],
1516 catalog[
"pm_decErr"]*timeDiffsYears)
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.