24__all__ = [
"getRefFluxField",
"getRefFluxKeys",
"LoadReferenceObjectsTask",
"LoadReferenceObjectsConfig",
25 "ReferenceObjectLoader",
"ReferenceObjectLoaderBase"]
30from deprecated.sphinx
import deprecated
39import lsst.pipe.base
as pipeBase
40from lsst
import sphgeom
42from lsst.utils.timer
import timeMethod
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
59 """Return True if the units of all flux and fluxErr are correct (nJy).
68 """"Return the format version stored in a reference catalog header.
73 Reference catalog to inspect.
78 Format verison integer. Returns `0` if the catalog has no metadata
79 or the metadata does
not include a
"REFCAT_FORMAT_VERSION" key.
83 deprecation_msg =
"Support for version 0 refcats (pre-nJy fluxes) will be removed after v25."
84 md = refCat.getMetadata()
86 warnings.warn(deprecation_msg)
89 return md.getScalar(
"REFCAT_FORMAT_VERSION")
91 warnings.warn(deprecation_msg)
96@deprecated(reason="Support for version 0 refcats (pre-nJy fluxes) will be removed after v25.
",
97 version="v24.0", category=FutureWarning)
99 """Convert fluxes in a catalog from jansky to nanojansky.
104 The catalog to convert.
106 Log to send messages to.
107 doConvert : `bool`, optional
108 Return a converted catalog,
or just identify the fields that need to be converted?
109 This supports the
"write=False" mode of `bin/convert_to_nJy.py`.
114 The converted catalog,
or None if ``doConvert``
is False.
118 Support
for old units
in reference catalogs will be removed after the
119 release of late calendar year 2019.
120 Use `meas_algorithms/bin/convert_to_nJy.py` to update your reference catalog.
124 mapper = afwTable.SchemaMapper(catalog.schema, shareAliasMap=
False)
125 mapper.addMinimalSchema(afwTable.SimpleTable.makeMinimalSchema())
128 for field
in catalog.schema:
129 oldName = field.field.getName()
130 oldUnits = field.field.getUnits()
134 if oldName.endswith(
'_fluxSigma'):
135 name = oldName.replace(
'_fluxSigma',
'_fluxErr')
138 newField = afwTable.Field[field.dtype](name, field.field.getDoc(), units)
139 mapper.addMapping(field.getKey(), newField)
140 input_fields.append(field.field)
141 output_fields.append(newField)
143 mapper.addMapping(field.getKey())
145 fluxFieldsStr =
'; '.join(
"(%s, '%s')" % (field.getName(), field.getUnits())
for field
in input_fields)
148 newSchema = mapper.getOutputSchema()
149 output = afwTable.SimpleCatalog(newSchema)
150 output.reserve(len(catalog))
151 output.extend(catalog, mapper=mapper)
152 for field
in output_fields:
153 output[field.getName()] *= 1e9
154 log.info(
"Converted refcat flux fields to nJy (name, units): %s", fluxFieldsStr)
157 log.info(
"Found old-style refcat flux fields (name, units): %s", fluxFieldsStr)
162 """This is a private helper class which filters catalogs by
163 row based on the row being inside the region used to initialize
169 The spatial region which all objects should lie within
175 """This call method on an instance of this class takes in a reference
176 catalog, and the region
from which the catalog was generated.
178 If the catalog region
is entirely contained within the region used to
179 initialize this
class, then all the entries
in the catalog must be
180 within the region
and so the whole catalog
is returned.
182 If the catalog region
is not entirely contained, then the location
for
183 each record
is tested against the region used to initialize the
class.
184 Records which fall inside this region are added to a new catalog,
and
185 this catalog
is then returned.
190 SourceCatalog to be filtered.
192 Region
in which the catalog was created
194 if catRegion.isWithin(self.
region):
198 filteredRefCat = type(refCat)(refCat.table)
199 for record
in refCat:
200 if self.
region.contains(record.getCoord().getVector()):
201 filteredRefCat.append(record)
202 return filteredRefCat
206 pixelMargin = pexConfig.RangeField(
207 doc=
"Padding to add to 4 all edges of the bounding box (pixels)",
212 anyFilterMapsToThis = pexConfig.Field(
213 doc=(
"Always use this reference catalog filter, no matter whether or what filter name is "
214 "supplied to the loader. Effectively a trivial filterMap: map all filter names to this filter."
215 " This can be set for purely-astrometric catalogs (e.g. Gaia DR2) where there is only one "
216 "reasonable choice for every camera filter->refcat mapping, but not for refcats used for "
217 "photometry, which need a filterMap and/or colorterms/transmission corrections."),
222 filterMap = pexConfig.DictField(
223 doc=(
"Mapping of camera filter name: reference catalog filter name; "
224 "each reference filter must exist in the refcat."
225 " Note that this does not perform any bandpass corrections: it is just a lookup."),
230 requireProperMotion = pexConfig.Field(
231 doc=
"Require that the fields needed to correct proper motion "
232 "(epoch, pm_ra and pm_dec) are present?",
240 msg =
"`filterMap` and `anyFilterMapsToThis` are mutually exclusive"
241 raise pexConfig.FieldValidationError(LoadReferenceObjectsConfig.anyFilterMapsToThis,
246 """Base class for reference object loaders, to facilitate gen2/gen3 code
251 config : `lsst.pex.config.Config`
252 Configuration for the loader.
254 ConfigClass = LoadReferenceObjectsConfig
260 """Apply proper motion correction to a reference catalog.
262 Adjust position and position error
in the ``catalog``
263 for proper motion to the specified ``epoch``,
264 modifying the catalog
in place.
269 Catalog of positions, containing at least these fields:
271 - Coordinates, retrieved by the table
's coordinate key.
272 - ``coord_raErr`` : Error in Right Ascension (rad).
273 - ``coord_decErr`` : Error
in Declination (rad).
274 - ``pm_ra`` : Proper motion
in Right Ascension (rad/yr,
276 - ``pm_raErr`` : Error
in ``pm_ra`` (rad/yr), optional.
277 - ``pm_dec`` : Proper motion
in Declination (rad/yr,
279 - ``pm_decErr`` : Error
in ``pm_dec`` (rad/yr), optional.
280 - ``epoch`` : Mean epoch of object (an astropy.time.Time)
281 epoch : `astropy.time.Time`
282 Epoch to which to correct proper motion.
283 If
None, do
not apply PM corrections
or raise if
284 ``config.requireProperMotion``
is True.
289 Raised
if ``config.requireProperMotion``
is set but we cannot
290 apply the proper motion correction
for some reason.
293 if self.
config.requireProperMotion:
294 raise RuntimeError(
"requireProperMotion=True but epoch not provided to loader.")
296 self.log.debug(
"No epoch provided: not applying proper motion corrections to refcat.")
300 if (
"pm_ra" in catalog.schema
301 and not isinstance(catalog.schema[
"pm_ra"].asKey(), afwTable.KeyAngle)):
302 if self.
config.requireProperMotion:
303 raise RuntimeError(
"requireProperMotion=True but refcat pm_ra field is not an Angle.")
305 self.log.warning(
"Reference catalog pm_ra field is not an Angle; cannot apply proper motion.")
308 if (
"epoch" not in catalog.schema
or "pm_ra" not in catalog.schema):
309 if self.
config.requireProperMotion:
310 raise RuntimeError(
"requireProperMotion=True but PM data not available from catalog.")
312 self.log.warning(
"Proper motion correction not available for this reference catalog.")
319 addIsPhotometric=False, addIsResolved=False,
320 addIsVariable=False, coordErrDim=2,
321 addProperMotion=False, properMotionErrDim=2,
323 """Make a standard schema for reference object catalogs.
327 filterNameList : `list` of `str`
328 List of filter names. Used to create <filterName>_flux fields.
329 addIsPhotometric : `bool`
330 If True then add field
"photometric".
331 addIsResolved : `bool`
332 If
True then add field
"resolved".
333 addIsVariable : `bool`
334 If
True then add field
"variable".
336 Number of coord error fields; must be one of 0, 2, 3:
338 - If 2
or 3: add fields
"coord_raErr" and "coord_decErr".
339 - If 3: also add field
"coord_radecErr".
340 addProperMotion : `bool`
341 If
True add fields
"epoch",
"pm_ra",
"pm_dec" and "pm_flag".
342 properMotionErrDim : `int`
343 Number of proper motion error fields; must be one of 0, 2, 3;
344 ignored
if addProperMotion false:
345 - If 2
or 3: add fields
"pm_raErr" and "pm_decErr".
346 - If 3: also add field
"pm_radecErr".
348 If
True add fields
"epoch",
"parallax",
"parallaxErr"
354 Schema
for reference catalog, an
359 Reference catalogs support additional covariances, such
as
360 covariance between RA
and proper motion
in declination,
361 that are
not supported by this method, but can be added after
364 schema = afwTable.SimpleTable.makeMinimalSchema()
366 afwTable.Point2DKey.addFields(
369 "centroid on an exposure, if relevant",
375 doc=
"is position known?",
377 for filterName
in filterNameList:
379 field=
"%s_flux" % (filterName,),
381 doc=
"flux in filter %s" % (filterName,),
384 for filterName
in filterNameList:
386 field=
"%s_fluxErr" % (filterName,),
388 doc=
"flux uncertainty in filter %s" % (filterName,),
395 doc=
"set if the object can be used for photometric calibration",
401 doc=
"set if the object is spatially resolved",
407 doc=
"set if the object has variable brightness",
409 if coordErrDim
not in (0, 2, 3):
410 raise ValueError(
"coordErrDim={}; must be (0, 2, 3)".format(coordErrDim))
412 afwTable.CovarianceMatrix2fKey.addFields(
416 units=[
"rad",
"rad"],
417 diagonalOnly=(coordErrDim == 2),
420 if addProperMotion
or addParallax:
424 doc=
"date of observation (TAI, MJD)",
432 doc=
"proper motion in the right ascension direction = dra/dt * cos(dec)",
438 doc=
"proper motion in the declination direction",
441 if properMotionErrDim
not in (0, 2, 3):
442 raise ValueError(
"properMotionErrDim={}; must be (0, 2, 3)".format(properMotionErrDim))
443 if properMotionErrDim > 0:
444 afwTable.CovarianceMatrix2fKey.addFields(
448 units=[
"rad/year",
"rad/year"],
449 diagonalOnly=(properMotionErrDim == 2),
454 doc=
"Set if proper motion or proper motion error is bad",
467 doc=
"uncertainty in parallax",
471 field=
"parallax_flag",
473 doc=
"Set if parallax or parallax error is bad",
478 def _remapReferenceCatalogSchema(refCat, *, anyFilterMapsToThis=None,
479 filterMap=None, centroids=False):
480 """This function takes in a reference catalog and returns a new catalog
481 with additional columns defined
from the remaining function arguments.
486 Reference catalog to map to new catalog
487 anyFilterMapsToThis : `str`, optional
488 Always use this reference catalog filter.
489 Mutually exclusive
with `filterMap`
490 filterMap : `dict` [`str`,`str`], optional
491 Mapping of camera filter name: reference catalog filter name.
492 centroids : `bool`, optional
493 Add centroid fields to the loaded Schema. ``loadPixelBox`` expects
494 these fields to exist.
499 Deep copy of input reference catalog
with additional columns added
501 if anyFilterMapsToThis
or filterMap:
502 ReferenceObjectLoaderBase._addFluxAliases(refCat.schema, anyFilterMapsToThis, filterMap)
504 mapper = afwTable.SchemaMapper(refCat.schema,
True)
505 mapper.addMinimalSchema(refCat.schema,
True)
506 mapper.editOutputSchema().disconnectAliases()
513 mapper.editOutputSchema().addField(
"centroid_x", type=float, doReplace=
True)
514 mapper.editOutputSchema().addField(
"centroid_y", type=float, doReplace=
True)
515 mapper.editOutputSchema().addField(
"hasCentroid", type=
"Flag", doReplace=
True)
516 mapper.editOutputSchema().getAliasMap().set(
"slot_Centroid",
"centroid")
518 expandedCat = afwTable.SimpleCatalog(mapper.getOutputSchema())
519 expandedCat.setMetadata(refCat.getMetadata())
520 expandedCat.extend(refCat, mapper=mapper)
525 def _addFluxAliases(schema, anyFilterMapsToThis=None, filterMap=None):
526 """Add aliases for camera filter fluxes to the schema.
528 For each camFilter: refFilter in filterMap, adds these aliases:
529 <camFilter>_camFlux: <refFilter>_flux
530 <camFilter>_camFluxErr: <refFilter>_fluxErr,
if the latter exists
531 or sets `anyFilterMapsToThis`
in the schema.
536 Schema
for reference catalog.
537 anyFilterMapsToThis : `str`, optional
538 Always use this reference catalog filter.
539 Mutually exclusive
with `filterMap`.
540 filterMap : `dict` [`str`,`str`], optional
541 Mapping of camera filter name: reference catalog filter name.
542 Mutually exclusive
with `anyFilterMapsToThis`.
547 Raised
if any required reference flux field
is missing
from the
551 if anyFilterMapsToThis
and filterMap:
552 raise ValueError(
"anyFilterMapsToThis and filterMap are mutually exclusive!")
554 aliasMap = schema.getAliasMap()
556 if anyFilterMapsToThis
is not None:
557 refFluxName = anyFilterMapsToThis +
"_flux"
558 if refFluxName
not in schema:
559 msg = f
"Unknown reference filter for anyFilterMapsToThis='{refFluxName}'"
560 raise RuntimeError(msg)
561 aliasMap.set(
"anyFilterMapsToThis", refFluxName)
564 def addAliasesForOneFilter(filterName, refFilterName):
565 """Add aliases for a single filter
569 filterName : `str` (optional)
570 Camera filter name. The resulting alias name is
572 refFilterName : `str`
573 Reference catalog filter name; the field
574 <refFilterName>_flux must exist.
576 camFluxName = filterName + "_camFlux"
577 refFluxName = refFilterName +
"_flux"
578 if refFluxName
not in schema:
579 raise RuntimeError(
"Unknown reference filter %s" % (refFluxName,))
580 aliasMap.set(camFluxName, refFluxName)
581 refFluxErrName = refFluxName +
"Err"
582 if refFluxErrName
in schema:
583 camFluxErrName = camFluxName +
"Err"
584 aliasMap.set(camFluxErrName, refFluxErrName)
586 if filterMap
is not None:
587 for filterName, refFilterName
in filterMap.items():
588 addAliasesForOneFilter(filterName, refFilterName)
591 def _makeBoxRegion(BBox, wcs, BBoxPadding):
604 outerLocalBBox.grow(BBoxPadding)
605 innerLocalBBox.grow(-1*BBoxPadding)
617 innerBoxCorners = innerLocalBBox.getCorners()
618 innerSphCorners = [wcs.pixelToSky(corner).getVector()
for corner
in innerBoxCorners]
621 outerBoxCorners = outerLocalBBox.getCorners()
622 outerSphCorners = [wcs.pixelToSky(corner).getVector()
for corner
in outerBoxCorners]
625 return innerSkyRegion, outerSkyRegion, innerSphCorners, outerSphCorners
628 def _calculateCircle(bbox, wcs, pixelMargin):
629 """Compute on-sky center and radius of search region.
636 WCS; used to convert pixel positions to sky coordinates.
638 Padding to add to 4 all edges of the bounding box (pixels).
642 results : `lsst.pipe.base.Struct`
646 ICRS center of the search region.
648 Radius of the search region.
650 Bounding box used to compute the circle.
653 bbox.grow(pixelMargin)
654 coord = wcs.pixelToSky(bbox.getCenter())
655 radius = max(coord.separation(wcs.pixelToSky(pp))
for pp
in bbox.getCorners())
656 return pipeBase.Struct(coord=coord, radius=radius, bbox=bbox)
660 """Return metadata about the reference catalog being loaded.
662 This metadata is used
for reloading the catalog (e.g.
for
663 reconstituting a normalized match list).
668 ICRS center of the search region.
670 Radius of the search region.
672 Name of the camera filter.
673 epoch : `astropy.time.Time`
or `
None`, optional
674 Epoch to which to correct proper motion
and parallax,
or `
None` to
675 not apply such corrections.
680 Metadata about the catalog.
683 md.add('RA', coord.getRa().asDegrees(),
'field center in degrees')
684 md.add(
'DEC', coord.getDec().asDegrees(),
'field center in degrees')
685 md.add(
'RADIUS', radius.asDegrees(),
'field radius in degrees, minimum')
686 md.add(
'SMATCHV', 1,
'SourceMatchVector version number')
687 md.add(
'FILTER', filterName,
'filter name for photometric data')
688 md.add(
'EPOCH',
"NONE" if epoch
is None else epoch.mjd,
'Epoch (TAI MJD) for catalog')
692 bboxToSpherePadding=100):
693 """Return metadata about the load
695 This metadata is used
for reloading the catalog (e.g.,
for
696 reconstituting a normalised match list).
701 Bounding box
for the pixels.
703 The WCS object associated
with ``bbox``.
705 Name of the camera filter.
706 epoch : `astropy.time.Time`
or `
None`, optional
707 Epoch to which to correct proper motion
and parallax,
or `
None` to
708 not apply such corrections.
709 bboxToSpherePadding : `int`, optional
710 Padding
in pixels to account
for translating a set of corners into
711 a spherical (convex) boundary that
is certain to encompass the
712 enitre area covered by the box.
717 The metadata detailing the search parameters used
for this
721 md = self.getMetadataCircle(circle.coord, circle.radius, filterName, epoch=epoch)
723 paddedBbox = circle.bbox
724 _, _, innerCorners, outerCorners = self._makeBoxRegion(paddedBbox, wcs, bboxToSpherePadding)
725 for box, corners
in zip((
"INNER",
"OUTER"), (innerCorners, outerCorners)):
726 for (name, corner)
in zip((
"UPPER_LEFT",
"UPPER_RIGHT",
"LOWER_LEFT",
"LOWER_RIGHT"),
728 md.add(f
"{box}_{name}_RA",
geom.SpherePoint(corner).getRa().asDegrees(), f
"{box}_corner")
729 md.add(f
"{box}_{name}_DEC",
geom.SpherePoint(corner).getDec().asDegrees(), f
"{box}_corner")
733 """Relink an unpersisted match list to sources and reference objects.
735 A match list is persisted
and unpersisted
as a catalog of IDs
736 produced by afw.table.packMatches(),
with match metadata
737 (
as returned by the astrometry tasks)
in the catalog
's metadata
738 attribute. This method converts such a match catalog into a match
739 list, with links to source records
and reference object records.
744 Unpersisted packed match list.
745 ``matchCat.table.getMetadata()`` must contain match metadata,
746 as returned by the astrometry tasks.
748 Source catalog. As a side effect, the catalog will be sorted
760 """This class facilitates loading reference catalogs with gen 3 middleware.
762 The QuantumGraph generation will create a list of datasets that may
763 possibly overlap a given region. These datasets are then used to construct
764 and instance of this
class. The
class instance should then be passed into
765 a task which needs reference catalogs. These tasks should then determine
766 the exact region of the sky reference catalogs will be loaded
for,
and
767 call a corresponding method to load the reference objects.
771 dataIds : iterable of `lsst.daf.butler.DataCoordinate`
772 An iterable object of data IDs that point to reference catalogs
773 in a gen 3 repository.
774 refCats : iterable of `lsst.daf.butler.DeferredDatasetHandle`
775 Handles to load refCats on demand
776 log : `
lsst.log.Log`, `logging.Logger`
or `
None`, optional
777 Logger object used to write out messages. If `
None` a default
780 def __init__(self, dataIds, refCats, log=None, config=None, **kwargs):
783 super().
__init__(config=config, **kwargs)
786 self.
log = log
or logging.getLogger(__name__).getChild(
"ReferenceObjectLoader")
789 bboxToSpherePadding=100):
790 """Load reference objects that are within a pixel-based rectangular
793 This algorithm works by creating a spherical box whose corners
794 correspond to the WCS converted corners of the input bounding box
795 (possibly padded). It then defines a filtering function which looks at
796 the pixel position of the reference objects and accepts only those that
797 lie within the specified bounding box.
799 The spherical box region
and filtering function are passed to the
800 generic loadRegion method which loads
and filters the reference objects
801 from the datastore
and returns a single catalog containing the filtered
802 set of reference objects.
807 Box which bounds a region
in pixel space.
809 Wcs object defining the pixel to sky (
and inverse) transform
for
810 the supplied ``bbox``.
812 Name of camera filter.
813 epoch : `astropy.time.Time`
or `
None`, optional
814 Epoch to which to correct proper motion
and parallax,
or `
None`
815 to
not apply such corrections.
816 bboxToSpherePadding : `int`, optional
817 Padding to account
for translating a set of corners into a
818 spherical (convex) boundary that
is certain to encompase the
819 enitre area covered by the box.
823 output : `lsst.pipe.base.Struct`
824 Results struct
with attributes:
827 Catalog containing reference objects inside the specified
828 bounding box (padded by self.
config.pixelMargin).
830 Name of the field containing the flux associated
with
836 Raised
if no reference catalogs could be found
for the specified
839 Raised
if the loaded reference catalogs do
not have matching
843 paddedBbox.grow(self.config.pixelMargin)
844 innerSkyRegion, outerSkyRegion, _, _ = self._makeBoxRegion(paddedBbox, wcs, bboxToSpherePadding)
846 def _filterFunction(refCat, region):
857 refCat = preFiltFunc(refCat, region)
863 afwTable.updateRefCentroids(wcs, refCat)
866 if innerSkyRegion.contains(region):
870 filteredRefCat = type(refCat)(refCat.table)
871 centroidKey = afwTable.Point2DKey(refCat.schema[
'centroid'])
872 for record
in refCat:
873 pixCoords = record[centroidKey]
875 filteredRefCat.append(record)
876 return filteredRefCat
877 return self.
loadRegion(outerSkyRegion, filterName, filtFunc=_filterFunction, epoch=epoch)
879 def loadRegion(self, region, filterName, filtFunc=None, epoch=None):
880 """Load reference objects within a specified region.
882 This function loads the DataIds used to construct an instance of this
883 class which intersect or are contained within the specified region. The
884 reference catalogs which intersect but are
not fully contained within
885 the input region are further filtered by the specified filter function.
886 This function returns a single source catalog containing all reference
887 objects inside the specified region.
893 should define the spatial region
for which reference objects are to
895 filtFunc : callable
or `
None`, optional
896 This optional parameter should be a callable object that takes a
897 reference catalog
and its corresponding region
as parameters,
898 filters the catalog by some criteria
and returns the filtered
899 reference catalog. If `
None`, an internal filter function
is used
900 which filters according to
if a reference object falls within the
903 Name of camera filter.
904 epoch : `astropy.time.Time`
or `
None`, optional
905 Epoch to which to correct proper motion
and parallax,
or `
None` to
906 not apply such corrections.
910 output : `lsst.pipe.base.Struct`
911 Results struct
with attributes:
914 Catalog containing reference objects which intersect the
915 input region, filtered by the specified filter function.
917 Name of the field containing the flux associated
with
923 Raised
if no reference catalogs could be found
for the specified
926 Raised
if the loaded reference catalogs do
not have matching
929 regionLat = region.getBoundingBox().getLat()
930 regionLon = region.getBoundingBox().getLon()
931 self.log.info("Loading reference objects from %s in region bounded by "
932 "[%.8f, %.8f], [%.8f, %.8f] RA Dec",
934 self.
refCats[0].ref.datasetType.name,
935 regionLon.getA().asDegrees(), regionLon.getB().asDegrees(),
936 regionLat.getA().asDegrees(), regionLat.getB().asDegrees())
945 intersects = dataId.region.intersects(region)
947 intersects = region.intersects(dataId.region)
950 overlapList.append((dataId, refCat))
952 if len(overlapList) == 0:
953 raise RuntimeError(
"No reference tables could be found for input region")
955 firstCat = overlapList[0][1].get()
956 refCat = filtFunc(firstCat, overlapList[0][0].region)
957 trimmedAmount = len(firstCat) - len(refCat)
960 for dataId, inputRefCat
in overlapList[1:]:
961 tmpCat = inputRefCat.get()
963 if tmpCat.schema != firstCat.schema:
964 raise TypeError(
"Reference catalogs have mismatching schemas")
966 filteredCat = filtFunc(tmpCat, dataId.region)
967 refCat.extend(filteredCat)
968 trimmedAmount += len(tmpCat) - len(filteredCat)
970 self.
log.debug(
"Trimmed %d refCat objects lying outside padded region, leaving %d",
971 trimmedAmount, len(refCat))
972 self.
log.info(
"Loaded %d reference objects", len(refCat))
975 if not refCat.isContiguous():
976 refCat = refCat.copy(deep=
True)
984 self.
log.warning(
"Found version 0 reference catalog with old style units in schema.")
985 self.
log.warning(
"run `meas_algorithms/bin/convert_refcat_to_nJy.py` to convert fluxes to nJy.")
986 self.
log.warning(
"See RFC-575 for more details.")
990 anyFilterMapsToThis=self.
config.anyFilterMapsToThis,
991 filterMap=self.
config.filterMap)
994 if not expandedCat.isContiguous():
995 expandedCat = expandedCat.copy(deep=
True)
998 return pipeBase.Struct(refCat=expandedCat, fluxField=fluxField)
1001 """Load reference objects that lie within a circular region on the sky.
1003 This method constructs a circular region from an input center
and
1004 angular radius, loads reference catalogs which are contained
in or
1005 intersect the circle,
and filters reference catalogs which intersect
1006 down to objects which lie within the defined circle.
1011 Point defining the center of the circular region.
1013 Defines the angular radius of the circular region.
1015 Name of camera filter.
1016 epoch : `astropy.time.Time`
or `
None`, optional
1017 Epoch to which to correct proper motion
and parallax,
or `
None` to
1018 not apply such corrections.
1022 output : `lsst.pipe.base.Struct`
1023 Results struct
with attributes:
1026 Catalog containing reference objects inside the specified
1029 Name of the field containing the flux associated
with
1032 centerVector = ctrCoord.getVector()
1035 return self.
loadRegion(circularRegion, filterName, epoch=epoch)
1039 """Get the name of a flux field from a schema.
1041 return the alias of
"anyFilterMapsToThis",
if present
1043 return "*filterName*_camFlux" if present
1044 else return "*filterName*_flux" if present (camera filter name
1045 matches reference filter name)
1046 else throw RuntimeError
1051 Reference catalog schema.
1053 Name of camera filter.
1057 fluxFieldName : `str`
1063 If an appropriate field
is not found.
1065 if not isinstance(schema, afwTable.Schema):
1066 raise RuntimeError(
"schema=%s is not a schema" % (schema,))
1068 return schema.getAliasMap().get(
"anyFilterMapsToThis")
1072 fluxFieldList = [filterName +
"_camFlux", filterName +
"_flux"]
1073 for fluxField
in fluxFieldList:
1074 if fluxField
in schema:
1077 raise RuntimeError(
"Could not find flux field(s) %s" % (
", ".join(fluxFieldList)))
1081 """Return keys for flux and flux error.
1086 Reference catalog schema.
1088 Name of camera filter.
1096 - flux error key, if present,
else None
1101 If flux field
not found.
1104 fluxErrField = fluxField + "Err"
1105 fluxKey = schema[fluxField].asKey()
1107 fluxErrKey = schema[fluxErrField].asKey()
1110 return (fluxKey, fluxErrKey)
1114 """Abstract gen2 base class to load objects from reference catalogs.
1116 _DefaultName = "LoadReferenceObjects"
1119 """Construct a LoadReferenceObjectsTask
1123 butler : `lsst.daf.persistence.Butler`
1124 Data butler, for access reference catalogs.
1126 pipeBase.Task.__init__(self, *args, **kwargs)
1130 def loadPixelBox(self, bbox, wcs, filterName, photoCalib=None, epoch=None):
1131 """Load reference objects that overlap a rectangular pixel region.
1136 Bounding box
for pixels.
1138 WCS; used to convert pixel positions to sky coordinates
1141 Name of filter. This can be used
for flux limit comparisons.
1143 Deprecated, only included
for api compatibility.
1144 epoch : `astropy.time.Time`
or `
None`, optional
1145 Epoch to which to correct proper motion
and parallax,
or `
None` to
1146 not apply such corrections.
1150 results : `lsst.pipe.base.Struct`
1151 A Struct containing the following fields:
1152 refCat : `lsst.afw.catalog.SimpleCatalog`
1153 A catalog of reference objects
with the standard
1154 schema,
as documented
in the main doc string
for
1155 `LoadReferenceObjects`.
1156 The catalog
is guaranteed to be contiguous.
1158 Name of flux field
for specified `filterName`.
1162 The search algorithm works by searching
in a region
in sky
1163 coordinates whose center
is the center of the bbox
and radius
1164 is large enough to just include all 4 corners of the bbox.
1165 Stars that lie outside the bbox are then trimmed
from the list.
1170 self.log.info(
"Loading reference objects from %s using center %s and radius %s deg",
1171 self.
config.ref_dataset_name, circle.coord, circle.radius.asDegrees())
1172 loadRes = self.
loadSkyCircle(circle.coord, circle.radius, filterName, epoch=epoch,
1174 refCat = loadRes.refCat
1175 numFound = len(refCat)
1178 refCat = self.
_trimToBBox(refCat=refCat, bbox=circle.bbox, wcs=wcs)
1179 numTrimmed = numFound - len(refCat)
1180 self.log.debug(
"trimmed %d out-of-bbox objects, leaving %d", numTrimmed, len(refCat))
1181 self.log.info(
"Loaded %d reference objects", len(refCat))
1184 if not refCat.isContiguous():
1185 loadRes.refCat = refCat.copy(deep=
True)
1190 def loadSkyCircle(self, ctrCoord, radius, filterName, epoch=None, centroids=False):
1191 """Load reference objects that overlap a circular sky region.
1196 ICRS center of search region.
1198 Radius of search region.
1200 Name of filter. This can be used for flux limit comparisons.
1201 epoch : `astropy.time.Time`
or `
None`, optional
1202 Epoch to which to correct proper motion
and parallax,
or `
None` to
1203 not apply such corrections.
1204 centroids : `bool`, optional
1205 Add centroid fields to the loaded Schema. ``loadPixelBox`` expects
1206 these fields to exist.
1210 results : `lsst.pipe.base.Struct`
1211 A Struct containing the following fields:
1212 refCat : `lsst.afw.catalog.SimpleCatalog`
1213 A catalog of reference objects
with the standard
1214 schema,
as documented
in the main doc string
for
1215 `LoadReferenceObjects`.
1216 The catalog
is guaranteed to be contiguous.
1218 Name of flux field
for specified `filterName`.
1222 Note that subclasses are responsible
for performing the proper motion
1223 correction, since this
is the lowest-level interface
for retrieving
1229 def _trimToBBox(refCat, bbox, wcs):
1230 """Remove objects outside a given pixel bounding box and set
1231 centroid and hasCentroid fields.
1236 A catalog of objects. The schema must include fields
1237 "coord",
"centroid" and "hasCentroid".
1238 The
"coord" field
is read.
1239 The
"centroid" and "hasCentroid" fields are set.
1243 WCS; used to convert sky coordinates to pixel positions.
1248 Reference objects
in the bbox,
with centroid
and
1249 hasCentroid fields set.
1251 afwTable.updateRefCentroids(wcs, refCat)
1252 centroidKey = afwTable.Point2DKey(refCat.schema["centroid"])
1253 retStarCat = type(refCat)(refCat.table)
1255 point = star.get(centroidKey)
1256 if bbox.contains(point):
1257 retStarCat.append(star)
1262 """Relink an unpersisted match list to sources and reference
1265 A match list is persisted
and unpersisted
as a catalog of IDs
1266 produced by afw.table.packMatches(),
with match metadata
1267 (
as returned by the astrometry tasks)
in the catalog
's metadata
1268 attribute. This method converts such a match catalog into a match
1269 list, with links to source records
and reference object records.
1274 Reference object loader to use
in getting reference objects
1276 Unperisted packed match list.
1277 ``matchCat.table.getMetadata()`` must contain match metadata,
1278 as returned by the astrometry tasks.
1280 Source catalog. As a side effect, the catalog will be sorted
1288 matchmeta = matchCat.table.getMetadata()
1289 version = matchmeta.getInt('SMATCHV')
1291 raise ValueError(
'SourceMatchVector version number is %i, not 1.' % version)
1292 filterName = matchmeta.getString(
'FILTER').strip()
1294 epoch = matchmeta.getDouble(
'EPOCH')
1295 except (LookupError, TypeError):
1297 if 'RADIUS' in matchmeta:
1300 matchmeta.getDouble(
'DEC'), geom.degrees)
1301 rad = matchmeta.getDouble(
'RADIUS')*geom.degrees
1302 refCat = refObjLoader.loadSkyCircle(ctrCoord, rad, filterName, epoch=epoch).refCat
1303 elif "INNER_UPPER_LEFT_RA" in matchmeta:
1309 for place
in (
"UPPER_LEFT",
"UPPER_RIGHT",
"LOWER_LEFT",
"LOWER_RIGHT"):
1311 matchmeta.getDouble(f
"OUTER_{place}_DEC"),
1312 geom.degrees).getVector()
1315 refCat = refObjLoader.loadRegion(outerBox, filterName, epoch=epoch).refCat
1319 return afwTable.unpackMatches(matchCat, refCat, sourceCat)
1323 """Apply proper motion correction to a reference catalog.
1325 Adjust position and position error
in the ``catalog``
1326 for proper motion to the specified ``epoch``,
1327 modifying the catalog
in place.
1332 Log object to write to.
1334 Catalog of positions, containing:
1336 - Coordinates, retrieved by the table
's coordinate key.
1337 - ``coord_raErr`` : Error in Right Ascension (rad).
1338 - ``coord_decErr`` : Error
in Declination (rad).
1339 - ``pm_ra`` : Proper motion
in Right Ascension (rad/yr,
1341 - ``pm_raErr`` : Error
in ``pm_ra`` (rad/yr), optional.
1342 - ``pm_dec`` : Proper motion
in Declination (rad/yr,
1344 - ``pm_decErr`` : Error
in ``pm_dec`` (rad/yr), optional.
1345 - ``epoch`` : Mean epoch of object (an astropy.time.Time)
1346 epoch : `astropy.time.Time`
1347 Epoch to which to correct proper motion.
1349 if "epoch" not in catalog.schema
or "pm_ra" not in catalog.schema
or "pm_dec" not in catalog.schema:
1350 log.warning(
"Proper motion correction not available from catalog")
1352 if not catalog.isContiguous():
1353 raise RuntimeError(
"Catalog must be contiguous")
1354 catEpoch = astropy.time.Time(catalog[
"epoch"], scale=
"tai", format=
"mjd")
1355 log.info(
"Correcting reference catalog for proper motion to %r", epoch)
1357 timeDiffsYears = (epoch.tai - catEpoch).to(astropy.units.yr).value
1358 coordKey = catalog.table.getCoordKey()
1361 pmRaRad = catalog[
"pm_ra"]
1362 pmDecRad = catalog[
"pm_dec"]
1363 offsetsRaRad = pmRaRad*timeDiffsYears
1364 offsetsDecRad = pmDecRad*timeDiffsYears
1372 offsetBearingsRad = numpy.arctan2(pmDecRad*1e6, pmRaRad*1e6)
1373 offsetAmountsRad = numpy.hypot(offsetsRaRad, offsetsDecRad)
1374 for record, bearingRad, amountRad
in zip(catalog, offsetBearingsRad, offsetAmountsRad):
1375 record.set(coordKey,
1376 record.get(coordKey).offset(bearing=bearingRad*geom.radians,
1377 amount=amountRad*geom.radians))
1379 if "coord_raErr" in catalog.schema:
1380 catalog[
"coord_raErr"] = numpy.hypot(catalog[
"coord_raErr"],
1381 catalog[
"pm_raErr"]*timeDiffsYears)
1382 if "coord_decErr" in catalog.schema:
1383 catalog[
"coord_decErr"] = numpy.hypot(catalog[
"coord_decErr"],
1384 catalog[
"pm_decErr"]*timeDiffsYears)
def __call__(self, refCat, catRegion)
def __init__(self, region)
def _trimToBBox(refCat, bbox, wcs)
def loadSkyCircle(self, ctrCoord, radius, filterName, epoch=None, centroids=False)
def loadPixelBox(self, bbox, wcs, filterName, photoCalib=None, epoch=None)
def __init__(self, butler=None, *args, **kwargs)
def getMetadataBox(self, bbox, wcs, filterName, epoch=None, bboxToSpherePadding=100)
def applyProperMotions(self, catalog, epoch)
def joinMatchListWithCatalog(self, matchCat, sourceCat)
def _makeBoxRegion(BBox, wcs, BBoxPadding)
def makeMinimalSchema(filterNameList, *addCentroid=False, addIsPhotometric=False, addIsResolved=False, addIsVariable=False, coordErrDim=2, addProperMotion=False, properMotionErrDim=2, addParallax=False)
def _calculateCircle(bbox, wcs, pixelMargin)
def getMetadataCircle(coord, radius, filterName, epoch=None)
def __init__(self, config=None, *args, **kwargs)
def _remapReferenceCatalogSchema(refCat, *anyFilterMapsToThis=None, filterMap=None, centroids=False)
def loadSkyCircle(self, ctrCoord, radius, filterName, epoch=None)
def __init__(self, dataIds, refCats, log=None, config=None, **kwargs)
def loadRegion(self, region, filterName, filtFunc=None, epoch=None)
def loadPixelBox(self, bbox, wcs, filterName, epoch=None, bboxToSpherePadding=100)
def hasNanojanskyFluxUnits(schema)
def getRefFluxKeys(schema, filterName)
def convertToNanojansky(catalog, log, doConvert=True)
def joinMatchListWithCatalogImpl(refObjLoader, matchCat, sourceCat)
def isOldFluxField(name, units)
def getRefFluxField(schema, filterName)
def getFormatVersionFromRefCat(refCat)
def applyProperMotionsImpl(log, catalog, epoch)