Coverage for tests/test_spanSets.py: 13%

284 statements  

« prev     ^ index     » next       coverage.py v7.1.0, created at 2023-02-05 17:50 -0800

1# 

2# LSST Data Management System 

3# 

4# Copyright 2008-2016 AURA/LSST. 

5# 

6# This product includes software developed by the 

7# LSST Project (http://www.lsst.org/). 

8# 

9# This program is free software: you can redistribute it and/or modify 

10# it under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# This program is distributed in the hope that it will be useful, 

15# but WITHOUT ANY WARRANTY; without even the implied warranty of 

16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the LSST License Statement and 

20# the GNU General Public License along with this program. If not, 

21# see <https://www.lsstcorp.org/LegalNotices/>. 

22# 

23 

24import unittest 

25import numpy as np 

26 

27import lsst.utils.tests 

28import lsst.geom 

29import lsst.afw.geom as afwGeom 

30import lsst.afw.geom.ellipses as afwGeomEllipses 

31import lsst.afw.image as afwImage 

32 

33 

34class SpanSetTestCase(lsst.utils.tests.TestCase): 

35 ''' 

36 This is a python level unit test of the SpanSets class. It is mean to work in conjuction 

37 with the c++ unit test. The C++ test has much more coverage, and tests some features which 

38 are not exposed to python. This test serves mainly as a way to test that the python bindings 

39 correctly work. Many of these tests are smaller versions of the C++ tests. 

40 ''' 

41 

42 def testNullSpanSet(self): 

43 nullSS = afwGeom.SpanSet() 

44 self.assertEqual(nullSS.getArea(), 0) 

45 self.assertEqual(len(nullSS), 0) 

46 self.assertEqual(nullSS.getBBox().getDimensions().getX(), 0) 

47 self.assertEqual(nullSS.getBBox().getDimensions().getY(), 0) 

48 

49 def testBBoxSpanSet(self): 

50 boxSS = afwGeom.SpanSet(lsst.geom.Box2I(lsst.geom.Point2I(2, 2), 

51 lsst.geom.Point2I(6, 6))) 

52 self.assertEqual(boxSS.getArea(), 25) 

53 bBox = boxSS.getBBox() 

54 self.assertEqual(bBox.getMinX(), 2) 

55 self.assertEqual(bBox.getMinY(), 2) 

56 

57 def testIteratorConstructor(self): 

58 spans = [afwGeom.Span(0, 2, 4), afwGeom.Span(1, 2, 4), 

59 afwGeom.Span(2, 2, 4)] 

60 spanSetFromList = afwGeom.SpanSet(spans) 

61 spanSetFromArray = afwGeom.SpanSet(np.array(spans)) 

62 

63 self.assertEqual(spanSetFromList.getBBox().getMinX(), 2) 

64 self.assertEqual(spanSetFromList.getBBox().getMaxX(), 4) 

65 self.assertEqual(spanSetFromList.getBBox().getMinY(), 0) 

66 

67 self.assertEqual(spanSetFromArray.getBBox().getMinX(), 2) 

68 self.assertEqual(spanSetFromArray.getBBox().getMaxX(), 4) 

69 self.assertEqual(spanSetFromArray.getBBox().getMinY(), 0) 

70 

71 def testIsContiguous(self): 

72 spanSetConList = [afwGeom.Span(0, 2, 5), afwGeom.Span(1, 5, 8)] 

73 spanSetCon = afwGeom.SpanSet(spanSetConList) 

74 self.assertTrue(spanSetCon.isContiguous()) 

75 

76 spanSetNotConList = [afwGeom.Span(0, 2, 5), afwGeom.Span(1, 20, 25)] 

77 spanSetNotCon = afwGeom.SpanSet(spanSetNotConList) 

78 self.assertFalse(spanSetNotCon.isContiguous()) 

79 

80 def testSplit(self): 

81 spanSetOne = afwGeom.SpanSet.fromShape(2, afwGeom.Stencil.BOX).shiftedBy(2, 2) 

82 spanSetTwo = afwGeom.SpanSet.fromShape(2, afwGeom.Stencil.BOX).shiftedBy(8, 8) 

83 

84 spanSetList = [] 

85 for spn in spanSetOne: 

86 spanSetList.append(spn) 

87 for spn in spanSetTwo: 

88 spanSetList.append(spn) 

89 spanSetTogether = afwGeom.SpanSet(spanSetList) 

90 

91 spanSetSplit = spanSetTogether.split() 

92 self.assertEqual(len(spanSetSplit), 2) 

93 

94 for a, b in zip(spanSetOne, spanSetSplit[0]): 

95 self.assertEqual(a, b) 

96 

97 for a, b in zip(spanSetTwo, spanSetSplit[1]): 

98 self.assertEqual(a, b) 

99 

100 def testTransform(self): 

101 transform = lsst.geom.LinearTransform(np.array([[2.0, 0.0], [0.0, 2.0]])) 

102 spanSetPreScale = afwGeom.SpanSet.fromShape(2, afwGeom.Stencil.CIRCLE) 

103 spanSetPostScale = spanSetPreScale.transformedBy(transform) 

104 

105 self.assertEqual(spanSetPostScale.getBBox().getMinX(), -4) 

106 self.assertEqual(spanSetPostScale.getBBox().getMinY(), -4) 

107 

108 def testOverlaps(self): 

109 spanSetNoShift = afwGeom.SpanSet.fromShape(4, afwGeom.Stencil.CIRCLE) 

110 spanSetShift = spanSetNoShift.shiftedBy(2, 2) 

111 

112 self.assertTrue(spanSetNoShift.overlaps(spanSetShift)) 

113 

114 def testContains(self): 

115 spanSetLarge = afwGeom.SpanSet.fromShape(4, afwGeom.Stencil.CIRCLE) 

116 spanSetSmall = afwGeom.SpanSet.fromShape(1, afwGeom.Stencil.CIRCLE) 

117 

118 self.assertTrue(spanSetLarge.contains(spanSetSmall)) 

119 self.assertFalse(spanSetSmall.contains(lsst.geom.Point2I(100, 100))) 

120 

121 def testComputeCentroid(self): 

122 spanSetShape = afwGeom.SpanSet.fromShape(4, afwGeom.Stencil.CIRCLE).shiftedBy(2, 2) 

123 center = spanSetShape.computeCentroid() 

124 

125 self.assertEqual(center.getX(), 2) 

126 self.assertEqual(center.getY(), 2) 

127 

128 def testComputeShape(self): 

129 spanSetShape = afwGeom.SpanSet.fromShape(1, afwGeom.Stencil.CIRCLE) 

130 quad = spanSetShape.computeShape() 

131 

132 self.assertEqual(quad.getIxx(), 0.4) 

133 self.assertEqual(quad.getIyy(), 0.4) 

134 self.assertEqual(quad.getIxy(), 0) 

135 

136 def testdilated(self): 

137 spanSetPredilated = afwGeom.SpanSet.fromShape(1, afwGeom.Stencil.CIRCLE) 

138 spanSetPostdilated = spanSetPredilated.dilated(1) 

139 

140 bBox = spanSetPostdilated.getBBox() 

141 self.assertEqual(bBox.getMinX(), -2) 

142 self.assertEqual(bBox.getMinY(), -2) 

143 

144 def testErode(self): 

145 spanSetPreErode = afwGeom.SpanSet.fromShape(2, afwGeom.Stencil.CIRCLE) 

146 spanSetPostErode = spanSetPreErode.eroded(1) 

147 

148 bBox = spanSetPostErode.getBBox() 

149 self.assertEqual(bBox.getMinX(), -1) 

150 self.assertEqual(bBox.getMinY(), -1) 

151 

152 def testFlatten(self): 

153 # Give an initial value to an input array 

154 inputArray = np.ones((6, 6)) * 9 

155 inputArray[1, 1] = 1 

156 inputArray[1, 2] = 2 

157 inputArray[2, 1] = 3 

158 inputArray[2, 2] = 4 

159 

160 inputSpanSet = afwGeom.SpanSet([afwGeom.Span(0, 0, 1), 

161 afwGeom.Span(1, 0, 1)]) 

162 flatArr = inputSpanSet.flatten(inputArray, lsst.geom.Point2I(-1, -1)) 

163 

164 self.assertEqual(flatArr.size, inputSpanSet.getArea()) 

165 

166 # Test flatttening a 3D array 

167 spanSetArea = afwGeom.SpanSet.fromShape(2, afwGeom.Stencil.BOX) 

168 spanSetArea = spanSetArea.shiftedBy(2, 2) 

169 

170 testArray = np.arange(5*5*3).reshape(5, 5, 3) 

171 flattened2DArray = spanSetArea.flatten(testArray) 

172 

173 truthArray = np.arange(5*5*3).reshape(5*5, 3) 

174 self.assertFloatsAlmostEqual(flattened2DArray, truthArray) 

175 

176 def testUnflatten(self): 

177 inputArray = np.ones(6) * 4 

178 inputSpanSet = afwGeom.SpanSet([afwGeom.Span(9, 2, 3), 

179 afwGeom.Span(10, 3, 4), 

180 afwGeom.Span(11, 2, 3)]) 

181 outputArray = inputSpanSet.unflatten(inputArray) 

182 

183 arrayShape = outputArray.shape 

184 bBox = inputSpanSet.getBBox() 

185 self.assertEqual(arrayShape[0], bBox.getHeight()) 

186 self.assertEqual(arrayShape[1], bBox.getWidth()) 

187 

188 # Test unflattening a 2D array 

189 spanSetArea = afwGeom.SpanSet.fromShape(2, afwGeom.Stencil.BOX) 

190 spanSetArea = spanSetArea.shiftedBy(2, 2) 

191 

192 testArray = np.arange(5*5*3).reshape(5*5, 3) 

193 unflattened3DArray = spanSetArea.unflatten(testArray) 

194 

195 truthArray = np.arange(5*5*3).reshape(5, 5, 3) 

196 self.assertFloatsAlmostEqual(unflattened3DArray, truthArray) 

197 

198 def populateMask(self): 

199 msk = afwImage.Mask(10, 10, 1) 

200 spanSetMask = afwGeom.SpanSet.fromShape(3, afwGeom.Stencil.CIRCLE).shiftedBy(5, 5) 

201 spanSetMask.setMask(msk, 2) 

202 return msk, spanSetMask 

203 

204 def testSetMask(self): 

205 mask, spanSetMask = self.populateMask() 

206 mskArray = mask.getArray() 

207 for i in range(mskArray.shape[0]): 

208 for j in range(mskArray.shape[1]): 

209 if lsst.geom.Point2I(i, j) in spanSetMask: 

210 self.assertEqual(mskArray[i, j], 3) 

211 else: 

212 self.assertEqual(mskArray[i, j], 1) 

213 

214 def testClearMask(self): 

215 mask, spanSetMask = self.populateMask() 

216 spanSetMask.clearMask(mask, 2) 

217 mskArray = mask.getArray() 

218 for i in range(mskArray.shape[0]): 

219 for j in range(mskArray.shape[1]): 

220 self.assertEqual(mskArray[i, j], 1) 

221 

222 def makeOverlapSpanSets(self): 

223 firstSpanSet = afwGeom.SpanSet.fromShape(2, afwGeom.Stencil.BOX).shiftedBy(2, 4) 

224 secondSpanSet = afwGeom.SpanSet.fromShape(2, afwGeom.Stencil.BOX).shiftedBy(2, 2) 

225 return firstSpanSet, secondSpanSet 

226 

227 def makeMaskAndSpanSetForOperationTest(self): 

228 firstMaskPart = afwGeom.SpanSet.fromShape(2, afwGeom.Stencil.BOX).shiftedBy(3, 2) 

229 secondMaskPart = afwGeom.SpanSet.fromShape(2, afwGeom.Stencil.BOX).shiftedBy(3, 8) 

230 spanSetMaskOperation = afwGeom.SpanSet.fromShape(2, afwGeom.Stencil.BOX).shiftedBy(3, 5) 

231 

232 mask = afwImage.Mask(20, 20) 

233 firstMaskPart.setMask(mask, 3) 

234 secondMaskPart.setMask(mask, 3) 

235 spanSetMaskOperation.setMask(mask, 4) 

236 

237 return mask, spanSetMaskOperation 

238 

239 def testIntersection(self): 

240 firstSpanSet, secondSpanSet = self.makeOverlapSpanSets() 

241 

242 overlap = firstSpanSet.intersect(secondSpanSet) 

243 for i, span in enumerate(overlap): 

244 self.assertEqual(span.getY(), i+2) 

245 self.assertEqual(span.getMinX(), 0) 

246 self.assertEqual(span.getMaxX(), 4) 

247 

248 mask, spanSetMaskOperation = self.makeMaskAndSpanSetForOperationTest() 

249 spanSetIntersectMask = spanSetMaskOperation.intersect(mask, 2) 

250 

251 expectedYRange = [3, 4, 6, 7] 

252 for expected, val in zip(expectedYRange, spanSetIntersectMask): 

253 self.assertEqual(expected, val.getY()) 

254 

255 def testIntersectNot(self): 

256 firstSpanSet, secondSpanSet = self.makeOverlapSpanSets() 

257 

258 overlap = firstSpanSet.intersectNot(secondSpanSet) 

259 for yVal, span in enumerate(overlap): 

260 self.assertEqual(span.getY(), yVal+5) 

261 self.assertEqual(span.getMinX(), 0) 

262 self.assertEqual(span.getMaxX(), 4) 

263 

264 mask, spanSetMaskOperation = self.makeMaskAndSpanSetForOperationTest() 

265 

266 spanSetIntersectNotMask = spanSetMaskOperation.intersectNot(mask, 2) 

267 

268 self.assertEqual(len(spanSetIntersectNotMask), 1) 

269 self.assertEqual(next(iter(spanSetIntersectNotMask)).getY(), 5) 

270 

271 # More complicated intersection with disconnected SpanSets 

272 spanList1 = [afwGeom.Span(0, 0, 10), 

273 afwGeom.Span(1, 0, 10), 

274 afwGeom.Span(2, 0, 10)] 

275 

276 spanList2 = [afwGeom.Span(1, 2, 4), afwGeom.Span(1, 7, 8)] 

277 

278 resultList = [afwGeom.Span(0, 0, 10), 

279 afwGeom.Span(1, 0, 1), 

280 afwGeom.Span(1, 5, 6), 

281 afwGeom.Span(1, 9, 10), 

282 afwGeom.Span(2, 0, 10)] 

283 

284 spanSet1 = afwGeom.SpanSet(spanList1) 

285 spanSet2 = afwGeom.SpanSet(spanList2) 

286 expectedSpanSet = afwGeom.SpanSet(resultList) 

287 

288 outputSpanSet = spanSet1.intersectNot(spanSet2) 

289 

290 self.assertEqual(outputSpanSet, expectedSpanSet) 

291 

292 numIntersectNotTrials = 100 

293 spanRow = 5 

294 # Set a seed for random functions 

295 np.random.seed(400) 

296 for N in range(numIntersectNotTrials): 

297 # Create two random SpanSets, both with holes in them 

298 listOfRandomSpanSets = [] 

299 for i in range(2): 

300 # Make two rectangles to be turned into a SpanSet 

301 rand1 = np.random.randint(0, 26, 2) 

302 rand2 = np.random.randint(rand1.max(), 51, 2) 

303 tempList = [afwGeom.Span(spanRow, rand1.min(), rand1.max()), 

304 afwGeom.Span(spanRow, rand2.min(), rand2.max())] 

305 listOfRandomSpanSets.append(afwGeom.SpanSet(tempList)) 

306 

307 # IntersectNot the SpanSets, randomly choosing which one is the one 

308 # to be the negated SpanSet 

309 randChoice = np.random.randint(0, 2) 

310 negatedRandChoice = int(not randChoice) 

311 sourceSpanSet = listOfRandomSpanSets[randChoice] 

312 targetSpanSet = listOfRandomSpanSets[negatedRandChoice] 

313 resultSpanSet = sourceSpanSet.intersectNot(targetSpanSet) 

314 for span in resultSpanSet: 

315 for point in span: 

316 self.assertTrue(sourceSpanSet.contains(point)) 

317 self.assertFalse(targetSpanSet.contains(point)) 

318 

319 for x in range(51): 

320 point = lsst.geom.Point2I(x, spanRow) 

321 if sourceSpanSet.contains(point) and not\ 

322 targetSpanSet.contains(point): 

323 self.assertTrue(resultSpanSet.contains(point)) 

324 

325 def testUnion(self): 

326 firstSpanSet, secondSpanSet = self.makeOverlapSpanSets() 

327 

328 overlap = firstSpanSet.union(secondSpanSet) 

329 

330 for yVal, span in enumerate(overlap): 

331 self.assertEqual(span.getY(), yVal) 

332 self.assertEqual(span.getMinX(), 0) 

333 self.assertEqual(span.getMaxX(), 4) 

334 

335 mask, spanSetMaskOperation = self.makeMaskAndSpanSetForOperationTest() 

336 

337 spanSetUnion = spanSetMaskOperation.union(mask, 2) 

338 

339 for yVal, span in enumerate(spanSetUnion): 

340 self.assertEqual(span.getY(), yVal) 

341 

342 def testMaskToSpanSet(self): 

343 mask, _ = self.makeMaskAndSpanSetForOperationTest() 

344 spanSetFromMask = afwGeom.SpanSet.fromMask(mask) 

345 

346 for yCoord, span in enumerate(spanSetFromMask): 

347 self.assertEqual(span, afwGeom.Span(yCoord, 1, 5)) 

348 

349 def testFromMask(self): 

350 xy0 = lsst.geom.Point2I(12345, 67890) # xy0 for image 

351 dims = lsst.geom.Extent2I(123, 45) # Dimensions of image 

352 box = lsst.geom.Box2I(xy0, dims) # Bounding box of image 

353 value = 32 

354 other = 16 

355 assert value & other == 0 # Setting 'other' unsets 'value' 

356 point = lsst.geom.Point2I(3 + xy0.getX(), 3 + xy0.getY()) # Point in the image 

357 

358 mask = afwImage.Mask(box) 

359 mask.set(value) 

360 

361 # We can create a SpanSet from bit planes 

362 spans = afwGeom.SpanSet.fromMask(mask, value) 

363 self.assertEqual(spans.getArea(), box.getArea()) 

364 self.assertEqual(spans.getBBox(), box) 

365 self.assertTrue(point in spans) 

366 

367 # Pixels not matching the desired bit plane are ignored as they should be 

368 mask[point] = other # unset one pixel 

369 spans = afwGeom.SpanSet.fromMask(mask, value) 

370 self.assertEqual(spans.getArea(), box.getArea() - 1) 

371 self.assertEqual(spans.getBBox(), box) 

372 self.assertFalse(point in spans) 

373 

374 def testEquality(self): 

375 firstSpanSet, secondSpanSet = self.makeOverlapSpanSets() 

376 secondSpanSetShift = secondSpanSet.shiftedBy(0, 2) 

377 

378 self.assertFalse(firstSpanSet == secondSpanSet) 

379 self.assertTrue(firstSpanSet != secondSpanSet) 

380 self.assertTrue(firstSpanSet == secondSpanSetShift) 

381 self.assertFalse(firstSpanSet != secondSpanSetShift) 

382 

383 def testSpanSetFromEllipse(self): 

384 axes = afwGeomEllipses.Axes(6, 6, 0) 

385 ellipse = afwGeom.Ellipse(axes, lsst.geom.Point2D(5, 6)) 

386 spanSet = afwGeom.SpanSet.fromShape(ellipse) 

387 for ss, es in zip(spanSet, afwGeomEllipses.PixelRegion(ellipse)): 

388 self.assertEqual(ss, es) 

389 

390 def testfromShapeOffset(self): 

391 shift = lsst.geom.Point2I(2, 2) 

392 spanSetShifted = afwGeom.SpanSet.fromShape(2, offset=shift) 

393 bbox = spanSetShifted.getBBox() 

394 self.assertEqual(bbox.getMinX(), 0) 

395 self.assertEqual(bbox.getMinY(), 0) 

396 

397 def testFindEdgePixels(self): 

398 spanSet = afwGeom.SpanSet.fromShape(6, afwGeom.Stencil.CIRCLE) 

399 spanSetEdge = spanSet.findEdgePixels() 

400 

401 truthSpans = [afwGeom.Span(-6, 0, 0), 

402 afwGeom.Span(-5, -3, -1), 

403 afwGeom.Span(-5, 1, 3), 

404 afwGeom.Span(-4, -4, -4), 

405 afwGeom.Span(-4, 4, 4), 

406 afwGeom.Span(-3, -5, -5), 

407 afwGeom.Span(-3, 5, 5), 

408 afwGeom.Span(-2, -5, -5), 

409 afwGeom.Span(-2, 5, 5), 

410 afwGeom.Span(-1, -5, -5), 

411 afwGeom.Span(-1, 5, 5), 

412 afwGeom.Span(0, -6, -6), 

413 afwGeom.Span(0, 6, 6), 

414 afwGeom.Span(1, -5, -5), 

415 afwGeom.Span(1, 5, 5), 

416 afwGeom.Span(2, -5, -5), 

417 afwGeom.Span(2, 5, 5), 

418 afwGeom.Span(3, -5, -5), 

419 afwGeom.Span(3, 5, 5), 

420 afwGeom.Span(4, -4, -4), 

421 afwGeom.Span(4, 4, 4), 

422 afwGeom.Span(5, -3, -1), 

423 afwGeom.Span(5, 1, 3), 

424 afwGeom.Span(6, 0, 0)] 

425 truthSpanSet = afwGeom.SpanSet(truthSpans) 

426 self.assertEqual(spanSetEdge, truthSpanSet) 

427 

428 def testIndices(self): 

429 dataArray = np.zeros((5, 5)) 

430 spanSet = afwGeom.SpanSet.fromShape(2, 

431 afwGeom.Stencil.BOX, 

432 offset=(2, 2)) 

433 yind, xind = spanSet.indices() 

434 dataArray[yind, xind] = 9 

435 self.assertTrue((dataArray == 9).all()) 

436 

437 def testSpanIteration(self): 

438 span = afwGeom.Span(4, 3, 8) 

439 points = list(span) 

440 self.assertEqual(len(span), len(points)) 

441 self.assertEqual(points, [lsst.geom.Point2I(x, 4) for x in range(3, 9)]) 

442 

443 

444class TestMemory(lsst.utils.tests.MemoryTestCase): 

445 pass 

446 

447 

448def setup_module(module): 

449 lsst.utils.tests.init() 

450 

451 

452if __name__ == "__main__": 452 ↛ 453line 452 didn't jump to line 453, because the condition on line 452 was never true

453 lsst.utils.tests.init() 

454 unittest.main()