Coverage for python/lsst/summit/extras/monitoring.py: 14%
92 statements
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-21 20:22 +0000
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-21 20:22 +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/>.
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)
32# TODO: maybe add option to create display and return URL?
35class Monitor():
36 """Create a monitor for AuxTel.
38 Scans the butler repo for new images and sends each one, after running
39 bestEffortIsr, to the display.
41 Now largely superceded by RubinTV.
43 Parameters
44 -------
45 fireflyDisplay : `lsst.afw.display.Display`
46 A Firefly display instance.
47 """
48 cadence = 1 # in seconds
49 runIsr = True
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
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
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}")
75 return elements
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)
85 elements = []
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
97 for k, v in dataId.items(): # dataId done per line for vertical display
98 elements.append(f"{k}:{v}")
100 if obj:
101 elements.append(f"{obj}")
102 else:
103 elements.append(f"{imageType}")
105 expTime = exp.getInfo().getVisitInfo().getExposureTime()
106 filt = exp.filter.physicalLabel
108 elements.append(f"{expTime}s exp")
109 elements.append(f"{filt}")
111 elements.extend(self._calcImageStats(exp))
113 if asList:
114 return elements
115 return " ".join([e for e in elements])
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
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")
130 def run(self, durationInSeconds=-1):
131 """Run the monitor, displaying new images as they are taken.
133 Parameters
134 ----------
135 durationInSeconds : `int`, optional
136 How long to run for. Use -1 for infinite.
137 """
139 if durationInSeconds == -1:
140 nLoops = int(1e9)
141 else:
142 nLoops = int(durationInSeconds//self.cadence)
144 lastDisplayed = -1
145 for i in range(nLoops):
146 try:
147 dataId, expId = self._getLatestImageDataIdAndExpId()
149 if lastDisplayed == expId:
150 sleep(self.cadence)
151 continue
153 if self.runIsr:
154 exp = self.bestEffort.getExposure(dataId)
155 else:
156 exp = self.butler.get('raw', dataId=dataId)
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))
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?
173 if self.overlayAmps:
174 cgUtils.overlayCcdBoxes(exp.getDetector(), display=self.display, isTrimmed=True)
176 self._printImageInfo(imageInfoText)
177 lastDisplayed = expId
179 except NotFoundError as e: # NotFoundError when filters aren't defined
180 print(f'Skipped displaying {dataId} due to {e}')
181 return