LSST Applications g0f08755f38+82efc23009,g12f32b3c4e+e7bdf1200e,g1653933729+a8ce1bb630,g1a0ca8cf93+50eff2b06f,g28da252d5a+52db39f6a5,g2bbee38e9b+37c5a29d61,g2bc492864f+37c5a29d61,g2cdde0e794+c05ff076ad,g3156d2b45e+41e33cbcdc,g347aa1857d+37c5a29d61,g35bb328faa+a8ce1bb630,g3a166c0a6a+37c5a29d61,g3e281a1b8c+fb992f5633,g414038480c+7f03dfc1b0,g41af890bb2+11b950c980,g5fbc88fb19+17cd334064,g6b1c1869cb+12dd639c9a,g781aacb6e4+a8ce1bb630,g80478fca09+72e9651da0,g82479be7b0+04c31367b4,g858d7b2824+82efc23009,g9125e01d80+a8ce1bb630,g9726552aa6+8047e3811d,ga5288a1d22+e532dc0a0b,gae0086650b+a8ce1bb630,gb58c049af0+d64f4d3760,gc28159a63d+37c5a29d61,gcf0d15dbbd+2acd6d4d48,gd7358e8bfb+778a810b6e,gda3e153d99+82efc23009,gda6a2b7d83+2acd6d4d48,gdaeeff99f8+1711a396fd,ge2409df99d+6b12de1076,ge79ae78c31+37c5a29d61,gf0baf85859+d0a5978c5a,gf3967379c6+4954f8c433,gfb92a5be7c+82efc23009,gfec2e1e490+2aaed99252,w.2024.46
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
26from deprecated.sphinx import deprecated
27
29import lsst.geom as geom
30from lsst.sphgeom import ConvexPolygon, Box
31
32from .detail import makeSkyPolygonFromBBox, Index2D
33
34
36 """Information about a tract in a SkyMap sky pixelization
37
38 Parameters
39 ----------
40 id : `int`
41 tract ID
42 tractBuilder : Subclass of `lsst.skymap.BaseTractBuilder`
43 Object used to compute patch geometry.
44 ctrCoord : `lsst.geom.SpherePoint`
45 ICRS sky coordinate of center of inner region of tract; also used as
46 the CRVAL for the WCS.
47 vertexCoordList : `list` of `lsst.geom.SpherePoint`
48 Vertices that define the boundaries of the inner region.
49 tractOverlap : `lsst.geom.Angle`
50 Minimum overlap between adjacent sky tracts; this defines the minimum
51 distance the tract extends beyond the inner region in all directions.
52 wcs : `lsst.afw.image.SkyWcs`
53 WCS for tract. The reference pixel will be shifted as required so that
54 the lower left-hand pixel (index 0,0) has pixel position 0.0, 0.0.
55 innerBoxCorners : `list` [`lsst.sphgeom.LonLat`], optional
56 If set then the ``inner_sky_region`` will be a `lsst.sphgeom.Box` with
57 these corners as opposed to a `lsst.sphgeom.ConvexPolygon` built from
58 the ``vertex_list``.
59
60 Notes
61 -----
62 The tract is subdivided into rectangular patches. Each patch has the
63 following properties:
64
65 - An inner region defined by an inner bounding box. The inner regions of
66 the patches exactly tile the tract, and all inner regions have the same
67 dimensions. The tract is made larger as required to make this work.
68
69 - An outer region defined by an outer bounding box. The outer region
70 extends beyond the inner region by patchBorder pixels in all directions,
71 except there is no border at the edges of the tract.
72 Thus patches overlap each other but never extend off the tract.
73 If you do not want any overlap between adjacent patches then set
74 patchBorder to 0.
75
76 - An index that consists of a pair of integers:
77
78 * 0 <= x index < numPatches[0]
79
80 * 0 <= y index < numPatches[1]
81
82 Patch 0,0 is at the minimum corner of the tract bounding box.
83
84 - It is not enforced that ctrCoord is the center of vertexCoordList, but
85 SkyMap relies on it.
86 """
87 def __init__(self, id, tractBuilder, ctrCoord, vertexCoordList, tractOverlap, wcs, innerBoxCorners=None):
88 self._id = id
89 self._ctrCoord = ctrCoord
90 self._vertexCoordList = tuple(vertexCoordList)
91 self._tractOverlap = tractOverlap
92 self._tractBuilder = tractBuilder
93 self._innerBoxCorners = innerBoxCorners
94
95 minBBox = self._minimumBoundingBox(wcs)
96 initialBBox, self._numPatches = self._tractBuilder.setupPatches(minBBox, wcs)
97 self._bbox, self._wcs = self._finalOrientation(initialBBox, wcs)
98
99 def _minimumBoundingBox(self, wcs):
100 """Calculate the minimum bounding box for the tract, given the WCS.
101
102 The bounding box is created in the frame of the supplied WCS,
103 so that it's OK if the coordinates are negative.
104
105 We compute the bounding box that holds all the vertices and the
106 desired overlap.
107 """
108 minBBoxD = geom.Box2D()
109 halfOverlap = self._tractOverlap / 2.0
110 for vertexCoord in self._vertexCoordList:
111 if self._tractOverlap == 0:
112 minBBoxD.include(wcs.skyToPixel(vertexCoord))
113 else:
114 numAngles = 24
115 angleIncr = geom.Angle(360.0, geom.degrees) / float(numAngles)
116 for i in range(numAngles):
117 offAngle = angleIncr * i
118 offCoord = vertexCoord.offset(offAngle, halfOverlap)
119 pixPos = wcs.skyToPixel(offCoord)
120 minBBoxD.include(pixPos)
121 return minBBoxD
122
123 def _finalOrientation(self, bbox, wcs):
124 """Determine the final orientation
125
126 We offset everything so the lower-left corner is at 0,0
127 and compute the final Wcs.
128
129 Parameters
130 ----------
131 bbox : `lsst.geom.Box2I`
132 Current bounding box.
133 wcs : `lsst.afw.geom.SkyWcs
134 Current Wcs.
135
136 Returns
137 -------
138 finalBBox : `lsst.geom.Box2I`
139 Revised bounding box.
140 wcs : `lsst.afw.geom.SkyWcs`
141 Revised Wcs.
142 """
143 finalBBox = geom.Box2I(geom.Point2I(0, 0), bbox.getDimensions())
144 # shift the WCS by the same amount as the bbox; extra code is required
145 # because simply subtracting makes an Extent2I
146 pixPosOffset = geom.Extent2D(finalBBox.getMinX() - bbox.getMinX(),
147 finalBBox.getMinY() - bbox.getMinY())
148 wcs = wcs.copyAtShiftedPixelOrigin(pixPosOffset)
149 return finalBBox, wcs
150
151 def getSequentialPatchIndex(self, patchInfo):
152 """Return a single integer that uniquely identifies
153 the given patch within this tract.
154
155 Parameters
156 ----------
157 patchInfo : `lsst.skymap.PatchInfo`
158
159 Returns
160 -------
161 sequentialIndex : `int`
162 """
163 return self._tractBuilder.getSequentialPatchIndex(patchInfo)
164
166 """Return a single integer that uniquely identifies
167 the patch index within the tract.
168
169 Parameters
170 ----------
171 index : `lsst.skymap.Index2D`
172
173 Returns
174 -------
175 sequentialIndex : `int`
176 """
178
179 def getPatchIndexPair(self, sequentialIndex):
180 """Convert sequential index into patch index (x,y) pair.
181
182 Parameters
183 ----------
184 sequentialIndex : `int`
185
186 Returns
187 -------
188 x, y : `lsst.skymap.Index2D`
189 """
190 return self._tractBuilder.getPatchIndexPair(sequentialIndex)
191
192 def findPatch(self, coord):
193 """Find the patch containing the specified coord.
194
195 Parameters
196 ----------
197 coord : `lsst.geom.SpherePoint`
198 ICRS sky coordinate to search for.
199
200 Returns
201 -------
202 result : `lsst.skymap.PatchInfo`
203 PatchInfo of patch whose inner bbox contains the specified coord
204
205 Raises
206 ------
207 LookupError
208 Raised if coord is not in tract or we cannot determine the
209 pixel coordinate (which likely means the coord is off the tract).
210 """
211 try:
212 pixel = self.wcs.skyToPixel(coord)
214 # Point must be way off the tract
215 raise LookupError("Unable to determine pixel position for coordinate %s" % (coord,))
216 pixelInd = geom.Point2I(pixel)
217 if not self._bbox.contains(pixelInd):
218 raise LookupError("coord %s is not in tract %s" % (coord, self.tract_id))
219 # This should probably be the same as above because we only
220 # care about the INNER dimensions.
221 patchInd = tuple(int(pixelInd[i]/self.patch_inner_dimensions[i]) for i in range(2))
222 return self.getPatchInfo(patchInd)
223
224 def findPatchList(self, coordList):
225 """Find patches containing the specified list of coords.
226
227 Parameters
228 ----------
229 coordList : `list` of `lsst.geom.SpherePoint`
230 ICRS sky coordinates to search for.
231
232 Returns
233 -------
234 result : `list` of `lsst.skymap.PatchInfo`
235 List of PatchInfo for patches that contain, or may contain, the
236 specified region. The list will be empty if there is no overlap.
237
238 Notes
239 -----
240 **Warning:**
241
242 - This may give incorrect answers on regions that are larger than a
243 tract.
244
245 - This uses a naive algorithm that may find some patches that do not
246 overlap the region (especially if the region is not a rectangle
247 aligned along patch x,y).
248 """
249 box2D = geom.Box2D()
250 for coord in coordList:
251 try:
252 pixelPos = self.wcs.skyToPixel(coord)
254 # The point is so far off the tract that its pixel position
255 # cannot be computed.
256 continue
257 box2D.include(pixelPos)
258 bbox = geom.Box2I(box2D)
259 bbox.grow(self.getPatchBorder())
260 bbox.clip(self._bbox)
261 if bbox.isEmpty():
262 return ()
263
264 llPatchInd = tuple(int(bbox.getMin()[i]/self.patch_inner_dimensions[i]) for i in range(2))
265 urPatchInd = tuple(int(bbox.getMax()[i]/self.patch_inner_dimensions[i]) for i in range(2))
266 return tuple(self.getPatchInfo((xInd, yInd))
267 for xInd in range(llPatchInd[0], urPatchInd[0]+1)
268 for yInd in range(llPatchInd[1], urPatchInd[1]+1))
269
270 def getBBox(self):
271 """Get bounding box of tract (as an geom.Box2I)
272 """
273 return geom.Box2I(self._bbox)
274
275 bbox = property(getBBox)
276
277 def getCtrCoord(self):
278 """Get ICRS sky coordinate of center of tract
279 (as an lsst.geom.SpherePoint)
280 """
281 return self._ctrCoord
282
283 ctr_coord = property(getCtrCoord)
284
285 def getId(self):
286 """Get ID of tract
287 """
288 return self._id
289
290 tract_id = property(getId)
291
292 def getNumPatches(self):
293 """Get the number of patches in x, y.
294
295 Returns
296 -------
297 result : `lsst.skymap.Index2D`
298 The number of patches in x, y
299 """
300 return self._numPatches
301
302 num_patches = property(getNumPatches)
303
304 def getPatchBorder(self):
305 return self._tractBuilder.getPatchBorder()
306
307 patch_border = property(getPatchBorder)
308
309 def getPatchInfo(self, index):
310 """Return information for the specified patch.
311
312 Parameters
313 ----------
314 index : `typing.NamedTuple` ['x': `int`, 'y': `int`]
315 Index of patch, as a pair of ints;
316 or a sequential index as returned by getSequentialPatchIndex;
317 negative values are not supported.
318
319 Returns
320 -------
321 result : `lsst.skymap.PatchInfo`
322 The patch info for that index.
323
324 Raises
325 ------
326 IndexError
327 Raised if index is out of range.
328 """
329 return self._tractBuilder.getPatchInfo(index, self._wcs)
330
332 """Get dimensions of inner region of the patches (all are the same)
333 """
335
336 patch_inner_dimensions = property(getPatchInnerDimensions)
337
339 """Get minimum overlap of adjacent sky tracts.
340 """
341 return self._tractOverlap
342
343 tract_overlap = property(getTractOverlap)
344
345 def getVertexList(self):
346 """Get list of ICRS sky coordinates of vertices that define the
347 boundary of the inner region.
348
349 Notes
350 -----
351 **warning:** this is not a deep copy.
352 """
353 return self._vertexCoordList
354
355 vertex_list = property(getVertexList)
356
357 # TODO: Remove with DM-44799
358 @deprecated(reason="getInnerSkyPolygon()/inner_sky_polygon has been deprecated in favor of "
359 "inner_sky_region, and will be removed after v28.",
360 category=FutureWarning, version=28)
362 """Get inner on-sky region as a sphgeom.ConvexPolygon.
363 """
364 skyUnitVectors = [sp.getVector() for sp in self.getVertexList()]
365 return ConvexPolygon.convexHull(skyUnitVectors)
366
367 inner_sky_polygon = property(getInnerSkyPolygon)
368
370 """Get inner on-sky region.
371 """
372 if self._innerBoxCorners:
373 return Box(point1=self._innerBoxCorners[0], point2=self._innerBoxCorners[1])
374 else:
375 skyUnitVectors = [sp.getVector() for sp in self.getVertexList()]
376 return ConvexPolygon.convexHull(skyUnitVectors)
377
378 inner_sky_region = property(getInnerSkyRegion)
379
381 """Get outer on-sky region as a sphgeom.ConvexPolygon
382 """
383 return makeSkyPolygonFromBBox(bbox=self.getBBox(), wcs=self.getWcs())
384
385 outer_sky_polygon = property(getOuterSkyPolygon)
386
387 def getWcs(self):
388 """Get WCS of tract.
389
390 Returns
391 -------
392 wcs : `lsst.afw.geom.SkyWcs`
393 The WCS of this tract
394 """
395 return self._wcs
396
397 wcs = property(getWcs)
398
399 def __str__(self):
400 return "TractInfo(id=%s)" % (self._id,)
401
402 def __repr__(self):
403 return "TractInfo(id=%s, ctrCoord=%s)" % (self._id, self._ctrCoord.getVector())
404
405 def __iter__(self):
406 xNum, yNum = self.getNumPatches()
407 for y in range(yNum):
408 for x in range(xNum):
409 yield self.getPatchInfo(Index2D(x=x, y=y))
410
411 def __len__(self):
412 xNum, yNum = self.getNumPatches()
413 return xNum*yNum
414
415 def __getitem__(self, index):
416 return self.getPatchInfo(index)
417
418 def contains(self, coord):
419 """Does this tract contain the coordinate?"""
420 try:
421 pixel = self.getWcs().skyToPixel(coord)
423 # Point must be way off the tract
424 return False
425 if not np.isfinite(pixel.getX()) or not np.isfinite(pixel.getY()):
426 # Point is definitely off the tract
427 return False
428 return self.getBBox().contains(geom.Point2I(pixel))
429
430
432 """Information for a tract specified explicitly.
433
434 A tract is placed at the explicitly defined coordinates, with the nominated
435 radius. The tracts are square (i.e., the radius is really a half-size).
436
437 Parameters
438 ----------
439 id : : `int`
440 tract ID
441 tractBuilder : Subclass of `lsst.skymap.BaseTractBuilder`
442 Object used to compute patch geometry.
443 ctrCoord : `lsst.geom.SpherePoint`
444 ICRS sky coordinate of center of inner region of tract; also used as
445 the CRVAL for the WCS.
446 radius : `lsst.geom.Angle`
447 Radius of the tract.
448 tractOverlap : `lsst.geom.Angle`
449 Minimum overlap between adjacent sky tracts; this defines the minimum
450 distance the tract extends beyond the inner region in all directions.
451 wcs : `lsst.afw.image.SkyWcs`
452 WCS for tract. The reference pixel will be shifted as required so that
453 the lower left-hand pixel (index 0,0) has pixel position 0.0, 0.0.
454 innerBoxCorners : `list` [`lsst.sphgeom.LonLat`], optional
455 If set then the ``inner_sky_region`` will be a `lsst.sphgeom.Box` with
456 these corners as oppsed to a `lsst.sphgeom.ConvexPolygon` built from
457 the ``vertex_list``.
458 """
459 def __init__(self, id, tractBuilder, ctrCoord, radius, tractOverlap, wcs, innerBoxCorners=None):
460 # We don't want TractInfo setting the bbox on the basis of vertices,
461 # but on the radius.
462 vertexList = []
463 self._radius = radius
464 super(ExplicitTractInfo, self).__init__(
465 id,
466 tractBuilder,
467 ctrCoord,
468 vertexList,
469 tractOverlap,
470 wcs,
471 innerBoxCorners=innerBoxCorners,
472 )
473 # Shrink the box slightly to make sure the vertices are in the tract
474 bboxD = geom.BoxD(self.getBBox())
475 bboxD.grow(-0.001)
476 finalWcs = self.getWcs()
477 self._vertexCoordList_vertexCoordList = finalWcs.pixelToSky(bboxD.getCorners())
478
479 def _minimumBoundingBox(self, wcs):
480 """Calculate the minimum bounding box for the tract, given the WCS, and
481 the nominated radius.
482 """
483 bbox = geom.Box2D()
484 for i in range(4):
485 cornerCoord = self._ctrCoord.offset(i*90*geom.degrees, self._radius + self._tractOverlap)
486 pixPos = wcs.skyToPixel(cornerCoord)
487 bbox.include(pixPos)
488 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, innerBoxCorners=None)
Definition tractInfo.py:459
getSequentialPatchIndexFromPair(self, index)
Definition tractInfo.py:165
getSequentialPatchIndex(self, patchInfo)
Definition tractInfo.py:151
_finalOrientation(self, bbox, wcs)
Definition tractInfo.py:123
getPatchIndexPair(self, sequentialIndex)
Definition tractInfo.py:179
findPatchList(self, coordList)
Definition tractInfo.py:224
__init__(self, id, tractBuilder, ctrCoord, vertexCoordList, tractOverlap, wcs, innerBoxCorners=None)
Definition tractInfo.py:87
Box represents a rectangle in spherical coordinate space that contains its boundary.
Definition Box.h:62