LSST Applications  21.0.0-172-gfb10e10a+18fedfabac,22.0.0+297cba6710,22.0.0+80564b0ff1,22.0.0+8d77f4f51a,22.0.0+a28f4c53b1,22.0.0+dcf3732eb2,22.0.1-1-g7d6de66+2a20fdde0d,22.0.1-1-g8e32f31+297cba6710,22.0.1-1-geca5380+7fa3b7d9b6,22.0.1-12-g44dc1dc+2a20fdde0d,22.0.1-15-g6a90155+515f58c32b,22.0.1-16-g9282f48+790f5f2caa,22.0.1-2-g92698f7+dcf3732eb2,22.0.1-2-ga9b0f51+7fa3b7d9b6,22.0.1-2-gd1925c9+bf4f0e694f,22.0.1-24-g1ad7a390+a9625a72a8,22.0.1-25-g5bf6245+3ad8ecd50b,22.0.1-25-gb120d7b+8b5510f75f,22.0.1-27-g97737f7+2a20fdde0d,22.0.1-32-gf62ce7b1+aa4237961e,22.0.1-4-g0b3f228+2a20fdde0d,22.0.1-4-g243d05b+871c1b8305,22.0.1-4-g3a563be+32dcf1063f,22.0.1-4-g44f2e3d+9e4ab0f4fa,22.0.1-42-gca6935d93+ba5e5ca3eb,22.0.1-5-g15c806e+85460ae5f3,22.0.1-5-g58711c4+611d128589,22.0.1-5-g75bb458+99c117b92f,22.0.1-6-g1c63a23+7fa3b7d9b6,22.0.1-6-g50866e6+84ff5a128b,22.0.1-6-g8d3140d+720564cf76,22.0.1-6-gd805d02+cc5644f571,22.0.1-8-ge5750ce+85460ae5f3,master-g6e05de7fdc+babf819c66,master-g99da0e417a+8d77f4f51a,w.2021.48
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", "BaseTractBuilder",
22  "LegacyTractBuilder", "CellTractBuilder"]
23 
24 import abc
25 import numbers
26 import struct
27 from collections.abc import Iterable
28 
29 import lsst.pex.config as pexConfig
30 import lsst.geom as geom
31 from .patchInfo import PatchInfo
32 from .detail import Index2D
33 
34 
35 class BaseTractBuilderConfig(pexConfig.Config):
36  """Configuration that is to be shared amongst all tract builders."""
37  pass
38 
39 
40 class BaseTractBuilder(metaclass=abc.ABCMeta):
41  """Base class for algorithms that define patches within the tract.
42 
43  Parameters
44  ----------
45  config : `lsst.pexConfig.Config`
46  Input for configuring the algorithm
47  """
48  def __init__(self, config):
49  self.configconfig = config
50 
51  def setupPatches(self, minBBox, wcs):
52  """Set up the patches of a particular size in a tract.
53 
54  We grow the tract bounding box to hold an exact multiple of
55  the desired size (patchInnerDimensions or
56  numCellsPerPatchInner*cellInnerDimensions), while keeping
57  the center roughly the same. We return the final tract
58  bounding box, and the number of patches in each dimension
59  (as an Index2D).
60 
61  Parameters
62  ----------
63  minBBox : `lsst.geom.Box2I`
64  Minimum bounding box for tract
65  wcs : `lsst.afw.geom.SkyWcs`
66  Wcs object
67 
68  Returns
69  -------
70  bbox : `lsst.geom.Box2I
71  final bounding box, number of patches
72  numPatches : `lsst.skymap.Index2D`
73  """
74  bbox = geom.Box2I(minBBox)
75  bboxMin = bbox.getMin()
76  bboxDim = bbox.getDimensions()
77  numPatchesList = [0, 0]
78  for i, innerDim in enumerate(self._patchInnerDimensions):
79  num = (bboxDim[i] + innerDim - 1) // innerDim # round up
80  deltaDim = (innerDim*num) - bboxDim[i]
81  if deltaDim > 0:
82  bboxDim[i] = innerDim * num
83  bboxMin[i] -= deltaDim // 2
84  numPatchesList[i] = num
85  numPatches = Index2D(*numPatchesList)
86  bbox = geom.Box2I(bboxMin, bboxDim)
87  self._numPatches_numPatches = numPatches
88  # The final tract BBox starts at zero.
89  self._tractBBox_tractBBox = geom.Box2I(geom.Point2I(0, 0), bbox.getDimensions())
90  self._initialized_initialized = True
91 
92  return bbox, numPatches
93 
94  def getPatchBorder(self):
95  return self._patchBorder
96 
97  @abc.abstractmethod
98  def getPatchInfo(self, index, tractWcs):
99  """Return information for the specified patch.
100 
101  Parameters
102  ----------
103  index : `lsst.skymap.Index2D` or `Iterable` [`int`, `int`]
104  Index of patch, as Index2D or pair of ints;
105  or a sequential index as returned by getSequentialPatchIndex;
106  negative values are not supported.
107  tractWcs : `lsst.afw.geom.SkyWcs`
108  WCS associated with the tract.
109 
110  Returns
111  -------
112  result : `lsst.skymap.PatchInfo`
113  The patch info for that index.
114 
115  Raises
116  ------
117  IndexError
118  If index is out of range.
119  """
120  raise NotImplementedError("Must be implemented by a subclass")
121 
123  """Get dimensions of inner region of the patches (all are the same)
124  """
125  return self._patchInnerDimensions
126 
127  def getSequentialPatchIndex(self, patchInfo):
128  """Return a single integer that uniquely identifies
129  the given patch within this tract.
130 
131  Parameters
132  ----------
133  patchInfo : `lsst.skymap.PatchInfo`
134 
135  Returns
136  -------
137  sequentialIndex : `int`
138  """
139  index = patchInfo.getIndex()
140  return self.getSequentialPatchIndexFromPairgetSequentialPatchIndexFromPair(index)
141 
143  """Return a single integer that uniquely identifies
144  the patch index within the tract.
145 
146  Parameters
147  ----------
148  index : `lsst.skymap.Index2D` or `Iterable` [`int`, `int`]
149 
150  Returns
151  -------
152  sequentialIndex : `int`
153  """
154  if isinstance(index, Index2D):
155  _index = index
156  else:
157  if not isinstance(index, Iterable):
158  raise ValueError("Input index is not an iterable.")
159  if len(index) != 2:
160  raise ValueError("Input index does not have two values.")
161  _index = Index2D(*index)
162  nx, ny = self._numPatches_numPatches
163  return nx*_index.y + _index.x
164 
165  def getPatchIndexPair(self, sequentialIndex):
166  """Convert sequential index into patch index (x,y) pair.
167 
168  Parameters
169  ----------
170  sequentialIndex : `int`
171 
172  Returns
173  -------
174  x, y : `lsst.skymap.Index2D`
175  """
176  nx, ny = self._numPatches_numPatches
177  x = sequentialIndex % nx
178  y = sequentialIndex // nx
179  return Index2D(x=x, y=y)
180 
181  @abc.abstractmethod
182  def getPackedConfig(self, config):
183  """Get a packed config suitable for using in a sha1.
184 
185  Parameters
186  ----------
187  config : `lsst.skymap.BaseTractBuilderConfig`
188 
189  Returns
190  -------
191  configPacked : `bytes`
192  """
193  raise NotImplementedError("Must be implemented by a subclass")
194 
195 
197  patchInnerDimensions = pexConfig.ListField(
198  doc="dimensions of inner region of patches (x,y pixels)",
199  dtype=int,
200  length=2,
201  default=(4000, 4000),
202  )
203  patchBorder = pexConfig.Field(
204  doc="border between patch inner and outer bbox (pixels)",
205  dtype=int,
206  default=100,
207  )
208 
209 
211  ConfigClass = LegacyTractBuilderConfig
212 
213  def __init__(self, config):
214  super().__init__(config)
215 
216  self._patchInnerDimensions_patchInnerDimensions = geom.Extent2I(*(val
217  for val in config.patchInnerDimensions))
218  self._patchBorder_patchBorder = config.patchBorder
219  self._initialized_initialized_initialized = False
220 
221  def getPatchInfo(self, index, tractWcs):
222  # This should always be initialized
223  if not self._initialized_initialized_initialized:
224  raise RuntimeError("Programmer error; this should always be initialized.")
225  if isinstance(index, Index2D):
226  _index = index
227  else:
228  if isinstance(index, numbers.Number):
229  _index = self.getPatchIndexPairgetPatchIndexPair(index)
230  else:
231  _index = Index2D(*index)
232  if (not 0 <= _index.x < self._numPatches_numPatches.x) \
233  or (not 0 <= _index.y < self._numPatches_numPatches.y):
234  raise IndexError("Patch index %s is not in range [0-%d, 0-%d]" %
235  (_index, self._numPatches_numPatches.x - 1, self._numPatches_numPatches.y - 1))
236  innerMin = geom.Point2I(*[_index[i] * self._patchInnerDimensions_patchInnerDimensions[i] for i in range(2)])
237  innerBBox = geom.Box2I(innerMin, self._patchInnerDimensions_patchInnerDimensions)
238  if not self._tractBBox_tractBBox.contains(innerBBox):
239  raise RuntimeError(
240  "Bug: patch index %s valid but inner bbox=%s not contained in tract bbox=%s" %
241  (_index, innerBBox, self._tractBBox_tractBBox))
242  outerBBox = geom.Box2I(innerBBox)
243  outerBBox.grow(self.getPatchBordergetPatchBorder())
244  outerBBox.clip(self._tractBBox_tractBBox)
245  return PatchInfo(
246  index=_index,
247  innerBBox=innerBBox,
248  outerBBox=outerBBox,
249  sequentialIndex=self.getSequentialPatchIndexFromPairgetSequentialPatchIndexFromPair(_index),
250  tractWcs=tractWcs
251  )
252 
253  def getPackedConfig(self, config):
254  subConfig = config.tractBuilder[config.tractBuilder.name]
255  configPacked = struct.pack(
256  "<iiidd3sd",
257  subConfig.patchInnerDimensions[0],
258  subConfig.patchInnerDimensions[1],
259  subConfig.patchBorder,
260  config.tractOverlap,
261  config.pixelScale,
262  config.projection.encode('ascii'),
263  config.rotation
264  )
265 
266  return configPacked
267 
268 
270  cellInnerDimensions = pexConfig.ListField(
271  doc="dimensions of inner region of cells (x,y pixels)",
272  dtype=int,
273  length=2,
274  default=(150, 150),
275  )
276  cellBorder = pexConfig.Field(
277  doc="Border between cell inner and outer bbox (pixels)",
278  dtype=int,
279  default=50,
280  )
281  numCellsPerPatchInner = pexConfig.Field(
282  doc="Number of cells per inner patch.",
283  dtype=int,
284  default=20,
285  )
286  numCellsInPatchBorder = pexConfig.Field(
287  doc="Number of cells in the patch border (outside the inner patch region).",
288  dtype=int,
289  default=1,
290  )
291 
292  def validate(self):
293  if len(self.cellInnerDimensionscellInnerDimensions) != 2:
294  raise ValueError("cellInnerDimensions must be 2 ints.")
295 
296  if self.cellInnerDimensionscellInnerDimensions[0] != self.cellInnerDimensionscellInnerDimensions[1]:
297  raise ValueError("cellInnerDimensions must be equal (for square cells).")
298 
299 
301  ConfigClass = CellTractBuilderConfig
302 
303  def __init__(self, config):
304  super().__init__(config)
305 
306  self._cellInnerDimensions_cellInnerDimensions = geom.Extent2I(*(val
307  for val in config.cellInnerDimensions))
308  self._cellBorder_cellBorder = config.cellBorder
309  self._numCellsPerPatchInner_numCellsPerPatchInner = config.numCellsPerPatchInner
310  self._numCellsInPatchBorder_numCellsInPatchBorder = config.numCellsInPatchBorder
311  self._patchInnerDimensions_patchInnerDimensions = geom.Extent2I(*(val*self._numCellsPerPatchInner_numCellsPerPatchInner
312  for val in config.cellInnerDimensions))
313  # The patch border is the number of cells in the border + the cell border
314  self._patchBorder_patchBorder = config.numCellsInPatchBorder*config.cellInnerDimensions[0] + self._cellBorder_cellBorder
315  self._initialized_initialized_initialized = False
316 
317  def getPatchInfo(self, index, tractWcs):
318  # This should always be initialized
319  if not self._initialized_initialized_initialized:
320  raise RuntimeError("Programmer error; this should always be initialized.")
321  if isinstance(index, Index2D):
322  _index = index
323  else:
324  if isinstance(index, numbers.Number):
325  _index = self.getPatchIndexPairgetPatchIndexPair(index)
326  else:
327  _index = Index2D(*index)
328  if (not 0 <= _index.x < self._numPatches_numPatches.x) \
329  or (not 0 <= _index.y < self._numPatches_numPatches.y):
330  raise IndexError("Patch index %s is not in range [0-%d, 0-%d]" %
331  (_index, self._numPatches_numPatches.x - 1, self._numPatches_numPatches.y - 1))
332  innerMin = geom.Point2I(*[_index[i]*self._patchInnerDimensions_patchInnerDimensions[i] for i in range(2)])
333  innerBBox = geom.Box2I(innerMin, self._patchInnerDimensions_patchInnerDimensions)
334  if not self._tractBBox_tractBBox.contains(innerBBox):
335  raise RuntimeError(
336  "Bug: patch index %s valid but inner bbox=%s not contained in tract bbox=%s" %
337  (_index, innerBBox, self._tractBBox_tractBBox))
338  outerBBox = geom.Box2I(innerBBox)
339  outerBBox.grow(self.getPatchBordergetPatchBorder())
340  # We do not clip the patch for cell-based tracts.
341  return PatchInfo(
342  index=_index,
343  innerBBox=innerBBox,
344  outerBBox=outerBBox,
345  sequentialIndex=self.getSequentialPatchIndexFromPairgetSequentialPatchIndexFromPair(_index),
346  tractWcs=tractWcs,
347  cellInnerDimensions=self._cellInnerDimensions_cellInnerDimensions,
348  cellBorder=self._cellBorder_cellBorder,
349  numCellsPerPatchInner=self._numCellsPerPatchInner_numCellsPerPatchInner,
350  numCellsInPatchBorder=self._numCellsInPatchBorder_numCellsInPatchBorder
351  )
352 
353  def getPackedConfig(self, config):
354  subConfig = config.tractBuilder[config.tractBuilder.name]
355  configPacked = struct.pack(
356  "<iiiiidd3sd",
357  subConfig.cellInnerDimensions[0],
358  subConfig.cellInnerDimensions[1],
359  subConfig.cellBorder,
360  subConfig.numCellsPerPatchInner,
361  subConfig.numCellsInPatchBorder,
362  config.tractOverlap,
363  config.pixelScale,
364  config.projection.encode('ascii'),
365  config.rotation
366  )
367 
368  return configPacked
369 
370 
371 tractBuilderRegistry = pexConfig.makeRegistry(
372  doc="A registry of Tract Builders (subclasses of BaseTractBuilder)",
373 )
374 
375 tractBuilderRegistry.register("legacy", LegacyTractBuilder)
376 tractBuilderRegistry.register("cells", CellTractBuilder)
An integer coordinate rectangle.
Definition: Box.h:55
def getPatchIndexPair(self, sequentialIndex)
def getPatchInfo(self, index, tractWcs)
Definition: tractBuilder.py:98
def setupPatches(self, minBBox, wcs)
Definition: tractBuilder.py:51
def getSequentialPatchIndex(self, patchInfo)
def getPatchInfo(self, index, tractWcs)
def getPatchInfo(self, index, tractWcs)