LSST Applications g180d380827+0f66a164bb,g2079a07aa2+86d27d4dc4,g2305ad1205+7d304bc7a0,g29320951ab+500695df56,g2bbee38e9b+0e5473021a,g337abbeb29+0e5473021a,g33d1c0ed96+0e5473021a,g3a166c0a6a+0e5473021a,g3ddfee87b4+e42ea45bea,g48712c4677+36a86eeaa5,g487adcacf7+2dd8f347ac,g50ff169b8f+96c6868917,g52b1c1532d+585e252eca,g591dd9f2cf+c70619cc9d,g5a732f18d5+53520f316c,g5ea96fc03c+341ea1ce94,g64a986408d+f7cd9c7162,g858d7b2824+f7cd9c7162,g8a8a8dda67+585e252eca,g99cad8db69+469ab8c039,g9ddcbc5298+9a081db1e4,ga1e77700b3+15fc3df1f7,gb0e22166c9+60f28cb32d,gba4ed39666+c2a2e4ac27,gbb8dafda3b+c92fc63c7e,gbd866b1f37+f7cd9c7162,gc120e1dc64+02c66aa596,gc28159a63d+0e5473021a,gc3e9b769f7+b0068a2d9f,gcf0d15dbbd+e42ea45bea,gdaeeff99f8+f9a426f77a,ge6526c86ff+84383d05b3,ge79ae78c31+0e5473021a,gee10cc3b42+585e252eca,gff1a9f87cc+f7cd9c7162,w.2024.17
LSST Data Management Base Package
Loading...
Searching...
No Matches
tractInfo.py
Go to the documentation of this file.
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__all__ = ["TractInfo"]
24
25import numpy as np
26
28import lsst.geom as geom
29from lsst.sphgeom import ConvexPolygon
30
31from .detail import makeSkyPolygonFromBBox, Index2D
32
33
35 """Information about a tract in a SkyMap sky pixelization
36
37 Parameters
38 ----------
39 id : `int`
40 tract ID
41 tractBuilder : Subclass of `lsst.skymap.BaseTractBuilder`
42 Object used to compute patch geometry.
43 ctrCoord : `lsst.geom.SpherePoint`
44 ICRS sky coordinate of center of inner region of tract; also used as
45 the CRVAL for the WCS.
46 vertexCoordList : `list` of `lsst.geom.SpherePoint`
47 Vertices that define the boundaries of the inner region.
48 tractOverlap : `lsst.geom.Angle`
49 Minimum overlap between adjacent sky tracts; this defines the minimum
50 distance the tract extends beyond the inner region in all directions.
51 wcs : `lsst.afw.image.SkyWcs`
52 WCS for tract. The reference pixel will be shifted as required so that
53 the lower left-hand pixel (index 0,0) has pixel position 0.0, 0.0.
54
55 Notes
56 -----
57 The tract is subdivided into rectangular patches. Each patch has the
58 following properties:
59
60 - An inner region defined by an inner bounding box. The inner regions of
61 the patches exactly tile the tract, and all inner regions have the same
62 dimensions. The tract is made larger as required to make this work.
63
64 - An outer region defined by an outer bounding box. The outer region
65 extends beyond the inner region by patchBorder pixels in all directions,
66 except there is no border at the edges of the tract.
67 Thus patches overlap each other but never extend off the tract.
68 If you do not want any overlap between adjacent patches then set
69 patchBorder to 0.
70
71 - An index that consists of a pair of integers:
72
73 * 0 <= x index < numPatches[0]
74
75 * 0 <= y index < numPatches[1]
76
77 Patch 0,0 is at the minimum corner of the tract bounding box.
78
79 - It is not enforced that ctrCoord is the center of vertexCoordList, but
80 SkyMap relies on it.
81 """
82 def __init__(self, id, tractBuilder, ctrCoord, vertexCoordList, tractOverlap, wcs):
83 self._id = id
84 self._ctrCoord = ctrCoord
85 self._vertexCoordList = tuple(vertexCoordList)
86 self._tractOverlap = tractOverlap
87 self._tractBuilder = tractBuilder
88
89 minBBox = self._minimumBoundingBox(wcs)
90 initialBBox, self._numPatches = self._tractBuilder.setupPatches(minBBox, wcs)
91 self._bbox, self._wcs = self._finalOrientation(initialBBox, wcs)
92
93 def _minimumBoundingBox(self, wcs):
94 """Calculate the minimum bounding box for the tract, given the WCS.
95
96 The bounding box is created in the frame of the supplied WCS,
97 so that it's OK if the coordinates are negative.
98
99 We compute the bounding box that holds all the vertices and the
100 desired overlap.
101 """
102 minBBoxD = geom.Box2D()
103 halfOverlap = self._tractOverlap / 2.0
104 for vertexCoord in self._vertexCoordList:
105 if self._tractOverlap == 0:
106 minBBoxD.include(wcs.skyToPixel(vertexCoord))
107 else:
108 numAngles = 24
109 angleIncr = geom.Angle(360.0, geom.degrees) / float(numAngles)
110 for i in range(numAngles):
111 offAngle = angleIncr * i
112 offCoord = vertexCoord.offset(offAngle, halfOverlap)
113 pixPos = wcs.skyToPixel(offCoord)
114 minBBoxD.include(pixPos)
115 return minBBoxD
116
117 def _finalOrientation(self, bbox, wcs):
118 """Determine the final orientation
119
120 We offset everything so the lower-left corner is at 0,0
121 and compute the final Wcs.
122
123 Parameters
124 ----------
125 bbox : `lsst.geom.Box2I`
126 Current bounding box.
127 wcs : `lsst.afw.geom.SkyWcs
128 Current Wcs.
129
130 Returns
131 -------
132 finalBBox : `lsst.geom.Box2I`
133 Revised bounding box.
134 wcs : `lsst.afw.geom.SkyWcs`
135 Revised Wcs.
136 """
137 finalBBox = geom.Box2I(geom.Point2I(0, 0), bbox.getDimensions())
138 # shift the WCS by the same amount as the bbox; extra code is required
139 # because simply subtracting makes an Extent2I
140 pixPosOffset = geom.Extent2D(finalBBox.getMinX() - bbox.getMinX(),
141 finalBBox.getMinY() - bbox.getMinY())
142 wcs = wcs.copyAtShiftedPixelOrigin(pixPosOffset)
143 return finalBBox, wcs
144
145 def getSequentialPatchIndex(self, patchInfo):
146 """Return a single integer that uniquely identifies
147 the given patch within this tract.
148
149 Parameters
150 ----------
151 patchInfo : `lsst.skymap.PatchInfo`
152
153 Returns
154 -------
155 sequentialIndex : `int`
156 """
157 return self._tractBuilder.getSequentialPatchIndex(patchInfo)
158
160 """Return a single integer that uniquely identifies
161 the patch index within the tract.
162
163 Parameters
164 ----------
165 index : `lsst.skymap.Index2D`
166
167 Returns
168 -------
169 sequentialIndex : `int`
170 """
172
173 def getPatchIndexPair(self, sequentialIndex):
174 """Convert sequential index into patch index (x,y) pair.
175
176 Parameters
177 ----------
178 sequentialIndex : `int`
179
180 Returns
181 -------
182 x, y : `lsst.skymap.Index2D`
183 """
184 return self._tractBuilder.getPatchIndexPair(sequentialIndex)
185
186 def findPatch(self, coord):
187 """Find the patch containing the specified coord.
188
189 Parameters
190 ----------
191 coord : `lsst.geom.SpherePoint`
192 ICRS sky coordinate to search for.
193
194 Returns
195 -------
196 result : `lsst.skymap.PatchInfo`
197 PatchInfo of patch whose inner bbox contains the specified coord
198
199 Raises
200 ------
201 LookupError
202 Raised if coord is not in tract or we cannot determine the
203 pixel coordinate (which likely means the coord is off the tract).
204 """
205 try:
206 pixel = self.wcs.skyToPixel(coord)
208 # Point must be way off the tract
209 raise LookupError("Unable to determine pixel position for coordinate %s" % (coord,))
210 pixelInd = geom.Point2I(pixel)
211 if not self._bbox.contains(pixelInd):
212 raise LookupError("coord %s is not in tract %s" % (coord, self.tract_id))
213 # This should probably be the same as above because we only
214 # care about the INNER dimensions.
215 patchInd = tuple(int(pixelInd[i]/self.patch_inner_dimensions[i]) for i in range(2))
216 return self.getPatchInfo(patchInd)
217
218 def findPatchList(self, coordList):
219 """Find patches containing the specified list of coords.
220
221 Parameters
222 ----------
223 coordList : `list` of `lsst.geom.SpherePoint`
224 ICRS sky coordinates to search for.
225
226 Returns
227 -------
228 result : `list` of `lsst.skymap.PatchInfo`
229 List of PatchInfo for patches that contain, or may contain, the
230 specified region. The list will be empty if there is no overlap.
231
232 Notes
233 -----
234 **Warning:**
235
236 - This may give incorrect answers on regions that are larger than a
237 tract.
238
239 - This uses a naive algorithm that may find some patches that do not
240 overlap the region (especially if the region is not a rectangle
241 aligned along patch x,y).
242 """
243 box2D = geom.Box2D()
244 for coord in coordList:
245 try:
246 pixelPos = self.wcs.skyToPixel(coord)
248 # The point is so far off the tract that its pixel position
249 # cannot be computed.
250 continue
251 box2D.include(pixelPos)
252 bbox = geom.Box2I(box2D)
253 bbox.grow(self.getPatchBorder())
254 bbox.clip(self._bbox)
255 if bbox.isEmpty():
256 return ()
257
258 llPatchInd = tuple(int(bbox.getMin()[i]/self.patch_inner_dimensions[i]) for i in range(2))
259 urPatchInd = tuple(int(bbox.getMax()[i]/self.patch_inner_dimensions[i]) for i in range(2))
260 return tuple(self.getPatchInfo((xInd, yInd))
261 for xInd in range(llPatchInd[0], urPatchInd[0]+1)
262 for yInd in range(llPatchInd[1], urPatchInd[1]+1))
263
264 def getBBox(self):
265 """Get bounding box of tract (as an geom.Box2I)
266 """
267 return geom.Box2I(self._bbox)
268
269 bbox = property(getBBox)
270
271 def getCtrCoord(self):
272 """Get ICRS sky coordinate of center of tract
273 (as an lsst.geom.SpherePoint)
274 """
275 return self._ctrCoord
276
277 ctr_coord = property(getCtrCoord)
278
279 def getId(self):
280 """Get ID of tract
281 """
282 return self._id
283
284 tract_id = property(getId)
285
286 def getNumPatches(self):
287 """Get the number of patches in x, y.
288
289 Returns
290 -------
291 result : `lsst.skymap.Index2D`
292 The number of patches in x, y
293 """
294 return self._numPatches
295
296 num_patches = property(getNumPatches)
297
298 def getPatchBorder(self):
299 return self._tractBuilder.getPatchBorder()
300
301 patch_border = property(getPatchBorder)
302
303 def getPatchInfo(self, index):
304 """Return information for the specified patch.
305
306 Parameters
307 ----------
308 index : `typing.NamedTuple` ['x': `int`, 'y': `int`]
309 Index of patch, as a pair of ints;
310 or a sequential index as returned by getSequentialPatchIndex;
311 negative values are not supported.
312
313 Returns
314 -------
315 result : `lsst.skymap.PatchInfo`
316 The patch info for that index.
317
318 Raises
319 ------
320 IndexError
321 Raised if index is out of range.
322 """
323 return self._tractBuilder.getPatchInfo(index, self._wcs)
324
326 """Get dimensions of inner region of the patches (all are the same)
327 """
329
330 patch_inner_dimensions = property(getPatchInnerDimensions)
331
333 """Get minimum overlap of adjacent sky tracts.
334 """
335 return self._tractOverlap
336
337 tract_overlap = property(getTractOverlap)
338
339 def getVertexList(self):
340 """Get list of ICRS sky coordinates of vertices that define the
341 boundary of the inner region.
342
343 Notes
344 -----
345 **warning:** this is not a deep copy.
346 """
347 return self._vertexCoordList
348
349 vertex_list = property(getVertexList)
350
352 """Get inner on-sky region as a sphgeom.ConvexPolygon.
353 """
354 skyUnitVectors = [sp.getVector() for sp in self.getVertexList()]
355 return ConvexPolygon.convexHull(skyUnitVectors)
356
357 inner_sky_polygon = property(getInnerSkyPolygon)
358
360 """Get outer on-sky region as a sphgeom.ConvexPolygon
361 """
362 return makeSkyPolygonFromBBox(bbox=self.getBBox(), wcs=self.getWcs())
363
364 outer_sky_polygon = property(getOuterSkyPolygon)
365
366 def getWcs(self):
367 """Get WCS of tract.
368
369 Returns
370 -------
371 wcs : `lsst.afw.geom.SkyWcs`
372 The WCS of this tract
373 """
374 return self._wcs
375
376 wcs = property(getWcs)
377
378 def __str__(self):
379 return "TractInfo(id=%s)" % (self._id,)
380
381 def __repr__(self):
382 return "TractInfo(id=%s, ctrCoord=%s)" % (self._id, self._ctrCoord.getVector())
383
384 def __iter__(self):
385 xNum, yNum = self.getNumPatches()
386 for y in range(yNum):
387 for x in range(xNum):
388 yield self.getPatchInfo(Index2D(x=x, y=y))
389
390 def __len__(self):
391 xNum, yNum = self.getNumPatches()
392 return xNum*yNum
393
394 def __getitem__(self, index):
395 return self.getPatchInfo(index)
396
397 def contains(self, coord):
398 """Does this tract contain the coordinate?"""
399 try:
400 pixel = self.getWcs().skyToPixel(coord)
402 # Point must be way off the tract
403 return False
404 if not np.isfinite(pixel.getX()) or not np.isfinite(pixel.getY()):
405 # Point is definitely off the tract
406 return False
407 return self.getBBox().contains(geom.Point2I(pixel))
408
409
411 """Information for a tract specified explicitly.
412
413 A tract is placed at the explicitly defined coordinates, with the nominated
414 radius. The tracts are square (i.e., the radius is really a half-size).
415 """
416 def __init__(self, id, tractBuilder, ctrCoord, radius, tractOverlap, wcs):
417 # We don't want TractInfo setting the bbox on the basis of vertices,
418 # but on the radius.
419 vertexList = []
420 self._radius = radius
421 super(ExplicitTractInfo, self).__init__(id, tractBuilder, ctrCoord,
422 vertexList, tractOverlap, wcs)
423 # Shrink the box slightly to make sure the vertices are in the tract
424 bboxD = geom.BoxD(self.getBBox())
425 bboxD.grow(-0.001)
426 finalWcs = self.getWcs()
427 self._vertexCoordList_vertexCoordList = finalWcs.pixelToSky(bboxD.getCorners())
428
429 def _minimumBoundingBox(self, wcs):
430 """Calculate the minimum bounding box for the tract, given the WCS, and
431 the nominated radius.
432 """
433 bbox = geom.Box2D()
434 for i in range(4):
435 cornerCoord = self._ctrCoord.offset(i*90*geom.degrees, self._radius + self._tractOverlap)
436 pixPos = wcs.skyToPixel(cornerCoord)
437 bbox.include(pixPos)
438 return bbox
A class representing an angle.
Definition Angle.h:128
A floating-point coordinate rectangle geometry.
Definition Box.h:413
An integer coordinate rectangle.
Definition Box.h:55
Reports arguments outside the domain of an operation.
Definition Runtime.h:57
Reports errors that are due to events beyond the control of the program.
Definition Runtime.h:104
__init__(self, id, tractBuilder, ctrCoord, radius, tractOverlap, wcs)
Definition tractInfo.py:416
__init__(self, id, tractBuilder, ctrCoord, vertexCoordList, tractOverlap, wcs)
Definition tractInfo.py:82
getSequentialPatchIndexFromPair(self, index)
Definition tractInfo.py:159
getSequentialPatchIndex(self, patchInfo)
Definition tractInfo.py:145
_finalOrientation(self, bbox, wcs)
Definition tractInfo.py:117
getPatchIndexPair(self, sequentialIndex)
Definition tractInfo.py:173
findPatchList(self, coordList)
Definition tractInfo.py:218