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
tractBuilder.py
Go to the documentation of this file.
1# LSST Data Management System
2# Copyright 2008, 2009, 2010 LSST Corporation.
3#
4# This product includes software developed by the
5# LSST Project (http://www.lsst.org/).
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the LSST License Statement and
18# the GNU General Public License along with this program. If not,
19# see <http://www.lsstcorp.org/LegalNotices/>.
20#
21__all__ = ["tractBuilderRegistry",
22 "BaseTractBuilderConfig", "BaseTractBuilder",
23 "LegacyTractBuilderConfig", "LegacyTractBuilder",
24 "CellTractBuilderConfig", "CellTractBuilder"]
25
26import abc
27import numbers
28import struct
29from collections.abc import Iterable
30
31import lsst.pex.config as pexConfig
32import lsst.geom as geom
33from .patchInfo import PatchInfo
34from .detail import Index2D
35
36
37class BaseTractBuilderConfig(pexConfig.Config):
38 """Configuration that is to be shared amongst all tract builders."""
39 pass
40
41
42class BaseTractBuilder(metaclass=abc.ABCMeta):
43 """Base class for algorithms that define patches within the tract.
44
45 Parameters
46 ----------
47 config : `lsst.pexConfig.Config`
48 Input for configuring the algorithm
49 """
50 def __init__(self, config):
51 self.config = config
52
53 def setupPatches(self, minBBox, wcs):
54 """Set up the patches of a particular size in a tract.
55
56 We grow the tract bounding box to hold an exact multiple of
57 the desired size (patchInnerDimensions or
58 numCellsPerPatchInner*cellInnerDimensions), while keeping
59 the center roughly the same. We return the final tract
60 bounding box, and the number of patches in each dimension
61 (as an Index2D).
62
63 Parameters
64 ----------
65 minBBox : `lsst.geom.Box2I`
66 Minimum bounding box for tract.
67 wcs : `lsst.afw.geom.SkyWcs`
68 Wcs object.
69
70 Returns
71 -------
72 bbox : `lsst.geom.Box2I`
73 final bounding box, number of patches.
74 numPatches : `lsst.skymap.Index2D`
75 """
76 bbox = geom.Box2I(minBBox)
77 bboxMin = bbox.getMin()
78 bboxDim = bbox.getDimensions()
79 numPatchesList = [0, 0]
80 for i, innerDim in enumerate(self._patchInnerDimensions):
81 num = (bboxDim[i] + innerDim - 1) // innerDim # round up
82 deltaDim = (innerDim*num) - bboxDim[i]
83 if deltaDim > 0:
84 bboxDim[i] = innerDim * num
85 bboxMin[i] -= deltaDim // 2
86 numPatchesList[i] = num
87 numPatches = Index2D(*numPatchesList)
88 bbox = geom.Box2I(bboxMin, bboxDim)
89 self._numPatches = numPatches
90 # The final tract BBox starts at zero.
91 self._tractBBox = geom.Box2I(geom.Point2I(0, 0), bbox.getDimensions())
92 self._initialized = True
93
94 return bbox, numPatches
95
96 def getPatchBorder(self):
97 return self._patchBorder
98
99 @abc.abstractmethod
100 def getPatchInfo(self, index, tractWcs):
101 """Return information for the specified patch.
102
103 Parameters
104 ----------
105 index : `lsst.skymap.Index2D` or `~collections.abc.Iterable` of 2 `int`
106 Index of patch, as Index2D or pair of ints;
107 or a sequential index as returned by getSequentialPatchIndex;
108 negative values are not supported.
109 tractWcs : `lsst.afw.geom.SkyWcs`
110 WCS associated with the tract.
111
112 Returns
113 -------
114 result : `lsst.skymap.PatchInfo`
115 The patch info for that index.
116
117 Raises
118 ------
119 IndexError
120 Raised if index is out of range.
121 """
122 raise NotImplementedError("Must be implemented by a subclass")
123
125 """Get dimensions of inner region of the patches (all are the same)
126 """
127 return self._patchInnerDimensions
128
129 def getSequentialPatchIndex(self, patchInfo):
130 """Return a single integer that uniquely identifies
131 the given patch within this tract.
132
133 Parameters
134 ----------
135 patchInfo : `lsst.skymap.PatchInfo`
136
137 Returns
138 -------
139 sequentialIndex : `int`
140 """
141 index = patchInfo.getIndex()
142 return self.getSequentialPatchIndexFromPair(index)
143
145 """Return a single integer that uniquely identifies
146 the patch index within the tract.
147
148 Parameters
149 ----------
150 index : `lsst.skymap.Index2D` or `~collections.abc.Iterable` of 2 `int`
151
152 Returns
153 -------
154 sequentialIndex : `int`
155 """
156 if isinstance(index, Index2D):
157 _index = index
158 else:
159 if not isinstance(index, Iterable):
160 raise ValueError("Input index is not an iterable.")
161 if len(index) != 2:
162 raise ValueError("Input index does not have two values.")
163 _index = Index2D(*index)
164 nx, ny = self._numPatches
165 return nx*_index.y + _index.x
166
167 def getPatchIndexPair(self, sequentialIndex):
168 """Convert sequential index into patch index (x,y) pair.
169
170 Parameters
171 ----------
172 sequentialIndex : `int`
173
174 Returns
175 -------
176 x, y : `lsst.skymap.Index2D`
177 """
178 nx, ny = self._numPatches
179 x = sequentialIndex % nx
180 y = sequentialIndex // nx
181 return Index2D(x=x, y=y)
182
183 @abc.abstractmethod
184 def getPackedConfig(self, config):
185 """Get a packed config suitable for using in a sha1.
186
187 Parameters
188 ----------
189 config : `lsst.skymap.BaseTractBuilderConfig`
190
191 Returns
192 -------
193 configPacked : `bytes`
194 """
195 raise NotImplementedError("Must be implemented by a subclass")
196
197
199 patchInnerDimensions = pexConfig.ListField(
200 doc="dimensions of inner region of patches (x,y pixels)",
201 dtype=int,
202 length=2,
203 default=(4000, 4000),
204 )
205 patchBorder = pexConfig.Field(
206 doc="border between patch inner and outer bbox (pixels)",
207 dtype=int,
208 default=100,
209 )
210
211
213 ConfigClass = LegacyTractBuilderConfig
214
215 def __init__(self, config):
216 super().__init__(config)
217
219 for val in config.patchInnerDimensions))
220 self._patchBorder = config.patchBorder
222
223 def getPatchInfo(self, index, tractWcs):
224 # This should always be initialized
225 if not self._initialized_initialized:
226 raise RuntimeError("Programmer error; this should always be initialized.")
227 if isinstance(index, Index2D):
228 _index = index
229 else:
230 if isinstance(index, numbers.Number):
231 _index = self.getPatchIndexPair(index)
232 else:
233 _index = Index2D(*index)
234 if (not 0 <= _index.x < self._numPatches.x) \
235 or (not 0 <= _index.y < self._numPatches.y):
236 raise IndexError("Patch index %s is not in range [0-%d, 0-%d]" %
237 (_index, self._numPatches.x - 1, self._numPatches.y - 1))
238 innerMin = geom.Point2I(*[_index[i] * self._patchInnerDimensions_patchInnerDimensions[i] for i in range(2)])
240 if not self._tractBBox_tractBBox.contains(innerBBox):
241 raise RuntimeError(
242 "Bug: patch index %s valid but inner bbox=%s not contained in tract bbox=%s" %
243 (_index, innerBBox, self._tractBBox_tractBBox))
244 outerBBox = geom.Box2I(innerBBox)
245 outerBBox.grow(self.getPatchBorder())
246 outerBBox.clip(self._tractBBox_tractBBox)
247 return PatchInfo(
248 index=_index,
249 innerBBox=innerBBox,
250 outerBBox=outerBBox,
251 sequentialIndex=self.getSequentialPatchIndexFromPair(_index),
252 tractWcs=tractWcs
253 )
254
255 def getPackedConfig(self, config):
256 subConfig = config.tractBuilder[config.tractBuilder.name]
257 configPacked = struct.pack(
258 "<iiidd3sd",
259 subConfig.patchInnerDimensions[0],
260 subConfig.patchInnerDimensions[1],
261 subConfig.patchBorder,
262 config.tractOverlap,
263 config.pixelScale,
264 config.projection.encode('ascii'),
265 config.rotation
266 )
267
268 return configPacked
269
270
272 cellInnerDimensions = pexConfig.ListField(
273 doc="dimensions of inner region of cells (x,y pixels)",
274 dtype=int,
275 length=2,
276 default=(150, 150),
277 )
278 cellBorder = pexConfig.Field(
279 doc="Border between cell inner and outer bbox (pixels)",
280 dtype=int,
281 default=50,
282 )
283 numCellsPerPatchInner = pexConfig.Field(
284 doc="Number of cells per inner patch.",
285 dtype=int,
286 default=20,
287 )
288 numCellsInPatchBorder = pexConfig.Field(
289 doc="Number of cells in the patch border (outside the inner patch region).",
290 dtype=int,
291 default=1,
292 )
293
294 def validate(self):
295 if len(self.cellInnerDimensions) != 2:
296 raise ValueError("cellInnerDimensions must be 2 ints.")
297
298 if self.cellInnerDimensions[0] != self.cellInnerDimensions[1]:
299 raise ValueError("cellInnerDimensions must be equal (for square cells).")
300
301
303 ConfigClass = CellTractBuilderConfig
304
305 def __init__(self, config):
306 super().__init__(config)
307
309 for val in config.cellInnerDimensions))
310 self._cellBorder = config.cellBorder
311 self._numCellsPerPatchInner = config.numCellsPerPatchInner
312 self._numCellsInPatchBorder = config.numCellsInPatchBorder
314 for val in config.cellInnerDimensions))
315 # The patch border is the number of cells in the border + the cell
316 # border.
317 self._patchBorder = config.numCellsInPatchBorder*config.cellInnerDimensions[0] + self._cellBorder
319
320 def getPatchInfo(self, index, tractWcs):
321 # This should always be initialized
322 if not self._initialized_initialized:
323 raise RuntimeError("Programmer error; this should always be initialized.")
324 if isinstance(index, Index2D):
325 _index = index
326 else:
327 if isinstance(index, numbers.Number):
328 _index = self.getPatchIndexPair(index)
329 else:
330 _index = Index2D(*index)
331 if (not 0 <= _index.x < self._numPatches.x) \
332 or (not 0 <= _index.y < self._numPatches.y):
333 raise IndexError("Patch index %s is not in range [0-%d, 0-%d]" %
334 (_index, self._numPatches.x - 1, self._numPatches.y - 1))
335 innerMin = geom.Point2I(*[_index[i]*self._patchInnerDimensions_patchInnerDimensions[i] for i in range(2)])
337 if not self._tractBBox_tractBBox.contains(innerBBox):
338 raise RuntimeError(
339 "Bug: patch index %s valid but inner bbox=%s not contained in tract bbox=%s" %
340 (_index, innerBBox, self._tractBBox_tractBBox))
341 outerBBox = geom.Box2I(innerBBox)
342 outerBBox.grow(self.getPatchBorder())
343 # We do not clip the patch for cell-based tracts.
344 return PatchInfo(
345 index=_index,
346 innerBBox=innerBBox,
347 outerBBox=outerBBox,
348 sequentialIndex=self.getSequentialPatchIndexFromPair(_index),
349 tractWcs=tractWcs,
350 cellInnerDimensions=self._cellInnerDimensions,
351 cellBorder=self._cellBorder,
352 numCellsPerPatchInner=self._numCellsPerPatchInner,
353 numCellsInPatchBorder=self._numCellsInPatchBorder
354 )
355
356 def getPackedConfig(self, config):
357 subConfig = config.tractBuilder[config.tractBuilder.name]
358 configPacked = struct.pack(
359 "<iiiiidd3sd",
360 subConfig.cellInnerDimensions[0],
361 subConfig.cellInnerDimensions[1],
362 subConfig.cellBorder,
363 subConfig.numCellsPerPatchInner,
364 subConfig.numCellsInPatchBorder,
365 config.tractOverlap,
366 config.pixelScale,
367 config.projection.encode('ascii'),
368 config.rotation
369 )
370
371 return configPacked
372
373
374tractBuilderRegistry = pexConfig.makeRegistry(
375 doc="A registry of Tract Builders (subclasses of BaseTractBuilder)",
376)
377
378tractBuilderRegistry.register("legacy", LegacyTractBuilder)
379tractBuilderRegistry.register("cells", CellTractBuilder)
An integer coordinate rectangle.
Definition Box.h:55