23todo: Consider tweaking pixel scale so the average scale is as specified,
24rather than the scale at the center.
27__all__ = [
"BaseSkyMapConfig",
"BaseSkyMap"]
34from lsst.geom import SpherePoint, Angle, arcseconds, degrees
36from .tractBuilder
import tractBuilderRegistry
40 tractBuilder = tractBuilderRegistry.makeField(
41 doc=
"Algorithm for creating patches within the tract.",
45 tractOverlap = pexConfig.Field(
46 doc=
"minimum overlap between adjacent sky tracts, on the sky (deg)",
50 pixelScale = pexConfig.Field(
51 doc=
"nominal pixel scale (arcsec/pixel)",
55 projection = pexConfig.Field(
56 doc=
"one of the FITS WCS projection codes, such as:"
57 "- STG: stereographic projection"
58 "- MOL: Molleweide's projection"
59 "- TAN: tangent-plane projection",
63 rotation = pexConfig.Field(
64 doc=
"Rotation for WCS (deg)",
72 """Get the patch inner dimensions, for backwards compatibility.
74 This value is only used
with the ``legacy`` tract builder,
75 and is ignored otherwise. In general, the config should be
76 accessed directly
with
77 ``config.tractBuilder[
"legacy"].patchInnerDimensions``.
81 innerDimensions : `list` [`int`, `int`]
86 """Set the patch inner dimensions, for backwards compatibility.
88 This value is only used
with the ``legacy`` tract builder,
89 and is ignored otherwise. In general, the config should be
90 accessed directly
with
91 ``config.tractBuilder[
"legacy"].patchInnerDimensions``.
95 value : `list` [`int`, `int`]
99 patchInnerDimensions = property(getPatchInnerDimensions, setPatchInnerDimensions)
102 """Get the patch border, for backwards compatibility.
104 This value is only used
with the ``legacy`` tract builder,
105 and is ignored otherwise. In general, the config should be
106 accessed directly
with config.tractBuilder[
"legacy"].patchBorder.
115 """Set the patch border, for backwards compatibility.
117 This value is only used
with the ``legacy`` tract builder,
118 and is ignored otherwise. In general, the config should be
119 accessed directly
with config.tractBuilder[
"legacy"].patchBorder.
127 patchBorder = property(getPatchBorder, setPatchBorder)
131 """A collection of overlapping Tracts that map part or all of the sky.
133 See TractInfo for more information.
137 config : `BaseSkyMapConfig`
or `
None` (optional)
138 The configuration
for this SkyMap;
if None use the default config.
142 BaseSkyMap
is an abstract base
class. Subclasses must do the following:
143 define ``__init__``
and have it construct the TractInfo objects
and put
144 them
in ``__tractInfoList__`` define ``__getstate__``
and ``__setstate__``
145 to allow pickling (the butler saves sky maps using pickle);
146 see DodecaSkyMap
for an example of how to do this. (Most of that code could
147 be moved into this base
class, but that would make it harder to handle
148 older versions of pickle data.) define updateSha1 to add any
149 subclass-specific state to the hash.
151 All SkyMap subclasses must be conceptually immutable; they must always
152 refer to the same set of mathematical tracts
and patches even
if the
in-
153 memory representation of those objects changes.
156 ConfigClass = BaseSkyMapConfig
165 pixelScale=
Angle(self.
config.pixelScale, arcseconds),
166 projection=self.
config.projection,
173 """Find the tract whose center is nearest the specified coord.
178 ICRS sky coordinate to search for.
183 TractInfo of tract whose center
is nearest the specified coord.
187 - If coord
is equidistant between multiple sky tract centers then one
188 is arbitrarily chosen.
190 - The default implementation
is not very efficient; subclasses may wish
195 If tracts do
not cover the whole sky then the returned tract may
not
198 distTractInfoList = []
199 for i, tractInfo
in enumerate(self):
200 angSep = coord.separation(tractInfo.getCtrCoord()).asDegrees()
202 distTractInfoList.append((angSep, i, tractInfo))
203 distTractInfoList.sort()
204 return distTractInfoList[0][2]
207 """Find array of tract IDs with vectorized operations (where
210 If a given sky map does not support vectorized operations, then a loop
211 over findTract will be called.
216 Array of Right Ascension. Units are radians unless
218 dec : `numpy.ndarray`
219 Array of Declination. Units are radians unless
221 degrees : `bool`, optional
222 Input ra, dec arrays are degrees
if `
True`.
226 tractId : `numpy.ndarray`
231 - If coord
is equidistant between multiple sky tract centers then one
232 is arbitrarily chosen.
236 If tracts do
not cover the whole sky then the returned tract may
not
237 include the given ra/dec.
239 units = geom.degrees if degrees
else geom.radians
240 coords = [
geom.SpherePoint(r*units, d*units)
for r, d
in zip(np.atleast_1d(ra),
243 tractId = np.array([self.
findTract(coord).getId()
for coord
in coords])
248 """Find tracts and patches that overlap a region.
253 List of ICRS sky coordinates to search for.
257 reList : `list` of (`TractInfo`, `list` of `PatchInfo`)
258 For tracts
and patches that contain,
or may contain, the specified
259 region. The list will be empty
if there
is no overlap.
265 This uses a naive algorithm that may find some tracts
and patches
266 that do
not overlap the region (especially
if the region
is not a
267 rectangle aligned along patch x, y).
270 for tractInfo
in self:
271 patchList = tractInfo.findPatchList(coordList)
273 retList.append((tractInfo, patchList))
277 """Find closest tract and patches that overlap coordinates.
282 List of ICRS sky coordinates to search for.
287 list of (TractInfo, list of PatchInfo)
for tracts
and patches
288 that contain,
or may contain, the specified region.
289 The list will be empty
if there
is no overlap.
292 for coord
in coordList:
294 patchList = tractInfo.findPatchList(coordList)
295 if patchList
and not (tractInfo, patchList)
in retList:
296 retList.append((tractInfo, patchList))
313 return self.
getSha1() == other.getSha1()
314 except AttributeError:
315 return NotImplemented
318 return not (self == other)
321 """Write information about a sky map to supplied log
325 log : `logging.Logger`
326 Log object that information about skymap will be written.
328 log.info("sky map has %s tracts" % (len(self),))
329 for tractInfo
in self:
330 wcs = tractInfo.getWcs()
338 skyPosList = [wcs.pixelToSky(pos).getPosition(geom.degrees)
for pos
in pixelPosList]
339 posStrList = [
"(%0.3f, %0.3f)" % tuple(skyPos)
for skyPos
in skyPosList]
340 log.info(
"tract %s has corners %s (RA, Dec deg) and %s x %s patches" %
341 (tractInfo.getId(),
", ".join(posStrList),
342 tractInfo.getNumPatches()[0], tractInfo.getNumPatches()[1]))
345 """Return a SHA1 hash that uniquely identifies this SkyMap instance.
350 A 20-byte hash that uniquely identifies this SkyMap instance.
354 Subclasses should almost always override ``updateSha1`` instead of
355 this function to add subclass-specific state to the hash.
357 if self.
_sha1 is None:
358 sha1 = hashlib.sha1()
359 sha1.update(
type(self).__name__.encode(
'utf-8'))
361 sha1.update(configPacked)
363 self.
_sha1 = sha1.digest()
367 """Add subclass-specific state or configuration options to the SHA1.
371 sha1 : `hashlib.sha1`
372 A hashlib object on which `update` can be called to add
373 additional state to the hash.
377 This method is conceptually
"protected" : it should be reimplemented by
378 all subclasses, but called only by the base
class implementation of
381 raise NotImplementedError()
383 SKYMAP_RUN_COLLECTION_NAME =
"skymaps"
385 SKYMAP_DATASET_TYPE_NAME =
"skyMap"
387 def register(self, name, butler):
388 """Add skymap, tract, and patch Dimension entries to the given Gen3
394 The name of the skymap.
395 butler : `lsst.daf.butler.Butler`
396 The butler to add to.
400 lsst.daf.butler.registry.ConflictingDefinitionError
401 Raised if a different skymap exists
with the same name.
405 Registering the same skymap multiple times (
with the exact same
406 definition)
is safe, but inefficient; most of the work of computing
407 the rows to be inserted must be done first
in order to check
for
408 consistency between the new skymap
and any existing one.
410 Re-registering a skymap
with different tract
and/
or patch definitions
411 but the same summary information may
not be detected
as a conflict but
412 will never result
in updating the skymap; there
is intentionally no
413 way to modify a registered skymap (aside
from manual administrative
414 operations on the database),
as it
is hard to guarantee that this can
415 be done without affecting reproducibility.
417 numPatches = [tractInfo.getNumPatches() for tractInfo
in self]
418 nxMax =
max(nn[0]
for nn
in numPatches)
419 nyMax =
max(nn[1]
for nn
in numPatches)
424 "tract_max": len(self),
425 "patch_nx_max": nxMax,
426 "patch_ny_max": nyMax,
432 from lsst.daf.butler
import DatasetType
433 from lsst.daf.butler.registry
import ConflictingDefinitionError
434 datasetType = DatasetType(
436 dimensions=[
"skymap"],
437 storageClass=
"SkyMap",
438 universe=butler.dimensions
440 butler.registry.registerDatasetType(datasetType)
441 with butler.transaction():
443 inserted = butler.registry.syncDimensionData(
"skymap", skyMapRecord)
444 except ConflictingDefinitionError
as err:
445 raise ConflictingDefinitionError(
446 f
"SkyMap with hash {self.getSha1().hex()} is already registered with a different name."
449 for tractInfo
in self:
450 tractId = tractInfo.getId()
451 tractRegion = tractInfo.getOuterSkyPolygon()
453 tractWcs = tractInfo.getWcs()
458 ra=centroid.getRa().asDegrees(),
459 dec=centroid.getDec().asDegrees(),
461 butler.registry.insertDimensionData(
"tract", tractRecord)
464 for patchInfo
in tractInfo:
465 xx, yy = patchInfo.getIndex()
470 patch=tractInfo.getSequentialPatchIndex(patchInfo),
473 region=patchInfo.getOuterSkyPolygon(tractWcs),
476 butler.registry.insertDimensionData(
"patch", *patchRecords)
A class representing an angle.
A floating-point coordinate rectangle geometry.
Point in an unspecified spherical coordinate system.
setPatchBorder(self, value)
setPatchInnerDimensions(self, value)
getPatchInnerDimensions(self)
findTractIdArray(self, ra, dec, degrees=False)
__init__(self, config=None)
findClosestTractPatchList(self, coordList)
findTractPatchList(self, coordList)
str SKYMAP_DATASET_TYPE_NAME
str SKYMAP_RUN_COLLECTION_NAME
SKYMAP_RUN_COLLECTION_NAME