Coverage for python/lsst/skymap/patchInfo.py: 29%

113 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-10-30 02:07 -0700

1# 

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__ = ["PatchInfo"] 

24 

25import numbers 

26from collections.abc import Iterable 

27 

28from lsst.geom import Extent2I, Point2I, Box2I 

29from .detail import makeSkyPolygonFromBBox, Index2D 

30from .cellInfo import CellInfo 

31 

32 

33class PatchInfo: 

34 """Information about a patch within a tract of a sky map. 

35 

36 If cellInnerDimensions and cellBorder are set then the patch 

37 will be gridded with cells. 

38 

39 See `TractInfo` for more information. 

40 

41 Parameters 

42 ---------- 

43 index : `lsst.skymap.Index2D` 

44 x,y index of patch (a pair of ints) 

45 innerBBox : `lsst.geom.Box2I` 

46 inner bounding box 

47 outerBBox : `lsst.geom.Box2I` 

48 inner bounding box 

49 sequentialIndex : `int` 

50 Patch sequential index 

51 tractWcs : `lsst.afw.geom.SkyWcs` 

52 Tract WCS object. 

53 cellInnerDimensions : `Iterable` [`int`, `int`] or `lsst.geom.Extent2I`, optional 

54 Inner dimensions of each cell (x,y pixels). 

55 cellBorder : `int`, optional 

56 Cell border size (pixels). 

57 numCellsPerPatchInner : `int`, optional 

58 Number of cells per inner patch region. 

59 numCellsInPatchBorder : `int`, optional 

60 Number of cells in the patch border. 

61 """ 

62 

63 def __init__(self, index, innerBBox, outerBBox, sequentialIndex, 

64 tractWcs, 

65 cellInnerDimensions=(0, 0), cellBorder=0, 

66 numCellsPerPatchInner=0, numCellsInPatchBorder=0): 

67 self._index = index 

68 self._sequentialIndex = sequentialIndex 

69 self._innerBBox = innerBBox 

70 self._outerBBox = outerBBox 

71 self._wcs = tractWcs 

72 if not outerBBox.contains(innerBBox): 

73 raise RuntimeError("outerBBox=%s does not contain innerBBox=%s" % (outerBBox, innerBBox)) 

74 if not isinstance(cellInnerDimensions, (Iterable, Extent2I)): 

75 raise ValueError("Input cellInnerDimensions is not an iterable.") 

76 if len(cellInnerDimensions) != 2: 

77 raise ValueError("Input cellInnerDimensions does not have two values.") 

78 self._cellInnerDimensions = Extent2I(*cellInnerDimensions) 

79 self._cellBorder = cellBorder 

80 self._numCellsInPatchBorder = numCellsInPatchBorder 

81 if numCellsPerPatchInner == 0: 

82 self._numCells = Index2D(x=0, y=0) 

83 else: 

84 # There are numCellsInPatchBorder extra boundary cell on each side 

85 self._numCells = Index2D(x=numCellsPerPatchInner + 2*numCellsInPatchBorder, 

86 y=numCellsPerPatchInner + 2*numCellsInPatchBorder) 

87 

88 def getIndex(self): 

89 """Return patch index: a tuple of (x, y) 

90 

91 Returns 

92 ------- 

93 result : `lsst.skymap.Index2D` 

94 Patch index (x, y). 

95 """ 

96 return self._index 

97 

98 index = property(getIndex) 

99 

100 def getSequentialIndex(self): 

101 """Return patch sequential index. 

102 

103 Returns 

104 ------- 

105 result : `int` 

106 Sequential patch index. 

107 """ 

108 return self._sequentialIndex 

109 

110 sequential_index = property(getSequentialIndex) 

111 

112 def getWcs(self): 

113 """Return the associated tract wcs 

114 

115 Returns 

116 ------- 

117 wcs : `lsst.afw.geom.SkyWcs` 

118 Tract WCS. 

119 """ 

120 return self._wcs 

121 

122 wcs = property(getWcs) 

123 

124 def getInnerBBox(self): 

125 """Get inner bounding box. 

126 

127 Returns 

128 ------- 

129 bbox : `lsst.geom.Box2I` 

130 The inner bounding Box. 

131 """ 

132 return self._innerBBox 

133 

134 inner_bbox = property(getInnerBBox) 

135 

136 def getOuterBBox(self): 

137 """Get outer bounding box. 

138 

139 Returns 

140 ------- 

141 bbox : `lsst.geom.Box2I` 

142 The outer bounding Box. 

143 """ 

144 return self._outerBBox 

145 

146 outer_bbox = property(getOuterBBox) 

147 

148 def getInnerSkyPolygon(self, tractWcs=None): 

149 """Get the inner on-sky region. 

150 

151 Parameters 

152 ---------- 

153 tractWcs : `lsst.afw.image.SkyWcs`, optional 

154 WCS for the associated tract. 

155 

156 Returns 

157 ------- 

158 result : `lsst.sphgeom.ConvexPolygon` 

159 The inner sky region. 

160 """ 

161 _tractWcs = tractWcs if tractWcs is not None else self._wcs 

162 return makeSkyPolygonFromBBox(bbox=self.getInnerBBox(), wcs=_tractWcs) 

163 

164 @property 

165 def inner_sky_polygon(self): 

166 return self.getInnerSkyPolygon() 

167 

168 def getOuterSkyPolygon(self, tractWcs=None): 

169 """Get the outer on-sky region. 

170 

171 Parameters 

172 ---------- 

173 tractWcs : `lsst.afw.image.SkyWcs`, optional 

174 WCS for the associated tract. 

175 

176 Returns 

177 ------- 

178 result : `lsst.sphgeom.ConvexPolygon` 

179 The outer sky region. 

180 """ 

181 _tractWcs = tractWcs if tractWcs is not None else self._wcs 

182 return makeSkyPolygonFromBBox(bbox=self.getOuterBBox(), wcs=_tractWcs) 

183 

184 @property 

185 def outer_sky_polygon(self): 

186 return self.getOuterSkyPolygon() 

187 

188 def getNumCells(self): 

189 """Get the number of cells in x, y. 

190 

191 May return (0, 0) if no cells are defined. 

192 

193 Returns 

194 ------- 

195 result : `lsst.skymap.Index2D` 

196 The number of cells in x, y. 

197 """ 

198 return self._numCells 

199 

200 num_cells = property(getNumCells) 

201 

202 def getCellBorder(self): 

203 return self._cellBorder 

204 

205 cell_border = property(getCellBorder) 

206 

207 def getCellInfo(self, index): 

208 """Return information for the specified cell. 

209 

210 Parameters 

211 ---------- 

212 index : `lsst.skymap.Index2D` or `int` 

213 Index of cell, as `Index2D`, or `Iterable` [`int`, `int`]; 

214 or a sequential index as returned by getSequentialCellIndex; 

215 negative values are not supported. 

216 

217 Returns 

218 ------- 

219 result : `lsst.skymap.CellInfo` 

220 The cell info for that index. 

221 

222 Raises 

223 ------ 

224 IndexError 

225 If index is out of range. 

226 """ 

227 if self._numCells.x == 0 or self._numCells.y == 0: 

228 raise IndexError("Patch does not contain cells.") 

229 if isinstance(index, Index2D): 

230 _index = index 

231 else: 

232 if isinstance(index, numbers.Number): 

233 _index = self.getCellIndexPair(index) 

234 else: 

235 _index = Index2D(*index) 

236 if (not 0 <= _index.x < self._numCells.x) \ 

237 or (not 0 <= _index.y < self._numCells.y): 

238 raise IndexError("Cell index %s is not in range [0-%d, 0-%d]" % 

239 (_index, self._numCells.x - 1, self._numCells.y - 1)) 

240 # We offset the index by numCellsInPatchBorder because the cells 

241 # start outside the inner dimensions. 

242 # The cells are defined relative to the patch bounding box (within the tract). 

243 patchInnerBBox = self.getInnerBBox() 

244 innerMin = Point2I(*[(_index[i] - self._numCellsInPatchBorder)*self._cellInnerDimensions[i] 

245 + patchInnerBBox.getBegin()[i] 

246 for i in range(2)]) 

247 

248 innerBBox = Box2I(innerMin, self._cellInnerDimensions) 

249 outerBBox = Box2I(innerBBox) 

250 outerBBox.grow(self._cellBorder) 

251 

252 return CellInfo( 

253 index=_index, 

254 innerBBox=innerBBox, 

255 outerBBox=outerBBox, 

256 sequentialIndex=self.getSequentialCellIndexFromPair(_index), 

257 tractWcs=self._wcs 

258 ) 

259 

260 def getCellInnerDimensions(self): 

261 """Get dimensions of inner region of the cells (all are the same) 

262 """ 

263 return self._cellInnerDimensions 

264 

265 cell_inner_dimensions = property(getCellInnerDimensions) 

266 

267 def getSequentialCellIndex(self, cellInfo): 

268 """Return a single integer that uniquely identifies 

269 the given cell within this patch. 

270 

271 Parameters 

272 ---------- 

273 cellInfo : `lsst.skymap.CellInfo` 

274 

275 Returns 

276 ------- 

277 sequentialIndex : `int` 

278 

279 Raises 

280 ------ 

281 IndexError 

282 If index is out of range. 

283 """ 

284 index = cellInfo.getIndex() 

285 return self.getSequentialCellIndexFromPair(index) 

286 

287 def getSequentialCellIndexFromPair(self, index): 

288 """Return a single integer that uniquely identifies 

289 the given cell within this patch. 

290 

291 Parameters 

292 ---------- 

293 index : `lsst.skymap.Index2D` 

294 

295 Returns 

296 ------- 

297 sequentialIndex : `int` 

298 

299 Raises 

300 ------ 

301 IndexError 

302 If index is out of range. 

303 """ 

304 if isinstance(index, Index2D): 

305 _index = index 

306 else: 

307 _index = Index2D(*index) 

308 nx, ny = self.getNumCells() 

309 return nx*_index.y + _index.x 

310 

311 def getCellIndexPair(self, sequentialIndex): 

312 """Convert a sequential index into an index pair. 

313 

314 Parameters 

315 ---------- 

316 sequentialIndex : `int` 

317 

318 Returns 

319 ------- 

320 x, y : `lsst.skymap.Index2D` 

321 

322 Raises 

323 ------ 

324 IndexError 

325 If index is out of range. 

326 """ 

327 if self._numCells.x == 0 or self._numCells.y == 0: 

328 raise IndexError("Patch does not contain cells.") 

329 

330 nx, ny = self.getNumCells() 

331 x = sequentialIndex % nx 

332 y = sequentialIndex // nx 

333 return Index2D(x=x, y=y) 

334 

335 def __iter__(self): 

336 xNum, yNum = self.getNumCells() 

337 for y in range(yNum): 

338 for x in range(xNum): 

339 yield self.getCellInfo(Index2D(x=x, y=y)) 

340 

341 def __len__(self): 

342 xNum, yNum = self.getNumCells() 

343 return xNum*yNum 

344 

345 def __getitem__(self, index): 

346 return self.getCellInfo(index) 

347 

348 def __eq__(self, rhs): 

349 return (self.getIndex() == rhs.getIndex()) \ 

350 and (self.getInnerBBox() == rhs.getInnerBBox()) \ 

351 and (self.getOuterBBox() == rhs.getOuterBBox()) \ 

352 and (self.getNumCells() == rhs.getNumCells()) \ 

353 and (self.getCellBorder() == rhs.getCellBorder()) 

354 

355 def __ne__(self, rhs): 

356 return not self.__eq__(rhs) 

357 

358 def __str__(self): 

359 return "PatchInfo(index=%s)" % (self.getIndex(),) 

360 

361 def __repr__(self): 

362 if self.getNumCells()[0] > 0: 

363 return ("PatchInfo(index=%s, innerBBox=%s, outerBBox=%s, cellInnerDimensions=%s, " 

364 "cellBorder=%s, numCellsPerPatchInner=%s)") % \ 

365 (self.getIndex(), self.getInnerBBox(), self.getOuterBBox(), 

366 self.getCellInnerDimensions(), self.getCellBorder(), 

367 self.getNumCells()[0]) 

368 else: 

369 return "PatchInfo(index=%s, innerBBox=%s, outerBBox=%s)" % \ 

370 (self.getIndex(), self.getInnerBBox(), self.getOuterBBox())