Coverage for tests/test_box_conversions.py: 24%
62 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-06 08:32 +0000
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-06 08:32 +0000
1# This file is part of meas_extensions_scarlet.
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/>.
22"""Round-trip tests for ``scarletBoxToBBox`` / ``bboxToScarletBox``."""
24import unittest
26import lsst.geom as geom
27import lsst.meas.extensions.scarlet as mes
28import lsst.scarlet.lite as scl
29import lsst.utils.tests
32class TestBoxConversions(lsst.utils.tests.TestCase):
33 """Round-trip tests for the box conversions in
34 ``lsst.meas.extensions.scarlet.utils``.
36 Scarlet stores boxes as ``(..., y, x)``-ordered shape and origin
37 tuples; afw uses ``Box2I`` with separate `(x, y)` accessors. The
38 conversion functions also accept an ``xy0`` offset so the same call
39 can hop between a blend-local frame and an exposure-global frame.
40 """
42 def test_scarletBoxToBBox_roundtrip(self):
43 """``bboxToScarletBox(scarletBoxToBBox(box)) == box`` over a
44 sweep of origins and shapes.
45 """
46 cases = [
47 # Zero origin — the most common parent-blend case.
48 ((5, 8), (0, 0)),
49 # Small positive origin with an asymmetric (y, x) shape.
50 ((25, 30), (17, 5)),
51 # Degenerate 1×1 box far from the origin.
52 ((1, 1), (100, 200)),
53 # Negative origin — exercises sub-image offsets where the
54 # blend extends below (0, 0).
55 ((50, 50), (-10, -20)),
56 # Origin pinned to a single axis (y == 0, x large).
57 ((3, 17), (0, 99)),
58 ]
59 for shape, origin in cases:
60 with self.subTest(shape=shape, origin=origin):
61 box = scl.Box(shape, origin)
62 bbox = mes.utils.scarletBoxToBBox(box)
63 roundtripped = mes.utils.bboxToScarletBox(bbox)
64 self.assertTupleEqual(roundtripped.origin, origin)
65 self.assertTupleEqual(roundtripped.shape, shape)
67 def test_bboxToScarletBox_roundtrip(self):
68 """``scarletBoxToBBox(bboxToScarletBox(bbox)) == bbox`` over a
69 sweep of corners and extents.
70 """
71 cases = [
72 # Zero-origin Box2I — the trivial case.
73 (geom.Point2I(0, 0), geom.Extent2I(5, 8)),
74 # Positive corner; mirrors the scarlet test above with
75 # axes swapped to confirm the (x, y) / (y, x) ordering
76 # convention is consistent in both directions.
77 (geom.Point2I(17, 5), geom.Extent2I(30, 25)),
78 # Negative corner — afw allows it; this pins that
79 # ``bboxToScarletBox`` doesn't clamp it to zero.
80 (geom.Point2I(-20, -10), geom.Extent2I(50, 50)),
81 # Single-axis offset, asymmetric extent.
82 (geom.Point2I(99, 0), geom.Extent2I(17, 3)),
83 ]
84 for minPoint, extent in cases:
85 # ``subTest`` kwargs are serialized through ``execnet`` when
86 # the suite runs under ``pytest-xdist`` (e.g. on Jenkins),
87 # and ``execnet`` can't dump pybind11 objects. Label the
88 # subtest with plain int tuples instead.
89 with self.subTest(
90 minPoint=(minPoint.getX(), minPoint.getY()),
91 extent=(extent.getX(), extent.getY()),
92 ):
93 bbox = geom.Box2I(minPoint, extent)
94 box = mes.utils.bboxToScarletBox(bbox)
95 roundtripped = mes.utils.scarletBoxToBBox(box)
96 self.assertEqual(roundtripped, bbox)
98 def test_box_negative_origin(self):
99 """Both conversions handle origins below ``(0, 0)``."""
100 box = scl.Box((10, 8), (-5, -3))
101 bbox = mes.utils.scarletBoxToBBox(box)
102 self.assertEqual(bbox.getMinY(), -5)
103 self.assertEqual(bbox.getMinX(), -3)
104 self.assertEqual(bbox.getHeight(), 10)
105 self.assertEqual(bbox.getWidth(), 8)
106 roundtripped = mes.utils.bboxToScarletBox(bbox)
107 self.assertTupleEqual(roundtripped.origin, box.origin)
108 self.assertTupleEqual(roundtripped.shape, box.shape)
110 def test_box_with_xy0_offset(self):
111 """A non-zero ``xy0`` shifts forward and back symmetrically."""
112 box = scl.Box((25, 30), (17, 5))
113 xy0 = geom.Point2I(100, 200)
114 bbox = mes.utils.scarletBoxToBBox(box, xy0)
115 self.assertEqual(bbox.getMinX(), 5 + 100)
116 self.assertEqual(bbox.getMinY(), 17 + 200)
117 self.assertEqual(bbox.getWidth(), 30)
118 self.assertEqual(bbox.getHeight(), 25)
119 roundtripped = mes.utils.bboxToScarletBox(bbox, xy0)
120 self.assertTupleEqual(roundtripped.origin, box.origin)
121 self.assertTupleEqual(roundtripped.shape, box.shape)
123 def test_box_transforms(self):
124 """Backward-continuity lift of ``TestUtils.test_box_transforms``
125 from the pre-refactor ``test_deblend.py``.
126 """
127 box = scl.Box((25, 30), (17, 5))
128 bbox = mes.utils.scarletBoxToBBox(box)
129 x0, y0 = bbox.getMin()
130 width = bbox.getWidth()
131 height = bbox.getHeight()
132 self.assertTupleEqual((y0, x0), box.origin)
133 self.assertTupleEqual((height, width), box.shape)
135 newBox = mes.utils.bboxToScarletBox(bbox)
136 self.assertTupleEqual(newBox.origin, box.origin)
137 self.assertTupleEqual(newBox.shape, box.shape)
140def setup_module(module):
141 lsst.utils.tests.init()
144class MemoryTester(lsst.utils.tests.MemoryTestCase):
145 pass
148if __name__ == "__main__": 148 ↛ 149line 148 didn't jump to line 149 because the condition on line 148 was never true
149 lsst.utils.tests.init()
150 unittest.main()