Coverage for python/lsst/summit/extras/monitoring.py: 16%
95 statements
« prev ^ index » next coverage.py v7.5.1, created at 2024-05-08 12:46 +0000
« prev ^ index » next coverage.py v7.5.1, created at 2024-05-08 12:46 +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/>.
22from time import sleep
23from typing import Any
25import numpy as np
27import lsst.afw.cameraGeom.utils as cgUtils
28import lsst.afw.display as afwDisplay
29import lsst.afw.image as afwImage
30import lsst.geom as geom
31from lsst.pex.exceptions import NotFoundError
32from lsst.summit.utils.bestEffort import BestEffortIsr
33from lsst.summit.utils.butlerUtils import (
34 getExpIdFromDayObsSeqNum,
35 getExpRecordFromDataId,
36 getMostRecentDataId,
37 makeDefaultLatissButler,
38)
40# TODO: maybe add option to create display and return URL?
43class Monitor:
44 """Create a monitor for AuxTel.
46 Scans the butler repo for new images and sends each one, after running
47 bestEffortIsr, to the display.
49 Now largely superceded by RubinTV.
51 Parameters
52 -------
53 fireflyDisplay : `lsst.afw.display.Display`
54 A Firefly display instance.
55 """
57 cadence = 1 # in seconds
58 runIsr = True
60 def __init__(self, fireflyDisplay: afwDisplay, **kwargs: Any):
61 """"""
62 self.butler = makeDefaultLatissButler()
63 self.display = fireflyDisplay
64 self.bestEffort = BestEffortIsr(**kwargs)
65 self.writeQuickLookImages = None
66 self.overlayAmps = False # do the overlay?
67 self.measureFromChipCenter = False
69 def _getLatestImageDataIdAndExpId(self) -> tuple:
70 """Get the dataId and expId for the most recent image in the repo."""
71 dataId = getMostRecentDataId(self.butler)
72 expId = getExpIdFromDayObsSeqNum(self.butler, dataId)["exposure"]
73 return dataId, expId
75 def _calcImageStats(self, exp: afwImage.Exposure) -> list[str]:
76 elements = []
77 median = np.median(exp.image.array)
78 elements.append(f"Median={median:.2f}")
79 mean = np.mean(exp.image.array)
80 # elements.append(f"Median={median:.2f}")
81 elements.append(f"Mean={mean:.2f}")
83 return elements
85 def _makeImageInfoText(self, dataId: dict, exp: afwImage.Exposure, asList: bool = False) -> list | str:
86 # TODO: add the following to the display:
87 # az, el, zenith angle
88 # main source centroid
89 # PSF
90 # num saturated pixels (or maybe just an isSaturated bool)
91 # main star max ADU (post isr)
93 elements = []
95 expRecord = getExpRecordFromDataId(self.butler, dataId)
96 imageType = expRecord.observation_type
97 obj = None
98 if imageType.upper() not in ["BIAS", "DARK", "FLAT"]:
99 try:
100 obj = expRecord.target_name
101 obj = obj.replace(" ", "")
102 except Exception:
103 pass
105 for k, v in dataId.items(): # dataId done per line for vertical display
106 elements.append(f"{k}:{v}")
108 if obj:
109 elements.append(f"{obj}")
110 else:
111 elements.append(f"{imageType}")
113 expTime = exp.getInfo().getVisitInfo().getExposureTime()
114 filt = exp.filter.physicalLabel
116 elements.append(f"{expTime}s exp")
117 elements.append(f"{filt}")
119 elements.extend(self._calcImageStats(exp))
121 if asList:
122 return elements
123 return " ".join([e for e in elements])
125 def _printImageInfo(self, elements: list) -> None:
126 size = 3
127 top = 3850 # just under title for size=3
128 xnom = -600 # 0 is the left edge of the image
129 vSpacing = 100 # about right for size=3, can make f(size) if needed
131 # TODO: add a with buffering and a .flush()
132 # Also maybe a sleep as it seems buggy
133 for i, item in enumerate(elements):
134 y = top - (i * vSpacing)
135 x = xnom + (size * 18.5 * len(item) // 2)
136 self.display.dot(str(item), x, y, size, ctype="red", fontFamily="courier")
138 def run(self, durationInSeconds: int = -1) -> None:
139 """Run the monitor, displaying new images as they are taken.
141 Parameters
142 ----------
143 durationInSeconds : `int`, optional
144 How long to run for. Use -1 for infinite.
145 """
147 if durationInSeconds == -1:
148 nLoops = int(1e9)
149 else:
150 nLoops = int(durationInSeconds // self.cadence)
152 lastDisplayed = -1
153 for i in range(nLoops):
154 try:
155 dataId, expId = self._getLatestImageDataIdAndExpId()
157 if lastDisplayed == expId:
158 sleep(self.cadence)
159 continue
161 if self.runIsr:
162 exp = self.bestEffort.getExposure(dataId)
163 else:
164 exp = self.butler.get("raw", dataId=dataId)
166 # TODO: add logic to deal with amp overlay and chip center
167 # being mutually exclusive
168 if self.measureFromChipCenter: # after writing only!
169 exp.setXY0(geom.PointI(-2036, -2000))
171 print(f"Displaying {dataId}...")
172 imageInfoText = self._makeImageInfoText(dataId, exp, asList=True)
173 # too long of a title breaks Java FITS i/o
174 fireflyTitle = " ".join([s for s in imageInfoText])[:67]
175 try:
176 self.display.scale("asinh", "zscale")
177 self.display.mtv(exp, title=fireflyTitle)
178 except Exception as e: # includes JSONDecodeError, HTTPError, anything else
179 print(f"Caught error {e}, skipping this image") # TODO: try again maybe?
181 if self.overlayAmps:
182 cgUtils.overlayCcdBoxes(exp.getDetector(), display=self.display, isTrimmed=True)
184 self._printImageInfo(imageInfoText)
185 lastDisplayed = expId
187 except NotFoundError as e: # NotFoundError when filters aren't defined
188 print(f"Skipped displaying {dataId} due to {e}")
189 return