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