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