24 __all__ = [
"getRefFluxField",
"getRefFluxKeys",
"LoadReferenceObjectsTask",
"LoadReferenceObjectsConfig",
25 "ReferenceObjectLoader"]
36 import lsst.pex.config
as pexConfig
42 from lsst
import sphgeom
47 """Return True if this name/units combination corresponds to an
48 "old-style" reference catalog flux field.
50 unitsCheck = units !=
'nJy'
51 isFlux = name.endswith(
'_flux')
52 isFluxSigma = name.endswith(
'_fluxSigma')
53 isFluxErr = name.endswith(
'_fluxErr')
54 return (isFlux
or isFluxSigma
or isFluxErr)
and unitsCheck
58 """Return True if the units of all flux and fluxErr are correct (nJy).
67 """"Return the format version stored in a reference catalog header.
71 refCat : `lsst.afw.table.SimpleCatalog`
72 Reference catalog to inspect.
76 version : `int` or `None`
77 Format version integer, or `None` if the catalog has no metadata
78 or the metadata does not include a "REFCAT_FORMAT_VERSION" key.
80 md = refCat.getMetadata()
84 return md.getScalar(
"REFCAT_FORMAT_VERSION")
90 """Convert fluxes in a catalog from jansky to nanojansky.
94 catalog : `lsst.afw.table.SimpleCatalog`
95 The catalog to convert.
97 Log to send messages to.
98 doConvert : `bool`, optional
99 Return a converted catalog, or just identify the fields that need to be converted?
100 This supports the "write=False" mode of `bin/convert_to_nJy.py`.
104 catalog : `lsst.afw.table.SimpleCatalog` or None
105 The converted catalog, or None if ``doConvert`` is False.
109 Support for old units in reference catalogs will be removed after the
110 release of late calendar year 2019.
111 Use `meas_algorithms/bin/convert_to_nJy.py` to update your reference catalog.
119 for field
in catalog.schema:
120 oldName = field.field.getName()
121 oldUnits = field.field.getUnits()
125 if oldName.endswith(
'_fluxSigma'):
126 name = oldName.replace(
'_fluxSigma',
'_fluxErr')
130 mapper.addMapping(field.getKey(), newField)
131 input_fields.append(field.field)
132 output_fields.append(newField)
134 mapper.addMapping(field.getKey())
136 fluxFieldsStr =
'; '.join(
"(%s, '%s')" % (field.getName(), field.getUnits())
for field
in input_fields)
139 newSchema = mapper.getOutputSchema()
141 output.extend(catalog, mapper=mapper)
142 for field
in output_fields:
143 output[field.getName()] *= 1e9
144 log.info(f
"Converted refcat flux fields to nJy (name, units): {fluxFieldsStr}")
147 log.info(f
"Found old-style refcat flux fields (name, units): {fluxFieldsStr}")
152 """This is a private helper class which filters catalogs by
153 row based on the row being inside the region used to initialize
158 region : `lsst.sphgeom.Region`
159 The spatial region which all objects should lie within
165 """This call method on an instance of this class takes in a reference
166 catalog, and the region from which the catalog was generated.
168 If the catalog region is entirely contained within the region used to
169 initialize this class, then all the entries in the catalog must be
170 within the region and so the whole catalog is returned.
172 If the catalog region is not entirely contained, then the location for
173 each record is tested against the region used to initialize the class.
174 Records which fall inside this region are added to a new catalog, and
175 this catalog is then returned.
179 refCat : `lsst.afw.table.SourceCatalog`
180 SourceCatalog to be filtered.
181 catRegion : `lsst.sphgeom.Region`
182 Region in which the catalog was created
184 if catRegion.isWithin(self.
region):
188 filteredRefCat =
type(refCat)(refCat.table)
189 for record
in refCat:
191 filteredRefCat.append(record)
192 return filteredRefCat
196 """ This class facilitates loading reference catalogs with gen 3 middleware
198 The middleware preflight solver will create a list of datarefs that may
199 possibly overlap a given region. These datarefs are then used to construct
200 and instance of this class. The class instance should then be passed into
201 a task which needs reference catalogs. These tasks should then determine
202 the exact region of the sky reference catalogs will be loaded for, and
203 call a corresponding method to load the reference objects.
205 def __init__(self, dataIds, refCats, config, log=None):
206 """ Constructs an instance of ReferenceObjectLoader
210 dataIds : iterable of `lsst.daf.butler.DataIds`
211 An iterable object of DataSetRefs which point to reference catalogs
212 in a gen 3 repository
213 refCats : Iterable of `lsst.daf.butler.DeferedDatasetHandle`
214 Handles to load refCats on demand
216 Logger object used to write out messages. If `None` (default) the default
217 lsst logger will be used
226 def _makeBoxRegion(BBox, wcs, BBoxPadding):
233 outerLocalBBox.grow(BBoxPadding)
234 innerLocalBBox.grow(-1*BBoxPadding)
242 innerBoxCorners = innerLocalBBox.getCorners()
243 innerSphCorners = [wcs.pixelToSky(corner).getVector()
for corner
in innerBoxCorners]
248 outerBoxCorners = outerLocalBBox.getCorners()
249 outerSphCorners = [wcs.pixelToSky(corner).getVector()
for corner
in outerBoxCorners]
253 return innerSkyRegion, outerSkyRegion, innerSphCorners, outerSphCorners
255 def loadPixelBox(self, bbox, wcs, filterName=None, epoch=None, photoCalib=None, bboxPadding=100):
256 """Load reference objects that are within a pixel-based rectangular region
258 This algorithm works by creating a spherical box whose corners correspond
259 to the WCS converted corners of the input bounding box (possibly padded).
260 It then defines a filtering function which will look at a reference
261 objects pixel position and accept objects that lie within the specified
264 The spherical box region and filtering function are passed to the generic
265 loadRegion method which will load and filter the reference objects from
266 the datastore and return a single catalog containing all reference objects
270 bbox : `lsst.geom.box2I`
271 Box which bounds a region in pixel space
272 wcs : `lsst.afw.geom.SkyWcs`
273 Wcs object defining the pixel to sky (and inverse) transform for the space
274 of pixels of the supplied bbox
276 Name of camera filter, or None or blank for the default filter
277 epoch : `astropy.time.Time` (optional)
278 Epoch to which to correct proper motion and parallax,
279 or None to not apply such corrections.
281 Deprecated and ignored, only included for api compatibility
283 Number describing how much to pad the input bbox by (in pixels), defaults
284 to 100. This parameter is necessary because optical distortions in telescopes
285 can cause a rectangular pixel grid to map into a non "rectangular" spherical
286 region in sky coordinates. This padding is used to create a spherical
287 "rectangle", which will for sure enclose the input box. This padding is only
288 used to determine if the reference catalog for a sky patch will be loaded from
289 the data store, this function will filter out objects which lie within the
290 padded region but fall outside the input bounding box region.
294 referenceCatalog : `lsst.afw.table.SimpleCatalog`
295 Catalog containing reference objects inside the specified bounding box
299 `lsst.pex.exception.RuntimeError`
300 Raised if no reference catalogs could be found for the specified region
302 `lsst.pex.exception.TypeError`
303 Raised if the loaded reference catalogs do not have matching schemas
305 innerSkyRegion, outerSkyRegion, _, _ = self.
_makeBoxRegion(bbox, wcs, bboxPadding)
307 def _filterFunction(refCat, region):
314 if innerSkyRegion.contains(region):
317 filteredRefCat =
type(refCat)(refCat.table)
319 for record
in refCat:
320 pixCoords = record[centroidKey]
322 filteredRefCat.append(record)
323 return filteredRefCat
324 return self.
loadRegion(outerSkyRegion, filtFunc=_filterFunction, epoch=epoch, filterName=filterName)
326 def loadRegion(self, region, filtFunc=None, filterName=None, epoch=None):
327 """ Load reference objects within a specified region
329 This function loads the DataIds used to construct an instance of this class
330 which intersect or are contained within the specified region. The reference
331 catalogs which intersect but are not fully contained within the input region are
332 further filtered by the specified filter function. This function will return a
333 single source catalog containing all reference objects inside the specified region.
337 region : `lsst.sphgeom.Region`
338 This can be any type that is derived from `lsst.sphgeom.region` and should
339 define the spatial region for which reference objects are to be loaded.
341 This optional parameter should be a callable object that takes a reference
342 catalog and its corresponding region as parameters, filters the catalog by
343 some criteria and returns the filtered reference catalog. If the value is
344 left as the default (None) than an internal filter function is used which
345 filters according to if a reference object falls within the input region.
347 Name of camera filter, or None or blank for the default filter
348 epoch : `astropy.time.Time` (optional)
349 Epoch to which to correct proper motion and parallax,
350 or None to not apply such corrections.
354 referenceCatalog : `lsst.afw.table.SourceCatalog`
355 Catalog containing reference objects which intersect the input region,
356 filtered by the specified filter function
360 `lsst.pex.exception.RuntimeError`
361 Raised if no reference catalogs could be found for the specified region
363 `lsst.pex.exception.TypeError`
364 Raised if the loaded reference catalogs do not have matching schemas
367 regionBounding = region.getBoundingBox()
368 self.
log.
info(
"Loading reference objects from region bounded by {}, {} lat lon".
format(
369 regionBounding.getLat(), regionBounding.getLon()))
378 intersects = dataId.region.intersects(region)
380 intersects = region.intersects(dataId.region)
383 overlapList.append((dataId, refCat))
385 if len(overlapList) == 0:
388 firstCat = overlapList[0][1].get()
389 refCat = filtFunc(firstCat, overlapList[0][0].region)
390 trimmedAmount = len(firstCat) - len(refCat)
393 for dataId, inputRefCat
in overlapList[1:]:
394 tmpCat = inputRefCat.get()
396 if tmpCat.schema != firstCat.schema:
399 filteredCat = filtFunc(tmpCat, dataId.region)
400 refCat.extend(filteredCat)
401 trimmedAmount += len(tmpCat) - len(filteredCat)
403 self.
log.
debug(f
"Trimmed {trimmedAmount} out of region objects, leaving {len(refCat)}")
404 self.
log.
info(f
"Loaded {len(refCat)} reference objects")
406 if epoch
is not None and "pm_ra" in refCat.schema:
408 if isinstance(refCat.schema[
"pm_ra"].asKey(), lsst.afw.table.KeyAngle):
411 self.
log.
warn(
"Catalog pm_ra field is not an Angle; not applying proper motion")
416 self.
log.
warn(
"Found version 0 reference catalog with old style units in schema.")
417 self.
log.
warn(
"run `meas_algorithms/bin/convert_refcat_to_nJy.py` to convert fluxes to nJy.")
418 self.
log.
warn(
"See RFC-575 for more details.")
427 if not expandedCat.isContiguous():
428 expandedCat = refCat.copy(deep=
True)
430 fluxField =
getRefFluxField(schema=expandedCat.schema, filterName=filterName)
431 return pipeBase.Struct(refCat=expandedCat, fluxField=fluxField)
434 """Load reference objects that lie within a circular region on the sky
436 This method constructs a circular region from an input center and angular radius,
437 loads reference catalogs which are contained in or intersect the circle, and
438 filters reference catalogs which intersect down to objects which lie within
443 ctrCoord : `lsst.geom.SpherePoint`
444 Point defining the center of the circular region
445 radius : `lsst.geom.Angle`
446 Defines the angular radius of the circular region
448 Name of camera filter, or None or blank for the default filter
449 epoch : `astropy.time.Time` (optional)
450 Epoch to which to correct proper motion and parallax,
451 or None to not apply such corrections.
455 referenceCatalog : `lsst.afw.table.SourceCatalog`
456 Catalog containing reference objects inside the specified bounding box
460 `lsst.pex.exception.RuntimeError`
461 Raised if no reference catalogs could be found for the specified region
463 `lsst.pex.exception.TypeError`
464 Raised if the loaded reference catalogs do not have matching schemas
467 centerVector = ctrCoord.getVector()
470 return self.
loadRegion(circularRegion, filterName=filterName, epoch=
None)
473 """Relink an unpersisted match list to sources and reference
476 A match list is persisted and unpersisted as a catalog of IDs
477 produced by afw.table.packMatches(), with match metadata
478 (as returned by the astrometry tasks) in the catalog's metadata
479 attribute. This method converts such a match catalog into a match
480 list, with links to source records and reference object records.
484 matchCat : `lsst.afw.table.BaseCatalog`
485 Unpersisted packed match list.
486 ``matchCat.table.getMetadata()`` must contain match metadata,
487 as returned by the astrometry tasks.
488 sourceCat : `lsst.afw.table.SourceCatalog`
489 Source catalog. As a side effect, the catalog will be sorted
494 matchList : `lsst.afw.table.ReferenceMatchVector`
500 def getMetadataBox(cls, bbox, wcs, filterName=None, photoCalib=None, epoch=None, bboxPadding=100):
501 """Return metadata about the load
503 This metadata is used for reloading the catalog (e.g., for
504 reconstituting a normalised match list.)
508 bbox : `lsst.geom.Box2I`
509 Bounding bos for the pixels
510 wcs : `lsst.afw.geom.SkyWcs
512 filterName : `str` or None
513 filterName of the camera filter, or None or blank for the default filter
515 Deprecated, only included for api compatibility
516 epoch : `astropy.time.Time` (optional)
517 Epoch to which to correct proper motion and parallax,
518 or None to not apply such corrections.
520 Number describing how much to pad the input bbox by (in pixels), defaults
521 to 100. This parameter is necessary because optical distortions in telescopes
522 can cause a rectangular pixel grid to map into a non "rectangular" spherical
523 region in sky coordinates. This padding is used to create a spherical
524 "rectangle", which will for sure enclose the input box. This padding is only
525 used to determine if the reference catalog for a sky patch will be loaded from
526 the data store, this function will filter out objects which lie within the
527 padded region but fall outside the input bounding box region.
530 md : `lsst.daf.base.PropertyList`
532 _, _, innerCorners, outerCorners = cls.
_makeBoxRegion(bbox, wcs, bboxPadding)
534 for box, corners
in zip((
"INNER",
"OUTER"), (innerCorners, outerCorners)):
535 for (name, corner)
in zip((
"UPPER_LEFT",
"UPPER_RIGHT",
"LOWER_LEFT",
"LOWER_RIGHT"),
537 md.add(f
"{box}_{name}_RA",
geom.SpherePoint(corner).getRa().asDegrees(), f
"{box}_corner")
538 md.add(f
"{box}_{name}_DEC",
geom.SpherePoint(corner).getDec().asDegrees(), f
"{box}_corner")
539 md.add(
"SMATCHV", 1,
'SourceMatchVector version number')
540 filterName =
"UNKNOWN" if filterName
is None else str(filterName)
541 md.add(
'FILTER', filterName,
'filter name for photometric data')
544 md.add(
'EPOCH',
"NONE" if epoch
is None else str(epoch),
'Epoch (TAI MJD) for catalog')
549 """Return metadata about the load
551 This metadata is used for reloading the catalog (e.g. for reconstituting
552 a normalized match list.)
556 coord : `lsst.geom.SpherePoint`
557 ICRS center of a circle
558 radius : `lsst.geom.angle`
560 filterName : `str` or None
561 filterName of the camera filter, or None or blank for the default filter
563 Deprecated, only included for api compatibility
564 epoch : `astropy.time.Time` (optional)
565 Epoch to which to correct proper motion and parallax,
566 or None to not apply such corrections.
570 md : `lsst.daf.base.PropertyList`
573 md.add(
'RA', coord.getRa().asDegrees(),
'field center in degrees')
574 md.add(
'DEC', coord.getDec().asDegrees(),
'field center in degrees')
575 md.add(
'RADIUS', radius.asDegrees(),
'field radius in degrees, minimum')
576 md.add(
'SMATCHV', 1,
'SourceMatchVector version number')
577 filterName =
"UNKNOWN" if filterName
is None else str(filterName)
578 md.add(
'FILTER', filterName,
'filter name for photometric data')
579 md.add(
'EPOCH',
"NONE" if epoch
is None else epoch,
'Epoch (TAI MJD) for catalog')
584 """This function creates a new catalog containing the information of the input refCat
585 as well as added flux columns and aliases between camera and reference flux.
589 refCat : `lsst.afw.table.SimpleCatalog`
590 Catalog of reference objects
591 defaultFilter : `str`
592 Name of the default reference filter
593 filterReferenceMap : `dict` of `str`
594 Dictionary with keys corresponding to a filter name, and values which
595 correspond to the name of the reference filter.
599 refCat : `lsst.afw.table.SimpleCatalog`
600 Reference catalog with columns added to track reference filters
605 If specified reference filter name is not a filter specifed as a key in the
606 reference filter map.
608 refCat = ReferenceObjectLoader.remapReferenceCatalogSchema(refCat,
609 filterNameList=filterReferenceMap.keys())
610 aliasMap = refCat.schema.getAliasMap()
611 if filterReferenceMap
is None:
612 filterReferenceMap = {}
613 for filterName, refFilterName
in itertools.chain([(
None, defaultFilter)],
614 filterReferenceMap.items()):
616 camFluxName = filterName +
"_camFlux" if filterName
is not None else "camFlux"
617 refFluxName = refFilterName +
"_flux"
618 if refFluxName
not in refCat.schema:
619 raise RuntimeError(
"Unknown reference filter %s" % (refFluxName,))
620 aliasMap.set(camFluxName, refFluxName)
622 refFluxErrName = refFluxName +
"Err"
623 camFluxErrName = camFluxName +
"Err"
624 aliasMap.set(camFluxErrName, refFluxErrName)
630 """This function takes in a reference catalog and creates a new catalog with additional
631 columns defined the remaining function arguments.
635 refCat : `lsst.afw.table.SimpleCatalog`
636 Reference catalog to map to new catalog
640 expandedCat : `lsst.afw.table.SimpleCatalog`
641 Deep copy of input reference catalog with additional columns added
644 mapper.addMinimalSchema(refCat.schema,
True)
645 mapper.editOutputSchema().disconnectAliases()
647 for filterName
in filterNameList:
648 mapper.editOutputSchema().addField(f
"{filterName}_flux",
650 doc=f
"flux in filter {filterName}",
653 mapper.editOutputSchema().addField(f
"{filterName}_fluxErr",
655 doc=f
"flux uncertanty in filter {filterName}",
660 mapper.editOutputSchema().addField(
"centroid_x", type=float, doReplace=
True)
661 mapper.editOutputSchema().addField(
"centroid_y", type=float, doReplace=
True)
662 mapper.editOutputSchema().addField(
"hasCentroid", type=
"Flag", doReplace=
True)
663 mapper.editOutputSchema().getAliasMap().
set(
"slot_Centroid",
"centroid")
666 mapper.editOutputSchema().addField(
"photometric",
668 doc=
"set if the object can be used for photometric"
671 mapper.editOutputSchema().addField(
"resolved",
673 doc=
"set if the object is spatially resolved"
675 mapper.editOutputSchema().addField(
"variable",
677 doc=
"set if the object has variable brightness"
681 expandedCat.setMetadata(refCat.getMetadata())
682 expandedCat.extend(refCat, mapper=mapper)
688 """Get the name of a flux field from a schema.
690 if filterName is specified:
691 return *filterName*_camFlux if present
692 else return *filterName*_flux if present (camera filter name matches reference filter name)
693 else throw RuntimeError
695 return camFlux, if present,
696 else throw RuntimeError
700 schema : `lsst.afw.table.Schema`
701 Reference catalog schema.
703 Name of camera filter.
707 fluxFieldName : `str`
713 If an appropriate field is not found.
716 raise RuntimeError(
"schema=%s is not a schema" % (schema,))
718 fluxFieldList = [filterName +
"_camFlux", filterName +
"_flux"]
720 fluxFieldList = [
"camFlux"]
721 for fluxField
in fluxFieldList:
722 if fluxField
in schema:
725 raise RuntimeError(
"Could not find flux field(s) %s" % (
", ".join(fluxFieldList)))
729 """Return keys for flux and flux error.
733 schema : `lsst.afw.table.Schema`
734 Reference catalog schema.
736 Name of camera filter.
740 keys : `tuple` of (`lsst.afw.table.Key`, `lsst.afw.table.Key`)
744 - flux error key, if present, else None
749 If flux field not found.
752 fluxErrField = fluxField +
"Err"
753 fluxKey = schema[fluxField].asKey()
755 fluxErrKey = schema[fluxErrField].asKey()
758 return (fluxKey, fluxErrKey)
762 pixelMargin = pexConfig.RangeField(
763 doc=
"Padding to add to 4 all edges of the bounding box (pixels)",
768 defaultFilter = pexConfig.Field(
769 doc=
"Default reference catalog filter to use if filter not specified in exposure; "
770 "if blank then filter must be specified in exposure",
774 filterMap = pexConfig.DictField(
775 doc=
"Mapping of camera filter name: reference catalog filter name; "
776 "each reference filter must exist",
781 requireProperMotion = pexConfig.Field(
782 doc=
"Require that the fields needed to correct proper motion "
783 "(epoch, pm_ra and pm_dec) are present?",
798 r"""!Abstract base class to load objects from reference catalogs
800 @anchor LoadReferenceObjectsTask_
802 @section meas_algorithms_loadReferenceObjects_Contents Contents
804 - @ref meas_algorithms_loadReferenceObjects_Purpose
805 - @ref meas_algorithms_loadReferenceObjects_Initialize
806 - @ref meas_algorithms_loadReferenceObjects_IO
807 - @ref meas_algorithms_loadReferenceObjects_Schema
808 - @ref meas_algorithms_loadReferenceObjects_Config
810 @section meas_algorithms_loadReferenceObjects_Purpose Description
812 Abstract base class for tasks that load objects from a reference catalog
813 in a particular region of the sky.
815 Implementations must subclass this class, override the loadSkyCircle method,
816 and will typically override the value of ConfigClass with a task-specific config class.
818 @section meas_algorithms_loadReferenceObjects_Initialize Task initialisation
820 @copydoc \_\_init\_\_
822 @section meas_algorithms_loadReferenceObjects_IO Invoking the Task
824 @copydoc loadPixelBox
826 @section meas_algorithms_loadReferenceObjects_Schema Schema of the reference object catalog
828 Reference object catalogs are instances of lsst.afw.table.SimpleCatalog with the following schema
829 (other fields may also be present).
830 The units use astropy quantity conventions, so a 2 suffix means squared.
831 See also makeMinimalSchema.
832 - coord: ICRS position of star on sky (an lsst.geom.SpherePoint)
833 - centroid: position of star on an exposure, if relevant (an lsst.afw.Point2D)
834 - hasCentroid: is centroid usable? (a Flag)
835 - *referenceFilterName*_flux: brightness in the specified reference catalog filter (nJy)
836 Note: you can use astropy.units to convert from AB Magnitude to nJy:
837 `u.Magnitude(value, u.ABmag).to_value(u.nJy)`
838 - *referenceFilterName*_fluxErr (optional): brightness standard deviation (nJy);
839 omitted if no data is available; possibly nan if data is available for some objects but not others
840 - camFlux: brightness in default camera filter (nJy); omitted if defaultFilter not specified
841 - camFluxErr: brightness standard deviation for default camera filter;
842 omitted if defaultFilter not specified or standard deviation not available that filter
843 - *cameraFilterName*_camFlux: brightness in specified camera filter (nJy)
844 - *cameraFilterName*_camFluxErr (optional): brightness standard deviation
845 in specified camera filter (nJy); omitted if no data is available;
846 possibly nan if data is available for some objects but not others
847 - photometric (optional): is the object usable for photometric calibration? (a Flag)
848 - resolved (optional): is the object spatially resolved? (a Flag)
849 - variable (optional): does the object have variable brightness? (a Flag)
850 - coord_raErr: uncertainty in `coord` along the direction of right ascension (radian, an Angle)
851 = uncertainty in ra * cos(dec); nan if unknown
852 - coord_decErr: uncertainty in `coord` along the direction of declination (radian, an Angle);
855 The following are optional; fields should only be present if the
856 information is available for at least some objects.
857 Numeric values are `nan` if unknown:
858 - epoch: date of observation as TAI MJD (day)
860 - pm_ra: proper motion along the direction of right ascension (rad/year, an Angle) = dra/dt * cos(dec)
861 - pm_dec: proper motion along the direction of declination (rad/year, and Angle)
862 - pm_raErr: uncertainty in `pm_ra` (rad/year)
863 - pm_decErr: uncertainty in `pm_dec` (rad/year)
864 - pm_ra_dec_Cov: covariance between pm_ra and pm_dec (rad2/year2)
865 - pm_flag: set if proper motion, error or covariance is bad
867 - parallax: parallax (rad, an Angle)
868 - parallaxErr: uncertainty in `parallax` (rad)
869 - parallax_flag: set if parallax value or parallaxErr is bad
871 - coord_ra_pm_ra_Cov: covariance between coord_ra and pm_ra (rad2/year)
872 - coord_ra_pm_dec_Cov: covariance between coord_ra and pm_dec (rad2/year)
873 - coord_ra_parallax_Cov: covariance between coord_ra and parallax (rad2/year)
874 - coord_dec_pm_ra_Cov: covariance between coord_dec and pm_ra (rad2/year)
875 - coord_dec_pm_dec_Cov: covariance between coord_dec and pm_dec (rad2/year)
876 - coord_dec_parallax_Cov: covariance between coord_dec and parallax (rad2/year)
877 - pm_ra_parallax_Cov: covariance between pm_ra and parallax (rad2/year)
878 - pm_dec_parallax_Cov: covariance between pm_dec and parallax (rad2/year)
880 @section meas_algorithms_loadReferenceObjects_Config Configuration parameters
882 See @ref LoadReferenceObjectsConfig for a base set of configuration parameters.
883 Most subclasses will add configuration variables.
885 ConfigClass = LoadReferenceObjectsConfig
886 _DefaultName =
"LoadReferenceObjects"
889 """Construct a LoadReferenceObjectsTask
893 butler : `lsst.daf.persistence.Butler`
894 Data butler, for access reference catalogs.
896 pipeBase.Task.__init__(self, *args, **kwargs)
900 def loadPixelBox(self, bbox, wcs, filterName=None, photoCalib=None, epoch=None):
901 """Load reference objects that overlap a rectangular pixel region.
905 bbox : `lsst.geom.Box2I` or `lsst.geom.Box2D`
906 Bounding box for pixels.
907 wcs : `lsst.afw.geom.SkyWcs`
908 WCS; used to convert pixel positions to sky coordinates
911 Name of filter, or `None` or `""` for the default filter.
912 This is used for flux values in case we have flux limits
913 (which are not yet implemented).
914 photoCalib : `lsst.afw.image.PhotoCalib` (optional)
915 Calibration, or `None` if unknown.
916 epoch : `astropy.time.Time` (optional)
917 Epoch to which to correct proper motion and parallax,
918 or None to not apply such corrections.
922 results : `lsst.pipe.base.Struct`
923 A Struct containing the following fields:
924 refCat : `lsst.afw.catalog.SimpleCatalog`
925 A catalog of reference objects with the standard
926 schema, as documented in the main doc string for
927 `LoadReferenceObjects`.
928 The catalog is guaranteed to be contiguous.
930 Name of flux field for specified `filterName`.
934 The search algorithm works by searching in a region in sky
935 coordinates whose center is the center of the bbox and radius
936 is large enough to just include all 4 corners of the bbox.
937 Stars that lie outside the bbox are then trimmed from the list.
942 self.log.
info(
"Loading reference objects using center %s and radius %s deg" %
943 (circle.coord, circle.radius.asDegrees()))
944 loadRes = self.
loadSkyCircle(circle.coord, circle.radius, filterName, centroids=
True)
945 refCat = loadRes.refCat
946 numFound = len(refCat)
949 refCat = self.
_trimToBBox(refCat=refCat, bbox=circle.bbox, wcs=wcs)
950 numTrimmed = numFound - len(refCat)
951 self.log.
debug(
"trimmed %d out-of-bbox objects, leaving %d", numTrimmed, len(refCat))
952 self.log.
info(
"Loaded %d reference objects", len(refCat))
955 if not refCat.isContiguous():
956 loadRes.refCat = refCat.copy(deep=
True)
961 def loadSkyCircle(self, ctrCoord, radius, filterName=None, epoch=None, centroids=False):
962 """Load reference objects that overlap a circular sky region.
966 ctrCoord : `lsst.geom.SpherePoint`
967 ICRS center of search region.
968 radius : `lsst.geom.Angle`
969 Radius of search region.
970 filterName : `str` (optional)
971 Name of filter, or `None` or `""` for the default filter.
972 This is used for flux values in case we have flux limits
973 (which are not yet implemented).
974 epoch : `astropy.time.Time` (optional)
975 Epoch to which to correct proper motion and parallax,
976 or None to not apply such corrections.
977 centroids : `bool` (optional)
978 Add centroid fields to the loaded Schema. ``loadPixelBox`` expects
979 these fields to exist.
983 results : `lsst.pipe.base.Struct`
984 A Struct containing the following fields:
985 refCat : `lsst.afw.catalog.SimpleCatalog`
986 A catalog of reference objects with the standard
987 schema, as documented in the main doc string for
988 `LoadReferenceObjects`.
989 The catalog is guaranteed to be contiguous.
991 Name of flux field for specified `filterName`.
995 Note that subclasses are responsible for performing the proper motion
996 correction, since this is the lowest-level interface for retrieving
1002 def _trimToBBox(refCat, bbox, wcs):
1003 """Remove objects outside a given pixel bounding box and set
1004 centroid and hasCentroid fields.
1008 refCat : `lsst.afw.table.SimpleCatalog`
1009 A catalog of objects. The schema must include fields
1010 "coord", "centroid" and "hasCentroid".
1011 The "coord" field is read.
1012 The "centroid" and "hasCentroid" fields are set.
1013 bbox : `lsst.geom.Box2D`
1015 wcs : `lsst.afw.geom.SkyWcs`
1016 WCS; used to convert sky coordinates to pixel positions.
1018 @return a catalog of reference objects in bbox, with centroid and hasCentroid fields set
1022 retStarCat =
type(refCat)(refCat.table)
1024 point = star.get(centroidKey)
1025 if bbox.contains(point):
1026 retStarCat.append(star)
1029 def _addFluxAliases(self, schema):
1030 """Add aliases for camera filter fluxes to the schema.
1032 If self.config.defaultFilter then adds these aliases:
1033 camFlux: <defaultFilter>_flux
1034 camFluxErr: <defaultFilter>_fluxErr, if the latter exists
1036 For each camFilter: refFilter in self.config.filterMap adds these aliases:
1037 <camFilter>_camFlux: <refFilter>_flux
1038 <camFilter>_camFluxErr: <refFilter>_fluxErr, if the latter exists
1042 schema : `lsst.afw.table.Schema`
1043 Schema for reference catalog.
1048 If any reference flux field is missing from the schema.
1050 aliasMap = schema.getAliasMap()
1052 def addAliasesForOneFilter(filterName, refFilterName):
1053 """Add aliases for a single filter
1057 filterName : `str` (optional)
1058 Camera filter name. The resulting alias name is
1059 <filterName>_camFlux, or simply "camFlux" if `filterName`
1061 refFilterName : `str`
1062 Reference catalog filter name; the field
1063 <refFilterName>_flux must exist.
1065 camFluxName = filterName +
"_camFlux" if filterName
is not None else "camFlux"
1066 refFluxName = refFilterName +
"_flux"
1067 if refFluxName
not in schema:
1068 raise RuntimeError(
"Unknown reference filter %s" % (refFluxName,))
1069 aliasMap.set(camFluxName, refFluxName)
1070 refFluxErrName = refFluxName +
"Err"
1071 if refFluxErrName
in schema:
1072 camFluxErrName = camFluxName +
"Err"
1073 aliasMap.set(camFluxErrName, refFluxErrName)
1075 if self.config.defaultFilter:
1076 addAliasesForOneFilter(
None, self.config.defaultFilter)
1078 for filterName, refFilterName
in self.config.filterMap.items():
1079 addAliasesForOneFilter(filterName, refFilterName)
1083 addIsPhotometric=False, addIsResolved=False,
1084 addIsVariable=False, coordErrDim=2,
1085 addProperMotion=False, properMotionErrDim=2,
1087 """Make a standard schema for reference object catalogs.
1091 filterNameList : `list` of `str`
1092 List of filter names. Used to create <filterName>_flux fields.
1093 addIsPhotometric : `bool`
1094 If True then add field "photometric".
1095 addIsResolved : `bool`
1096 If True then add field "resolved".
1097 addIsVariable : `bool`
1098 If True then add field "variable".
1100 Number of coord error fields; must be one of 0, 2, 3:
1102 - If 2 or 3: add fields "coord_raErr" and "coord_decErr".
1103 - If 3: also add field "coord_radecErr".
1104 addProperMotion : `bool`
1105 If True add fields "epoch", "pm_ra", "pm_dec" and "pm_flag".
1106 properMotionErrDim : `int`
1107 Number of proper motion error fields; must be one of 0, 2, 3;
1108 ignored if addProperMotion false:
1109 - If 2 or 3: add fields "pm_raErr" and "pm_decErr".
1110 - If 3: also add field "pm_radecErr".
1111 addParallax : `bool`
1112 If True add fields "epoch", "parallax", "parallaxErr"
1113 and "parallax_flag".
1117 schema : `lsst.afw.table.Schema`
1118 Schema for reference catalog, an
1119 `lsst.afw.table.SimpleCatalog`.
1123 Reference catalogs support additional covariances, such as
1124 covariance between RA and proper motion in declination,
1125 that are not supported by this method, but can be added after
1126 calling this method.
1128 schema = afwTable.SimpleTable.makeMinimalSchema()
1130 afwTable.Point2DKey.addFields(
1133 "centroid on an exposure, if relevant",
1137 field=
"hasCentroid",
1139 doc=
"is position known?",
1141 for filterName
in filterNameList:
1143 field=
"%s_flux" % (filterName,),
1145 doc=
"flux in filter %s" % (filterName,),
1148 for filterName
in filterNameList:
1150 field=
"%s_fluxErr" % (filterName,),
1152 doc=
"flux uncertainty in filter %s" % (filterName,),
1155 if addIsPhotometric:
1157 field=
"photometric",
1159 doc=
"set if the object can be used for photometric calibration",
1165 doc=
"set if the object is spatially resolved",
1171 doc=
"set if the object has variable brightness",
1173 if coordErrDim
not in (0, 2, 3):
1174 raise ValueError(
"coordErrDim={}; must be (0, 2, 3)".
format(coordErrDim))
1176 afwTable.CovarianceMatrix2fKey.addFields(
1179 names=[
"ra",
"dec"],
1180 units=[
"rad",
"rad"],
1181 diagonalOnly=(coordErrDim == 2),
1184 if addProperMotion
or addParallax:
1188 doc=
"date of observation (TAI, MJD)",
1196 doc=
"proper motion in the right ascension direction = dra/dt * cos(dec)",
1202 doc=
"proper motion in the declination direction",
1205 if properMotionErrDim
not in (0, 2, 3):
1206 raise ValueError(
"properMotionErrDim={}; must be (0, 2, 3)".
format(properMotionErrDim))
1207 if properMotionErrDim > 0:
1208 afwTable.CovarianceMatrix2fKey.addFields(
1211 names=[
"ra",
"dec"],
1212 units=[
"rad/year",
"rad/year"],
1213 diagonalOnly=(properMotionErrDim == 2),
1218 doc=
"Set if proper motion or proper motion error is bad",
1229 field=
"parallaxErr",
1231 doc=
"uncertainty in parallax",
1235 field=
"parallax_flag",
1237 doc=
"Set if parallax or parallax error is bad",
1241 def _calculateCircle(self, bbox, wcs):
1242 """Compute on-sky center and radius of search region.
1246 bbox : `lsst.geom.Box2I` or `lsst.geom.Box2D`
1248 wcs : `lsst.afw.geom.SkyWcs`
1249 WCS; used to convert pixel positions to sky coordinates.
1253 results : `lsst.pipe.base.Struct`
1254 A Struct containing:
1256 - coord : `lsst.geom.SpherePoint`
1257 ICRS center of the search region.
1258 - radius : `lsst.geom.Angle`
1259 Radius of the search region.
1260 - bbox : `lsst.geom.Box2D`
1261 Bounding box used to compute the circle.
1264 bbox.grow(self.config.pixelMargin)
1265 coord = wcs.pixelToSky(bbox.getCenter())
1266 radius =
max(coord.separation(wcs.pixelToSky(pp))
for pp
in bbox.getCorners())
1267 return pipeBase.Struct(coord=coord, radius=radius, bbox=bbox)
1270 """Return metadata about the load.
1272 This metadata is used for reloading the catalog (e.g., for
1273 reconstituting a normalised match list.
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.
1282 Name of camera filter, or `None` or `""` for the default
1284 photoCalib : `lsst.afw.image.PhotoCalib` (optional)
1285 Calibration, or `None` if unknown.
1286 epoch : `astropy.time.Time` (optional)
1287 Epoch to which to correct proper motion and parallax,
1288 or None to not apply such corrections.
1292 metadata : lsst.daf.base.PropertyList
1293 Metadata about the load.
1296 return self.
getMetadataCircle(circle.coord, circle.radius, filterName, photoCalib)
1299 """Return metadata about the load.
1301 This metadata is used for reloading the catalog (e.g., for
1302 reconstituting a normalised match list.
1306 coord : `lsst.geom.SpherePoint`
1307 ICRS center of the search region.
1308 radius : `lsst.geom.Angle`
1309 Radius of the search region.
1311 Name of camera filter, or `None` or `""` for the default
1313 photoCalib : `lsst.afw.image.PhotoCalib` (optional)
1314 Calibration, or `None` if unknown.
1315 epoch : `astropy.time.Time` (optional)
1316 Epoch to which to correct proper motion and parallax,
1317 or None to not apply such corrections.
1321 metadata : lsst.daf.base.PropertyList
1322 Metadata about the load
1325 md.add(
'RA', coord.getRa().asDegrees(),
'field center in degrees')
1326 md.add(
'DEC', coord.getDec().asDegrees(),
'field center in degrees')
1327 md.add(
'RADIUS', radius.asDegrees(),
'field radius in degrees, minimum')
1328 md.add(
'SMATCHV', 1,
'SourceMatchVector version number')
1329 filterName =
"UNKNOWN" if filterName
is None else str(filterName)
1330 md.add(
'FILTER', filterName,
'filter name for photometric data')
1331 md.add(
'EPOCH',
"NONE" if epoch
is None else epoch,
'Epoch (TAI MJD) for catalog')
1335 """Relink an unpersisted match list to sources and reference
1338 A match list is persisted and unpersisted as a catalog of IDs
1339 produced by afw.table.packMatches(), with match metadata
1340 (as returned by the astrometry tasks) in the catalog's metadata
1341 attribute. This method converts such a match catalog into a match
1342 list, with links to source records and reference object records.
1346 matchCat : `lsst.afw.table.BaseCatalog`
1347 Unperisted packed match list.
1348 ``matchCat.table.getMetadata()`` must contain match metadata,
1349 as returned by the astrometry tasks.
1350 sourceCat : `lsst.afw.table.SourceCatalog`
1351 Source catalog. As a side effect, the catalog will be sorted
1356 matchList : `lsst.afw.table.ReferenceMatchVector`
1362 """Apply proper motion correction to a reference catalog.
1364 Adjust position and position error in the ``catalog``
1365 for proper motion to the specified ``epoch``,
1366 modifying the catalong in place.
1370 catalog : `lsst.afw.table.SimpleCatalog`
1371 Catalog of positions, containing:
1373 - Coordinates, retrieved by the table's coordinate key.
1374 - ``coord_raErr`` : Error in Right Ascension (rad).
1375 - ``coord_decErr`` : Error in Declination (rad).
1376 - ``pm_ra`` : Proper motion in Right Ascension (rad/yr,
1378 - ``pm_raErr`` : Error in ``pm_ra`` (rad/yr), optional.
1379 - ``pm_dec`` : Proper motion in Declination (rad/yr,
1381 - ``pm_decErr`` : Error in ``pm_dec`` (rad/yr), optional.
1382 - ``epoch`` : Mean epoch of object (an astropy.time.Time)
1383 epoch : `astropy.time.Time` (optional)
1384 Epoch to which to correct proper motion and parallax,
1385 or None to not apply such corrections.
1387 if (
"epoch" not in catalog.schema
or "pm_ra" not in catalog.schema
or "pm_dec" not in catalog.schema):
1388 if self.config.requireProperMotion:
1389 raise RuntimeError(
"Proper motion correction required but not available from catalog")
1390 self.log.
warn(
"Proper motion correction not available from catalog")
1396 """Relink an unpersisted match list to sources and reference
1399 A match list is persisted and unpersisted as a catalog of IDs
1400 produced by afw.table.packMatches(), with match metadata
1401 (as returned by the astrometry tasks) in the catalog's metadata
1402 attribute. This method converts such a match catalog into a match
1403 list, with links to source records and reference object records.
1408 Reference object loader to use in getting reference objects
1409 matchCat : `lsst.afw.table.BaseCatalog`
1410 Unperisted packed match list.
1411 ``matchCat.table.getMetadata()`` must contain match metadata,
1412 as returned by the astrometry tasks.
1413 sourceCat : `lsst.afw.table.SourceCatalog`
1414 Source catalog. As a side effect, the catalog will be sorted
1419 matchList : `lsst.afw.table.ReferenceMatchVector`
1422 matchmeta = matchCat.table.getMetadata()
1423 version = matchmeta.getInt(
'SMATCHV')
1425 raise ValueError(
'SourceMatchVector version number is %i, not 1.' % version)
1426 filterName = matchmeta.getString(
'FILTER').
strip()
1428 epoch = matchmeta.getDouble(
'EPOCH')
1431 if 'RADIUS' in matchmeta:
1434 matchmeta.getDouble(
'DEC'), lsst.geom.degrees)
1435 rad = matchmeta.getDouble(
'RADIUS') * lsst.geom.degrees
1436 refCat = refObjLoader.loadSkyCircle(ctrCoord, rad, filterName, epoch=epoch).refCat
1437 elif "INNER_UPPER_LEFT_RA" in matchmeta:
1443 for place
in (
"UPPER_LEFT",
"UPPER_RIGHT",
"LOWER_LEFT",
"LOWER_RIGHT"):
1445 matchmeta.getDouble(f
"OUTER_{place}_DEC"),
1446 lsst.geom.degrees).getVector()
1449 refCat = refObjLoader.loadRegion(outerBox, filterName=filterName, epoch=epoch).refCat
1457 """Apply proper motion correction to a reference catalog.
1459 Adjust position and position error in the ``catalog``
1460 for proper motion to the specified ``epoch``,
1461 modifying the catalong in place.
1465 log : `lsst.log.log`
1466 log object to write to
1467 catalog : `lsst.afw.table.SimpleCatalog`
1468 Catalog of positions, containing:
1470 - Coordinates, retrieved by the table's coordinate key.
1471 - ``coord_raErr`` : Error in Right Ascension (rad).
1472 - ``coord_decErr`` : Error in Declination (rad).
1473 - ``pm_ra`` : Proper motion in Right Ascension (rad/yr,
1475 - ``pm_raErr`` : Error in ``pm_ra`` (rad/yr), optional.
1476 - ``pm_dec`` : Proper motion in Declination (rad/yr,
1478 - ``pm_decErr`` : Error in ``pm_dec`` (rad/yr), optional.
1479 - ``epoch`` : Mean epoch of object (an astropy.time.Time)
1480 epoch : `astropy.time.Time` (optional)
1481 Epoch to which to correct proper motion and parallax,
1482 or None to not apply such corrections.
1484 if "epoch" not in catalog.schema
or "pm_ra" not in catalog.schema
or "pm_dec" not in catalog.schema:
1485 log.warn(
"Proper motion correction not available from catalog")
1487 if not catalog.isContiguous():
1488 raise RuntimeError(
"Catalog must be contiguous")
1489 catEpoch = astropy.time.Time(catalog[
"epoch"], scale=
"tai", format=
"mjd")
1490 log.debug(
"Correcting reference catalog for proper motion to %r", epoch)
1492 timeDiffsYears = (epoch.tai - catEpoch).
to(astropy.units.yr).value
1493 coordKey = catalog.table.getCoordKey()
1496 pmRaRad = catalog[
"pm_ra"]
1497 pmDecRad = catalog[
"pm_dec"]
1498 offsetsRaRad = pmRaRad*timeDiffsYears
1499 offsetsDecRad = pmDecRad*timeDiffsYears
1507 offsetBearingsRad = numpy.arctan2(pmDecRad*1e6, pmRaRad*1e6)
1508 offsetAmountsRad = numpy.hypot(offsetsRaRad, offsetsDecRad)
1509 for record, bearingRad, amountRad
in zip(catalog, offsetBearingsRad, offsetAmountsRad):
1510 record.set(coordKey,
1511 record.get(coordKey).offset(bearing=bearingRad*lsst.geom.radians,
1512 amount=amountRad*lsst.geom.radians))
1514 if "coord_raErr" in catalog.schema:
1515 catalog[
"coord_raErr"] = numpy.hypot(catalog[
"coord_raErr"],
1516 catalog[
"pm_raErr"]*timeDiffsYears)
1517 if "coord_decErr" in catalog.schema:
1518 catalog[
"coord_decErr"] = numpy.hypot(catalog[
"coord_decErr"],
1519 catalog[
"pm_decErr"]*timeDiffsYears)