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

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/>. 

21 

22from lsst.summit.extras.imageSorter import TAGS, ImageSorter 

23 

24 

25def _idTrans(dataIdDictOrTuple: dict | tuple[int, int]) -> tuple[int, int]: 

26 """Convert a dataId to the internal ``(dayObs, seqNum)`` tuple form. 

27 

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. 

33 

34 Returns 

35 ------- 

36 dataId : `tuple` [`int`, `int`] 

37 The dataId as a ``(dayObs, seqNum)`` tuple. 

38 

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}") 

50 

51 

52class Annotations: 

53 """Interface for reading annotations written by `ImageSorter`. 

54 

55 Loads the tag and note dictionaries from an annotations file and 

56 provides lookup helpers keyed by dataId. 

57 

58 Parameters 

59 ---------- 

60 filename : `str` 

61 Path to the annotations file produced by `ImageSorter`. 

62 """ 

63 

64 def __init__(self, filename: str): 

65 self.filename = filename 

66 self.tags, self.notes = self._load(filename) 

67 

68 def _load(self, filename: str) -> tuple[dict, dict]: 

69 """Load tags and notes from the specified annotations file. 

70 

71 Parameters 

72 ---------- 

73 filename : `str` 

74 Path to the annotations file. 

75 

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 

85 

86 def getTags(self, dataId: dict | tuple[int, int]) -> str | None: 

87 """Get the tags for the specified dataId. 

88 

89 Parameters 

90 ---------- 

91 dataId : `dict` or `tuple` [`int`, `int`] 

92 The dataId to look up. 

93 

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) 

102 

103 def getNotes(self, dataId: dict | tuple[int, int]) -> str | None: 

104 """Get the notes for the specified dataId. 

105 

106 Parameters 

107 ---------- 

108 dataId : `dict` or `tuple` [`int`, `int`] 

109 The dataId to look up. 

110 

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) 

117 

118 def hasTags(self, dataId: dict | tuple[int, int], flags: str) -> bool | None: 

119 """Check whether a dataId has all of the specified tags. 

120 

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. 

128 

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()) 

139 

140 def getListOfCheckedData(self) -> list: 

141 """Return the sorted list of all examined dataIds. 

142 

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())) 

150 

151 def getListOfDataWithNotes(self) -> list: 

152 """Return the sorted list of all dataIds that have notes. 

153 

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())) 

161 

162 def isExamined(self, dataId: dict) -> bool: 

163 """Check whether the dataId has been examined. 

164 

165 Parameters 

166 ---------- 

167 dataId : `dict` or `tuple` [`int`, `int`] 

168 The dataId to look up. 

169 

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 

176 

177 def printTags(self) -> None: 

178 """Print the list of tag definitions used by `ImageSorter`.""" 

179 print(TAGS) 

180 

181 def getIdsWithGivenTags(self, tags: str, exactMatches: bool = False) -> list: 

182 """Return dataIds that match the specified tag string. 

183 

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. 

194 

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())]