Coverage for tests/test_mask_scoping.py: 30%
71 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-05-29 08:25 +0000
« prev ^ index » next coverage.py v7.14.1, created at 2026-05-29 08:25 +0000
1#
2# LSST Data Management System
3# Copyright 2026 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#
23"""Tests that mask overlays sent to the Firefly server are keyed by both
24frame and plane name, so that multiple frames carrying same-named planes
25(e.g. ``DETECTED`` on three different exposures) do not overwrite each
26other on the server.
27"""
29import unittest
30from types import SimpleNamespace
31from unittest import mock
33import lsst.utils.tests
34from lsst.display.firefly import firefly as firefly_mod
37def _make_impl(frame, mask_ids=None, mask_dict=None, mask_plane_colors=None,
38 default_mask_plane_color=None, mask_transparencies=None):
39 """Construct a ``DisplayImpl`` without running ``__init__``.
41 ``DisplayImpl.__init__`` requires a live Firefly server; we sidestep it
42 via ``__new__`` and inject only the attributes the methods under test
43 read. ``display`` is stubbed as a SimpleNamespace exposing the small
44 surface those methods touch (``frame``, ``getMaskPlaneColor``,
45 ``_defaultMaskPlaneColor``).
46 """
47 impl = firefly_mod.DisplayImpl.__new__(firefly_mod.DisplayImpl)
48 impl.display = SimpleNamespace(
49 frame=frame,
50 getMaskPlaneColor=lambda name: (mask_plane_colors or {}).get(name, "red"),
51 _defaultMaskPlaneColor=default_mask_plane_color or {},
52 )
53 impl._maskIds = list(mask_ids) if mask_ids is not None else []
54 impl._maskDict = dict(mask_dict) if mask_dict is not None else {}
55 impl._maskPlaneColors = dict(mask_plane_colors) if mask_plane_colors is not None else {}
56 impl._maskTransparencies = dict(mask_transparencies) if mask_transparencies is not None else {}
57 impl._fireflyFitsID = "fits-id-stub"
58 # ``__del__`` -> ``_close()`` reads these attributes; satisfy it
59 # since we are bypassing ``__init__``.
60 impl.verbose = False
61 impl._client = None
62 return impl
65class ScopedMaskIdTest(unittest.TestCase):
66 """The helper itself is pure -- verify it produces distinct ids per
67 frame while remaining human-readable in the layer panel."""
69 def test_distinct_per_frame(self):
70 self.assertNotEqual(firefly_mod.DisplayImpl._scoped_mask_id(0, "DETECTED"),
71 firefly_mod.DisplayImpl._scoped_mask_id(1, "DETECTED"))
73 def test_includes_plane_name(self):
74 self.assertIn("DETECTED", firefly_mod.DisplayImpl._scoped_mask_id(0, "DETECTED"))
75 self.assertIn("0", firefly_mod.DisplayImpl._scoped_mask_id(0, "DETECTED"))
78class RemoveMasksTest(unittest.TestCase):
79 """``_remove_masks`` is invoked when a new image is loaded into a
80 frame; it must only clear *that* frame's overlays."""
82 def test_only_removes_current_frame(self):
83 impl = _make_impl(
84 frame=1,
85 mask_ids=[(0, "DETECTED"), (1, "DETECTED"), (1, "BAD"), (2, "SAT")],
86 )
87 with mock.patch.object(firefly_mod, "_fireflyClient") as client:
88 impl._remove_masks()
89 removed = [(c.kwargs["plot_id"], c.kwargs["mask_id"])
90 for c in client.remove_mask.call_args_list]
91 # Frame 1 layers removed, frames 0 and 2 left alone.
92 self.assertEqual(set(removed),
93 {("1", "f1__DETECTED"), ("1", "f1__BAD")})
94 self.assertEqual(set(impl._maskIds), {(0, "DETECTED"), (2, "SAT")})
97class SetMaskPlaneColorTest(unittest.TestCase):
98 """``setMaskPlaneColor`` should retarget only the current frame's
99 layer, leaving sibling frames' layers in place."""
101 def test_scopes_mask_id(self):
102 impl = _make_impl(
103 frame=2,
104 mask_dict={"DETECTED": 5},
105 mask_plane_colors={"DETECTED": "red"},
106 )
107 with mock.patch.object(firefly_mod, "_fireflyClient") as client:
108 impl._setMaskPlaneColor("DETECTED", "cyan")
109 (remove_call,) = client.remove_mask.call_args_list
110 (add_call,) = client.add_mask.call_args_list
111 self.assertEqual(remove_call.kwargs["plot_id"], "2")
112 self.assertEqual(remove_call.kwargs["mask_id"], "f2__DETECTED")
113 self.assertEqual(add_call.kwargs["plot_id"], "2")
114 self.assertEqual(add_call.kwargs["mask_id"], "f2__DETECTED")
115 self.assertEqual(impl._maskPlaneColors["DETECTED"], "cyan")
117 def test_ignore_color_skips_add(self):
118 impl = _make_impl(
119 frame=0,
120 mask_dict={"DETECTED": 5},
121 mask_plane_colors={"DETECTED": "red"},
122 )
123 with mock.patch.object(firefly_mod, "_fireflyClient") as client:
124 impl._setMaskPlaneColor("DETECTED", "ignore")
125 self.assertEqual(client.remove_mask.call_count, 1)
126 self.assertEqual(client.add_mask.call_count, 0)
129class SetMaskTransparencyTest(unittest.TestCase):
130 """``setMaskTransparency`` dispatches per-layer attribute changes;
131 the dispatched ``imageOverlayId`` must be the frame-scoped id."""
133 def test_named_plane_uses_scoped_overlay_id(self):
134 impl = _make_impl(frame=3)
135 with mock.patch.object(firefly_mod, "_fireflyClient") as client:
136 impl._setMaskTransparency(40, "DETECTED")
137 (call,) = client.dispatch.call_args_list
138 payload = call.kwargs["payload"]
139 self.assertEqual(payload["plotId"], "3")
140 self.assertEqual(payload["imageOverlayId"], "f3__DETECTED")
141 self.assertAlmostEqual(payload["attributes"]["opacity"], 0.6)
143 def test_all_planes_filters_by_frame(self):
144 # ``maskName=None`` means "all of this frame's planes". Layers
145 # registered against other frames must not be touched.
146 impl = _make_impl(
147 frame=1,
148 mask_ids=[(0, "DETECTED"), (1, "DETECTED"), (1, "BAD")],
149 default_mask_plane_color={},
150 )
151 with mock.patch.object(firefly_mod, "_fireflyClient") as client:
152 impl._setMaskTransparency(0, None)
153 ids = {c.kwargs["payload"]["imageOverlayId"]
154 for c in client.dispatch.call_args_list}
155 self.assertEqual(ids, {"f1__DETECTED", "f1__BAD"})
158class TestMemory(lsst.utils.tests.MemoryTestCase):
159 pass
162def setup_module(module):
163 lsst.utils.tests.init()
166if __name__ == "__main__": 166 ↛ 167line 166 didn't jump to line 167 because the condition on line 166 was never true
167 lsst.utils.tests.init()
168 unittest.main()