Coverage for python/lsst/obs/lsst/rawFormatter.py: 96%
123 statements
« prev ^ index » next coverage.py v7.5.0, created at 2024-04-24 04:05 -0700
« prev ^ index » next coverage.py v7.5.0, created at 2024-04-24 04:05 -0700
1# This file is part of obs_lsst.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (http://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 <http://www.gnu.org/licenses/>.
22"""Gen3 Butler Formatters for LSST raw data.
23"""
25__all__ = (
26 "LsstCamRawFormatter",
27 "LatissRawFormatter",
28 "LsstCamImSimRawFormatter",
29 "LsstCamPhoSimRawFormatter",
30 "LsstCamSimRawFormatter",
31 "LsstTS8RawFormatter",
32 "LsstTS3RawFormatter",
33 "LsstComCamRawFormatter",
34 "LsstComCamSimRawFormatter",
35 "LsstUCDCamRawFormatter",
36)
38import numpy as np
39from astro_metadata_translator import fix_header, merge_headers
41import lsst.afw.fits
42from lsst.obs.base import FitsRawFormatterBase
43from lsst.obs.base.formatters.fitsExposure import standardizeAmplifierParameters
44from lsst.afw.cameraGeom import makeUpdatedDetector
46from ._instrument import LsstCam, Latiss, \
47 LsstCamImSim, LsstCamPhoSim, LsstTS8, \
48 LsstTS3, LsstUCDCam, LsstComCam, LsstComCamSim, LsstCamSim
49from .translators import LatissTranslator, LsstCamTranslator, \
50 LsstUCDCamTranslator, LsstTS3Translator, LsstComCamTranslator, \
51 LsstCamPhoSimTranslator, LsstTS8Translator, LsstCamImSimTranslator, \
52 LsstComCamSimTranslator, LsstCamSimTranslator
53from .assembly import fixAmpsAndAssemble, fixAmpGeometry, readRawAmps, warn_once
56class LsstCamRawFormatter(FitsRawFormatterBase):
57 translatorClass = LsstCamTranslator
58 filterDefinitions = LsstCam.filterDefinitions
59 _instrument = LsstCam
61 # These named HDUs' headers will be checked for and added to metadata.
62 _extraFitsHeaders = ["REB_COND"]
64 def readMetadata(self):
65 """Read all header metadata directly into a PropertyList.
67 Will merge additional headers if required.
69 Returns
70 -------
71 metadata : `~lsst.daf.base.PropertyList`
72 Header metadata.
73 """
74 file = self.fileDescriptor.location.path
76 with lsst.afw.fits.Fits(file, "r") as hdu:
77 hdu.setHdu(0)
78 base_md = hdu.readMetadata()
80 # Any extra HDUs we need to read.
81 ehdrs = []
82 for hduname in self._extraFitsHeaders:
83 try:
84 hdu.setHdu(hduname)
85 ehdr = hdu.readMetadata()
86 except lsst.afw.fits.FitsError:
87 # The header doesn't exist in this file. Skip.
88 continue
89 else:
90 ehdrs.append(ehdr)
92 final_md = merge_headers([base_md] + ehdrs, mode="overwrite")
93 fix_header(final_md, translator_class=self.translatorClass)
94 return final_md
96 def stripMetadata(self):
97 """Remove metadata entries that are parsed into components."""
98 if "CRVAL1" not in self.metadata:
99 # No need to strip WCS since we do not seem to have any WCS.
100 return
101 super().stripMetadata()
103 def getDetector(self, id):
104 in_detector = self._instrument.getCamera()[id]
105 # The detectors attached to the Camera object represent the on-disk
106 # amplifier geometry, not the assembled raw. But Butler users
107 # shouldn't know or care about what's on disk; they want the Detector
108 # that's equivalent to `butler.get("raw", ...).getDetector()`, so we
109 # adjust it accordingly. This parallels the logic in
110 # fixAmpsAndAssemble, but that function and the ISR AssembleCcdTask it
111 # calls aren't set up to handle bare bounding boxes with no pixels. We
112 # also can't remove those without API breakage. So this is fairly
113 # duplicative, and hence fragile.
114 # We start by fixing amp bounding boxes based on the size of the amp
115 # images themselves, because the camera doesn't have the right overscan
116 # regions for all images.
117 filename = self.fileDescriptor.location.path
118 temp_detector = in_detector.rebuild()
119 temp_detector.clear()
120 with warn_once(filename) as logCmd:
121 for n, in_amp in enumerate(in_detector):
122 reader = lsst.afw.image.ImageFitsReader(filename, hdu=(n + 1))
123 out_amp, _ = fixAmpGeometry(in_amp,
124 bbox=reader.readBBox(),
125 metadata=reader.readMetadata(),
126 logCmd=logCmd)
127 temp_detector.append(out_amp)
128 adjusted_detector = temp_detector.finish()
129 # Now we need to apply flips and offsets to reflect assembly. The
130 # function call that does this in fixAmpsAndAssemble is down inside
131 # ip.isr.AssembleCcdTask.
132 return makeUpdatedDetector(adjusted_detector)
134 def readImage(self):
135 # Docstring inherited.
136 return self.readFull().getImage()
138 def readFull(self):
139 # Docstring inherited.
140 rawFile = self.fileDescriptor.location.path
141 amplifier, detector, _ = standardizeAmplifierParameters(
142 self.checked_parameters,
143 self._instrument.getCamera()[self.observationInfo.detector_num],
144 )
145 if amplifier is not None:
146 # LSST raws are already per-amplifier on disk, and in a different
147 # assembly state than all of the other images we see in
148 # DM-maintained formatters. And we also need to deal with the
149 # on-disk image having different overscans from our nominal
150 # detector. So we can't use afw.cameraGeom.AmplifierIsolator for
151 # most of the implementation (as other formatters do), but we can
152 # call most of the same underlying code to do the work.
154 def findAmpHdu(name):
155 """Find the HDU for the amplifier with the given name,
156 according to cameraGeom.
157 """
158 for hdu, amp in enumerate(detector): 158 ↛ 161line 158 didn't jump to line 161, because the loop on line 158 didn't complete
159 if amp.getName() == name:
160 return hdu + 1
161 raise LookupError(f"Could not find HDU for amp with name {name}.")
163 reader = lsst.afw.image.ImageFitsReader(rawFile, hdu=findAmpHdu(amplifier.getName()))
164 image = reader.read(dtype=np.dtype(np.int32), allowUnsafe=True)
165 with warn_once(rawFile) as logCmd:
166 # Extract an amplifier from the on-disk detector and fix its
167 # overscan bboxes as necessary to match the on-disk bbox.
168 adjusted_amplifier_builder, _ = fixAmpGeometry(
169 detector[amplifier.getName()],
170 bbox=image.getBBox(),
171 metadata=reader.readMetadata(),
172 logCmd=logCmd,
173 )
174 on_disk_amplifier = adjusted_amplifier_builder.finish()
175 # We've now got two Amplifier objects in play:
176 # A) 'amplifier' is what the user wants
177 # B) 'on_disk_amplifier' represents the subimage we have.
178 # The one we want has the orientation/shift state of (A) with
179 # the overscan regions of (B).
180 comparison = amplifier.compareGeometry(on_disk_amplifier)
181 # If the flips or origins differ, we need to modify the image
182 # itself.
183 if comparison & comparison.FLIPPED:
184 from lsst.afw.math import flipImage
185 image = flipImage(
186 image,
187 comparison & comparison.FLIPPED_X,
188 comparison & comparison.FLIPPED_Y,
189 )
190 if comparison & comparison.SHIFTED:
191 image.setXY0(amplifier.getRawBBox().getMin())
192 # Make a single-amplifier detector that reflects the image we're
193 # returning.
194 detector_builder = detector.rebuild()
195 detector_builder.clear()
196 detector_builder.unsetCrosstalk()
197 if comparison & comparison.REGIONS_DIFFER: 197 ↛ 202line 197 didn't jump to line 202, because the condition on line 197 was never true
198 # We can't just install the amplifier the user gave us, because
199 # that has the wrong overscan regions; instead we transform the
200 # on-disk amplifier to have the same orientation and offsets as
201 # the given one.
202 adjusted_amplifier_builder.transform(
203 outOffset=on_disk_amplifier.getRawXYOffset(),
204 outFlipX=amplifier.getRawFlipX(),
205 outFlipY=amplifier.getRawFlipY(),
206 )
207 detector_builder.append(adjusted_amplifier_builder)
208 detector_builder.setBBox(adjusted_amplifier_builder.getBBox())
209 else:
210 detector_builder.append(amplifier.rebuild())
211 detector_builder.setBBox(amplifier.getBBox())
212 exposure = lsst.afw.image.makeExposure(lsst.afw.image.makeMaskedImage(image))
213 exposure.setDetector(detector_builder.finish())
214 else:
215 ampExps = readRawAmps(rawFile, detector)
216 exposure = fixAmpsAndAssemble(ampExps, rawFile)
217 self.attachComponentsFromMetadata(exposure)
218 return exposure
221class LatissRawFormatter(LsstCamRawFormatter):
222 translatorClass = LatissTranslator
223 _instrument = Latiss
224 filterDefinitions = Latiss.filterDefinitions
225 wcsFlipX = True
228class LsstCamImSimRawFormatter(LsstCamRawFormatter):
229 translatorClass = LsstCamImSimTranslator
230 _instrument = LsstCamImSim
231 filterDefinitions = LsstCamImSim.filterDefinitions
234class LsstCamPhoSimRawFormatter(LsstCamRawFormatter):
235 translatorClass = LsstCamPhoSimTranslator
236 _instrument = LsstCamPhoSim
237 filterDefinitions = LsstCamPhoSim.filterDefinitions
238 _extraFitsHeaders = [1]
241class LsstCamSimRawFormatter(LsstCamRawFormatter):
242 translatorClass = LsstCamSimTranslator
243 _instrument = LsstCamSim
244 filterDefinitions = LsstCamSim.filterDefinitions
247class LsstTS8RawFormatter(LsstCamRawFormatter):
248 translatorClass = LsstTS8Translator
249 _instrument = LsstTS8
250 filterDefinitions = LsstTS8.filterDefinitions
253class LsstTS3RawFormatter(LsstCamRawFormatter):
254 translatorClass = LsstTS3Translator
255 _instrument = LsstTS3
256 filterDefinitions = LsstTS3.filterDefinitions
259class LsstComCamRawFormatter(LsstCamRawFormatter):
260 translatorClass = LsstComCamTranslator
261 _instrument = LsstComCam
262 filterDefinitions = LsstComCam.filterDefinitions
265class LsstComCamSimRawFormatter(LsstCamRawFormatter):
266 translatorClass = LsstComCamSimTranslator
267 _instrument = LsstComCamSim
268 filterDefinitions = LsstComCamSim.filterDefinitions
271class LsstUCDCamRawFormatter(LsstCamRawFormatter):
272 translatorClass = LsstUCDCamTranslator
273 _instrument = LsstUCDCam
274 filterDefinitions = LsstUCDCam.filterDefinitions