LSSTApplications  10.0+286,10.0+36,10.0+46,10.0-2-g4f67435,10.1+152,10.1+37,11.0,11.0+1,11.0-1-g47edd16,11.0-1-g60db491,11.0-1-g7418c06,11.0-2-g04d2804,11.0-2-g68503cd,11.0-2-g818369d,11.0-2-gb8b8ce7
LSSTDataManagementBasePackage
utils.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 
3 #
4 # LSST Data Management System
5 # Copyright 2008, 2009, 2010 LSST Corporation.
6 #
7 # This product includes software developed by the
8 # LSST Project (http://www.lsst.org/).
9 #
10 # This program is free software: you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation, either version 3 of the License, or
13 # (at your option) any later version.
14 #
15 # This program is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
19 #
20 # You should have received a copy of the LSST License Statement and
21 # the GNU General Public License along with this program. If not,
22 # see <http://www.lsstcorp.org/LegalNotices/>.
23 #
24 
25 """
26 Support for cameraGeom
27 """
28 from __future__ import division
29 import math
30 import numpy
31 import itertools
32 
33 import lsst.afw.geom as afwGeom
34 import lsst.afw.image as afwImage
35 import lsst.afw.math as afwMath
36 import lsst.daf.base as dafBase
37 
38 from .rotateBBoxBy90 import rotateBBoxBy90
39 from .assembleImage import assembleAmplifierImage, assembleAmplifierRawImage
40 from .cameraGeomLib import PUPIL, FOCAL_PLANE
41 from lsst.afw.display.utils import _getDisplayFromDisplayOrFrame
42 
43 import lsst.afw.display as afwDisplay
44 import lsst.afw.display.utils as displayUtils
45 
46 def prepareWcsData(wcs, amp, isTrimmed=True):
47  """!Put Wcs from an Amp image into CCD coordinates
48 
49  @param[in, out] wcs WCS object to modify in place
50  @param[in] amp Amp object to use
51  @param[in] isTrimmed Is the image to which the WCS refers trimmed of non-imaging pixels?
52  """
53  if not amp.getHasRawInfo():
54  raise RuntimeError("Cannot modify wcs without raw amp information")
55  if isTrimmed:
56  ampBox = amp.getRawDataBBox()
57  else:
58  ampBox = amp.getRawBBox()
59  wcs.flipImage(amp.getRawFlipX(), amp.getRawFlipY(), ampBox.getDimensions())
60  #Shift WCS for trimming
61  wcs.shiftReferencePixel(-ampBox.getMinX(), -ampBox.getMinY())
62  #Account for shift of amp data in larger ccd matrix
63  offset = amp.getRawXYOffset()
64  wcs.shiftReferencePixel(offset.getX(), offset.getY())
65 
66 def plotFocalPlane(camera, pupilSizeDeg_x=0, pupilSizeDeg_y=None, dx=0.1, dy=0.1, figsize=(10., 10.),
67  useIds=False, showFig=True, savePath=None):
68  """!Make a plot of the focal plane along with a set points that sample the Pupil
69 
70  @param[in] camera a camera object
71  @param[in] pupilSizeDeg_x Amount of the pupil to sample in x in degrees
72  @param[in] pupilSizeDeg_y Amount of the pupil to sample in y in degrees
73  @param[in] dx Spacing of sample points in x in degrees
74  @param[in] dy Spacing of sample points in y in degrees
75  @param[in] figsize matplotlib style tuple indicating the size of the figure in inches
76  @param[in] useIds Label detectors by name, not id
77  @param[in] showFig Display the figure on the screen?
78  @param[in] savePath If not None, save a copy of the figure to this name
79  """
80  try:
81  from matplotlib.patches import Polygon
82  from matplotlib.collections import PatchCollection
83  import matplotlib.pyplot as plt
84  except ImportError:
85  raise ImportError("Can't run plotFocalPlane: matplotlib has not been set up")
86 
87  if pupilSizeDeg_x:
88  if pupilSizeDeg_y is None:
89  pupilSizeDeg_y = pupilSizeDeg_x
90 
91  pupil_gridx, pupil_gridy = numpy.meshgrid(numpy.arange(0., pupilSizeDeg_x+dx, dx) - pupilSizeDeg_x/2.,
92  numpy.arange(0., pupilSizeDeg_y+dy, dy) - pupilSizeDeg_y/2.)
93  pupil_gridx, pupil_gridy = pupil_gridx.flatten(), pupil_gridy.flatten()
94  else:
95  pupil_gridx, pupil_gridy = [], []
96 
97  xs = []
98  ys = []
99  pcolors = []
100  for pos in zip(pupil_gridx, pupil_gridy):
101  posRad = afwGeom.Point2D(math.radians(pos[0]), math.radians(pos[1]))
102  cp = camera.makeCameraPoint(posRad, PUPIL)
103  ncp = camera.transform(cp, FOCAL_PLANE)
104  xs.append(ncp.getPoint().getX())
105  ys.append(ncp.getPoint().getY())
106  dets = camera.findDetectors(cp)
107  if len(dets) > 0:
108  pcolors.append('w')
109  else:
110  pcolors.append('k')
111 
112 
113  colorMap = {0:'b', 1:'y', 2:'g', 3:'r'}
114 
115  patches = []
116  colors = []
117  plt.figure(figsize=figsize)
118  ax = plt.gca()
119  xvals = []
120  yvals = []
121  for det in camera:
122  corners = [(c.getX(), c.getY()) for c in det.getCorners(FOCAL_PLANE)]
123  for corner in corners:
124  xvals.append(corner[0])
125  yvals.append(corner[1])
126  colors.append(colorMap[det.getType()])
127  patches.append(Polygon(corners, True))
128  center = det.getOrientation().getFpPosition()
129  ax.text(center.getX(), center.getY(), det.getId() if useIds else det.getName(),
130  horizontalalignment='center', size=6)
131 
132  patchCollection = PatchCollection(patches, alpha=0.6, facecolor=colors)
133  ax.add_collection(patchCollection)
134  ax.scatter(xs, ys, s=10, alpha=.7, linewidths=0., c=pcolors)
135  ax.set_xlim(min(xvals) - abs(0.1*min(xvals)), max(xvals) + abs(0.1*max(xvals)))
136  ax.set_ylim(min(yvals) - abs(0.1*min(yvals)), max(yvals) + abs(0.1*max(yvals)))
137  ax.set_xlabel('Focal Plane X (mm)')
138  ax.set_ylabel('Focal Plane Y (mm)')
139  if savePath is not None:
140  plt.savefig(savePath)
141  if showFig:
142  plt.show()
143 
144 def makeImageFromAmp(amp, imValue=None, imageFactory=afwImage.ImageU, markSize=10, markValue=0,
145  scaleGain = lambda gain: (gain*1000)//10):
146  """!Make an image from an amp object
147 
148  Since images are integer images by default, the gain needs to be scaled to give enough dynamic range
149  to see variation from amp to amp. The scaling algorithm is assignable.
150 
151  @param[in] amp Amp record to use for constructing the raw amp image
152  @param[in] imValue Value to assign to the constructed image scaleGain(gain) is used if not set
153  @param[in] imageFactory Type of image to construct
154  @param[in] markSize Size of mark at read corner in pixels
155  @param[in] markValue Value of pixels in the read corner mark
156  @param[in] scaleGain The function by which to scale the gain
157  @return an untrimmed amp image
158  """
159  if not amp.getHasRawInfo():
160  raise RuntimeError("Can't create a raw amp image without raw amp information")
161  bbox = amp.getRawBBox()
162  dbbox = amp.getRawDataBBox()
163  img = imageFactory(bbox)
164  if imValue is None:
165  img.set(scaleGain(amp.getGain()))
166  else:
167  img.set(imValue)
168  #Set the first pixel read to a different value
169  markbbox = afwGeom.Box2I()
170  if amp.getReadoutCorner() == 0:
171  markbbox.include(dbbox.getMin())
172  markbbox.include(dbbox.getMin()+afwGeom.Extent2I(markSize, markSize))
173  elif amp.getReadoutCorner() == 1:
174  cornerPoint = afwGeom.Point2I(dbbox.getMaxX(), dbbox.getMinY())
175  markbbox.include(cornerPoint)
176  markbbox.include(cornerPoint + afwGeom.Extent2I(-markSize, markSize))
177  elif amp.getReadoutCorner() == 2:
178  cornerPoint = afwGeom.Point2I(dbbox.getMax())
179  markbbox.include(cornerPoint)
180  markbbox.include(cornerPoint + afwGeom.Extent2I(-markSize, -markSize))
181  elif amp.getReadoutCorner() == 3:
182  cornerPoint = afwGeom.Point2I(dbbox.getMinX(), dbbox.getMaxY())
183  markbbox.include(cornerPoint)
184  markbbox.include(cornerPoint + afwGeom.Extent2I(markSize, -markSize))
185  else:
186  raise RuntimeError("Could not set readout corner")
187  mimg = imageFactory(img, markbbox, False)
188  mimg.set(markValue)
189  return img
190 
191 def calcRawCcdBBox(ccd):
192  """!Calculate the raw ccd bounding box
193 
194  @param[in] ccd Detector for with to calculate the un-trimmed bounding box
195  @return Box2I of the un-trimmed Detector,
196  or None if there is not enough information to calculate raw BBox
197  """
198  bbox = afwGeom.Box2I()
199  for amp in ccd:
200  if not amp.getHasRawInfo():
201  return None
202  tbbox = amp.getRawBBox()
203  tbbox.shift(amp.getRawXYOffset())
204  bbox.include(tbbox)
205  return bbox
206 
207 def makeImageFromCcd(ccd, isTrimmed=True, showAmpGain=True, imageFactory=afwImage.ImageU, rcMarkSize=10,
208  binSize=1):
209  """!Make an Image of a Ccd
210 
211  @param[in] ccd Detector to use in making the image
212  @param[in] isTrimmed Assemble a trimmed Detector image if True
213  @param[in] showAmpGain Use the per amp gain to color the pixels in the image
214  @param[in] imageFactory Image type to generate
215  @param[in] rcMarkSize Size of the mark to make in the amp images at the read corner
216  @param[in] binSize Bin the image by this factor in both dimensions
217  @return Image of the Detector
218  """
219  ampImages = []
220  index = 0
221  if isTrimmed:
222  bbox = ccd.getBBox()
223  else:
224  bbox = calcRawCcdBBox(ccd)
225  for amp in ccd:
226  if amp.getHasRawInfo():
227  if showAmpGain:
228  ampImages.append(makeImageFromAmp(amp, imageFactory=imageFactory, markSize=rcMarkSize))
229  else:
230  ampImages.append(makeImageFromAmp(amp, imValue=(index+1)*1000,
231  imageFactory=imageFactory, markSize=rcMarkSize))
232  index += 1
233 
234  if len(ampImages) > 0:
235  ccdImage = imageFactory(bbox)
236  for ampImage, amp in itertools.izip(ampImages, ccd):
237  if isTrimmed:
238  assembleAmplifierImage(ccdImage, ampImage, amp)
239  else:
240  assembleAmplifierRawImage(ccdImage, ampImage, amp)
241  else:
242  if not isTrimmed:
243  raise RuntimeError("Cannot create untrimmed CCD without amps with raw information")
244  ccdImage = imageFactory(ccd.getBBox())
245  ccdImage = afwMath.binImage(ccdImage, binSize)
246  return ccdImage
247 
248 class FakeImageDataSource(object):
249  """A class to retrieve synthetic images for display by the show* methods"""
250  def __init__(self, isTrimmed=True, verbose=False, background=numpy.nan,
251  showAmpGain=True, markSize=10, markValue=0,
252  ampImValue=None, scaleGain=lambda gain: (gain*1000)//10):
253  """!Construct a FakeImageDataSource
254 
255  @param[in] isTrimmed Should amps be trimmed?
256  @param[in] verbose Be chatty
257  @param[in] background The value of any pixels that lie outside the CCDs
258  @param[in] showAmpGain color the amp segments with the gain of the amp
259  @param[in] markSize size of the side of the box used to mark the read corner
260  @param[in] markValue value to assing the read corner mark
261  @param[in] ampImValue Value to assing to amps. scaleGain(gain) is used if None
262  @param[in] scaleGain function to scale the gain by
263  """
264  self.isTrimmed = isTrimmed
265  self.verbose = verbose
266  self.background = background
267  self.showAmpGain = showAmpGain
268  self.markSize = markSize
269  self.markValue = markValue
270  self.ampImValue = ampImValue
271  self.scaleGain = scaleGain
272 
273  def getCcdImage(self, det, imageFactory, binSize):
274  """!Return a CCD image for the detector
275 
276  @param[in] det: Detector to use for making the image
277  @param[in] imageFactory: image constructor for making the image
278  @param[in] binSize: number of pixels per bin axis
279  """
280  return makeImageFromCcd(det, isTrimmed=self.isTrimmed, showAmpGain=self.showAmpGain,
281  imageFactory=imageFactory, binSize=binSize)
282 
283  def getAmpImage(self, amp, imageFactory):
284  """!Return an amp segment image
285 
286  @param[in] amp AmpInfoTable for this amp
287  @param[in] imageFactory image constructor fo making the imag
288  """
289  ampImage = makeImageFromAmp(amp, imValue=self.ampImValue, imageFactory=imageFactory,
290  markSize=self.markSize,
291  markValue=self.markValue, scaleGain=self.scaleGain)
292  if self.isTrimmed:
293  ampImage = ampImage.Factory(ampImage, amp.getRawDataBBox(), False)
294  return ampImage
295 
297  """A class to return an Image of a given Ccd using the butler"""
298 
299  def __init__(self, butler=None, type="raw",
300  isTrimmed=True, verbose=False, background=numpy.nan, gravity=None, *args, **kwargs):
301  """!Create an object that knows how to prepare images for showCamera using the butler
302 
303  \param The butler to use. If no butler is provided an empty image is returned
304  \param type The type of image to read (e.g. raw, bias, flat, calexp)
305  \param isTrimmed If true, the showCamera command expects to be given trimmed images
306  \param verbose Be chatty (in particular, print any error messages from the butler)
307  \param background The value of any pixels that lie outside the CCDs
308  \param gravity If the image returned by the butler is trimmed (e.g. some of the SuprimeCam CCDs)
309  Specify how to fit the image into the available space; N => align top, W => align left
310  \param *args, *kwargs Passed to the butler
311  """
312  super(ButlerImage, self).__init__(*args)
313  self.isTrimmed = isTrimmed
314  self.type = type
315  self.butler = butler
316  self.kwargs = kwargs
317  self.isRaw = False
318  self.gravity = gravity
319  self.background = background
320  self.verbose = verbose
321 
322  def _prepareImage(self, ccd, im, binSize, allowRotate=True):
323  if binSize > 1:
324  im = afwMath.binImage(im, binSize)
325 
326  if allowRotate:
327  im = afwMath.rotateImageBy90(im, ccd.getOrientation().getNQuarter())
328 
329  return im
330 
331  def getCcdImage(self, ccd, imageFactory=afwImage.ImageF, binSize=1):
332  """Return an image of the specified amp in the specified ccd"""
333 
334  if self.isTrimmed:
335  bbox = ccd.getBBox()
336  else:
337  bbox = calcRawCcdBBox(ccd)
338 
339  im = None
340  if self.butler is not None:
341  e = None
342  if self.type == "calexp": # reading the exposure can die if the PSF's unknown
343  try:
344  fileName = self.butler.get(self.type + "_filename", ccd=ccd.getId(),
345  **self.kwargs)[0]
346  im = imageFactory(fileName)
347  except Exception as e:
348  pass
349  else:
350  try:
351  im = self.butler.get(self.type, ccd=ccd.getId(),
352  **self.kwargs).getMaskedImage().getImage()
353  except Exception as e:
354  pass
355 
356  if e:
357  if self.verbose:
358  print "Reading %s: %s" % (ccd.getId(), e)
359 
360  if im is None:
361  return self._prepareImage(ccd, imageFactory(*bbox.getDimensions()), binSize)
362 
363  if self.type == "raw":
364  if hasattr(im, 'convertF'):
365  im = im.convertF()
366  else:
367  return self._prepareImage(ccd, im, binSize, allowRotate=False) # calexps were rotated by the ISR
368 
369  ccdImage = im.Factory(bbox)
370 
371  ampImages = []
372  med0 = None
373  for a in ccd:
374  bias = im[a.getRawHorizontalOverscanBBox()]
375  data = im[a.getRawDataBBox()]
376  data -= afwMath.makeStatistics(bias, afwMath.MEANCLIP).getValue()
377  data *= a.getGain()
378 
379  ampImages.append(data)
380 
381  ccdImage = imageFactory(bbox)
382  for ampImage, amp in itertools.izip(ampImages, ccd):
383  if self.isTrimmed:
384  assembleAmplifierImage(ccdImage, ampImage, amp)
385  else:
386  assembleAmplifierRawImage(ccdImage, ampImage, amp)
387 
388  return ccdImage
389 
390 def overlayCcdBoxes(ccd, untrimmedCcdBbox, nQuarter, isTrimmed, ccdOrigin, display, binSize):
391  """!Overlay bounding boxes on an image display
392 
393  @param[in] ccd Detector to iterate for the amp bounding boxes
394  @param[in] untrimmedCcdBbox Bounding box of the un-trimmed Detector
395  @param[in] nQuarter number of 90 degree rotations to apply to the bounding boxes
396  @param[in] isTrimmed Is the Detector image over which the boxes are layed trimmed?
397  @param[in] ccdOrigin Detector origin relative to the parent origin if in a larger pixel grid
398  @param[in] display image display to display on
399  @param[in] binSize binning factor
400  """
401  with display.Buffering():
402  ccdDim = untrimmedCcdBbox.getDimensions()
403  ccdBbox = rotateBBoxBy90(untrimmedCcdBbox, nQuarter, ccdDim)
404  for amp in ccd:
405  if isTrimmed:
406  ampbbox = amp.getBBox()
407  else:
408  ampbbox = amp.getRawBBox()
409  ampbbox.shift(amp.getRawXYOffset())
410  if nQuarter != 0:
411  ampbbox = rotateBBoxBy90(ampbbox, nQuarter, ccdDim)
412 
413  displayUtils.drawBBox(ampbbox, origin=ccdOrigin, borderWidth=0.49,
414  display=display, bin=binSize)
415 
416  if not isTrimmed and amp.getHasRawInfo():
417  for bbox, ctype in ((amp.getRawHorizontalOverscanBBox(), afwDisplay.RED),
418  (amp.getRawDataBBox(), afwDisplay.BLUE),
419  (amp.getRawVerticalOverscanBBox(), afwDisplay.MAGENTA),
420  (amp.getRawPrescanBBox(), afwDisplay.YELLOW)):
421  if amp.getRawFlipX():
422  bbox.flipLR(amp.getRawBBox().getDimensions().getX())
423  if amp.getRawFlipY():
424  bbox.flipTB(amp.getRawBBox().getDimensions().getY())
425  bbox.shift(amp.getRawXYOffset())
426  if nQuarter != 0:
427  bbox = rotateBBoxBy90(bbox, nQuarter, ccdDim)
428  displayUtils.drawBBox(bbox, origin=ccdOrigin, borderWidth=0.49, ctype=ctype,
429  display=display, bin=binSize)
430  # Label each Amp
431  xc, yc = (ampbbox.getMin()[0] + ampbbox.getMax()[0])//2, (ampbbox.getMin()[1] +
432  ampbbox.getMax()[1])//2
433  #
434  # Rotate the amp labels too
435  #
436  if nQuarter == 0:
437  c, s = 1, 0
438  elif nQuarter == 1:
439  c, s = 0, -1
440  elif nQuarter == 2:
441  c, s = -1, 0
442  elif nQuarter == 3:
443  c, s = 0, 1
444  c, s = 1, 0
445  ccdHeight = ccdBbox.getHeight()
446  ccdWidth = ccdBbox.getWidth()
447  xc -= 0.5*ccdHeight
448  yc -= 0.5*ccdWidth
449 
450  xc, yc = 0.5*ccdHeight + c*xc + s*yc, 0.5*ccdWidth + -s*xc + c*yc
451 
452  if ccdOrigin:
453  xc += ccdOrigin[0]
454  yc += ccdOrigin[1]
455  display.dot(str(amp.getName()), xc/binSize, yc/binSize, textAngle=nQuarter*90)
456 
457  displayUtils.drawBBox(ccdBbox, origin=ccdOrigin,
458  borderWidth=0.49, ctype=afwDisplay.MAGENTA, display=display, bin=binSize)
459 
460 def showAmp(amp, imageSource=FakeImageDataSource(isTrimmed=False), display=None, overlay=True,
461  imageFactory=afwImage.ImageU):
462  """!Show an amp in an image display
463 
464  @param[in] amp amp record to use in display
465  @param[in] imageSource Source for getting the amp image. Must have a getAmpImage method.
466  @param[in] display image display to use
467  @param[in] overlay Overlay bounding boxes?
468  @param[in] imageFactory Type of image to display (only used if ampImage is None)
469  """
470 
471  if not display:
473 
474  ampImage = imageSource.getAmpImage(amp, imageFactory=imageFactory)
475  ampImSize = ampImage.getDimensions()
476  title = amp.getName()
477  display.mtv(ampImage, title=title)
478  if overlay:
479  with display.Buffering():
480  if amp.getHasRawInfo() and ampImSize == amp.getRawBBox().getDimensions():
481  bboxes = [(amp.getRawBBox(), 0.49, afwDisplay.GREEN),]
482  xy0 = bboxes[0][0].getMin()
483  bboxes.append((amp.getRawHorizontalOverscanBBox(), 0.49, afwDisplay.RED))
484  bboxes.append((amp.getRawDataBBox(), 0.49, afwDisplay.BLUE))
485  bboxes.append((amp.getRawPrescanBBox(), 0.49, afwDisplay.YELLOW))
486  bboxes.append((amp.getRawVerticalOverscanBBox(), 0.49, afwDisplay.MAGENTA))
487  else:
488  bboxes = [(amp.getBBox(), 0.49, None),]
489  xy0 = bboxes[0][0].getMin()
490 
491  for bbox, borderWidth, ctype in bboxes:
492  if bbox.isEmpty():
493  continue
494  bbox = afwGeom.Box2I(bbox)
495  bbox.shift(-afwGeom.ExtentI(xy0))
496  displayUtils.drawBBox(bbox, borderWidth=borderWidth, ctype=ctype, display=display)
497 
498 def showCcd(ccd, imageSource=FakeImageDataSource(), display=None, frame=None, overlay=True,
499  imageFactory=afwImage.ImageF, binSize=1, inCameraCoords=False):
500  """!Show a CCD on display
501 
502  @param[in] ccd Detector to use in display
503  @param[in] imageSource Source for producing images to display. Must have a getCcdImage method.
504  @param[in] display image display to use
505  @param[in] overlay Show amp bounding boxes on the displayed image?
506  @param[in] imageFactory The image factory to use in generating the images.
507  @param[in] binSize Binning factor
508  @param[in] inCameraCoords Show the Detector in camera coordinates?
509  """
510  display = _getDisplayFromDisplayOrFrame(display, frame)
511 
512  ccdOrigin = afwGeom.Point2I(0,0)
513  nQuarter = 0
514  ccdImage = imageSource.getCcdImage(ccd, imageFactory=imageFactory, binSize=binSize)
515 
516  ccdBbox = ccdImage.getBBox()
517  if ccdBbox.getDimensions() == ccd.getBBox().getDimensions():
518  isTrimmed = True
519  else:
520  isTrimmed = False
521 
522  if inCameraCoords:
523  nQuarter = ccd.getOrientation().getNQuarter()
524  ccdImage = afwMath.rotateImageBy90(ccdImage, nQuarter)
525  title = ccd.getName()
526  if isTrimmed:
527  title += "(trimmed)"
528 
529  if display:
530  display.mtv(ccdImage, title=title)
531 
532  if overlay:
533  overlayCcdBoxes(ccd, ccdBbox, nQuarter, isTrimmed, ccdOrigin, display, binSize)
534 
535  return ccdImage
536 
537 def getCcdInCamBBoxList(ccdList, binSize, pixelSize_o, origin):
538  """!Get the bounding boxes of a list of Detectors within a camera sized pixel grid
539 
540  @param[in] ccdList List of Detector
541  @param[in] binSize Binning factor
542  @param[in] pixelSize_o Size of the pixel in mm.
543  @param[in] origin origin of the camera pixel grid in pixels
544  @return a list of bounding boxes in camera pixel coordinates
545  """
546  boxList = []
547  for ccd in ccdList:
548  if not pixelSize_o == ccd.getPixelSize():
549  raise RuntimeError(
550  "Cameras with detectors with different pixel scales are not currently supported")
551 
552  dbbox = afwGeom.Box2D()
553  for corner in ccd.getCorners(FOCAL_PLANE):
554  dbbox.include(corner)
555  llc = dbbox.getMin()
556  nQuarter = ccd.getOrientation().getNQuarter()
557  cbbox = ccd.getBBox()
558  ex = cbbox.getDimensions().getX()//binSize
559  ey = cbbox.getDimensions().getY()//binSize
560  bbox = afwGeom.Box2I(cbbox.getMin(), afwGeom.Extent2I(int(ex), int(ey)))
561  bbox = rotateBBoxBy90(bbox, nQuarter, bbox.getDimensions())
562  bbox.shift(afwGeom.Extent2I(int(llc.getX()//pixelSize_o.getX()/binSize),
563  int(llc.getY()//pixelSize_o.getY()/binSize)))
564  bbox.shift(afwGeom.Extent2I(-int(origin.getX()//binSize), -int(origin.getY())//binSize))
565  boxList.append(bbox)
566  return boxList
567 
568 def getCameraImageBBox(camBbox, pixelSize, bufferSize):
569  """!Get the bounding box of a camera sized image in pixels
570 
571  @param[in] camBbox Camera bounding box in focal plane coordinates (mm)
572  @param[in] pixelSize Size of a detector pixel in mm
573  @param[in] bufferSize Buffer around edge of image in pixels
574  @return the resulting bounding box
575  """
576  pixMin = afwGeom.Point2I(int(camBbox.getMinX()//pixelSize.getX()),
577  int(camBbox.getMinY()//pixelSize.getY()))
578  pixMax = afwGeom.Point2I(int(camBbox.getMaxX()//pixelSize.getX()),
579  int(camBbox.getMaxY()//pixelSize.getY()))
580  retBox = afwGeom.Box2I(pixMin, pixMax)
581  retBox.grow(bufferSize)
582  return retBox
583 
584 def makeImageFromCamera(camera, detectorNameList=None, background=numpy.nan, bufferSize=10,
585  imageSource=FakeImageDataSource(), imageFactory=afwImage.ImageU, binSize=1):
586  """!Make an Image of a Camera
587 
588  @param[in] camera Camera object to use to make the image
589  @param[in] detectorNameList List of detector names to use in building the image.
590  Use all Detectors if None.
591  @param[in] background Value to use where there is no Detector
592  @param[in] bufferSize Size of border in binned pixels to make around the camera image
593  @param[in] imageSource Source to get ccd images. Must have a getCcdImage method
594  @param[in] imageFactory Type of image to build
595  @param[in] binSize bin factor
596  @return an image of the camera
597  """
598  if detectorNameList is None:
599  ccdList = camera
600  else:
601  ccdList = [camera[name] for name in detectorNameList]
602 
603  if detectorNameList is None:
604  camBbox = camera.getFpBBox()
605  else:
606  camBbox = afwGeom.Box2D()
607  for detName in detectorNameList:
608  for corner in camera[detName].getCorners(FOCAL_PLANE):
609  camBbox.include(corner)
610 
611  pixelSize_o = camera[camera.getNameIter().next()].getPixelSize()
612  camBbox = getCameraImageBBox(camBbox, pixelSize_o, bufferSize*binSize)
613  origin = camBbox.getMin()
614 
615  camIm = imageFactory(int(math.ceil(camBbox.getDimensions().getX()/binSize)),
616  int(math.ceil(camBbox.getDimensions().getY()/binSize)))
617  camIm[:] = imageSource.background
618 
619  assert imageSource.isTrimmed, "isTrimmed is False isn't supported by getCcdInCamBBoxList"
620 
621  boxList = getCcdInCamBBoxList(ccdList, binSize, pixelSize_o, origin)
622  for det, bbox in itertools.izip(ccdList, boxList):
623  im = imageSource.getCcdImage(det, imageFactory, binSize)
624 
625  nQuarter = det.getOrientation().getNQuarter()
626  im = afwMath.rotateImageBy90(im, nQuarter)
627 
628  imView = camIm.Factory(camIm, bbox, afwImage.LOCAL)
629  try:
630  imView <<= im
631  except Exception as e:
632  pass
633 
634  return camIm
635 
636 def showCamera(camera, imageSource=FakeImageDataSource(), imageFactory=afwImage.ImageF,
637  detectorNameList=None, binSize=10, bufferSize=10, frame=None, overlay=True, title="",
638  ctype=afwDisplay.GREEN, textSize=1.25, originAtCenter=True, display=None, **kwargs):
639  """!Show a Camera on display, with the specified display
640 
641  The rotation of the sensors is snapped to the nearest multiple of 90 deg.
642  Also note that the pixel size is constant over the image array. The lower left corner (LLC) of each
643  sensor amp is snapped to the LLC of the pixel containing the LLC of the image.
644  if overlay show the IDs and detector boundaries
645 
646  @param[in] camera Camera to show
647  @param[in] imageSource Source to get Ccd images from. Must have a getCcdImage method.
648  @param[in] imageFactory Type of image to make
649  @param[in] detectorNameList List of names of Detectors to use. If None use all
650  @param[in] binSize bin factor
651  @param[in] bufferSize size of border in binned pixels to make around camera image.
652  @param[in] frame specify image display (@deprecated; new code should use display)
653  @param[in] overlay Overlay Detector IDs and boundaries?
654  @param[in] title Title in display
655  @param[in] ctype Color to use when drawing Detector boundaries
656  @param[in] textSize Size of detector labels
657  @param[in] originAtCenter Put origin of the camera WCS at the center of the image? Else it will be LL
658  @param[in] display image display on which to display
659  @param[in] **kwargs all remaining keyword arguments are passed to makeImageFromCamera
660  @return the mosaic image
661  """
662  display = _getDisplayFromDisplayOrFrame(display, frame)
663 
664  if binSize < 1:
665  binSize = 1
666  cameraImage = makeImageFromCamera(camera, detectorNameList=detectorNameList, bufferSize=bufferSize,
667  imageSource=imageSource, imageFactory=imageFactory, binSize=binSize,
668  **kwargs)
669 
670  if detectorNameList is None:
671  ccdList = [camera[name] for name in camera.getNameIter()]
672  else:
673  ccdList = [camera[name] for name in detectorNameList]
674 
675  if detectorNameList is None:
676  camBbox = camera.getFpBBox()
677  else:
678  camBbox = afwGeom.Box2D()
679  for detName in detectorNameList:
680  for corner in camera[detName].getCorners(FOCAL_PLANE):
681  camBbox.include(corner)
682  pixelSize = ccdList[0].getPixelSize()
683  if originAtCenter:
684  #Can't divide SWIGGED extent type things when division is imported
685  #from future. This is DM-83
686  ext = cameraImage.getBBox().getDimensions()
687 
688  wcsReferencePixel = afwGeom.PointI(ext.getX()//2, ext.getY()//2)
689  else:
690  wcsReferencePixel = afwGeom.Point2I(0,0)
691  wcs = makeFocalPlaneWcs(pixelSize*binSize, wcsReferencePixel)
692 
693  if display:
694  if title == "":
695  title = camera.getName()
696  display.mtv(cameraImage, title=title, wcs=wcs)
697 
698  if overlay:
699  with display.Buffering():
700  camBbox = getCameraImageBBox(camBbox, pixelSize, bufferSize*binSize)
701  bboxList = getCcdInCamBBoxList(ccdList, binSize, pixelSize, camBbox.getMin())
702  for bbox, ccd in itertools.izip(bboxList, ccdList):
703  nQuarter = ccd.getOrientation().getNQuarter()
704  # borderWidth to 0.5 to align with the outside edge of the pixel
705  displayUtils.drawBBox(bbox, borderWidth=0.5, ctype=ctype, display=display)
706  dims = bbox.getDimensions()
707  display.dot(ccd.getName(), bbox.getMinX()+dims.getX()/2, bbox.getMinY()+dims.getY()/2,
708  ctype=ctype, size=textSize, textAngle=nQuarter*90)
709 
710  return cameraImage
711 
712 def makeFocalPlaneWcs(pixelSize, referencePixel):
713  """!Make a WCS for the focal plane geometry (i.e. returning positions in "mm")
714 
715  @param[in] pixelSize Size of the image pixels in physical units
716  @param[in] referencePixel Pixel for origin of WCS
717  @return Wcs object for mapping between pixels and focal plane.
718  """
719 
720  md = dafBase.PropertySet()
721  if referencePixel is None:
722  referencePixel = afwGeom.PointD(0,0)
723  for i in range(2):
724  md.set("CRPIX%d"%(i+1), referencePixel[i])
725  md.set("CRVAL%d"%(i+1), 0.)
726  md.set("CDELT1", pixelSize[0])
727  md.set("CDELT2", pixelSize[1])
728  md.set("CTYPE1", "CAMERA_X")
729  md.set("CTYPE2", "CAMERA_Y")
730  md.set("CUNIT1", "mm")
731  md.set("CUNIT2", "mm")
732 
733  return afwImage.makeWcs(md)
734 
735 def findAmp(ccd, pixelPosition):
736  """!Find the Amp with the specified pixel position within the composite
737 
738  @param[in] ccd Detector to look in
739  @param[in] pixelPosition Point2I containing the pixel position
740  @return Amp record in which pixelPosition falls or None if no Amp found.
741  """
742 
743  for amp in ccd:
744  if amp.getBBox().contains(pixelPosition):
745  return amp
746 
747  return None
def getCcdImage
Return a CCD image for the detector.
Definition: utils.py:273
def __init__
Construct a FakeImageDataSource.
Definition: utils.py:252
def findAmp
Find the Amp with the specified pixel position within the composite.
Definition: utils.py:735
def showCamera
Show a Camera on display, with the specified display.
Definition: utils.py:638
boost::shared_ptr< ImageT > binImage(ImageT const &inImage, int const binsize, lsst::afw::math::Property const flags=lsst::afw::math::MEAN)
Definition: binImage.cc:39
def getCameraImageBBox
Get the bounding box of a camera sized image in pixels.
Definition: utils.py:568
def showCcd
Show a CCD on display.
Definition: utils.py:499
An integer coordinate rectangle.
Definition: Box.h:53
def makeImageFromCcd
Make an Image of a Ccd.
Definition: utils.py:208
def assembleAmplifierRawImage
Assemble the amplifier region of a raw CCD image.
def makeFocalPlaneWcs
Make a WCS for the focal plane geometry (i.e.
Definition: utils.py:712
def makeImageFromAmp
Make an image from an amp object.
Definition: utils.py:145
def getCcdInCamBBoxList
Get the bounding boxes of a list of Detectors within a camera sized pixel grid.
Definition: utils.py:537
def getAmpImage
Return an amp segment image.
Definition: utils.py:283
def rotateBBoxBy90
Rotate a bounding box by an integer multiple of 90 degrees.
Wcs::Ptr makeWcs(coord::Coord const &crval, geom::Point2D const &crpix, double CD11, double CD12, double CD21, double CD22)
Create a Wcs object from crval, crpix, CD, using CD elements (useful from python) ...
Definition: makeWcs.cc:141
def __init__
Create an object that knows how to prepare images for showCamera using the butler.
Definition: utils.py:300
def showAmp
Show an amp in an image display.
Definition: utils.py:461
ImageT::Ptr rotateImageBy90(ImageT const &image, int nQuarter)
Definition: rotateImage.cc:41
def calcRawCcdBBox
Calculate the raw ccd bounding box.
Definition: utils.py:191
def makeImageFromCamera
Make an Image of a Camera.
Definition: utils.py:585
def plotFocalPlane
Make a plot of the focal plane along with a set points that sample the Pupil.
Definition: utils.py:67
def assembleAmplifierImage
Assemble the amplifier region of an image from a raw image.
Class for storing generic metadata.
Definition: PropertySet.h:82
def _getDisplayFromDisplayOrFrame
Return an afwDisplay.Display given either a display or a frame ID.
Definition: utils.py:36
Statistics makeStatistics(afwImage::Mask< afwImage::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl)
Specialization to handle Masks.
Definition: Statistics.cc:1082
A floating-point coordinate rectangle geometry.
Definition: Box.h:271
def overlayCcdBoxes
Overlay bounding boxes on an image display.
Definition: utils.py:390
def prepareWcsData
Put Wcs from an Amp image into CCD coordinates.
Definition: utils.py:46