LSST Applications g0b6bd0c080+a72a5dd7e6,g1182afd7b4+2a019aa3bb,g17e5ecfddb+2b8207f7de,g1d67935e3f+06cf436103,g38293774b4+ac198e9f13,g396055baef+6a2097e274,g3b44f30a73+6611e0205b,g480783c3b1+98f8679e14,g48ccf36440+89c08d0516,g4b93dc025c+98f8679e14,g5c4744a4d9+a302e8c7f0,g613e996a0d+e1c447f2e0,g6c8d09e9e7+25247a063c,g7271f0639c+98f8679e14,g7a9cd813b8+124095ede6,g9d27549199+a302e8c7f0,ga1cf026fa3+ac198e9f13,ga32aa97882+7403ac30ac,ga786bb30fb+7a139211af,gaa63f70f4e+9994eb9896,gabf319e997+ade567573c,gba47b54d5d+94dc90c3ea,gbec6a3398f+06cf436103,gc6308e37c7+07dd123edb,gc655b1545f+ade567573c,gcc9029db3c+ab229f5caf,gd01420fc67+06cf436103,gd877ba84e5+06cf436103,gdb4cecd868+6f279b5b48,ge2d134c3d5+cc4dbb2e3f,ge448b5faa6+86d1ceac1d,gecc7e12556+98f8679e14,gf3ee170dca+25247a063c,gf4ac96e456+ade567573c,gf9f5ea5b4d+ac198e9f13,gff490e6085+8c2580be5c,w.2022.27
LSST Data Management Base Package
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.configconfig = 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
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 = numPatches
90 # The final tract BBox starts at zero.
91 self._tractBBox_tractBBox = geom.Box2I(geom.Point2I(0, 0), bbox.getDimensions())
92 self._initialized_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 `Iterable` [`int`, `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 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.getSequentialPatchIndexFromPairgetSequentialPatchIndexFromPair(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 `Iterable` [`int`, `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_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_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 ----------
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
218 self._patchInnerDimensions_patchInnerDimensions = geom.Extent2I(*(val
219 for val in config.patchInnerDimensions))
220 self._patchBorder_patchBorder = config.patchBorder
221 self._initialized_initialized_initialized = False
222
223 def getPatchInfo(self, index, tractWcs):
224 # This should always be initialized
225 if not self._initialized_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.getPatchIndexPairgetPatchIndexPair(index)
232 else:
233 _index = Index2D(*index)
234 if (not 0 <= _index.x < self._numPatches_numPatches.x) \
235 or (not 0 <= _index.y < self._numPatches_numPatches.y):
236 raise IndexError("Patch index %s is not in range [0-%d, 0-%d]" %
237 (_index, self._numPatches_numPatches.x - 1, self._numPatches_numPatches.y - 1))
238 innerMin = geom.Point2I(*[_index[i] * self._patchInnerDimensions_patchInnerDimensions[i] for i in range(2)])
239 innerBBox = geom.Box2I(innerMin, self._patchInnerDimensions_patchInnerDimensions)
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.getPatchBordergetPatchBorder())
246 outerBBox.clip(self._tractBBox_tractBBox)
247 return PatchInfo(
248 index=_index,
249 innerBBox=innerBBox,
250 outerBBox=outerBBox,
251 sequentialIndex=self.getSequentialPatchIndexFromPairgetSequentialPatchIndexFromPair(_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.cellInnerDimensionscellInnerDimensions) != 2:
296 raise ValueError("cellInnerDimensions must be 2 ints.")
297
298 if self.cellInnerDimensionscellInnerDimensions[0] != self.cellInnerDimensionscellInnerDimensions[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
308 self._cellInnerDimensions_cellInnerDimensions = geom.Extent2I(*(val
309 for val in config.cellInnerDimensions))
310 self._cellBorder_cellBorder = config.cellBorder
311 self._numCellsPerPatchInner_numCellsPerPatchInner = config.numCellsPerPatchInner
312 self._numCellsInPatchBorder_numCellsInPatchBorder = config.numCellsInPatchBorder
313 self._patchInnerDimensions_patchInnerDimensions = geom.Extent2I(*(val*self._numCellsPerPatchInner_numCellsPerPatchInner
314 for val in config.cellInnerDimensions))
315 # The patch border is the number of cells in the border + the cell border
316 self._patchBorder_patchBorder = config.numCellsInPatchBorder*config.cellInnerDimensions[0] + self._cellBorder_cellBorder
317 self._initialized_initialized_initialized = False
318
319 def getPatchInfo(self, index, tractWcs):
320 # This should always be initialized
321 if not self._initialized_initialized_initialized:
322 raise RuntimeError("Programmer error; this should always be initialized.")
323 if isinstance(index, Index2D):
324 _index = index
325 else:
326 if isinstance(index, numbers.Number):
327 _index = self.getPatchIndexPairgetPatchIndexPair(index)
328 else:
329 _index = Index2D(*index)
330 if (not 0 <= _index.x < self._numPatches_numPatches.x) \
331 or (not 0 <= _index.y < self._numPatches_numPatches.y):
332 raise IndexError("Patch index %s is not in range [0-%d, 0-%d]" %
333 (_index, self._numPatches_numPatches.x - 1, self._numPatches_numPatches.y - 1))
334 innerMin = geom.Point2I(*[_index[i]*self._patchInnerDimensions_patchInnerDimensions[i] for i in range(2)])
335 innerBBox = geom.Box2I(innerMin, self._patchInnerDimensions_patchInnerDimensions)
336 if not self._tractBBox_tractBBox.contains(innerBBox):
337 raise RuntimeError(
338 "Bug: patch index %s valid but inner bbox=%s not contained in tract bbox=%s" %
339 (_index, innerBBox, self._tractBBox_tractBBox))
340 outerBBox = geom.Box2I(innerBBox)
341 outerBBox.grow(self.getPatchBordergetPatchBorder())
342 # We do not clip the patch for cell-based tracts.
343 return PatchInfo(
344 index=_index,
345 innerBBox=innerBBox,
346 outerBBox=outerBBox,
347 sequentialIndex=self.getSequentialPatchIndexFromPairgetSequentialPatchIndexFromPair(_index),
348 tractWcs=tractWcs,
349 cellInnerDimensions=self._cellInnerDimensions_cellInnerDimensions,
350 cellBorder=self._cellBorder_cellBorder,
351 numCellsPerPatchInner=self._numCellsPerPatchInner_numCellsPerPatchInner,
352 numCellsInPatchBorder=self._numCellsInPatchBorder_numCellsInPatchBorder
353 )
354
355 def getPackedConfig(self, config):
356 subConfig = config.tractBuilder[config.tractBuilder.name]
357 configPacked = struct.pack(
358 "<iiiiidd3sd",
359 subConfig.cellInnerDimensions[0],
360 subConfig.cellInnerDimensions[1],
361 subConfig.cellBorder,
362 subConfig.numCellsPerPatchInner,
363 subConfig.numCellsInPatchBorder,
364 config.tractOverlap,
365 config.pixelScale,
366 config.projection.encode('ascii'),
367 config.rotation
368 )
369
370 return configPacked
371
372
373tractBuilderRegistry = pexConfig.makeRegistry(
374 doc="A registry of Tract Builders (subclasses of BaseTractBuilder)",
375)
376
377tractBuilderRegistry.register("legacy", LegacyTractBuilder)
378tractBuilderRegistry.register("cells", CellTractBuilder)
A 2-dimensional celestial WCS that transform pixels to ICRS RA/Dec, using the LSST standard for pixel...
Definition: SkyWcs.h:117
An integer coordinate rectangle.
Definition: Box.h:55
def getPatchIndexPair(self, sequentialIndex)
def getPatchInfo(self, index, tractWcs)
def setupPatches(self, minBBox, wcs)
Definition: tractBuilder.py:53
def getSequentialPatchIndex(self, patchInfo)
def getPatchInfo(self, index, tractWcs)
def getPatchInfo(self, index, tractWcs)