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

92 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-01-07 03:41 -0800

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 

22import numpy as np 

23import lsst.afw.cameraGeom.utils as cgUtils 

24from lsst.pex.exceptions import NotFoundError 

25from lsst.summit.utils.bestEffort import BestEffortIsr 

26import lsst.geom as geom 

27from time import sleep 

28from lsst.summit.utils.butlerUtils import (makeDefaultLatissButler, 

29 getMostRecentDataId, getExpIdFromDayObsSeqNum, 

30 getExpRecordFromDataId) 

31 

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

33 

34 

35class Monitor(): 

36 """Create a monitor for AuxTel. 

37 

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

39 bestEffortIsr, to the display. 

40 

41 Now largely superceded by RubinTV. 

42 

43 Parameters 

44 ------- 

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

46 A Firefly display instance. 

47 """ 

48 cadence = 1 # in seconds 

49 runIsr = True 

50 

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

52 """""" 

53 self.butler = makeDefaultLatissButler() 

54 self.display = fireflyDisplay 

55 self.bestEffort = BestEffortIsr(**kwargs) 

56 self.writeQuickLookImages = None 

57 self.overlayAmps = False # do the overlay? 

58 self.measureFromChipCenter = False 

59 

60 def _getLatestImageDataIdAndExpId(self): 

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

62 """ 

63 dataId = getMostRecentDataId(self.butler) 

64 expId = getExpIdFromDayObsSeqNum(self.butler, dataId)['exposure'] 

65 return dataId, expId 

66 

67 def _calcImageStats(self, exp): 

68 elements = [] 

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

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

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

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

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

74 

75 return elements 

76 

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

78 # TODO: add the following to the display: 

79 # az, el, zenith angle 

80 # main source centroid 

81 # PSF 

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

83 # main star max ADU (post isr) 

84 

85 elements = [] 

86 

87 expRecord = getExpRecordFromDataId(self.butler, dataId) 

88 imageType = expRecord.observation_type 

89 obj = None 

90 if imageType.upper() not in ['BIAS', 'DARK', 'FLAT']: 

91 try: 

92 obj = expRecord.target_name 

93 obj = obj.replace(' ', '') 

94 except Exception: 

95 pass 

96 

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

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

99 

100 if obj: 

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

102 else: 

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

104 

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

106 filt = exp.filter.physicalLabel 

107 

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

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

110 

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

112 

113 if asList: 

114 return elements 

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

116 

117 def _printImageInfo(self, elements): 

118 size = 3 

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

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

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

122 

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

124 # Also maybe a sleep as it seems buggy 

125 for i, item in enumerate(elements): 

126 y = top - (i*vSpacing) 

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

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

129 

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

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

132 

133 Parameters 

134 ---------- 

135 durationInSeconds : `int`, optional 

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

137 """ 

138 

139 if durationInSeconds == -1: 

140 nLoops = int(1e9) 

141 else: 

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

143 

144 lastDisplayed = -1 

145 for i in range(nLoops): 

146 try: 

147 dataId, expId = self._getLatestImageDataIdAndExpId() 

148 

149 if lastDisplayed == expId: 

150 sleep(self.cadence) 

151 continue 

152 

153 if self.runIsr: 

154 exp = self.bestEffort.getExposure(dataId) 

155 else: 

156 exp = self.butler.get('raw', dataId=dataId) 

157 

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

159 # being mutually exclusive 

160 if self.measureFromChipCenter: # after writing only! 

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

162 

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

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

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

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

167 try: 

168 self.display.scale('asinh', 'zscale') 

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

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

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

172 

173 if self.overlayAmps: 

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

175 

176 self._printImageInfo(imageInfoText) 

177 lastDisplayed = expId 

178 

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

180 print(f'Skipped displaying {dataId} due to {e}') 

181 return