Coverage for tests/test_plotImageSubtractionCutouts.py: 18%

222 statements  

« prev     ^ index     » next       coverage.py v7.5.1, created at 2024-05-15 03:02 -0700

1# This file is part of analysis_ap. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (https://www.lsst.org). 

6# See the COPYRIGHT file at the top-level directory of this distribution 

7# for details of code ownership. 

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 GNU General Public License 

20# along with this program. If not, see <https://www.gnu.org/licenses/>. 

21 

22import os 

23import pickle 

24import sys 

25import tempfile 

26import unittest 

27 

28import lsst.afw.table 

29import lsst.geom 

30import lsst.meas.base.tests 

31import lsst.utils.tests 

32import pandas as pd 

33import PIL 

34from lsst.analysis.ap import plotImageSubtractionCutouts 

35from lsst.meas.algorithms import SourceDetectionTask 

36 

37# Sky center chosen to test metadata annotations (3-digit RA and negative Dec). 

38skyCenter = lsst.geom.SpherePoint(245.0, -45.0, lsst.geom.degrees) 

39 

40# A two-row mock APDB DiaSource table. 

41DATA = pd.DataFrame( 

42 data={ 

43 "diaSourceId": [506428274000265570, 527736141479149732], 

44 "ra": [skyCenter.getRa().asDegrees()+0.0001, skyCenter.getRa().asDegrees()-0.0001], 

45 "dec": [skyCenter.getDec().asDegrees()+0.0001, skyCenter.getDec().asDegrees()-0.001], 

46 "detector": [50, 60], 

47 "visit": [1234, 5678], 

48 "instrument": ["TestMock", "TestMock"], 

49 "band": ['r', 'g'], 

50 "psfFlux": [1234.5, 1234.5], 

51 "psfFluxErr": [123.5, 123.5], 

52 "snr": [10.0, 11.0], 

53 "psfChi2": [40.0, 50.0], 

54 "psfNdata": [10, 100], 

55 "apFlux": [2222.5, 3333.4], 

56 "apFluxErr": [222.5, 333.4], 

57 "scienceFlux": [2222000.5, 33330000.4], 

58 "scienceFluxErr": [22200.5, 333000.4], 

59 "isDipole": [True, False], 

60 "reliability": [0, 1.0], 

61 # First diaSource has all flags set, and second diaSource has none 

62 "slot_PsfFlux_flag": [1, 0], 

63 "slot_PsfFlux_flag_noGoodPixels": [1, 0], 

64 "slot_PsfFlux_flag_edge": [1, 0], 

65 "slot_ApFlux_flag": [1, 0], 

66 "slot_ApFlux_flag_apertureTruncated": [1, 0], 

67 "ip_diffim_forced_PsfFlux_flag": [1, 0], 

68 "ip_diffim_forced_PsfFlux_flag_noGoodPixels": [1, 0], 

69 "ip_diffim_forced_PsfFlux_flag_edge": [1, 0], 

70 "pixelFlags_edge": [1, 0], 

71 "pixelFlags_interpolated": [1, 0], 

72 "pixelFlags_interpolatedCenter": [1, 0], 

73 "pixelFlags_saturated": [1, 0], 

74 "pixelFlags_saturatedCenter": [1, 0], 

75 "pixelFlags_cr": [1, 0], 

76 "pixelFlags_crCenter": [1, 0], 

77 "pixelFlags_bad": [1, 0], 

78 "pixelFlags_suspect": [1, 0], 

79 "pixelFlags_suspectCenter": [1, 0], 

80 "slot_Centroid_flag": [1, 0], 

81 "slot_Shape_flag": [1, 0], 

82 "slot_Shape_flag_no_pixels": [1, 0], 

83 "slot_Shape_flag_not_contained": [1, 0], 

84 "slot_Shape_flag_parent_source": [1, 0], 

85 } 

86) 

87 

88 

89def make_mock_catalog(image): 

90 """Make a simple SourceCatalog from the image, containing Footprints. 

91 """ 

92 schema = lsst.afw.table.SourceTable.makeMinimalSchema() 

93 table = lsst.afw.table.SourceTable.make(schema) 

94 detect = SourceDetectionTask() 

95 return detect.run(table, image).sources 

96 

97 

98class TestPlotImageSubtractionCutouts(lsst.utils.tests.TestCase): 

99 """Test that PlotImageSubtractionCutoutsTask generates images and manifest 

100 files correctly. 

101 """ 

102 def setUp(self): 

103 bbox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), lsst.geom.Point2I(100, 100)) 

104 # source at the center of the image 

105 self.centroid = lsst.geom.Point2D(50, 50) 

106 dataset = lsst.meas.base.tests.TestDataset(bbox, crval=skyCenter) 

107 self.scale = 0.3 # arbitrary arcseconds/pixel 

108 dataset.addSource(instFlux=1e5, centroid=self.centroid) 

109 self.science, self.scienceCat = dataset.realize( 

110 noise=1000.0, schema=dataset.makeMinimalSchema() 

111 ) 

112 self.template, self.templateCat = dataset.realize( 

113 noise=5.0, schema=dataset.makeMinimalSchema() 

114 ) 

115 # A simple image difference to have something to plot. 

116 self.difference = lsst.afw.image.ExposureF(self.science, deep=True) 

117 self.difference.image -= self.template.image 

118 

119 def test_generate_image(self): 

120 """Test that we get some kind of image out. 

121 

122 It's useful to have a person look at the output via: 

123 im.show() 

124 """ 

125 # output_path does nothing here, since we never write the file to disk. 

126 cutouts = plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask(output_path="") 

127 cutout = cutouts.generate_image(self.science, self.template, self.difference, skyCenter, self.scale) 

128 with PIL.Image.open(cutout) as im: 

129 # NOTE: uncomment this to show the resulting image. 

130 # im.show() 

131 # NOTE: the dimensions here are determined by the matplotlib figure 

132 # size (in inches) and the dpi (default=100), plus borders. 

133 self.assertEqual((im.height, im.width), (233, 630)) 

134 

135 def test_generate_image_larger_cutout(self): 

136 """A different cutout size: the resulting cutout image is the same 

137 size but shows more pixels. 

138 """ 

139 config = plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask.ConfigClass() 

140 config.sizes = [100] 

141 # output_path does nothing here, since we never write the file to disk. 

142 cutouts = plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask(config=config, output_path="") 

143 cutout = cutouts.generate_image(self.science, self.template, self.difference, skyCenter, self.scale) 

144 with PIL.Image.open(cutout) as im: 

145 # NOTE: uncomment this to show the resulting image. 

146 # im.show() 

147 # NOTE: the dimensions here are determined by the matplotlib figure 

148 # size (in inches) and the dpi (default=100), plus borders. 

149 self.assertEqual((im.height, im.width), (233, 630)) 

150 

151 def test_generate_image_metadata(self): 

152 """Test that we can add metadata to the image; it changes the height 

153 a lot, and the width a little for the text boxes. 

154 

155 It's useful to have a person look at the output via: 

156 im.show() 

157 """ 

158 config = plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask.ConfigClass() 

159 config.add_metadata = True 

160 # output_path does nothing here, since we never write the file to disk. 

161 cutouts = plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask(config=config, output_path="") 

162 cutout = cutouts.generate_image(self.science, 

163 self.template, 

164 self.difference, 

165 skyCenter, 

166 self.scale, 

167 source=DATA.iloc[0]) 

168 with PIL.Image.open(cutout) as im: 

169 # NOTE: uncomment this to show the resulting image. 

170 # im.show() 

171 # NOTE: the dimensions here are determined by the matplotlib figure 

172 # size (in inches) and the dpi (default=100), plus borders. 

173 self.assertEqual((im.height, im.width), (343, 645)) 

174 

175 # A cutout without any flags: the dimensions should be unchanged. 

176 cutout = cutouts.generate_image(self.science, 

177 self.template, 

178 self.difference, 

179 skyCenter, 

180 self.scale, 

181 source=DATA.iloc[1]) 

182 with PIL.Image.open(cutout) as im: 

183 # NOTE: uncomment this to show the resulting image. 

184 # im.show() 

185 # NOTE: the dimensions here are determined by the matplotlib figure 

186 # size (in inches) and the dpi (default=100), plus borders. 

187 self.assertEqual((im.height, im.width), (343, 645)) 

188 

189 def test_generate_image_multisize_cutouts_without_metadata(self): 

190 """Multiple cutout sizes: the resulting image is larger in size 

191 and contains cutouts of multiple sizes. 

192 """ 

193 config = plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask.ConfigClass() 

194 config.sizes = [32, 64] 

195 # output_path does nothing here, since we never write the file to disk. 

196 cutouts = plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask(config=config, output_path="") 

197 cutout = cutouts.generate_image(self.science, self.template, self.difference, skyCenter, self.scale) 

198 with PIL.Image.open(cutout) as im: 

199 # NOTE: uncomment this to show the resulting image. 

200 # im.show() 

201 # NOTE: the dimensions here are determined by the matplotlib figure 

202 # size (in inches) and the dpi (default=100), plus borders. 

203 self.assertEqual((im.height, im.width), (450, 630)) 

204 

205 def test_generate_image_multisize_cutouts_with_metadata(self): 

206 """Test that we can add metadata to the image; it changes the height 

207 a lot, and the width a little for the text boxes. 

208 

209 It's useful to have a person look at the output via: 

210 im.show() 

211 """ 

212 config = plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask.ConfigClass() 

213 config.add_metadata = True 

214 config.sizes = [32, 64] 

215 # output_path does nothing here, since we never write the file to disk. 

216 cutouts = plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask(config=config, output_path="") 

217 cutout = cutouts.generate_image(self.science, 

218 self.template, 

219 self.difference, 

220 skyCenter, 

221 self.scale, 

222 source=DATA.iloc[0]) 

223 with PIL.Image.open(cutout) as im: 

224 # NOTE: uncomment this to show the resulting image. 

225 # im.show() 

226 # NOTE: the dimensions here are determined by the matplotlib figure 

227 # size (in inches) and the dpi (default=100), plus borders. 

228 self.assertEqual((im.height, im.width), (576, 645)) 

229 

230 # A cutout without any flags: the dimensions should be unchanged. 

231 cutout = cutouts.generate_image(self.science, 

232 self.template, 

233 self.difference, 

234 skyCenter, 

235 self.scale, 

236 source=DATA.iloc[1]) 

237 with PIL.Image.open(cutout) as im: 

238 # NOTE: uncomment this to show the resulting image. 

239 # im.show() 

240 # NOTE: the dimensions here are determined by the matplotlib figure 

241 # size (in inches) and the dpi (default=100), plus borders. 

242 self.assertEqual((im.height, im.width), (576, 645)) 

243 

244 def test_write_images(self): 

245 """Test that images get written to a temporary directory.""" 

246 butler = unittest.mock.Mock(spec=lsst.daf.butler.Butler) 

247 # We don't care what the output images look like here, just that 

248 # butler.get() returns an Exposure for every call. 

249 butler.get.return_value = self.science 

250 

251 with tempfile.TemporaryDirectory() as path: 

252 config = plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask.ConfigClass() 

253 cutouts = plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask(config=config, 

254 output_path=path) 

255 result = cutouts.write_images(DATA, butler) 

256 self.assertEqual(result, list(DATA["diaSourceId"])) 

257 for file in ("images/506428274000260000/506428274000265570.png", 

258 "images/527736141479140000/527736141479149732.png"): 

259 filename = os.path.join(path, file) 

260 self.assertTrue(os.path.exists(filename)) 

261 with PIL.Image.open(filename) as image: 

262 self.assertEqual(image.format, "PNG") 

263 

264 def test_use_footprint(self): 

265 """Test the use_footprint config option, generating a fake diaSrc 

266 catalog that contains footprints that get used instead of config.sizes. 

267 """ 

268 butler = unittest.mock.Mock(spec=lsst.daf.butler.Butler) 

269 

270 def mock_get(dataset, dataId, *args, **kwargs): 

271 if "_diaSrc" in dataset: 

272 # The science image is the only mock image with a source in it. 

273 catalog = make_mock_catalog(self.science) 

274 # Assign the matching source id to the detection. 

275 match = DATA["visit"] == dataId["visit"] 

276 catalog["id"] = DATA["diaSourceId"].to_numpy()[match][0] 

277 return catalog 

278 else: 

279 return self.science 

280 

281 butler.get.side_effect = mock_get 

282 

283 with tempfile.TemporaryDirectory() as path: 

284 config = plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask.ConfigClass() 

285 config.use_footprint = True 

286 cutouts = plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask(config=config, 

287 output_path=path) 

288 result = cutouts.write_images(DATA, butler) 

289 self.assertEqual(result, list(DATA["diaSourceId"])) 

290 for file in ("images/506428274000260000/506428274000265570.png", 

291 "images/527736141479140000/527736141479149732.png"): 

292 filename = os.path.join(path, file) 

293 self.assertTrue(os.path.exists(filename)) 

294 with PIL.Image.open(filename) as image: 

295 self.assertEqual(image.format, "PNG") 

296 

297 def test_write_images_exception(self): 

298 """Test that write_images() catches errors in loading data.""" 

299 butler = unittest.mock.Mock(spec=lsst.daf.butler.Butler) 

300 err = "Dataset not found" 

301 butler.get.side_effect = LookupError(err) 

302 

303 with tempfile.TemporaryDirectory() as path: 

304 config = plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask.ConfigClass() 

305 cutouts = plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask(config=config, 

306 output_path=path) 

307 

308 with self.assertLogs("lsst.plotImageSubtractionCutouts", "ERROR") as cm: 

309 cutouts.write_images(DATA, butler) 

310 self.assertIn( 

311 "LookupError processing diaSourceId 506428274000265570: Dataset not found", cm.output[0] 

312 ) 

313 self.assertIn( 

314 "LookupError processing diaSourceId 527736141479149732: Dataset not found", cm.output[1] 

315 ) 

316 

317 def check_make_manifest(self, url_root, url_list): 

318 """Check that make_manifest returns an appropriate DataFrame.""" 

319 data = [5, 10, 20] 

320 config = plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask.ConfigClass() 

321 config.url_root = url_root 

322 # output_path does nothing here 

323 cutouts = plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask(config=config, output_path="") 

324 manifest = cutouts._make_manifest(data) 

325 self.assertEqual(manifest["metadata:diaSourceId"].to_list(), [5, 10, 20]) 

326 self.assertEqual(manifest["location:1"].to_list(), url_list) 

327 

328 def test_make_manifest(self): 

329 # check without an ending slash 

330 root = "http://example.org/zooniverse" 

331 url_list = [ 

332 f"{root}/images/5.png", 

333 f"{root}/images/10.png", 

334 f"{root}/images/20.png", 

335 ] 

336 self.check_make_manifest(root, url_list) 

337 

338 # check with an ending slash 

339 root = "http://example.org/zooniverse/" 

340 url_list = [ 

341 f"{root}images/5.png", 

342 f"{root}images/10.png", 

343 f"{root}images/20.png", 

344 ] 

345 self.check_make_manifest(root, url_list) 

346 

347 def test_pickle(self): 

348 """Test that the task is pickleable (necessary for multiprocessing). 

349 """ 

350 config = plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask.ConfigClass() 

351 config.sizes = [63] 

352 cutouts = plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask(config=config, 

353 output_path="something") 

354 other = pickle.loads(pickle.dumps(cutouts)) 

355 self.assertEqual(cutouts.config.sizes, other.config.sizes) 

356 self.assertEqual(cutouts._output_path, other._output_path) 

357 

358 

359class TestPlotImageSubtractionCutoutsMain(lsst.utils.tests.TestCase): 

360 """Test the commandline interface main() function via mocks.""" 

361 def setUp(self): 

362 datadir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data/") 

363 self.sqlitefile = os.path.join(datadir, "apdb.sqlite3") 

364 self.repo = "/not/a/real/butler" 

365 self.collection = "mockRun" 

366 self.outputPath = "/an/output/path" 

367 self.configFile = os.path.join(datadir, "plotImageSubtractionCutoutsConfig.py") 

368 self.instrument = "LATISS" 

369 

370 # DM-39501: mock butler, until detector/visit are in APDB. 

371 butlerPatch = unittest.mock.patch("lsst.daf.butler.Butler") 

372 self._butler = butlerPatch.start() 

373 self.addCleanup(butlerPatch.stop) 

374 # DM-39501: mock unpacker, until detector/visit are in APDB. 

375 import lsst.obs.lsst 

376 universe = lsst.daf.butler.DimensionUniverse() 

377 data_id = lsst.daf.butler.DataCoordinate.standardize({"instrument": self.instrument}, 

378 universe=universe) 

379 # ObservationDimensionPacker is harder to use in a butler-less 

380 # environment, so we need a temporary dependency on obs_lsst until 

381 # this all goes away on DM-39501. 

382 packer = lsst.obs.lsst.RubinDimensionPacker(data_id, is_exposure=False) 

383 instrumentPatch = unittest.mock.patch.object(lsst.pipe.base.Instrument, 

384 "make_default_dimension_packer", 

385 return_value=packer) 

386 self._instrument = instrumentPatch.start() 

387 self.addCleanup(instrumentPatch.stop) 

388 

389 def test_main_args(self): 

390 """Test typical arguments to main().""" 

391 args = [ 

392 "plotImageSubtractionCutouts", 

393 f"--sqlitefile={self.sqlitefile}", 

394 f"--collections={self.collection}", 

395 f"-C={self.configFile}", 

396 f"--instrument={self.instrument}", 

397 self.repo, 

398 self.outputPath, 

399 ] 

400 with unittest.mock.patch.object( 

401 plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask, "run", autospec=True 

402 ) as run, unittest.mock.patch.object(sys, "argv", args): 

403 plotImageSubtractionCutouts.main() 

404 self.assertEqual(self._butler.call_args.args, (self.repo,)) 

405 self.assertEqual( 

406 self._butler.call_args.kwargs, {"collections": [self.collection]} 

407 ) 

408 # NOTE: can't easily test the `data` arg to run, as select_sources 

409 # reads in a random order every time. 

410 self.assertEqual(run.call_args.args[2], self._butler.return_value) 

411 

412 def test_main_args_no_collections(self): 

413 """Test with no collections argument.""" 

414 args = [ 

415 "plotImageSubtractionCutouts", 

416 f"--sqlitefile={self.sqlitefile}", 

417 f"-C={self.configFile}", 

418 f"--instrument={self.instrument}", 

419 self.repo, 

420 self.outputPath, 

421 ] 

422 with unittest.mock.patch.object( 

423 plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask, "run", autospec=True 

424 ) as run, unittest.mock.patch.object(sys, "argv", args): 

425 plotImageSubtractionCutouts.main() 

426 self.assertEqual(self._butler.call_args.args, (self.repo,)) 

427 self.assertEqual(self._butler.call_args.kwargs, {"collections": None}) 

428 self.assertIsInstance(run.call_args.args[1], pd.DataFrame) 

429 self.assertEqual(run.call_args.args[2], self._butler.return_value) 

430 

431 def test_main_collection_list(self): 

432 """Test passing a list of collections.""" 

433 collections = ["mock1", "mock2", "mock3"] 

434 args = [ 

435 "plotImageSubtractionCutouts", 

436 f"--sqlitefile={self.sqlitefile}", 

437 f"--instrument={self.instrument}", 

438 self.repo, 

439 self.outputPath, 

440 f"-C={self.configFile}", 

441 "--collections", 

442 ] 

443 args.extend(collections) 

444 with unittest.mock.patch.object( 

445 plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask, "run", autospec=True 

446 ) as run, unittest.mock.patch.object(sys, "argv", args): 

447 plotImageSubtractionCutouts.main() 

448 self.assertEqual(self._butler.call_args.args, (self.repo,)) 

449 self.assertEqual( 

450 self._butler.call_args.kwargs, {"collections": collections} 

451 ) 

452 self.assertIsInstance(run.call_args.args[1], pd.DataFrame) 

453 self.assertEqual(run.call_args.args[2], self._butler.return_value) 

454 

455 def test_main_args_limit_offset(self): 

456 """Test typical arguments to main().""" 

457 args = [ 

458 "plotImageSubtractionCutouts", 

459 f"--sqlitefile={self.sqlitefile}", 

460 f"--collections={self.collection}", 

461 f"-C={self.configFile}", 

462 f"--instrument={self.instrument}", 

463 "--all", 

464 "--limit=5", 

465 self.repo, 

466 self.outputPath, 

467 ] 

468 with unittest.mock.patch.object( 

469 plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask, 

470 "write_images", 

471 autospec=True, 

472 return_value=[5] 

473 ) as write_images, unittest.mock.patch.object( 

474 plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask, 

475 "write_manifest", 

476 autospec=True 

477 ) as write_manifest, unittest.mock.patch.object(sys, "argv", args): 

478 plotImageSubtractionCutouts.main() 

479 self.assertEqual(self._butler.call_args.args, (self.repo,)) 

480 self.assertEqual( 

481 self._butler.call_args.kwargs, {"collections": [self.collection]} 

482 ) 

483 self.assertIsInstance(write_images.call_args.args[1], pd.DataFrame) 

484 self.assertEqual(write_images.call_args.args[2], self._butler.return_value) 

485 # The test apdb contains 290 DiaSources, so we get the return of 

486 # `write_images` (enforced as `5` above) 58 times. 

487 self.assertEqual(write_manifest.call_args.args[1], 58*[5]) 

488 

489 @unittest.skip("Mock and multiprocess don't mix: https://github.com/python/cpython/issues/100090") 

490 def test_main_args_multiprocessing(self): 

491 """Test running with multiprocessing. 

492 """ 

493 args = [ 

494 "plotImageSubtractionCutouts", 

495 f"--sqlitefile={self.sqlitefile}", 

496 f"--collections={self.collection}", 

497 "-j2", 

498 f"-C={self.configFile}", 

499 f"--instrument={self.instrument}", 

500 self.repo, 

501 self.outputPath, 

502 ] 

503 with unittest.mock.patch.object( 

504 plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask, "run", autospec=True 

505 ) as run, unittest.mock.patch.object(sys, "argv", args): 

506 plotImageSubtractionCutouts.main() 

507 self.assertEqual(self._butler.call_args.args, (self.repo,)) 

508 self.assertEqual(self._butler.call_args.kwargs, {"collections": [self.collection]}) 

509 # NOTE: can't easily test the `data` arg to run, as select_sources 

510 # reads in a random order every time. 

511 self.assertEqual(run.call_args.args[2], self._butler.return_value) 

512 

513 

514class TestCutoutPath(lsst.utils.tests.TestCase): 

515 def test_normal_path(self): 

516 """Can the path manager handles non-chunked paths? 

517 """ 

518 manager = plotImageSubtractionCutouts.CutoutPath("some/root/path") 

519 path = manager(id=12345678) 

520 self.assertEqual(path, "some/root/path/images/12345678.png") 

521 

522 def test_chunking(self): 

523 """Can the path manager handle ids chunked into 10,000 file 

524 directories? 

525 """ 

526 manager = plotImageSubtractionCutouts.CutoutPath("some/root/path", chunk_size=10000) 

527 path = manager(id=12345678) 

528 self.assertEqual(path, "some/root/path/images/12340000/12345678.png") 

529 

530 def test_chunk_sizes(self): 

531 """Test valid and invalid values for the chunk_size parameter. 

532 """ 

533 with self.assertRaisesRegex(RuntimeError, "chunk_size must be a power of 10"): 

534 plotImageSubtractionCutouts.CutoutPath("some/root/path", chunk_size=123) 

535 

536 with self.assertRaisesRegex(RuntimeError, "chunk_size must be a power of 10"): 

537 plotImageSubtractionCutouts.CutoutPath("some/root/path", chunk_size=12300) 

538 

539 # should not raise 

540 plotImageSubtractionCutouts.CutoutPath("some/root/path", chunk_size=1000) 

541 plotImageSubtractionCutouts.CutoutPath("some/root/path", chunk_size=1000000) 

542 

543 

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

545 pass 

546 

547 

548def setup_module(module): 

549 lsst.utils.tests.init() 

550 

551 

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

553 lsst.utils.tests.init() 

554 unittest.main()