26 from .patchInfo
import PatchInfo
28 __all__ = [
"TractInfo"]
31 """Information about a tract in a SkyMap sky pixelization
33 The tract is subdivided into rectangular patches. Each patch has the following properties:
34 - An inner region defined by an inner bounding. The inner regions of the patches exactly tile the tract,
35 and all inner regions have the same dimensions. The tract is made larger as required to make this work.
36 - An outer region defined by an outer bounding box. The outer region extends beyond the inner region
37 by patchBorder pixels in all directions, except there is no border at the edges of the tract.
38 Thus patches overlap each other but never extend off the tract. If you do not want any overlap
39 between adjacent patches then set patchBorder to 0.
40 - An index that consists of a pair of integers:
41 0 <= x index < numPatches[0]
42 0 <= y index < numPatches[1]
43 Patch 0,0 is at the minimum corner of the tract bounding box.
45 def __init__(self, id, patchInnerDimensions, patchBorder, ctrCoord, vertexCoordList, tractOverlap, wcs):
46 """Construct a TractInfo
48 @param[in] id: tract ID
49 @param[in] patchInnerDimensions: dimensions of inner region of patches (x,y pixels)
50 @param[in] patchBorder: overlap between adjacent patches (in pixels, one int)
51 @param[in] ctrCoord: sky coordinate of center of inner region of tract, as an afwCoord.Coord;
52 also used as the CRVAL for the WCS.
53 @param[in] vertexCoordList: list of sky coordinates (afwCoord.Coord)
54 of vertices that define the boundaries of the inner region
55 @param[in] tractOverlap: minimum overlap between adjacent sky tracts; an afwGeom.Angle;
56 this defines the minimum distance the tract extends beyond the inner region in all directions
57 @param[in,out] wcs: an afwImage.Wcs; the reference pixel will be shifted as required
58 so that the lower left-hand pixel (index 0,0) has pixel position 0.0, 0.0
61 - It is not enforced that ctrCoord is the center of vertexCoordList, but SkyMap relies on it
62 - vertexCoordList will likely become a geom SphericalConvexPolygon someday.
66 assert len(patchInnerDimensions) == 2
69 raise TypeError(
"patchInnerDimensions=%s; must be two ints" % (patchInnerDimensions,))
81 """Calculate the minimum bounding box for the tract, given the WCS
83 The bounding box is created in the frame of the supplied WCS,
84 so that it's OK if the coordinates are negative.
86 We compute the bounding box that holds all the vertices and the
92 vertexDeg = vertexCoord.getPosition(afwGeom.degrees)
94 minBBoxD.include(wcs.skyToPixel(vertexCoord))
97 angleIncr =
afwGeom.Angle(360.0, afwGeom.degrees) / float(numAngles)
98 for i
in range(numAngles):
99 offAngle = angleIncr * i
100 offCoord = vertexCoord.clone()
101 offCoord.offset(offAngle, halfOverlap)
102 pixPos = wcs.skyToPixel(offCoord)
103 minBBoxD.include(pixPos)
107 """Setup for patches of a particular size.
109 We grow the bounding box to hold an exact multiple of
110 the desired size (patchInnerDimensions), while keeping
111 the center roughly the same. We return the final
112 bounding box, and the number of patches in each dimension
115 @param minBBox Minimum bounding box for tract
116 @param wcs Wcs object
117 @return final bounding box, number of patches
120 bboxMin = bbox.getMin()
121 bboxDim = bbox.getDimensions()
124 num = (bboxDim[i] + innerDim - 1) // innerDim
125 deltaDim = (innerDim * num) - bboxDim[i]
127 bboxDim[i] = innerDim * num
128 bboxMin[i] -= deltaDim // 2
131 return bbox, numPatches
135 """Determine the final orientation
137 We offset everything so the lower-left corner is at 0,0
138 and compute the final Wcs.
140 @param bbox Current bounding box
141 @param wcs Current Wcs
142 @return revised bounding box, revised Wcs
148 finalBBox.getMinY() - bbox.getMinY())
149 wcs.shiftReferencePixel(pixPosOffset)
150 return finalBBox, wcs
154 """Find the patch containing the specified coord
156 @param[in] coord: sky coordinate (afwCoord.Coord)
157 @return PatchInfo of patch whose inner bbox contains the specified coord
159 @raise LookupError if coord is not in tract
161 @note This routine will be more efficient if coord is ICRS.
164 if not self.
getBBox().contains(pixelInd):
165 raise LookupError(
"coord %s is not in tract %s" % (coord, self.
getId()))
170 """Find patches containing the specified list of coords
172 @param[in] coordList: list of sky coordinates (afwCoord.Coord)
173 @return list of PatchInfo for patches that contain, or may contain, the specified region.
174 The list will be empty if there is no overlap.
177 * This may give incorrect answers on regions that are larger than a tract
178 * This uses a naive algorithm that may find some patches that do not overlap the region
179 (especially if the region is not a rectangle aligned along patch x,y).
182 for coord
in coordList:
184 pixelPos = self.
getWcs().skyToPixel(coord.toIcrs())
188 box2D.include(pixelPos)
198 for xInd
in range(llPatchInd[0], urPatchInd[0]+1)
199 for yInd
in range(llPatchInd[1], urPatchInd[1]+1))
202 """Get bounding box of tract (as an afwGeom.Box2I)
207 """Get sky coordinate of center of tract (as an afwCoord.Coord)
217 """Get the number of patches in x, y
219 @return the number of patches in x, y
226 @return patch border (pixels)
231 """Return information for the specified patch
233 @param[in] index: index of patch, as a pair of ints
234 @return patch info, an instance of PatchInfo
236 @raise IndexError if index is out of range
240 raise IndexError(
"Patch index %s is not in range [0-%d, 0-%d]" % \
244 if not self._bbox.contains(innerBBox):
246 "Bug: patch index %s valid but inner bbox=%s not contained in tract bbox=%s" % \
247 (index, innerBBox, self._bbox))
250 outerBBox.clip(self._bbox)
253 innerBBox = innerBBox,
254 outerBBox = outerBBox,
258 """Get dimensions of inner region of the patches (all are the same)
260 @return dimensions of inner region of the patches (as an afwGeom Extent2I)
265 """Get minimum overlap of adjacent sky tracts
267 @return minimum overlap between adjacent sky tracts, as an afwGeom Angle
272 """Get list of sky coordinates of vertices that define the boundary of the inner region
274 @warning: this is not a deep copy
275 @warning vertexCoordList will likely become a geom SphericalConvexPolygon someday.
282 @warning: this is not a deep copy
287 return "TractInfo(id=%s)" % (self.
_id,)
290 return "TractInfo(id=%s, ctrCoord=%s)" % (self.
_id, self._ctrCoord.getVector())
294 for y
in range(yNum):
295 for x
in range(xNum):
307 """Information for a tract specified explicitly
309 A tract is placed at the explicitly defined coordinates, with the nominated
310 radius. The tracts are square (i.e., the radius is really a half-size).
312 def __init__(self, ident, patchInnerDimensions, patchBorder, ctrCoord, radius, tractOverlap, wcs):
315 super(ExplicitTractInfo, self).
__init__(ident, patchInnerDimensions, patchBorder, ctrCoord,
316 vertexList, tractOverlap, wcs)
319 """The minimum bounding box is calculated using the nominated radius"""
322 coord = self._ctrCoord.clone()
324 pixPos = wcs.skyToPixel(coord)
An integer coordinate rectangle.
def getPatchInnerDimensions
A floating-point coordinate rectangle geometry.