26 Support for cameraGeom
28 from __future__
import division
38 from .rotateBBoxBy90
import rotateBBoxBy90
39 from .assembleImage
import assembleAmplifierImage, assembleAmplifierRawImage
40 from .cameraGeomLib
import PUPIL, FOCAL_PLANE
51 """!Put Wcs from an Amp image into CCD coordinates
53 @param[in, out] wcs WCS object to modify in place
54 @param[in] amp Amp object to use
55 @param[in] isTrimmed Is the image to which the WCS refers trimmed of non-imaging pixels?
57 if not amp.getHasRawInfo():
58 raise RuntimeError(
"Cannot modify wcs without raw amp information")
60 ampBox = amp.getRawDataBBox()
62 ampBox = amp.getRawBBox()
63 wcs.flipImage(amp.getRawFlipX(), amp.getRawFlipY(), ampBox.getDimensions())
65 wcs.shiftReferencePixel(-ampBox.getMinX(), -ampBox.getMinY())
67 offset = amp.getRawXYOffset()
68 wcs.shiftReferencePixel(offset.getX(), offset.getY())
70 def plotFocalPlane(camera, pupilSizeDeg_x, pupilSizeDeg_y, dx=0.1, dy=0.1, figsize=(10., 10.),
71 showFig=
True, savePath=
None):
72 """!Make a plot of the focal plane along with a set points that sample the Pupil
74 @param[in] camera a camera object
75 @param[in] pupilSizeDeg_x Amount of the pupil to sample in x in degrees
76 @param[in] pupilSizeDeg_y Amount of the pupil to sample in y in degrees
77 @param[in] dx Spacing of sample points in x in degrees
78 @param[in] dy Spacing of sample points in y in degrees
79 @param[in] figsize matplotlib style tuple indicating the size of the figure in inches
80 @param[in] showFig Display the figure on the screen?
81 @param[in] savePath If not None, save a copy of the figure to this name
84 from matplotlib.patches
import Polygon
85 from matplotlib.collections
import PatchCollection
86 import matplotlib.pyplot
as plt
88 raise ImportError(
"Can't run plotFocalPlane: matplotlib has not been set up")
89 pupil_gridx, pupil_gridy = numpy.meshgrid(numpy.arange(0., pupilSizeDeg_x+dx, dx) - pupilSizeDeg_x/2.,
90 numpy.arange(0., pupilSizeDeg_y+dy, dy) - pupilSizeDeg_y/2.)
94 for pos
in zip(pupil_gridx.flatten(), pupil_gridy.flatten()):
96 cp = camera.makeCameraPoint(posRad, PUPIL)
97 ncp = camera.transform(cp, FOCAL_PLANE)
98 xs.append(ncp.getPoint().getX())
99 ys.append(ncp.getPoint().getY())
100 dets = camera.findDetectors(cp)
107 colorMap = {0:
'b', 1:
'y', 2:
'g', 3:
'r'}
111 plt.figure(figsize=figsize)
116 corners = [(c.getX(), c.getY())
for c
in det.getCorners(FOCAL_PLANE)]
117 for corner
in corners:
118 xvals.append(corner[0])
119 yvals.append(corner[1])
120 colors.append(colorMap[det.getType()])
121 patches.append(Polygon(corners,
True))
122 center = det.getOrientation().getFpPosition()
123 ax.text(center.getX(), center.getY(), det.getName(), horizontalalignment=
'center', size=6)
125 patchCollection = PatchCollection(patches, alpha=0.6, facecolor=colors)
126 ax.add_collection(patchCollection)
127 ax.scatter(xs, ys, s=10, alpha=.7, linewidths=0., c=pcolors)
128 ax.set_xlim(min(xvals) - abs(0.1*min(xvals)), max(xvals) + abs(0.1*max(xvals)))
129 ax.set_ylim(min(yvals) - abs(0.1*min(yvals)), max(yvals) + abs(0.1*max(yvals)))
130 ax.set_xlabel(
'Focal Plane X (mm)')
131 ax.set_ylabel(
'Focal Plane Y (mm)')
132 if savePath
is not None:
133 plt.savefig(savePath)
137 def makeImageFromAmp(amp, imValue=None, imageFactory=afwImage.ImageU, markSize=10, markValue=0,
138 scaleGain =
lambda gain: (gain*1000)//10):
139 """!Make an image from an amp object
141 Since images are integer images by default, the gain needs to be scaled to give enough dynamic range
142 to see variation from amp to amp. The scaling algorithm is assignable.
144 @param[in] amp Amp record to use for constructing the raw amp image
145 @param[in] imValue Value to assign to the constructed image scaleGain(gain) is used if not set
146 @param[in] imageFactory Type of image to construct
147 @param[in] markSize Size of mark at read corner in pixels
148 @param[in] markValue Value of pixels in the read corner mark
149 @param[in] scaleGain The function by which to scale the gain
150 @return an untrimmed amp image
152 if not amp.getHasRawInfo():
153 raise RuntimeError(
"Can't create a raw amp image without raw amp information")
154 bbox = amp.getRawBBox()
155 dbbox = amp.getRawDataBBox()
156 img = imageFactory(bbox)
158 img.set(scaleGain(amp.getGain()))
163 if amp.getReadoutCorner() == 0:
164 markbbox.include(dbbox.getMin())
166 elif amp.getReadoutCorner() == 1:
168 markbbox.include(cornerPoint)
170 elif amp.getReadoutCorner() == 2:
172 markbbox.include(cornerPoint)
174 elif amp.getReadoutCorner() == 3:
176 markbbox.include(cornerPoint)
179 raise RuntimeError(
"Could not set readout corner")
180 mimg = imageFactory(img, markbbox,
False)
185 """!Calculate the raw ccd bounding box
187 @param[in] ccd Detector for with to calculate the un-trimmed bounding box
188 @return Box2I of the un-trimmed Detector,
189 or None if there is not enough information to calculate raw BBox
193 if not amp.getHasRawInfo():
195 tbbox = amp.getRawBBox()
196 tbbox.shift(amp.getRawXYOffset())
200 def makeImageFromCcd(ccd, isTrimmed=True, showAmpGain=True, imageFactory=afwImage.ImageU, rcMarkSize=10,
202 """!Make an Image of a Ccd
204 @param[in] ccd Detector to use in making the image
205 @param[in] isTrimmed Assemble a trimmed Detector image if True
206 @param[in] showAmpGain Use the per amp gain to color the pixels in the image
207 @param[in] imageFactory Image type to generate
208 @param[in] rcMarkSize Size of the mark to make in the amp images at the read corner
209 @param[in] binSize Bin the image by this factor in both dimensions
210 @return Image of the Detector
219 if amp.getHasRawInfo():
221 ampImages.append(
makeImageFromAmp(amp, imageFactory=imageFactory, markSize=rcMarkSize))
224 imageFactory=imageFactory, markSize=rcMarkSize))
227 if len(ampImages) > 0:
228 ccdImage = imageFactory(bbox)
229 for ampImage, amp
in itertools.izip(ampImages, ccd):
236 raise RuntimeError(
"Cannot create untrimmed CCD without amps with raw information")
237 ccdImage = imageFactory(ccd.getBBox())
242 """A class to retrieve synthetic images for display by the show* methods"""
243 def __init__(self, isTrimmed=True, showAmpGain=True, markSize=10, markValue=0,
244 ampImValue=
None, scaleGain =
lambda gain: (gain*1000)//10):
245 """!Construct a FakeImageDataSource
247 @param[in] isTrimmed Should amps be trimmed?
248 @param[in] showAmpGain color the amp segments with the gain of the amp
249 @param[in] markSize size of the side of the box used to mark the read corner
250 @param[in] markValue value to assing the read corner mark
251 @param[in] ampImValue Value to assing to amps. scaleGain(gain) is used if None
252 @param[in] scaleGain function to scale the gain by
262 """!Return a CCD image for the detector
264 @param[in] det: Detector to use for making the image
265 @param[in] imageFactory: image constructor for making the image
266 @param[in] binSize: number of pixels per bin axis
269 imageFactory=imageFactory, binSize=binSize)
272 """!Return an amp segment image
274 @param[in] amp AmpInfoTable for this amp
275 @param[in] imageFactory image constructor fo making the imag
281 ampImage = ampImage.Factory(ampImage, amp.getRawDataBBox(),
False)
284 def overlayCcdBoxes(ccd, untrimmedCcdBbox, nQuarter, isTrimmed, ccdOrigin, display, binSize):
285 """!Overlay bounding boxes on an image display
287 @param[in] ccd Detector to iterate for the amp bounding boxes
288 @param[in] untrimmedCcdBbox Bounding box of the un-trimmed Detector
289 @param[in] nQuarter number of 90 degree rotations to apply to the bounding boxes
290 @param[in] isTrimmed Is the Detector image over which the boxes are layed trimmed?
291 @param[in] ccdOrigin Detector origin relative to the parent origin if in a larger pixel grid
292 @param[in] display image display to display on
293 @param[in] binSize binning factor
295 with afwDisplay.Buffering():
296 ccdDim = untrimmedCcdBbox.getDimensions()
300 ampbbox = amp.getBBox()
302 ampbbox = amp.getRawBBox()
303 ampbbox.shift(amp.getRawXYOffset())
307 displayUtils.drawBBox(ampbbox, origin=ccdOrigin, borderWidth=0.49,
308 display=display, bin=binSize)
310 if not isTrimmed
and amp.getHasRawInfo():
311 for bbox, ctype
in ((amp.getRawHorizontalOverscanBBox(), afwDisplay.RED),
312 (amp.getRawDataBBox(), afwDisplay.BLUE),
313 (amp.getRawVerticalOverscanBBox(), afwDisplay.MAGENTA),
314 (amp.getRawPrescanBBox(), afwDisplay.YELLOW)):
315 if amp.getRawFlipX():
316 bbox.flipLR(amp.getRawBBox().getDimensions().getX())
317 if amp.getRawFlipY():
318 bbox.flipTB(amp.getRawBBox().getDimensions().getY())
319 bbox.shift(amp.getRawXYOffset())
322 displayUtils.drawBBox(bbox, origin=ccdOrigin, borderWidth=0.49, ctype=ctype,
323 display=display, bin=binSize)
325 xc, yc = (ampbbox.getMin()[0] + ampbbox.getMax()[0])//2, (ampbbox.getMin()[1] +
326 ampbbox.getMax()[1])//2
339 ccdHeight = ccdBbox.getHeight()
340 ccdWidth = ccdBbox.getWidth()
344 xc, yc = 0.5*ccdHeight + c*xc + s*yc, 0.5*ccdWidth + -s*xc + c*yc
349 display.dot(str(amp.getName()), xc/binSize, yc/binSize, textAngle=nQuarter*90)
351 displayUtils.drawBBox(ccdBbox, origin=ccdOrigin,
352 borderWidth=0.49, ctype=afwDisplay.MAGENTA, display=display, bin=binSize)
354 def showAmp(amp, imageSource=FakeImageDataSource(isTrimmed=
False), display=
None, overlay=
True,
355 imageFactory=afwImage.ImageU):
356 """!Show an amp in an image display
358 @param[in] amp amp record to use in display
359 @param[in] imageSource Source for getting the amp image. Must have a getAmpImage method.
360 @param[in] display image display to use
361 @param[in] overlay Overlay bounding boxes?
362 @param[in] imageFactory Type of image to display (only used if ampImage is None)
366 display = afwDisplay.getDisplay()
368 ampImage = imageSource.getAmpImage(amp, imageFactory=imageFactory)
369 ampImSize = ampImage.getDimensions()
370 title = amp.getName()
371 display.mtv(ampImage, title=title)
373 with afwDisplay.Buffering():
374 if amp.getHasRawInfo()
and ampImSize == amp.getRawBBox().getDimensions():
375 bboxes = [(amp.getRawBBox(), 0.49, afwDisplay.GREEN),]
376 xy0 = bboxes[0][0].getMin()
377 bboxes.append((amp.getRawHorizontalOverscanBBox(), 0.49, afwDisplay.RED))
378 bboxes.append((amp.getRawDataBBox(), 0.49, afwDisplay.BLUE))
379 bboxes.append((amp.getRawPrescanBBox(), 0.49, afwDisplay.YELLOW))
380 bboxes.append((amp.getRawVerticalOverscanBBox(), 0.49, afwDisplay.MAGENTA))
382 bboxes = [(amp.getBBox(), 0.49,
None),]
383 xy0 = bboxes[0][0].getMin()
385 for bbox, borderWidth, ctype
in bboxes:
390 displayUtils.drawBBox(bbox, borderWidth=borderWidth, ctype=ctype, display=display)
392 def showCcd(ccd, imageSource=FakeImageDataSource(), display=
None, overlay=
True,
393 imageFactory=afwImage.ImageU, binSize=1, inCameraCoords=
False):
394 """!Show a CCD on display
396 @param[in] ccd Detector to use in display
397 @param[in] imageSource Source for producing images to display. Must have a getCcdImage method.
398 @param[in] display image display to use
399 @param[in] overlay Show amp bounding boxes on the displayed image?
400 @param[in] imageFactory The image factory to use in generating the images.
401 @param[in] binSize Binning factor
402 @param[in] inCameraCoords Show the Detector in camera coordinates?
406 ccdImage = imageSource.getCcdImage(ccd, imageFactory=imageFactory, binSize=binSize)
408 ccdBbox = ccdImage.getBBox()
409 if ccdBbox.getDimensions() == ccd.getBBox().getDimensions():
415 nQuarter = ccd.getOrientation().getNQuarter()
417 title = ccd.getName()
420 display.mtv(ccdImage, title=title)
423 overlayCcdBoxes(ccd, ccdBbox, nQuarter, isTrimmed, ccdOrigin, display, binSize)
426 """!Get the bounding boxes of a list of Detectors within a camera sized pixel grid
428 @param[in] ccdList List of Detector
429 @param[in] binSize Binning factor
430 @param[in] pixelSize_o Size of the pixel in mm.
431 @param[in] origin origin of the camera pixel grid in pixels
432 @return a list of bounding boxes in camera pixel coordinates
436 if not pixelSize_o == ccd.getPixelSize():
438 "Cameras with detectors with different pixel scales are not currently supported")
441 for corner
in ccd.getCorners(FOCAL_PLANE):
442 dbbox.include(corner)
444 nQuarter = ccd.getOrientation().getNQuarter()
445 cbbox = ccd.getBBox()
446 ex = cbbox.getDimensions().getX()//binSize
447 ey = cbbox.getDimensions().getY()//binSize
451 int(llc.getY()//pixelSize_o.getY()/binSize)))
452 bbox.shift(
afwGeom.Extent2I(-int(origin.getX()//binSize), -int(origin.getY())//binSize))
457 """!Get the bounding box of a camera sized image in pixels
459 @param[in] camBbox Camera bounding box in focal plane coordinates (mm)
460 @param[in] pixelSize Size of a detector pixel in mm
461 @param[in] bufferSize Buffer around edge of image in pixels
462 @return the resulting bounding box
465 int(camBbox.getMinY()//pixelSize.getY()))
467 int(camBbox.getMaxY()//pixelSize.getY()))
469 retBox.grow(bufferSize)
472 def makeImageFromCamera(camera, detectorNameList=None, background=numpy.nan, bufferSize=10,
474 """!Make an Image of a Camera
476 @param[in] camera Camera object to use to make the image
477 @param[in] detectorNameList List of detector names to use in building the image.
478 Use all Detectors if None.
479 @param[in] background Value to use where there is no Detector
480 @param[in] bufferSize Size of border in binned pixels to make around the camera image
481 @param[in] imageSource Source to get ccd images. Must have a getCcdImage method
482 @param[in] imageFactory Type of image to build
483 @param[in] binSize bin factor
484 @return an image of the camera
486 if detectorNameList
is None:
489 ccdList = [camera[name]
for name
in detectorNameList]
491 if detectorNameList
is None:
492 camBbox = camera.getFpBBox()
495 for detName
in detectorNameList:
496 for corner
in camera[detName].getCorners(FOCAL_PLANE):
497 camBbox.include(corner)
499 pixelSize_o = camera[camera.getNameIter().next()].getPixelSize()
501 origin = camBbox.getMin()
503 camIm = imageFactory(int(math.ceil(camBbox.getDimensions().getX()/binSize)),
504 int(math.ceil(camBbox.getDimensions().getY()/binSize)))
506 for det, bbox
in itertools.izip(ccdList, boxList):
507 im = imageSource.getCcdImage(det, imageFactory, binSize)
508 nQuarter = det.getOrientation().getNQuarter()
510 imView = camIm.Factory(camIm, bbox, afwImage.LOCAL)
515 def showCamera(camera, imageSource=FakeImageDataSource(), imageFactory=afwImage.ImageU,
516 detectorNameList=
None, binSize=10, bufferSize=10, frame=
None, overlay=
True, title=
"",
517 ctype=afwDisplay.GREEN, textSize=1.25, originAtCenter=
True, display=
None, **kwargs):
518 """!Show a Camera on display, with the specified display
520 The rotation of the sensors is snapped to the nearest multiple of 90 deg.
521 Also note that the pixel size is constant over the image array. The lower left corner (LLC) of each
522 sensor amp is snapped to the LLC of the pixel containing the LLC of the image.
523 if overlay show the IDs and detector boundaries
525 @param[in] camera Camera to show
526 @param[in] imageSource Source to get Ccd images from. Must have a getCcdImage method.
527 @param[in] imageFactory Type of image to make
528 @param[in] detectorNameList List of names of Detectors to use. If None use all
529 @param[in] binSize bin factor
530 @param[in] bufferSize size of border in binned pixels to make around camera image.
531 @param[in] frame specify image display (@deprecated; new code should use display)
532 @param[in] overlay Overlay Detector IDs and boundaries?
533 @param[in] title Title in display
534 @param[in] ctype Color to use when drawing Detector boundaries
535 @param[in] textSize Size of detector labels
536 @param[in] originAtCenter Put origin of the camera WCS at the center of the image? Else it will be LL
537 @param[in] display image display on which to display
538 @param[in] **kwargs all remaining keyword arguments are passed to makeImageFromCamera
539 @return the mosaic image
541 display = displayUtils.getDisplay(display, frame)
545 cameraImage =
makeImageFromCamera(camera, detectorNameList=detectorNameList, bufferSize=bufferSize,
546 imageSource=imageSource, imageFactory=imageFactory, binSize=binSize,
549 if detectorNameList
is None:
550 ccdList = [camera[name]
for name
in camera.getNameIter()]
552 ccdList = [camera[name]
for name
in detectorNameList]
554 if detectorNameList
is None:
555 camBbox = camera.getFpBBox()
558 for detName
in detectorNameList:
559 for corner
in camera[detName].getCorners(FOCAL_PLANE):
560 camBbox.include(corner)
561 pixelSize = ccdList[0].getPixelSize()
565 ext = cameraImage.getBBox().getDimensions()
572 title = camera.getName()
573 display.mtv(cameraImage, title=title, wcs=wcs)
576 with afwDisplay.Buffering():
579 for bbox, ccd
in itertools.izip(bboxList, ccdList):
580 nQuarter = ccd.getOrientation().getNQuarter()
582 displayUtils.drawBBox(bbox, borderWidth=0.5, ctype=ctype, display=display)
583 dims = bbox.getDimensions()
584 display.dot(ccd.getName(), bbox.getMinX()+dims.getX()/2, bbox.getMinY()+dims.getY()/2,
585 ctype=ctype, size=textSize, textAngle=nQuarter*90)
590 """!Make a WCS for the focal plane geometry (i.e. returning positions in "mm")
592 @param[in] pixelSize Size of the image pixels in physical units
593 @param[in] referencePixel Pixel for origin of WCS
594 @return Wcs object for mapping between pixels and focal plane.
598 if referencePixel
is None:
601 md.set(
"CRPIX%d"%(i+1), referencePixel[i])
602 md.set(
"CRVAL%d"%(i+1), 0.)
603 md.set(
"CDELT1", pixelSize[0])
604 md.set(
"CDELT2", pixelSize[1])
605 md.set(
"CTYPE1",
"CAMERA_X")
606 md.set(
"CTYPE2",
"CAMERA_Y")
607 md.set(
"CUNIT1",
"mm")
608 md.set(
"CUNIT2",
"mm")
613 """!Find the Amp with the specified pixel position within the composite
615 @param[in] ccd Detector to look in
616 @param[in] pixelPosition Point2I containing the pixel position
617 @return Amp record in which pixelPosition falls or None if no Amp found.
621 if amp.getBBox().contains(pixelPosition):
def getCcdImage
Return a CCD image for the detector.
def __init__
Construct a FakeImageDataSource.
def findAmp
Find the Amp with the specified pixel position within the composite.
def showCamera
Show a Camera on display, with the specified display.
boost::shared_ptr< ImageT > binImage(ImageT const &inImage, int const binsize, lsst::afw::math::Property const flags=lsst::afw::math::MEAN)
def getCameraImageBBox
Get the bounding box of a camera sized image in pixels.
def showCcd
Show a CCD on display.
An integer coordinate rectangle.
def makeImageFromCcd
Make an Image of a Ccd.
def assembleAmplifierRawImage
Assemble the amplifier region of a raw CCD image.
def makeFocalPlaneWcs
Make a WCS for the focal plane geometry (i.e.
def makeImageFromAmp
Make an image from an amp object.
def getCcdInCamBBoxList
Get the bounding boxes of a list of Detectors within a camera sized pixel grid.
def getAmpImage
Return an amp segment image.
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) ...
def showAmp
Show an amp in an image display.
ImageT::Ptr rotateImageBy90(ImageT const &image, int nQuarter)
def calcRawCcdBBox
Calculate the raw ccd bounding box.
def makeImageFromCamera
Make an Image of a Camera.
def plotFocalPlane
Make a plot of the focal plane along with a set points that sample the Pupil.
def assembleAmplifierImage
Assemble the amplifier region of an image from a raw image.
Class for storing generic metadata.
A floating-point coordinate rectangle geometry.
def overlayCcdBoxes
Overlay bounding boxes on an image display.
def prepareWcsData
Put Wcs from an Amp image into CCD coordinates.