lsst.skymap  15.0-5-gb31927c
baseSkyMap.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 # Copyright 2008, 2009, 2010 LSST Corporation.
4 #
5 # This product includes software developed by the
6 # LSST Project (http://www.lsst.org/).
7 #
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the LSST License Statement and
19 # the GNU General Public License along with this program. If not,
20 # see <http://www.lsstcorp.org/LegalNotices/>.
21 #
22 """
23 @todo
24 - Consider tweaking pixel scale so the average scale is as specified, rather than the scale at the center
25 """
26 import hashlib
27 import struct
28 
29 import lsst.pex.config as pexConfig
30 import lsst.afw.geom as afwGeom
31 from . import detail
32 
33 __all__ = ["BaseSkyMap"]
34 
35 
36 class BaseSkyMapConfig(pexConfig.Config):
37  patchInnerDimensions = pexConfig.ListField(
38  doc="dimensions of inner region of patches (x,y pixels)",
39  dtype=int,
40  length=2,
41  default=(4000, 4000),
42  )
43  patchBorder = pexConfig.Field(
44  doc="border between patch inner and outer bbox (pixels)",
45  dtype=int,
46  default=100,
47  )
48  tractOverlap = pexConfig.Field(
49  doc="minimum overlap between adjacent sky tracts, on the sky (deg)",
50  dtype=float,
51  default=1.0,
52  )
53  pixelScale = pexConfig.Field(
54  doc="nominal pixel scale (arcsec/pixel)",
55  dtype=float,
56  default=0.333
57  )
58  projection = pexConfig.Field(
59  doc="""one of the FITS WCS projection codes, such as:
60  - STG: stereographic projection
61  - MOL: Molleweide's projection
62  - TAN: tangent-plane projection
63  """,
64  dtype=str,
65  default="STG",
66  )
67  rotation = pexConfig.Field(
68  doc="Rotation for WCS (deg)",
69  dtype=float,
70  default=0,
71  )
72 
73 
74 class BaseSkyMap:
75  """A collection of overlapping Tracts that map part or all of the sky.
76 
77  See TractInfo for more information.
78 
79  BaseSkyMap is an abstract base class. Subclasses must do the following:
80  @li define __init__ and have it construct the TractInfo objects and put them in _tractInfoList
81  @li define __getstate__ and __setstate__ to allow pickling (the butler saves sky maps using pickle);
82  see DodecaSkyMap for an example of how to do this. (Most of that code could be moved
83  into this base class, but that would make it harder to handle older versions of pickle data.)
84  @li define updateSha1 to add any subclass-specific state to the hash.
85 
86  All SkyMap subclasses must be conceptually immutable; they must always
87  refer to the same set of mathematical tracts and patches even if the in-
88  memory representation of those objects changes.
89  """
90  ConfigClass = BaseSkyMapConfig
91 
92  def __init__(self, config=None):
93  """Construct a BaseSkyMap
94 
95  @param[in] config: an instance of self.ConfigClass; if None the default config is used
96  """
97  if config is None:
98  config = self.ConfigClass()
99  config.freeze() # just to be sure, e.g. for pickling
100  self.config = config
101  self._tractInfoList = []
103  pixelScale=afwGeom.Angle(self.config.pixelScale, afwGeom.arcseconds),
104  projection=self.config.projection,
105  rotation=afwGeom.Angle(self.config.rotation, afwGeom.degrees),
106  )
107  self._sha1 = None
108 
109  def findTract(self, coord):
110  """Find the tract whose center is nearest the specified coord.
111 
112  @param[in] coord: ICRS sky coordinate (lsst.afw.geom.SpherePoint)
113  @return TractInfo of tract whose center is nearest the specified coord
114 
115  @warning:
116  - if tracts do not cover the whole sky then the returned tract may not include the coord
117 
118  @note
119  - This routine will be more efficient if coord is ICRS.
120  - If coord is equidistant between multiple sky tract centers then one is arbitrarily chosen.
121  - The default implementation is not very efficient; subclasses may wish to override.
122  """
123  distTractInfoList = []
124  for tractInfo in self:
125  angSep = coord.separation(tractInfo.getCtrCoord()).asDegrees()
126  distTractInfoList.append((angSep, tractInfo))
127  distTractInfoList.sort()
128  return distTractInfoList[0][1]
129 
130  def findTractPatchList(self, coordList):
131  """Find tracts and patches that overlap a region
132 
133  @param[in] coordList: list of ICRS sky coordinates (lsst.afw.geom.SpherePoint)
134  @return list of (TractInfo, list of PatchInfo) for tracts and patches that contain,
135  or may contain, the specified region. The list will be empty if there is no overlap.
136 
137  @warning this uses a naive algorithm that may find some tracts and patches that do not overlap
138  the region (especially if the region is not a rectangle aligned along patch x,y).
139  """
140  retList = []
141  for tractInfo in self:
142  patchList = tractInfo.findPatchList(coordList)
143  if patchList:
144  retList.append((tractInfo, patchList))
145  return retList
146 
147  def findClosestTractPatchList(self, coordList):
148  """Find closest tract and patches that overlap coordinates
149 
150  @param[in] coordList: list of ICRS sky coordinates (lsst.afw.geom.SpherePoint)
151  @return list of (TractInfo, list of PatchInfo) for tracts and patches that contain,
152  or may contain, the specified region. The list will be empty if there is no overlap.
153  """
154  retList = []
155  for coord in coordList:
156  tractInfo = self.findTract(coord)
157  patchList = tractInfo.findPatchList(coordList)
158  if patchList and not (tractInfo, patchList) in retList:
159  retList.append((tractInfo, patchList))
160  return retList
161 
162  def __getitem__(self, ind):
163  return self._tractInfoList[ind]
164 
165  def __iter__(self):
166  return iter(self._tractInfoList)
167 
168  def __len__(self):
169  return len(self._tractInfoList)
170 
171  def __hash__(self):
172  return hash(self.getSha1())
173 
174  def __eq__(self, other):
175  try:
176  return self.getSha1() == other.getSha1()
177  except AttributeError:
178  return NotImplemented
179 
180  def __ne__(self, other):
181  return not (self == other)
182 
183  def getSha1(self):
184  """Return a SHA1 hash that uniquely identifies this SkyMap instance.
185 
186  Returns
187  -------
188  sha1 : bytes
189  A 20-byte hash that uniquely identifies this SkyMap instance.
190 
191  Subclasses should almost always override `updateSha1()` instead of
192  this function to add subclass-specific state to the hash.
193  """
194  if self._sha1 is None:
195  sha1 = hashlib.sha1()
196  sha1.update(type(self).__name__.encode('utf-8'))
197  configPacked = struct.pack(
198  "<iiidd3sd",
199  self.config.patchInnerDimensions[0],
200  self.config.patchInnerDimensions[1],
201  self.config.patchBorder,
202  self.config.tractOverlap,
203  self.config.pixelScale,
204  self.config.projection.encode('ascii'),
205  self.config.rotation
206  )
207  sha1.update(configPacked)
208  self.updateSha1(sha1)
209  self._sha1 = sha1.digest()
210  return self._sha1
211 
212  def updateSha1(self, sha1):
213  """Add subclass-specific state or configuration options to the SHA1.
214 
215  Parameters
216  ----------
217  sha1 : hashlib.sha1
218  A hashlib object on which `update()` can be called to add
219  additional state to the hash.
220 
221  This method is conceptually "protected": it should be reimplemented by
222  all subclasses, but called only by the base class implementation of
223  `getSha1()`.
224  """
225  raise NotImplementedError()
226 
227  def register(self, name, registry):
228  """Add SkyMap, Tract, and Patch DataUnits to the given Gen3 Butler Registry.
229  """
230  registry.addDataUnitEntry("SkyMap", {"skymap": name, "sha1": self.getSha1()})
231  for tractInfo in self:
232  registry.addDataUnitEntry(
233  "Tract",
234  {"skymap": name, "tract": tractInfo.getId(),
235  "region": tractInfo.getPolygon().encode()}
236  )
237  for patchInfo in tractInfo:
238  cellX, cellY = patchInfo.getIndex()
239  registry.addDataUnitEntry(
240  "Patch",
241  {"skymap": name, "tract": tractInfo.getId(),
242  "patch": tractInfo.getSequentialPatchIndex(patchInfo),
243  "cell_x": cellX, "cell_y": cellY,
244  "region": patchInfo.getPolygon(tractInfo.getWcs()).encode()}
245  )
def findTractPatchList(self, coordList)
Definition: baseSkyMap.py:130
def findClosestTractPatchList(self, coordList)
Definition: baseSkyMap.py:147
def register(self, name, registry)
Definition: baseSkyMap.py:227
def __init__(self, config=None)
Definition: baseSkyMap.py:92