Coverage for python/lsst/summit/extras/monitoring.py: 14%
92 statements
« prev ^ index » next coverage.py v7.5.0, created at 2024-05-01 18:03 -0700
« prev ^ index » next coverage.py v7.5.0, created at 2024-05-01 18:03 -0700
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 time import sleep
24import numpy as np
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)
37# TODO: maybe add option to create display and return URL?
40class Monitor:
41 """Create a monitor for AuxTel.
43 Scans the butler repo for new images and sends each one, after running
44 bestEffortIsr, to the display.
46 Now largely superceded by RubinTV.
48 Parameters
49 -------
50 fireflyDisplay : `lsst.afw.display.Display`
51 A Firefly display instance.
52 """
54 cadence = 1 # in seconds
55 runIsr = True
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
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
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}")
80 return elements
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)
90 elements = []
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
102 for k, v in dataId.items(): # dataId done per line for vertical display
103 elements.append(f"{k}:{v}")
105 if obj:
106 elements.append(f"{obj}")
107 else:
108 elements.append(f"{imageType}")
110 expTime = exp.getInfo().getVisitInfo().getExposureTime()
111 filt = exp.filter.physicalLabel
113 elements.append(f"{expTime}s exp")
114 elements.append(f"{filt}")
116 elements.extend(self._calcImageStats(exp))
118 if asList:
119 return elements
120 return " ".join([e for e in elements])
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
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")
135 def run(self, durationInSeconds=-1):
136 """Run the monitor, displaying new images as they are taken.
138 Parameters
139 ----------
140 durationInSeconds : `int`, optional
141 How long to run for. Use -1 for infinite.
142 """
144 if durationInSeconds == -1:
145 nLoops = int(1e9)
146 else:
147 nLoops = int(durationInSeconds // self.cadence)
149 lastDisplayed = -1
150 for i in range(nLoops):
151 try:
152 dataId, expId = self._getLatestImageDataIdAndExpId()
154 if lastDisplayed == expId:
155 sleep(self.cadence)
156 continue
158 if self.runIsr:
159 exp = self.bestEffort.getExposure(dataId)
160 else:
161 exp = self.butler.get("raw", dataId=dataId)
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))
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?
178 if self.overlayAmps:
179 cgUtils.overlayCcdBoxes(exp.getDetector(), display=self.display, isTrimmed=True)
181 self._printImageInfo(imageInfoText)
182 lastDisplayed = expId
184 except NotFoundError as e: # NotFoundError when filters aren't defined
185 print(f"Skipped displaying {dataId} due to {e}")
186 return