LSSTApplications  11.0-13-gbb96280,12.1.rc1,12.1.rc1+1,12.1.rc1+2,12.1.rc1+5,12.1.rc1+8,12.1.rc1-1-g06d7636+1,12.1.rc1-1-g253890b+5,12.1.rc1-1-g3d31b68+7,12.1.rc1-1-g3db6b75+1,12.1.rc1-1-g5c1385a+3,12.1.rc1-1-g83b2247,12.1.rc1-1-g90cb4cf+6,12.1.rc1-1-g91da24b+3,12.1.rc1-2-g3521f8a,12.1.rc1-2-g39433dd+4,12.1.rc1-2-g486411b+2,12.1.rc1-2-g4c2be76,12.1.rc1-2-gc9c0491,12.1.rc1-2-gda2cd4f+6,12.1.rc1-3-g3391c73+2,12.1.rc1-3-g8c1bd6c+1,12.1.rc1-3-gcf4b6cb+2,12.1.rc1-4-g057223e+1,12.1.rc1-4-g19ed13b+2,12.1.rc1-4-g30492a7
LSSTDataManagementBasePackage
assembleCcdTask.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 # Copyright 2008-2016 AURA/LSST.
4 #
5 # This product includes software developed by the
6 # LSST Project (http://www.lsst.org/).
7 #
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the LSST License Statement and
19 # the GNU General Public License along with this program. If not,
20 # see <http://www.lsstcorp.org/LegalNotices/>.
21 #
22 import lsst.afw.cameraGeom as cameraGeom
23 import lsst.afw.cameraGeom.utils as cameraGeomUtils
24 import lsst.afw.image as afwImage
25 import lsst.pex.config as pexConfig
26 import lsst.pipe.base as pipeBase
27 from lsstDebug import getDebugFrame
28 from lsst.afw.display import getDisplay
29 from .isr import calcEffectiveGain
30 
31 __all__ = ["AssembleCcdTask"]
32 
33 
34 class AssembleCcdConfig(pexConfig.Config):
35  setGain = pexConfig.Field(
36  doc="set gain?",
37  dtype=bool,
38  default=True,
39  )
40  doRenorm = pexConfig.Field(
41  doc="renormalize to a gain of 1? (ignored if setGain false). "
42  "Setting to True gives 1 ADU per electron. "
43  "Setting to True is not recommended for mosaic cameras because it breaks normalization across "
44  "the focal plane. However, if the CCDs are sufficiently flat then the resulting error "
45  "may be acceptable.",
46  dtype=bool,
47  default=False,
48  )
49  doTrim = pexConfig.Field(
50  doc="trim out non-data regions?",
51  dtype=bool,
52  default=True,
53  )
54  keysToRemove = pexConfig.ListField(
55  doc="FITS headers to remove (in addition to DATASEC, BIASSEC, TRIMSEC and perhaps GAIN)",
56  dtype=str,
57  default=(),
58  )
59 
60 ## \addtogroup LSST_task_documentation
61 ## \{
62 ## \page AssembleCcdTask
63 ## \ref AssembleCcdTask_ "AssembleCcdTask"
64 ## \copybrief AssembleCcdTask
65 ## \}
66 
67 
68 class AssembleCcdTask(pipeBase.Task):
69  """!
70  \anchor AssembleCcdTask_
71 
72  \brief Assemble a set of amplifier images into a full detector size set of pixels.
73 
74  \section ip_isr_assemble_Contents Contents
75 
76  - \ref ip_isr_assemble_Purpose
77  - \ref ip_isr_assemble_Initialize
78  - \ref ip_isr_assemble_IO
79  - \ref ip_isr_assemble_Config
80  - \ref ip_isr_assemble_Debug
81  - \ref ip_isr_assemble_Example
82 
83  \section ip_isr_assemble_Purpose Description
84 
85  This task assembles sections of an image into a larger mosaic. The sub-sections
86  are typically amplifier sections and are to be assembled into a detector size pixel grid.
87  The assembly is driven by the entries in the raw amp information. The task can be configured
88  to return a detector image with non-data (e.g. overscan) pixels included. The task can also
89  renormalize the pixel values to a nominal gain of 1. The task also removes exposure metadata that
90  has context in raw amps, but not in trimmed detectors (e.g. 'BIASSEC').
91 
92  \section ip_isr_assemble_Initialize Task initialization
93 
94  \copydoc \_\_init\_\_
95 
96  \section ip_isr_assemble_IO Inputs/Outputs to the assembleCcd method
97 
98  \copydoc assembleCcd
99 
100  \section ip_isr_assemble_Config Configuration parameters
101 
102  See \ref AssembleCcdConfig
103 
104  \section ip_isr_assemble_Debug Debug variables
105 
106  The \link lsst.pipe.base.cmdLineTask.CmdLineTask command line task\endlink interface supports a
107  flag \c -d to import \b debug.py from your \c PYTHONPATH; see <a
108  href="http://lsst-web.ncsa.illinois.edu/~buildbot/doxygen/x_masterDoxyDoc/base_debug.html">
109  Using lsstDebug to control debugging output</a> for more about \b debug.py files.
110 
111  The available variables in AssembleCcdTask are:
112  <DL>
113  <DT> \c display
114  <DD> A dictionary containing debug point names as keys with frame number as value. Valid keys are:
115  <DL>
116  <DT> assembledExposure
117  <DD> display assembled exposure
118  </DL>
119  </DL>
120 
121  \section ip_isr_assemble_Example A complete example of using AssembleCcdTask
122 
123  This code is in runAssembleTask.py in the examples directory, and can be run as \em e.g.
124  \code
125  python examples/runAssembleTask.py
126  \endcode
127 
128  \dontinclude runAssembleTask.py
129  Import the task. There are other imports. Read the source file for more info.
130  \skipline AssembleCcdTask
131 
132  \dontinclude exampleUtils.py
133  Create some input images with the help of some utilities in examples/exampleUtils.py
134  \skip makeAssemblyInput
135  @until inputData
136  The above numbers can be changed. The assumption that the readout corner is flipped on every other amp is
137  hardcoded in createDetector.
138 
139  \dontinclude runAssembleTask.py
140  Run the assembler task
141  \skip runAssembler
142  @until frame += 1
143 
144  <HR>
145  To investigate the \ref ip_isr_assemble_Debug, put something like
146  \code{.py}
147  import lsstDebug
148  def DebugInfo(name):
149  di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
150  if name == "lsst.ip.isr.assembleCcdTask":
151  di.display = {'assembledExposure':2}
152  return di
153 
154  lsstDebug.Info = DebugInfo
155  \endcode
156  into your debug.py file and run runAssembleTask.py with the \c --debug flag.
157 
158 
159  Conversion notes:
160  Display code should be updated once we settle on a standard way of controlling what is displayed.
161  """
162  ConfigClass = AssembleCcdConfig
163  _DefaultName = "assembleCcd"
164 
165  def __init__(self, **kwargs):
166  """!Initialize the AssembleCcdTask
167 
168  The keys for removal specified in the config are added to a default set:
169  ('DATASEC', 'BIASSEC', 'TRIMSEC', 'GAIN')
170  """
171  pipeBase.Task.__init__(self, **kwargs)
172 
173  self.allKeysToRemove = ('DATASEC', 'BIASSEC', 'TRIMSEC', 'GAIN') + tuple(self.config.keysToRemove)
174 
175  def assembleCcd(self, assembleInput):
176  """!Assemble a set of amps into a single CCD size image
177  \param[in] assembleInput -- Either a dictionary of amp lsst.afw.image.Exposures or a single
178  lsst.afw.image.Exposure containing all raw
179  amps. If a dictionary of amp exposures,
180  the key should be the amp name.
181  \return assembledCcd -- An lsst.afw.image.Exposure of the assembled amp sections.
182 
183  \throws TypeError with the following string:
184 
185  <DL>
186  <DT> Expected either a dictionary of amp exposures or a single raw exposure
187  <DD> The input exposures to be assembled do not adhere to the required format.
188  </DL>
189 
190  \throws RuntimeError with the following string:
191 
192  <DL>
193  <DT> No ccd detector found
194  <DD> The detector set on the input exposure is not set.
195  </DL>
196  """
197  ccd = None
198  if hasattr(assembleInput, "has_key"):
199  # Get a detector object for this set of amps
200  ccd = next(assembleInput.values()).getDetector()
201  # Sent a dictionary of input exposures, assume one amp per key keyed on amp name
202 
203  def getNextExposure(amp):
204  return assembleInput[amp.getName()]
205  elif hasattr(assembleInput, "getMaskedImage"):
206  ccd = assembleInput.getDetector()
207  # A single exposure was sent. Use this to assemble.
208 
209  def getNextExposure(amp):
210  return assembleInput
211  else:
212  raise TypeError("Expected either a dictionary of amp exposures or a single raw exposure")
213 
214  if ccd is None:
215  raise RuntimeError("No ccd detector found")
216 
217  if not self.config.doTrim:
218  outBox = cameraGeomUtils.calcRawCcdBBox(ccd)
219  else:
220  outBox = ccd.getBBox()
221  outExposure = afwImage.ExposureF(outBox)
222  outMI = outExposure.getMaskedImage()
223 
224  if self.config.doTrim:
225  assemble = cameraGeom.assembleAmplifierImage
226  else:
227  assemble = cameraGeom.assembleAmplifierRawImage
228 
229  for amp in ccd:
230  inMI = getNextExposure(amp).getMaskedImage()
231  assemble(outMI, inMI, amp)
232  outExposure.setDetector(ccd)
233  self.postprocessExposure(outExposure=outExposure, inExposure=getNextExposure(ccd[0]))
234 
235  return outExposure
236 
237  def postprocessExposure(self, outExposure, inExposure):
238  """Set exposure non-image attributes, including wcs and metadata and display exposure (if requested)
239 
240  Call after assembling the pixels
241 
242  @param[in,out] outExposure assembled exposure:
243  - removes unwanted keywords
244  - sets calib, filter, and detector
245  @param[in] inExposure input exposure
246  """
247  self.setWcs(outExposure=outExposure, inExposure=inExposure)
248 
249  exposureMetadata = inExposure.getMetadata()
250  for key in self.allKeysToRemove:
251  if exposureMetadata.exists(key):
252  exposureMetadata.remove(key)
253  outExposure.setMetadata(exposureMetadata)
254 
255  if self.config.setGain:
256  self.setGain(outExposure=outExposure)
257 
258  inCalib = inExposure.getCalib()
259  outCalib = outExposure.getCalib()
260  outCalib.setExptime(inCalib.getExptime())
261  outCalib.setMidTime(inCalib.getMidTime())
262 
263  outExposure.setFilter(inExposure.getFilter())
264 
265  frame = getDebugFrame(self._display, "assembledExposure")
266  if frame:
267  getDisplay(frame).mtv(outExposure)
268 
269  def setWcs(self, outExposure, inExposure):
270  """Set output WCS = input WCS, adjusted as required for datasecs not starting at lower left corner
271 
272  @param[in,out] outExposure assembled exposure; wcs is set
273  @param[in] inExposure input exposure
274  """
275  if inExposure.hasWcs():
276  wcs = inExposure.getWcs()
277  ccd = outExposure.getDetector()
278  amp0 = ccd[0]
279  if amp0 is None:
280  raise RuntimeError("No amplifier detector information found")
281  cameraGeomUtils.prepareWcsData(wcs, amp0, isTrimmed=self.config.doTrim)
282  outExposure.setWcs(wcs)
283  else:
284  self.log.warn("No WCS found in input exposure")
285 
286  def setGain(self, outExposure):
287  """Renormalize, if requested, and set gain metadata
288 
289  @param[in,out] outExposure assembled exposure:
290  - scales the pixels if config.doRenorm is true
291  - adds some gain keywords to the metadata
292  """
293  if outExposure.getMaskedImage().getVariance().getArray().max() == 0:
294  raise RuntimeError("Can't calculate the effective gain since the variance plane is set to zero")
295  ccd = outExposure.getDetector()
296  exposureMetadata = outExposure.getMetadata()
297  gain = 0.
298  namps = 0
299  for amp in ccd:
300  gain += amp.getGain()
301  namps += 1
302  gain /= namps
303  if self.config.doRenorm:
304  mi = outExposure.getMaskedImage()
305  mi *= gain
306  exposureMetadata.set("GAIN", 1.0)
307  medgain, meangain = calcEffectiveGain(outExposure.getMaskedImage())
308  exposureMetadata.add("MEDGAIN", medgain)
309  exposureMetadata.add("MEANGAIN", meangain)
310  exposureMetadata.add("GAINEFF", medgain)
Assemble a set of amplifier images into a full detector size set of pixels.
def assembleCcd
Assemble a set of amps into a single CCD size image.
def __init__
Initialize the AssembleCcdTask.
def calcEffectiveGain
Definition: isr.py:47
def getDebugFrame
Definition: lsstDebug.py:66