Coverage for tests / test_plotImageSubtractionCutouts.py: 20%
241 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-07 08:54 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-07 08:54 +0000
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/>.
22import os
23import pickle
24import sys
25import tempfile
26import unittest
28import lsst.afw.table
29import lsst.geom
30import lsst.meas.base.tests
31import lsst.utils.tests
32import numpy as np
33import pandas as pd
34import PIL
35from lsst.analysis.ap import plotImageSubtractionCutouts
36from lsst.meas.algorithms import SourceDetectionTask
38# Sky center chosen to test metadata annotations (3-digit RA and negative Dec).
39skyCenter = lsst.geom.SpherePoint(245.0, -45.0, lsst.geom.degrees)
41# A two-row mock APDB DiaSource table.
42DATA = pd.DataFrame(
43 data={
44 "diaSourceId": [506428274000265570, 527736141479149732],
45 "ra": [skyCenter.getRa().asDegrees()+0.0001, skyCenter.getRa().asDegrees()-0.0001],
46 "dec": [skyCenter.getDec().asDegrees()+0.0001, skyCenter.getDec().asDegrees()-0.001],
47 "detector": [50, 60],
48 "visit": [1234, 5678],
49 "instrument": ["TestMock", "TestMock"],
50 "band": ['r', 'g'],
51 "psfFlux": [1234.5, 1234.5],
52 "psfFluxErr": [123.5, 123.5],
53 "snr": [10.0, 11.0],
54 "psfChi2": [40.0, 50.0],
55 "psfNdata": [10, 100],
56 "apFlux": [2222.5, 3333.4],
57 "apFluxErr": [222.5, 333.4],
58 "scienceFlux": [2222000.5, 33330000.4],
59 "scienceFluxErr": [22200.5, 333000.4],
60 "isDipole": [True, False],
61 "reliability": [0, 1.0],
62 # First diaSource has all flags set, and second diaSource has none
63 "psfFlux_flag": [1, 0],
64 "psfFlux_flag_noGoodPixels": [1, 0],
65 "psfFlux_flag_edge": [1, 0],
66 "apFlux_flag": [1, 0],
67 "apFlux_flag_apertureTruncated": [1, 0],
68 "forced_PsfFlux_flag": [1, 0],
69 "forced_PsfFlux_flag_noGoodPixels": [1, 0],
70 "forced_PsfFlux_flag_edge": [1, 0],
71 "pixelFlags_edge": [1, 0],
72 "pixelFlags_interpolated": [1, 0],
73 "pixelFlags_interpolatedCenter": [1, 0],
74 "pixelFlags_saturated": [1, 0],
75 "pixelFlags_saturatedCenter": [1, 0],
76 "pixelFlags_cr": [1, 0],
77 "pixelFlags_crCenter": [1, 0],
78 "pixelFlags_bad": [1, 0],
79 "pixelFlags_suspect": [1, 0],
80 "pixelFlags_suspectCenter": [1, 0],
81 "centroid_flag": [1, 0],
82 "shape_flag": [1, 0],
83 "shape_flag_no_pixels": [1, 0],
84 "shape_flag_not_contained": [1, 0],
85 "shape_flag_parent_source": [1, 0],
86 }
87)
90def make_mock_catalog(image):
91 """Make a simple SourceCatalog from the image, containing Footprints.
92 """
93 schema = lsst.afw.table.SourceTable.makeMinimalSchema()
94 table = lsst.afw.table.SourceTable.make(schema)
95 detect = SourceDetectionTask()
96 return detect.run(table, image).sources
99class TestPlotImageSubtractionCutouts(lsst.utils.tests.TestCase):
100 """Test that PlotImageSubtractionCutoutsTask generates images and manifest
101 files correctly.
102 """
103 def setUp(self):
104 bbox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), lsst.geom.Point2I(100, 100))
105 # source at the center of the image
106 self.centroid = lsst.geom.Point2D(50, 50)
107 dataset = lsst.meas.base.tests.TestDataset(bbox, crval=skyCenter)
108 self.scale = 0.3 # arbitrary arcseconds/pixel
109 dataset.addSource(instFlux=1e5, centroid=self.centroid)
110 self.science, self.scienceCat = dataset.realize(
111 noise=1000.0, schema=dataset.makeMinimalSchema()
112 )
113 self.template, self.templateCat = dataset.realize(
114 noise=5.0, schema=dataset.makeMinimalSchema()
115 )
116 # A simple image difference to have something to plot.
117 self.difference = lsst.afw.image.ExposureF(self.science, deep=True)
118 self.difference.image -= self.template.image
120 def test_generate_image(self):
121 """Test that we get some kind of image out.
123 It's useful to have a person look at the output via:
124 im.show()
125 """
126 # output_path does nothing here, since we never write the file to disk.
127 cutouts = plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask(output_path="")
128 cutout = cutouts.generate_image(self.science, self.template, self.difference, skyCenter, self.scale)
129 with PIL.Image.open(cutout) as im:
130 # NOTE: uncomment this to show the resulting image.
131 # im.show()
132 # NOTE: the dimensions here are determined by the matplotlib figure
133 # size (in inches) and the dpi (default=100), plus borders.
134 self.assertEqual((im.height, im.width), (245, 651))
136 def test_generate_image_larger_cutout(self):
137 """A different cutout size: the resulting cutout image is the same
138 size but shows more pixels.
139 """
140 config = plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask.ConfigClass()
141 config.sizes = [100]
142 # output_path does nothing here, since we never write the file to disk.
143 cutouts = plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask(config=config, output_path="")
144 cutout = cutouts.generate_image(self.science, self.template, self.difference, skyCenter, self.scale)
145 with PIL.Image.open(cutout) as im:
146 # NOTE: uncomment this to show the resulting image.
147 # im.show()
148 # NOTE: the dimensions here are determined by the matplotlib figure
149 # size (in inches) and the dpi (default=100), plus borders.
150 self.assertEqual((im.height, im.width), (245, 651))
152 def test_generate_image_metadata(self):
153 """Test that we can add metadata to the image; it changes the height
154 a lot, and the width a little for the text boxes.
156 It's useful to have a person look at the output via:
157 im.show()
158 """
159 config = plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask.ConfigClass()
160 config.add_metadata = True
161 # output_path does nothing here, since we never write the file to disk.
162 cutouts = plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask(config=config, output_path="")
163 cutout = cutouts.generate_image(self.science,
164 self.template,
165 self.difference,
166 skyCenter,
167 self.scale,
168 source=DATA.iloc[0])
169 with PIL.Image.open(cutout) as im:
170 # NOTE: uncomment this to show the resulting image.
171 # im.show()
172 # NOTE: the dimensions here are determined by the matplotlib figure
173 # size (in inches) and the dpi (default=100), plus borders.
174 self.assertEqual((im.height, im.width), (349, 655))
176 # A cutout without any flags: the dimensions should be unchanged.
177 cutout = cutouts.generate_image(self.science,
178 self.template,
179 self.difference,
180 skyCenter,
181 self.scale,
182 source=DATA.iloc[1])
183 with PIL.Image.open(cutout) as im:
184 # NOTE: uncomment this to show the resulting image.
185 # im.show()
186 # NOTE: the dimensions here are determined by the matplotlib figure
187 # size (in inches) and the dpi (default=100), plus borders.
188 self.assertEqual((im.height, im.width), (349, 655))
190 def test_generate_image_multisize_cutouts_without_metadata(self):
191 """Multiple cutout sizes: the resulting image is larger in size
192 and contains cutouts of multiple sizes.
193 """
194 config = plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask.ConfigClass()
195 config.sizes = [32, 64]
196 # output_path does nothing here, since we never write the file to disk.
197 cutouts = plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask(config=config, output_path="")
198 cutout = cutouts.generate_image(self.science, self.template, self.difference, skyCenter, self.scale)
199 with PIL.Image.open(cutout) as im:
200 # NOTE: uncomment this to show the resulting image.
201 # im.show()
202 # NOTE: the dimensions here are determined by the matplotlib figure
203 # size (in inches) and the dpi (default=100), plus borders.
204 self.assertEqual((im.height, im.width), (480, 651))
206 def test_generate_image_multisize_cutouts_with_metadata(self):
207 """Test that we can add metadata to the image; it changes the height
208 a lot, and the width a little for the text boxes.
210 It's useful to have a person look at the output via:
211 im.show()
212 """
213 config = plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask.ConfigClass()
214 config.add_metadata = True
215 config.sizes = [32, 64]
216 # output_path does nothing here, since we never write the file to disk.
217 cutouts = plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask(config=config, output_path="")
218 cutout = cutouts.generate_image(self.science,
219 self.template,
220 self.difference,
221 skyCenter,
222 self.scale,
223 source=DATA.iloc[0])
224 with PIL.Image.open(cutout) as im:
225 # NOTE: uncomment this to show the resulting image.
226 # im.show()
227 # NOTE: the dimensions here are determined by the matplotlib figure
228 # size (in inches) and the dpi (default=100), plus borders.
229 self.assertEqual((im.height, im.width), (591, 655))
231 # A cutout without any flags: the dimensions should be unchanged.
232 cutout = cutouts.generate_image(self.science,
233 self.template,
234 self.difference,
235 skyCenter,
236 self.scale,
237 source=DATA.iloc[1])
238 with PIL.Image.open(cutout) as im:
239 # NOTE: uncomment this to show the resulting image.
240 # im.show()
241 # NOTE: the dimensions here are determined by the matplotlib figure
242 # size (in inches) and the dpi (default=100), plus borders.
243 self.assertEqual((im.height, im.width), (591, 655))
245 def test_generate_image_and_save_as_numpy(self):
246 """Test that we can save an image as a .npy file and then read it back.
247 """
248 config = plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask.ConfigClass()
249 config.sizes = [100]
250 with tempfile.TemporaryDirectory() as path:
251 cutouts = plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask(config=config,
252 output_path=path)
253 dia_source_id = 506428274000265570
254 cutouts.generate_image(self.science, self.template, self.difference, skyCenter, self.scale,
255 dia_source_id=dia_source_id, save_as_numpy=True)
256 numpy_dir_path = cutouts.numpy_path.directory(dia_source_id)
257 for file in os.listdir(numpy_dir_path):
258 image_with_channel = np.load(numpy_dir_path + "/" + file)
259 image = np.squeeze(image_with_channel, axis=0)
260 self.assertEqual((image.shape[0], image.shape[1]), (100, 100))
262 def test_write_images(self):
263 """Test that images get written to a temporary directory."""
264 butler = unittest.mock.Mock(spec=lsst.daf.butler.Butler)
265 # We don't care what the output images look like here, just that
266 # butler.get() returns an Exposure for every call.
267 butler.get.return_value = self.science
269 with tempfile.TemporaryDirectory() as path:
270 config = plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask.ConfigClass()
271 cutouts = plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask(config=config,
272 output_path=path)
273 result = cutouts.write_images(DATA, butler)
274 self.assertEqual(result, list(DATA["diaSourceId"]))
275 for file in ("images/506428274000260000/506428274000265570.png",
276 "images/527736141479140000/527736141479149732.png"):
277 filename = os.path.join(path, file)
278 self.assertTrue(os.path.exists(filename))
279 with PIL.Image.open(filename) as image:
280 self.assertEqual(image.format, "PNG")
282 def test_use_footprint(self):
283 """Test the use_footprint config option, generating a fake diaSrc
284 catalog that contains footprints that get used instead of config.sizes.
285 """
286 butler = unittest.mock.Mock(spec=lsst.daf.butler.Butler)
288 def mock_get(dataset, dataId, *args, **kwargs):
289 if "_diaSrc" in dataset:
290 # The science image is the only mock image with a source in it.
291 catalog = make_mock_catalog(self.science)
292 # Assign the matching source id to the detection.
293 match = DATA["visit"] == dataId["visit"]
294 catalog["id"] = DATA["diaSourceId"].to_numpy()[match][0]
295 return catalog
296 else:
297 return self.science
299 butler.get.side_effect = mock_get
301 with tempfile.TemporaryDirectory() as path:
302 config = plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask.ConfigClass()
303 config.use_footprint = True
304 cutouts = plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask(config=config,
305 output_path=path)
306 result = cutouts.write_images(DATA, butler)
307 self.assertEqual(result, list(DATA["diaSourceId"]))
308 for file in ("images/506428274000260000/506428274000265570.png",
309 "images/527736141479140000/527736141479149732.png"):
310 filename = os.path.join(path, file)
311 self.assertTrue(os.path.exists(filename))
312 with PIL.Image.open(filename) as image:
313 self.assertEqual(image.format, "PNG")
315 def test_write_images_exception(self):
316 """Test that write_images() catches errors in loading data."""
317 butler = unittest.mock.Mock(spec=lsst.daf.butler.Butler)
318 err = "Dataset not found"
319 butler.get.side_effect = LookupError(err)
321 with tempfile.TemporaryDirectory() as path:
322 config = plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask.ConfigClass()
323 cutouts = plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask(config=config,
324 output_path=path)
326 with self.assertLogs("lsst.plotImageSubtractionCutouts", "ERROR") as cm:
327 cutouts.write_images(DATA, butler)
328 self.assertIn(
329 "LookupError processing diaSourceId 506428274000265570: Dataset not found", cm.output[0]
330 )
331 self.assertIn(
332 "LookupError processing diaSourceId 527736141479149732: Dataset not found", cm.output[1]
333 )
335 def check_make_manifest(self, url_root, url_list):
336 """Check that make_manifest returns an appropriate DataFrame."""
337 data = [5, 10, 20]
338 config = plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask.ConfigClass()
339 config.url_root = url_root
340 # output_path does nothing here
341 cutouts = plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask(config=config, output_path="")
342 manifest = cutouts._make_manifest(data)
343 self.assertEqual(manifest["metadata:diaSourceId"].to_list(), [5, 10, 20])
344 self.assertEqual(manifest["location:1"].to_list(), url_list)
346 def test_make_manifest(self):
347 # check without an ending slash
348 root = "http://example.org/zooniverse"
349 url_list = [
350 f"{root}/images/5.png",
351 f"{root}/images/10.png",
352 f"{root}/images/20.png",
353 ]
354 self.check_make_manifest(root, url_list)
356 # check with an ending slash
357 root = "http://example.org/zooniverse/"
358 url_list = [
359 f"{root}images/5.png",
360 f"{root}images/10.png",
361 f"{root}images/20.png",
362 ]
363 self.check_make_manifest(root, url_list)
365 def test_pickle(self):
366 """Test that the task is pickleable (necessary for multiprocessing).
367 """
368 config = plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask.ConfigClass()
369 config.sizes = [63]
370 cutouts = plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask(config=config,
371 output_path="something")
372 other = pickle.loads(pickle.dumps(cutouts))
373 self.assertEqual(cutouts.config.sizes, other.config.sizes)
374 self.assertEqual(cutouts._output_path, other._output_path)
377class TestPlotImageSubtractionCutoutsMain(lsst.utils.tests.TestCase):
378 """Test the commandline interface main() function via mocks."""
379 def setUp(self):
380 datadir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data/")
381 self.sqlitefile = os.path.join(datadir, "apdb.sqlite3")
382 self.repo = "/not/a/real/butler"
383 self.collection = "mockRun"
384 self.outputPath = "/an/output/path"
385 self.configFile = os.path.join(datadir, "plotImageSubtractionCutoutsConfig.py")
386 self.instrument = "LATISS"
388 # DM-39501: mock butler, until detector/visit are in APDB.
389 butlerPatch = unittest.mock.patch("lsst.daf.butler.Butler")
390 self._butler = butlerPatch.start()
391 self.addCleanup(butlerPatch.stop)
392 # DM-39501: mock unpacker, until detector/visit are in APDB.
393 import lsst.obs.lsst
394 universe = lsst.daf.butler.DimensionUniverse()
395 data_id = lsst.daf.butler.DataCoordinate.standardize({"instrument": self.instrument},
396 universe=universe)
397 # ObservationDimensionPacker is harder to use in a butler-less
398 # environment, so we need a temporary dependency on obs_lsst until
399 # this all goes away on DM-39501.
400 packer = lsst.obs.lsst.RubinDimensionPacker(data_id, is_exposure=False)
401 instrumentPatch = unittest.mock.patch.object(lsst.pipe.base.Instrument,
402 "make_default_dimension_packer",
403 return_value=packer)
404 self._instrument = instrumentPatch.start()
405 self.addCleanup(instrumentPatch.stop)
407 def test_main_args(self):
408 """Test typical arguments to main()."""
409 args = [
410 "plotImageSubtractionCutouts",
411 f"--sqlitefile={self.sqlitefile}",
412 f"--collections={self.collection}",
413 f"-C={self.configFile}",
414 self.repo,
415 self.outputPath,
416 ]
417 with unittest.mock.patch.object(
418 plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask, "run", autospec=True
419 ) as run, unittest.mock.patch.object(sys, "argv", args):
420 plotImageSubtractionCutouts.main()
421 self.assertEqual(self._butler.call_args.args, (self.repo,))
422 self.assertEqual(
423 self._butler.call_args.kwargs, {"collections": [self.collection]}
424 )
425 # NOTE: can't easily test the `data` arg to run, as select_sources
426 # reads in a random order every time.
427 self.assertEqual(run.call_args.args[2], self._butler.return_value)
429 def test_main_args_no_collections(self):
430 """Test with no collections argument."""
431 args = [
432 "plotImageSubtractionCutouts",
433 f"--sqlitefile={self.sqlitefile}",
434 f"-C={self.configFile}",
435 self.repo,
436 self.outputPath,
437 ]
438 with unittest.mock.patch.object(
439 plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask, "run", autospec=True
440 ) as run, unittest.mock.patch.object(sys, "argv", args):
441 plotImageSubtractionCutouts.main()
442 self.assertEqual(self._butler.call_args.args, (self.repo,))
443 self.assertEqual(self._butler.call_args.kwargs, {"collections": None})
444 self.assertIsInstance(run.call_args.args[1], pd.DataFrame)
445 self.assertEqual(run.call_args.args[2], self._butler.return_value)
447 def test_main_collection_list(self):
448 """Test passing a list of collections."""
449 collections = ["mock1", "mock2", "mock3"]
450 args = [
451 "plotImageSubtractionCutouts",
452 f"--sqlitefile={self.sqlitefile}",
453 self.repo,
454 self.outputPath,
455 f"-C={self.configFile}",
456 "--collections",
457 ]
458 args.extend(collections)
459 with unittest.mock.patch.object(
460 plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask, "run", autospec=True
461 ) as run, unittest.mock.patch.object(sys, "argv", args):
462 plotImageSubtractionCutouts.main()
463 self.assertEqual(self._butler.call_args.args, (self.repo,))
464 self.assertEqual(
465 self._butler.call_args.kwargs, {"collections": collections}
466 )
467 self.assertIsInstance(run.call_args.args[1], pd.DataFrame)
468 self.assertEqual(run.call_args.args[2], self._butler.return_value)
470 def test_main_args_limit_offset(self):
471 """Test typical arguments to main()."""
472 args = [
473 "plotImageSubtractionCutouts",
474 f"--sqlitefile={self.sqlitefile}",
475 f"--collections={self.collection}",
476 f"-C={self.configFile}",
477 "--all",
478 "--limit=5",
479 self.repo,
480 self.outputPath,
481 ]
482 with unittest.mock.patch.object(
483 plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask,
484 "write_images",
485 autospec=True,
486 return_value=[5]
487 ) as write_images, unittest.mock.patch.object(
488 plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask,
489 "write_manifest",
490 autospec=True
491 ) as write_manifest, unittest.mock.patch.object(sys, "argv", args):
492 plotImageSubtractionCutouts.main()
493 self.assertEqual(self._butler.call_args.args, (self.repo,))
494 self.assertEqual(
495 self._butler.call_args.kwargs, {"collections": [self.collection]}
496 )
497 self.assertIsInstance(write_images.call_args.args[1], pd.DataFrame)
498 self.assertEqual(write_images.call_args.args[2], self._butler.return_value)
499 # The test apdb contains 290 DiaSources, so we get the return of
500 # `write_images` (enforced as `5` above) 58 times.
501 self.assertEqual(write_manifest.call_args.args[1], 58*[5])
503 @unittest.skip("Mock and multiprocess don't mix: https://github.com/python/cpython/issues/100090")
504 def test_main_args_multiprocessing(self):
505 """Test running with multiprocessing.
506 """
507 args = [
508 "plotImageSubtractionCutouts",
509 f"--sqlitefile={self.sqlitefile}",
510 f"--collections={self.collection}",
511 "-j2",
512 f"-C={self.configFile}",
513 self.repo,
514 self.outputPath,
515 ]
516 with unittest.mock.patch.object(
517 plotImageSubtractionCutouts.PlotImageSubtractionCutoutsTask, "run", autospec=True
518 ) as run, unittest.mock.patch.object(sys, "argv", args):
519 plotImageSubtractionCutouts.main()
520 self.assertEqual(self._butler.call_args.args, (self.repo,))
521 self.assertEqual(self._butler.call_args.kwargs, {"collections": [self.collection]})
522 # NOTE: can't easily test the `data` arg to run, as select_sources
523 # reads in a random order every time.
524 self.assertEqual(run.call_args.args[2], self._butler.return_value)
527class TestCutoutPath(lsst.utils.tests.TestCase):
529 def setUp(self):
530 self.id = 12345678
532 def test_normal_path(self):
533 """Can the path manager handles non-chunked paths?
534 """
535 manager = plotImageSubtractionCutouts.CutoutPath("some/root/path")
536 path = manager(id=self.id, filename=f"{self.id}.png")
537 self.assertEqual(path, f"some/root/path/images/{self.id}.png")
539 def test_chunking(self):
540 """Can the path manager handle ids chunked into 10,000 file
541 directories?
542 """
543 manager = plotImageSubtractionCutouts.CutoutPath("some/root/path", chunk_size=10000)
544 path = manager(id=self.id, filename=f"{self.id}.png")
545 self.assertEqual(path, f"some/root/path/images/12340000/{self.id}.png")
547 def test_subdir(self):
548 manager = plotImageSubtractionCutouts.CutoutPath("some/root/path", subdirectory="foo")
549 path = manager(id=self.id, filename=f"{self.id}.png")
550 self.assertEqual(path, f"some/root/path/foo/{self.id}.png")
552 def test_chunk_sizes(self):
553 """Test valid and invalid values for the chunk_size parameter.
554 """
555 with self.assertRaisesRegex(RuntimeError, "chunk_size must be a power of 10"):
556 plotImageSubtractionCutouts.CutoutPath("some/root/path", chunk_size=123)
558 with self.assertRaisesRegex(RuntimeError, "chunk_size must be a power of 10"):
559 plotImageSubtractionCutouts.CutoutPath("some/root/path", chunk_size=12300)
561 # should not raise
562 plotImageSubtractionCutouts.CutoutPath("some/root/path", chunk_size=1000)
563 plotImageSubtractionCutouts.CutoutPath("some/root/path", chunk_size=1000000)
566class TestMemory(lsst.utils.tests.MemoryTestCase):
567 pass
570def setup_module(module):
571 lsst.utils.tests.init()
574if __name__ == "__main__": 574 ↛ 575line 574 didn't jump to line 575 because the condition on line 574 was never true
575 lsst.utils.tests.init()
576 unittest.main()