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 #
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 
23 ## \file
24 ## \brief Utilities to use with displaying images
25 
26 from __future__ import absolute_import, division, print_function
27 
28 import lsst.afw.image as afwImage
29 import lsst.afw.geom as afwGeom
30 
31 __all__ = (
32  "Mosaic",
33  "drawBBox", "drawFootprint", "drawCoaddInputs",
34  )
35 
36 def _getDisplayFromDisplayOrFrame(display, frame=None):
37  """!Return an afwDisplay.Display given either a display or a frame ID.
38 
39  If the two arguments are consistent, return the desired display; if they are not,
40  raise a RuntimeError exception.
41 
42  If the desired display is None, return None;
43  if (display, frame) == ("deferToFrame", None), return the default display"""
44 
45  import lsst.afw.display as afwDisplay # import locally to allow this file to be imported by __init__
46 
47  if display in ("deferToFrame", None):
48  if display is None and frame is None:
49  return None
50 
51  # "deferToFrame" is the default value, and means "obey frame"
52  display = None
53 
54  if display and not hasattr(display, "frame"):
55  raise RuntimeError("display == %s doesn't support .frame" % display)
56 
57  if frame and display and display.frame != frame:
58  raise RuntimeError("Please specify display *or* frame")
59 
60  if display:
61  frame = display.frame
62 
63  display = afwDisplay.getDisplay(frame, create=True)
64 
65  return display
66 
67 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
68 
69 class Mosaic(object):
70  """A class to handle mosaics of one or more identically-sized images (or Masks or MaskedImages)
71  E.g.
72  m = Mosaic()
73  m.setGutter(5)
74  m.setBackground(10)
75  m.setMode("square") # the default; other options are "x" or "y"
76 
77  mosaic = m.makeMosaic(im1, im2, im3) # build the mosaic
78  display = afwDisplay.getDisplay()
79  display.mtv(mosaic) # display it
80  m.drawLabels(["Label 1", "Label 2", "Label 3"], display) # label the panels
81 
82  # alternative way to build a mosaic
83  images = [im1, im2, im3]
84  labels = ["Label 1", "Label 2", "Label 3"]
85 
86  mosaic = m.makeMosaic(images)
87  display.mtv(mosaic)
88  m.drawLabels(labels, display)
89 
90  # Yet another way to build a mosaic (no need to build the images/labels lists)
91  for i in range(len(images)):
92  m.append(images[i], labels[i])
93  # You may optionally include a colour, e.g. afwDisplay.YELLOW, as a third argument
94 
95  mosaic = m.makeMosaic()
96  display.mtv(mosaic)
97  m.drawLabels(display=display)
98 
99  Or simply:
100  mosaic = m.makeMosaic(display=display)
101 
102  You can return the (ix, iy)th (or nth) bounding box (in pixels) with getBBox()
103  """
104  def __init__(self, gutter=3, background=0, mode="square"):
105  self.gutter = gutter # number of pixels between panels in a mosaic
106  self.background = background # value in gutters
107  self.setMode(mode) # mosaicing mode
108  self.xsize = 0 # column size of panels
109  self.ysize = 0 # row size of panels
110 
111  self.reset()
112 
113  def reset(self):
114  """Reset the list of images to be mosaiced"""
115  self.images = [] # images to mosaic together
116  self.labels = [] # labels for images
117 
118  def append(self, image, label=None, ctype=None):
119  """Add an image to the list of images to be mosaiced
120  Set may be cleared with Mosaic.reset()
121 
122  Returns the index of this image (may be passed to getBBox())
123  """
124  if not self.xsize:
125  self.xsize = image.getWidth()
126  self.ysize = image.getHeight()
127 
128  self.images.append(image)
129  self.labels.append((label, ctype))
130 
131  return len(self.images)
132 
133  def makeMosaic(self, images=None, display="deferToFrame", mode=None, background=None, title="", frame=None):
134  """Return a mosaic of all the images provided; if none are specified,
135  use the list accumulated with Mosaic.append().
136 
137  Note that this mosaic is a patchwork of the input images; if you want to
138  make a mosaic of a set images of the sky, you probably want to use the coadd code
139 
140  If display or frame (deprecated) is specified, display the mosaic
141  """
142 
143  if not images:
144  images = self.images
145 
146  self.nImage = len(images)
147  if self.nImage == 0:
148  raise RuntimeError, "You must provide at least one image"
149 
150  self.xsize, self.ysize = 0, 0
151  for im in images:
152  w, h = im.getWidth(), im.getHeight()
153  if w > self.xsize:
154  self.xsize = w
155  if h > self.ysize:
156  self.ysize = h
157 
158  if background is None:
159  background = self.background
160  if mode is None:
161  mode = self.mode
162 
163  if mode == "square":
164  nx, ny = 1, self.nImage
165  while nx*im.getWidth() < ny*im.getHeight():
166  nx += 1
167  ny = self.nImage//nx
168 
169  if nx*ny < self.nImage:
170  ny += 1
171  if nx*ny < self.nImage:
172  nx += 1
173 
174  if nx > self.nImage:
175  nx = self.nImage
176 
177  assert(nx*ny >= self.nImage)
178  elif mode == "x":
179  nx, ny = self.nImage, 1
180  elif mode == "y":
181  nx, ny = 1, self.nImage
182  elif isinstance(mode, int):
183  nx = mode
184  ny = self.nImage//nx
185  if nx*ny < self.nImage:
186  ny += 1
187  else:
188  raise RuntimeError, ("Unknown mosaicing mode: %s" % mode)
189 
190  self.nx, self.ny = nx, ny
191 
192  mosaic = images[0].Factory(
193  afwGeom.Extent2I(nx*self.xsize + (nx - 1)*self.gutter, ny*self.ysize + (ny - 1)*self.gutter)
194  )
195  try:
196  mosaic.set(self.background)
197  except AttributeError:
198  raise RuntimeError("Attempt to mosaic images of type %s which don't support set" %
199  type(mosaic))
200 
201  for i in range(len(images)):
202  smosaic = mosaic.Factory(mosaic, self.getBBox(i%nx, i//nx), afwImage.LOCAL)
203  im = images[i]
204 
205  if smosaic.getDimensions() != im.getDimensions(): # im is smaller than smosaic
206  llc = afwGeom.PointI((smosaic.getWidth() - im.getWidth())//2,
207  (smosaic.getHeight() - im.getHeight())//2)
208  smosaic = smosaic.Factory(smosaic, afwGeom.Box2I(llc, im.getDimensions()), afwImage.LOCAL)
209 
210  smosaic <<= im
211 
212  display = _getDisplayFromDisplayOrFrame(display, frame)
213  if display:
214  display.mtv(mosaic, title=title)
215 
216  if images == self.images:
217  self.drawLabels(display=display)
218 
219  return mosaic
220 
221  def setGutter(self, gutter):
222  """Set the number of pixels between panels in a mosaic"""
223  self.gutter = gutter
224 
225  def setBackground(self, background):
226  """Set the value in the gutters"""
227  self.background = background
228 
229  def setMode(self, mode):
230  """Set mosaicing mode. Valid options:
231  square Make mosaic as square as possible
232  x Make mosaic one image high
233  y Make mosaic one image wide
234  """
235 
236  if mode not in ("square", "x", "y"):
237  raise RuntimeError, ("Unknown mosaicing mode: %s" % mode)
238 
239  self.mode = mode
240 
241  def getBBox(self, ix, iy=None):
242  """Get the BBox for the nth or (ix, iy)the panel"""
243 
244  if iy is None:
245  ix, iy = ix % self.nx, ix//self.nx
246 
247  return afwGeom.Box2I(afwGeom.PointI(ix*(self.xsize + self.gutter), iy*(self.ysize + self.gutter)),
248  afwGeom.ExtentI(self.xsize, self.ysize))
249 
250  def drawLabels(self, labels=None, display="deferToFrame", frame=None):
251  """Draw the list labels at the corners of each panel. If labels is None, use the ones
252  specified by Mosaic.append()"""
253 
254  if not labels:
255  labels = self.labels
256 
257  if not labels:
258  return
259 
260  if len(labels) != self.nImage:
261  raise RuntimeError, ("You provided %d labels for %d panels" % (len(labels), self.nImage))
262 
263  display = _getDisplayFromDisplayOrFrame(display, frame)
264  if not display:
265  return
266 
267  with display.Buffering():
268  for i in range(len(labels)):
269  if labels[i]:
270  label, ctype = labels[i], None
271  try:
272  label, ctype = label
273  except:
274  pass
275 
276  if not label:
277  continue
278 
279  display.dot(str(label), self.getBBox(i).getMinX(), self.getBBox(i).getMinY(), ctype=ctype)
280 
281 def drawBBox(bbox, borderWidth=0.0, origin=None, display="deferToFrame", ctype=None, bin=1, frame=None):
282  """Draw an afwImage::BBox on a display frame with the specified ctype. Include an extra borderWidth pixels
283 If origin is present, it's Added to the BBox
284 
285 All BBox coordinates are divided by bin, as is right and proper for overlaying on a binned image
286  """
287  x0, y0 = bbox.getMinX(), bbox.getMinY()
288  x1, y1 = bbox.getMaxX(), bbox.getMaxY()
289 
290  if origin:
291  x0 += origin[0]; x1 += origin[0]
292  y0 += origin[1]; y1 += origin[1]
293 
294  x0 /= bin; y0 /= bin
295  x1 /= bin; y1 /= bin
296  borderWidth /= bin
297 
298  display = _getDisplayFromDisplayOrFrame(display, frame)
299  display.line([(x0 - borderWidth, y0 - borderWidth),
300  (x0 - borderWidth, y1 + borderWidth),
301  (x1 + borderWidth, y1 + borderWidth),
302  (x1 + borderWidth, y0 - borderWidth),
303  (x0 - borderWidth, y0 - borderWidth),
304  ], ctype=ctype)
305 
306 def drawFootprint(foot, borderWidth=0.5, origin=None, XY0=None, frame=None, ctype=None, bin=1,
307  peaks=False, symb="+", size=0.4, ctypePeak=None, display="deferToFrame"):
308  """Draw an afwDetection::Footprint on a display frame with the specified ctype. Include an extra borderWidth
309 pixels If origin is present, it's Added to the Footprint; if XY0 is present is Subtracted from the Footprint
310 
311 If peaks is True, also show the object's Peaks using the specified symbol and size and ctypePeak
312 
313 All Footprint coordinates are divided by bin, as is right and proper for overlaying on a binned image
314  """
315 
316  if XY0:
317  if origin:
318  raise RuntimeError("You may not specify both origin and XY0")
319  origin = (-XY0[0], -XY0[1])
320 
321  display = _getDisplayFromDisplayOrFrame(display, frame)
322  with display.Buffering():
323  borderWidth /= bin
324  for s in foot.getSpans():
325  y, x0, x1 = s.getY(), s.getX0(), s.getX1()
326 
327  if origin:
328  x0 += origin[0]; x1 += origin[0]
329  y += origin[1]
330 
331  x0 /= bin; x1 /= bin; y /= bin
332 
333  display.line([(x0 - borderWidth, y - borderWidth),
334  (x0 - borderWidth, y + borderWidth),
335  (x1 + borderWidth, y + borderWidth),
336  (x1 + borderWidth, y - borderWidth),
337  (x0 - borderWidth, y - borderWidth),
338  ], ctype=ctype)
339 
340  if peaks:
341  for p in foot.getPeaks():
342  x, y = p.getIx(), p.getIy()
343 
344  if origin:
345  x += origin[0]; y += origin[1]
346 
347  x /= bin; y /= bin
348 
349  display.dot(symb, x, y, size=size, ctype=ctypePeak)
350 
351 def drawCoaddInputs(exposure, frame=None, ctype=None, bin=1, display="deferToFrame"):
352  """Draw the bounding boxes of input exposures to a coadd on a display frame with the specified ctype,
353  assuming display.mtv() has already been called on the given exposure on this frame.
354 
355 
356  All coordinates are divided by bin, as is right and proper for overlaying on a binned image
357  """
358  coaddWcs = exposure.getWcs()
359  catalog = exposure.getInfo().getCoaddInputs().ccds
360 
361  offset = afwGeom.PointD() - afwGeom.PointD(exposure.getXY0())
362 
363  display = _getDisplayFromDisplayOrFrame(display, frame)
364 
365  with display.Buffering():
366  for record in catalog:
367  ccdBox = afwGeom.Box2D(record.getBBox())
368  ccdCorners = ccdBox.getCorners()
369  coaddCorners = [coaddWcs.skyToPixel(record.getWcs().pixelToSky(point)) + offset
370  for point in ccdCorners]
371  display.line([(coaddCorners[i].getX()/bin, coaddCorners[i].getY()/bin)
372  for i in range(-1, 4)], ctype=ctype)
An integer coordinate rectangle.
Definition: Box.h:53
def _getDisplayFromDisplayOrFrame
Return an afwDisplay.Display given either a display or a frame ID.
Definition: utils.py:36
A floating-point coordinate rectangle geometry.
Definition: Box.h:271