Coverage for python / lsst / summit / extras / annotations.py: 41%
36 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-07 09:03 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-07 09:03 +0000
1# This file is part of summit_extras.
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/>.
22from lsst.summit.extras.imageSorter import TAGS, ImageSorter
25def _idTrans(dataIdDictOrTuple: dict | tuple[int, int]) -> tuple[int, int]:
26 """Convert a dataId to the internal ``(dayObs, seqNum)`` tuple form.
28 Parameters
29 ----------
30 dataIdDictOrTuple : `dict` or `tuple` [`int`, `int`]
31 A dataId expressed either as a dict with ``dayObs`` and ``seqNum``
32 keys or as a ``(dayObs, seqNum)`` tuple.
34 Returns
35 -------
36 dataId : `tuple` [`int`, `int`]
37 The dataId as a ``(dayObs, seqNum)`` tuple.
39 Raises
40 ------
41 RuntimeError
42 Raised if ``dataIdDictOrTuple`` is neither a `dict` nor a `tuple`.
43 """
44 if isinstance(dataIdDictOrTuple, tuple): 44 ↛ 45line 44 didn't jump to line 45 because the condition on line 44 was never true
45 return dataIdDictOrTuple
46 elif isinstance(dataIdDictOrTuple, dict): 46 ↛ 49line 46 didn't jump to line 49 because the condition on line 46 was always true
47 return (dataIdDictOrTuple["dayObs"], dataIdDictOrTuple["seqNum"])
48 else:
49 raise RuntimeError(f"Failed to parse dataId {dataIdDictOrTuple}")
52class Annotations:
53 """Interface for reading annotations written by `ImageSorter`.
55 Loads the tag and note dictionaries from an annotations file and
56 provides lookup helpers keyed by dataId.
58 Parameters
59 ----------
60 filename : `str`
61 Path to the annotations file produced by `ImageSorter`.
62 """
64 def __init__(self, filename: str):
65 self.filename = filename
66 self.tags, self.notes = self._load(filename)
68 def _load(self, filename: str) -> tuple[dict, dict]:
69 """Load tags and notes from the specified annotations file.
71 Parameters
72 ----------
73 filename : `str`
74 Path to the annotations file.
76 Returns
77 -------
78 tags : `dict`
79 Mapping of dataId tuples to tag strings.
80 notes : `dict`
81 Mapping of dataId tuples to note strings.
82 """
83 tags, notes = ImageSorter.loadAnnotations(filename)
84 return tags, notes
86 def getTags(self, dataId: dict | tuple[int, int]) -> str | None:
87 """Get the tags for the specified dataId.
89 Parameters
90 ----------
91 dataId : `dict` or `tuple` [`int`, `int`]
92 The dataId to look up.
94 Returns
95 -------
96 tags : `str` or `None`
97 The tag string for the dataId. An empty string means the image
98 was examined but no tags were set; `None` means the image was
99 not examined.
100 """
101 return self.tags.get(_idTrans(dataId), None)
103 def getNotes(self, dataId: dict | tuple[int, int]) -> str | None:
104 """Get the notes for the specified dataId.
106 Parameters
107 ----------
108 dataId : `dict` or `tuple` [`int`, `int`]
109 The dataId to look up.
111 Returns
112 -------
113 notes : `str` or `None`
114 The note string for the dataId, or `None` if no notes exist.
115 """
116 return self.notes.get(_idTrans(dataId), None)
118 def hasTags(self, dataId: dict | tuple[int, int], flags: str) -> bool | None:
119 """Check whether a dataId has all of the specified tags.
121 Parameters
122 ----------
123 dataId : `dict` or `tuple` [`int`, `int`]
124 The dataId to look up.
125 flags : `str`
126 String of single-character tag flags to test for. The
127 comparison is case-insensitive.
129 Returns
130 -------
131 hasAll : `bool` or `None`
132 `True` if all requested tags are present, `False` if any are
133 missing, or `None` if the dataId has not been examined.
134 """
135 tag = self.getTags(dataId)
136 if tag is None: # not just 'if tag' because '' is not the same as None but both are falsy
137 return None
138 return all(i in tag for i in flags.upper())
140 def getListOfCheckedData(self) -> list:
141 """Return the sorted list of all examined dataIds.
143 Returns
144 -------
145 dataIds : `list` [`tuple` [`int`, `int`]]
146 Sorted list of ``(dayObs, seqNum)`` tuples that have been
147 examined.
148 """
149 return sorted(list(self.tags.keys()))
151 def getListOfDataWithNotes(self) -> list:
152 """Return the sorted list of all dataIds that have notes.
154 Returns
155 -------
156 dataIds : `list` [`tuple` [`int`, `int`]]
157 Sorted list of ``(dayObs, seqNum)`` tuples with notes
158 attached.
159 """
160 return sorted(list(self.notes.keys()))
162 def isExamined(self, dataId: dict) -> bool:
163 """Check whether the dataId has been examined.
165 Parameters
166 ----------
167 dataId : `dict` or `tuple` [`int`, `int`]
168 The dataId to look up.
170 Returns
171 -------
172 examined : `bool`
173 `True` if the dataId has an entry in the tag dictionary.
174 """
175 return _idTrans(dataId) in self.tags
177 def printTags(self) -> None:
178 """Print the list of tag definitions used by `ImageSorter`."""
179 print(TAGS)
181 def getIdsWithGivenTags(self, tags: str, exactMatches: bool = False) -> list:
182 """Return dataIds that match the specified tag string.
184 Parameters
185 ----------
186 tags : `str`
187 String of single-character tag flags to match. The comparison
188 is case-insensitive.
189 exactMatches : `bool`, optional
190 If `True`, only return dataIds whose tag string contains
191 exactly the requested tags and no others. If `False`
192 (default), return all dataIds whose tags are a superset of
193 the requested tags.
195 Returns
196 -------
197 dataIds : `list` [`tuple` [`int`, `int`]]
198 List of matching ``(dayObs, seqNum)`` tuples.
199 """
200 if exactMatches:
201 wanted = set(tags.upper())
202 return [dId for (dId, tag) in self.tags.items() if set(tag) == wanted]
203 else:
204 return [dId for (dId, tag) in self.tags.items() if all(t in tag for t in tags.upper())]