Coverage for python/lsst/summit/extras/monitoring.py: 14%

92 statements  

« prev     ^ index     » next       coverage.py v7.4.3, created at 2024-03-12 14:11 +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 time import sleep 

23 

24import numpy as np 

25 

26import lsst.afw.cameraGeom.utils as cgUtils 

27import lsst.geom as geom 

28from lsst.pex.exceptions import NotFoundError 

29from lsst.summit.utils.bestEffort import BestEffortIsr 

30from lsst.summit.utils.butlerUtils import ( 

31 getExpIdFromDayObsSeqNum, 

32 getExpRecordFromDataId, 

33 getMostRecentDataId, 

34 makeDefaultLatissButler, 

35) 

36 

37# TODO: maybe add option to create display and return URL? 

38 

39 

40class Monitor: 

41 """Create a monitor for AuxTel. 

42 

43 Scans the butler repo for new images and sends each one, after running 

44 bestEffortIsr, to the display. 

45 

46 Now largely superceded by RubinTV. 

47 

48 Parameters 

49 ------- 

50 fireflyDisplay : `lsst.afw.display.Display` 

51 A Firefly display instance. 

52 """ 

53 

54 cadence = 1 # in seconds 

55 runIsr = True 

56 

57 def __init__(self, fireflyDisplay, **kwargs): 

58 """""" 

59 self.butler = makeDefaultLatissButler() 

60 self.display = fireflyDisplay 

61 self.bestEffort = BestEffortIsr(**kwargs) 

62 self.writeQuickLookImages = None 

63 self.overlayAmps = False # do the overlay? 

64 self.measureFromChipCenter = False 

65 

66 def _getLatestImageDataIdAndExpId(self): 

67 """Get the dataId and expId for the most recent image in the repo.""" 

68 dataId = getMostRecentDataId(self.butler) 

69 expId = getExpIdFromDayObsSeqNum(self.butler, dataId)["exposure"] 

70 return dataId, expId 

71 

72 def _calcImageStats(self, exp): 

73 elements = [] 

74 median = np.median(exp.image.array) 

75 elements.append(f"Median={median:.2f}") 

76 mean = np.mean(exp.image.array) 

77 # elements.append(f"Median={median:.2f}") 

78 elements.append(f"Mean={mean:.2f}") 

79 

80 return elements 

81 

82 def _makeImageInfoText(self, dataId, exp, asList=False): 

83 # TODO: add the following to the display: 

84 # az, el, zenith angle 

85 # main source centroid 

86 # PSF 

87 # num saturated pixels (or maybe just an isSaturated bool) 

88 # main star max ADU (post isr) 

89 

90 elements = [] 

91 

92 expRecord = getExpRecordFromDataId(self.butler, dataId) 

93 imageType = expRecord.observation_type 

94 obj = None 

95 if imageType.upper() not in ["BIAS", "DARK", "FLAT"]: 

96 try: 

97 obj = expRecord.target_name 

98 obj = obj.replace(" ", "") 

99 except Exception: 

100 pass 

101 

102 for k, v in dataId.items(): # dataId done per line for vertical display 

103 elements.append(f"{k}:{v}") 

104 

105 if obj: 

106 elements.append(f"{obj}") 

107 else: 

108 elements.append(f"{imageType}") 

109 

110 expTime = exp.getInfo().getVisitInfo().getExposureTime() 

111 filt = exp.filter.physicalLabel 

112 

113 elements.append(f"{expTime}s exp") 

114 elements.append(f"{filt}") 

115 

116 elements.extend(self._calcImageStats(exp)) 

117 

118 if asList: 

119 return elements 

120 return " ".join([e for e in elements]) 

121 

122 def _printImageInfo(self, elements): 

123 size = 3 

124 top = 3850 # just under title for size=3 

125 xnom = -600 # 0 is the left edge of the image 

126 vSpacing = 100 # about right for size=3, can make f(size) if needed 

127 

128 # TODO: add a with buffering and a .flush() 

129 # Also maybe a sleep as it seems buggy 

130 for i, item in enumerate(elements): 

131 y = top - (i * vSpacing) 

132 x = xnom + (size * 18.5 * len(item) // 2) 

133 self.display.dot(str(item), x, y, size, ctype="red", fontFamily="courier") 

134 

135 def run(self, durationInSeconds=-1): 

136 """Run the monitor, displaying new images as they are taken. 

137 

138 Parameters 

139 ---------- 

140 durationInSeconds : `int`, optional 

141 How long to run for. Use -1 for infinite. 

142 """ 

143 

144 if durationInSeconds == -1: 

145 nLoops = int(1e9) 

146 else: 

147 nLoops = int(durationInSeconds // self.cadence) 

148 

149 lastDisplayed = -1 

150 for i in range(nLoops): 

151 try: 

152 dataId, expId = self._getLatestImageDataIdAndExpId() 

153 

154 if lastDisplayed == expId: 

155 sleep(self.cadence) 

156 continue 

157 

158 if self.runIsr: 

159 exp = self.bestEffort.getExposure(dataId) 

160 else: 

161 exp = self.butler.get("raw", dataId=dataId) 

162 

163 # TODO: add logic to deal with amp overlay and chip center 

164 # being mutually exclusive 

165 if self.measureFromChipCenter: # after writing only! 

166 exp.setXY0(geom.PointI(-2036, -2000)) 

167 

168 print(f"Displaying {dataId}...") 

169 imageInfoText = self._makeImageInfoText(dataId, exp, asList=True) 

170 # too long of a title breaks Java FITS i/o 

171 fireflyTitle = " ".join([s for s in imageInfoText])[:67] 

172 try: 

173 self.display.scale("asinh", "zscale") 

174 self.display.mtv(exp, title=fireflyTitle) 

175 except Exception as e: # includes JSONDecodeError, HTTPError, anything else 

176 print(f"Caught error {e}, skipping this image") # TODO: try again maybe? 

177 

178 if self.overlayAmps: 

179 cgUtils.overlayCcdBoxes(exp.getDetector(), display=self.display, isTrimmed=True) 

180 

181 self._printImageInfo(imageInfoText) 

182 lastDisplayed = expId 

183 

184 except NotFoundError as e: # NotFoundError when filters aren't defined 

185 print(f"Skipped displaying {dataId} due to {e}") 

186 return