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