LSSTApplications  18.0.0+53,19.0.0,19.0.0+1,19.0.0+15,19.0.0+16,19.0.0+18,19.0.0+23,19.0.0+3,19.0.0-1-g20d9b18+9,19.0.0-1-g425ff20,19.0.0-1-g5549ca4,19.0.0-1-g580fafe+9,19.0.0-1-g6fe20d0+2,19.0.0-1-g8c57eb9+9,19.0.0-1-gbfe0924,19.0.0-1-gdc0e4a7+14,19.0.0-1-ge272bc4+9,19.0.0-1-ge3aa853+1,19.0.0-14-gbb28fe44,19.0.0-16-g8258e2a,19.0.0-2-g0d9f9cd+16,19.0.0-2-g260436e,19.0.0-2-g9b11441+3,19.0.0-2-gd955cfd+22,19.0.0-3-g6513920,19.0.0-3-gc4f6e04,19.0.0-4-g41ffa1d+2,19.0.0-4-g725f80e+18,19.0.0-4-g75300c1e,19.0.0-4-ga8eba22,19.0.0-5-g0745e3f,19.0.0-6-g6637c4fb6,19.0.0-6-gb6b8b0a,19.0.0-7-gea0a0fe+5,w.2020.03
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 `lsst.afw.display.Display` given either a display or a frame ID.
37 
38  Notes
39  -----
40  If the two arguments are consistent, return the desired display; if they are not,
41  raise a `RuntimeError` exception.
42 
43  If the desired display is `None`, return `None`;
44  if ``(display, frame) == ("deferToFrame", None)``, return the default display
45  """
46 
47  # import locally to allow this file to be imported by __init__
48  import lsst.afw.display as afwDisplay
49 
50  if display in ("deferToFrame", None):
51  if display is None and frame is None:
52  return None
53 
54  # "deferToFrame" is the default value, and means "obey frame"
55  display = None
56 
57  if display and not hasattr(display, "frame"):
58  raise RuntimeError("display == %s doesn't support .frame" % display)
59 
60  if frame and display and display.frame != frame:
61  raise RuntimeError("Please specify display *or* frame")
62 
63  if display:
64  frame = display.frame
65 
66  display = afwDisplay.getDisplay(frame, create=True)
67 
68  return display
69 
70 
71 class Mosaic:
72  """A class to handle mosaics of one or more identically-sized images
73  (or `~lsst.afw.image.Mask` or `~lsst.afw.image.MaskedImage`)
74 
75  Notes
76  -----
77  Note that this mosaic is a patchwork of the input images; if you want to
78  make a mosaic of a set images of the sky, you probably want to use the coadd code
79 
80  Examples
81  --------
82 
83  .. code-block:: py
84 
85  m = Mosaic()
86  m.setGutter(5)
87  m.setBackground(10)
88  m.setMode("square") # the default; other options are "x" or "y"
89 
90  mosaic = m.makeMosaic(im1, im2, im3) # build the mosaic
91  display = afwDisplay.getDisplay()
92  display.mtv(mosaic) # display it
93  m.drawLabels(["Label 1", "Label 2", "Label 3"], display) # label the panels
94 
95  # alternative way to build a mosaic
96  images = [im1, im2, im3]
97  labels = ["Label 1", "Label 2", "Label 3"]
98 
99  mosaic = m.makeMosaic(images)
100  display.mtv(mosaic)
101  m.drawLabels(labels, display)
102 
103  # Yet another way to build a mosaic (no need to build the images/labels lists)
104  for i in range(len(images)):
105  m.append(images[i], labels[i])
106  # You may optionally include a colour, e.g. afwDisplay.YELLOW, as a third argument
107 
108  mosaic = m.makeMosaic()
109  display.mtv(mosaic)
110  m.drawLabels(display=display)
111 
112  Or simply:
113 
114  .. code-block:: py
115 
116  mosaic = m.makeMosaic(display=display)
117 
118  You can return the (ix, iy)th (or nth) bounding box (in pixels) with `getBBox()`
119  """
120 
121  def __init__(self, gutter=3, background=0, mode="square"):
122  self.gutter = gutter # number of pixels between panels in a mosaic
123  self.background = background # value in gutters
124  self.setMode(mode) # mosaicing mode
125  self.xsize = 0 # column size of panels
126  self.ysize = 0 # row size of panels
127 
128  self.reset()
129 
130  def reset(self):
131  """Reset the list of images to be mosaiced"""
132  self.images = [] # images to mosaic together
133  self.labels = [] # labels for images
134 
135  def append(self, image, label=None, ctype=None):
136  """Add an image to the list of images to be mosaiced
137 
138  Returns
139  -------
140  index
141  the index of this image (may be passed to `getBBox()`)
142 
143  Notes
144  -----
145  Set may be cleared with ``Mosaic.reset()``
146  """
147  if not self.xsize:
148  self.xsize = image.getWidth()
149  self.ysize = image.getHeight()
150 
151  self.images.append(image)
152  self.labels.append((label, ctype))
153 
154  return len(self.images)
155 
156  def makeMosaic(self, images=None, display="deferToFrame", mode=None,
157  background=None, title=""):
158  """Return a mosaic of all the images provided.
159 
160  If none are specified, use the list accumulated with `Mosaic.append()`.
161 
162  If display is specified, display the mosaic
163  """
164 
165  if images:
166  if self.images:
167  raise RuntimeError(
168  "You have already appended %d images to this Mosaic" % len(self.images))
169 
170  try:
171  len(images) # check that it quacks like a list
172  except TypeError:
173  images = [images]
174 
175  self.images = images
176  else:
177  images = self.images
178 
179  if self.nImage == 0:
180  raise RuntimeError("You must provide at least one image")
181 
182  self.xsize, self.ysize = 0, 0
183  for im in images:
184  w, h = im.getWidth(), im.getHeight()
185  if w > self.xsize:
186  self.xsize = w
187  if h > self.ysize:
188  self.ysize = h
189 
190  if background is None:
191  background = self.background
192  if mode is None:
193  mode = self.mode
194 
195  if mode == "square":
196  nx, ny = 1, self.nImage
197  while nx*im.getWidth() < ny*im.getHeight():
198  nx += 1
199  ny = self.nImage//nx
200 
201  if nx*ny < self.nImage:
202  ny += 1
203  if nx*ny < self.nImage:
204  nx += 1
205 
206  if nx > self.nImage:
207  nx = self.nImage
208 
209  assert(nx*ny >= self.nImage)
210  elif mode == "x":
211  nx, ny = self.nImage, 1
212  elif mode == "y":
213  nx, ny = 1, self.nImage
214  elif isinstance(mode, int):
215  nx = mode
216  ny = self.nImage//nx
217  if nx*ny < self.nImage:
218  ny += 1
219  else:
220  raise RuntimeError("Unknown mosaicing mode: %s" % mode)
221 
222  self.nx, self.ny = nx, ny
223 
224  mosaic = images[0].Factory(
225  lsst.geom.Extent2I(nx*self.xsize + (nx - 1)*self.gutter,
226  ny*self.ysize + (ny - 1)*self.gutter)
227  )
228  try:
229  mosaic.set(self.background)
230  except AttributeError:
231  raise RuntimeError("Attempt to mosaic images of type %s which don't support set" %
232  type(mosaic))
233 
234  for i in range(len(images)):
235  smosaic = mosaic.Factory(
236  mosaic, self.getBBox(i%nx, i//nx), afwImage.LOCAL)
237  im = images[i]
238 
239  if smosaic.getDimensions() != im.getDimensions(): # im is smaller than smosaic
240  llc = lsst.geom.PointI((smosaic.getWidth() - im.getWidth())//2,
241  (smosaic.getHeight() - im.getHeight())//2)
242  smosaic = smosaic.Factory(smosaic, lsst.geom.Box2I(
243  llc, im.getDimensions()), afwImage.LOCAL)
244 
245  smosaic[:] = im
246 
247  display = _getDisplayFromDisplayOrFrame(display)
248  if display:
249  display.mtv(mosaic, title=title)
250 
251  if images == self.images:
252  self.drawLabels(display=display)
253 
254  return mosaic
255 
256  def setGutter(self, gutter):
257  """Set the number of pixels between panels in a mosaic
258  """
259  self.gutter = gutter
260 
261  def setBackground(self, background):
262  """Set the value in the gutters
263  """
264  self.background = background
265 
266  def setMode(self, mode):
267  """Set mosaicing mode.
268 
269  Parameters
270  ----------
271  mode : {"square", "x", "y"}
272  Valid options:
273 
274  square
275  Make mosaic as square as possible
276  x
277  Make mosaic one image high
278  y
279  Make mosaic one image wide
280  """
281 
282  if mode not in ("square", "x", "y"):
283  raise RuntimeError("Unknown mosaicing mode: %s" % mode)
284 
285  self.mode = mode
286 
287  def getBBox(self, ix, iy=None):
288  """Get the BBox for a panel
289 
290  Parameters
291  ----------
292  ix : `int`
293  If ``iy`` is not `None`, this is the x coordinate of the panel.
294  If ``iy`` is `None`, this is the number of the panel.
295  iy : `int`, optional
296  The y coordinate of the panel.
297  """
298 
299  if iy is None:
300  ix, iy = ix % self.nx, ix//self.nx
301 
302  return lsst.geom.Box2I(lsst.geom.PointI(ix*(self.xsize + self.gutter), iy*(self.ysize + self.gutter)),
303  lsst.geom.ExtentI(self.xsize, self.ysize))
304 
305  def drawLabels(self, labels=None, display="deferToFrame", frame=None):
306  """Draw the list labels at the corners of each panel.
307 
308  Notes
309  -----
310  If labels is None, use the ones specified by ``Mosaic.append()``
311  """
312 
313  if not labels:
314  labels = self.labels
315 
316  if not labels:
317  return
318 
319  if len(labels) != self.nImage:
320  raise RuntimeError("You provided %d labels for %d panels" % (
321  len(labels), self.nImage))
322 
323  display = _getDisplayFromDisplayOrFrame(display, frame)
324  if not display:
325  return
326 
327  with display.Buffering():
328  for i in range(len(labels)):
329  if labels[i]:
330  label, ctype = labels[i], None
331  try:
332  label, ctype = label
333  except Exception:
334  pass
335 
336  if not label:
337  continue
338 
339  display.dot(str(label), self.getBBox(i).getMinX(),
340  self.getBBox(i).getMinY(), ctype=ctype)
341 
342  @property
343  def nImage(self):
344  """Number of images
345  """
346  return len(self.images)
347 
348 
349 def drawBBox(bbox, borderWidth=0.0, origin=None, display="deferToFrame", ctype=None, bin=1, frame=None):
350  """Draw a bounding box on a display frame with the specified ctype.
351 
352  Parameters
353  ----------
354  bbox : `lsst.geom.Box2I` or `lsst.geom.Box2D`
355  The box to draw
356  borderWidth : `float`
357  Include this many pixels
358  origin
359  If specified, the box is shifted by ``origin``
360  display : `str`
361  ctype : `str`
362  The desired color, either e.g. `lsst.afw.display.RED` or a color name known to X11
363  bin : `int`
364  All BBox coordinates are divided by bin, as is right and proper for overlaying on a binned image
365  frame
366  """
367  x0, y0 = bbox.getMinX(), bbox.getMinY()
368  x1, y1 = bbox.getMaxX(), bbox.getMaxY()
369 
370  if origin:
371  x0 += origin[0]
372  x1 += origin[0]
373  y0 += origin[1]
374  y1 += origin[1]
375 
376  x0 /= bin
377  y0 /= bin
378  x1 /= bin
379  y1 /= bin
380  borderWidth /= bin
381 
382  display = _getDisplayFromDisplayOrFrame(display, frame)
383  display.line([(x0 - borderWidth, y0 - borderWidth),
384  (x0 - borderWidth, y1 + borderWidth),
385  (x1 + borderWidth, y1 + borderWidth),
386  (x1 + borderWidth, y0 - borderWidth),
387  (x0 - borderWidth, y0 - borderWidth),
388  ], ctype=ctype)
389 
390 
391 def drawFootprint(foot, borderWidth=0.5, origin=None, XY0=None, frame=None, ctype=None, bin=1,
392  peaks=False, symb="+", size=0.4, ctypePeak=None, display="deferToFrame"):
393  """Draw an `lsst.afw.detection.Footprint` on a display frame with the specified ctype.
394 
395  Parameters
396  ----------
397  foot : `lsst.afw.detection.Footprint`
398  borderWidth : `float`
399  Include an extra borderWidth pixels
400  origin
401  If ``origin`` is present, it's arithmetically added to the Footprint
402  XY0
403  if ``XY0`` is present is subtracted from the Footprint
404  frame
405  ctype : `str`
406  The desired color, either e.g. `lsst.afw.display.RED` or a color name known to X11
407  bin : `int`
408  All Footprint coordinates are divided by bin, as is right and proper
409  for overlaying on a binned image
410  peaks : `bool`
411  If peaks is `True`, also show the object's Peaks using the specified
412  ``symb`` and ``size`` and ``ctypePeak``
413  symb : `str`
414  size : `float`
415  ctypePeak : `str`
416  The desired color for peaks, either e.g. `lsst.afw.display.RED` or a color name known to X11
417  display : `str`
418  """
419 
420  if XY0:
421  if origin:
422  raise RuntimeError("You may not specify both origin and XY0")
423  origin = (-XY0[0], -XY0[1])
424 
425  display = _getDisplayFromDisplayOrFrame(display, frame)
426  with display.Buffering():
427  borderWidth /= bin
428  for s in foot.getSpans():
429  y, x0, x1 = s.getY(), s.getX0(), s.getX1()
430 
431  if origin:
432  x0 += origin[0]
433  x1 += origin[0]
434  y += origin[1]
435 
436  x0 /= bin
437  x1 /= bin
438  y /= bin
439 
440  display.line([(x0 - borderWidth, y - borderWidth),
441  (x0 - borderWidth, y + borderWidth),
442  (x1 + borderWidth, y + borderWidth),
443  (x1 + borderWidth, y - borderWidth),
444  (x0 - borderWidth, y - borderWidth),
445  ], ctype=ctype)
446 
447  if peaks:
448  for p in foot.getPeaks():
449  x, y = p.getIx(), p.getIy()
450 
451  if origin:
452  x += origin[0]
453  y += origin[1]
454 
455  x /= bin
456  y /= bin
457 
458  display.dot(symb, x, y, size=size, ctype=ctypePeak)
459 
460 
461 def drawCoaddInputs(exposure, frame=None, ctype=None, bin=1, display="deferToFrame"):
462  """Draw the bounding boxes of input exposures to a coadd on a display
463  frame with the specified ctype, assuming ``display.mtv()`` has already been
464  called on the given exposure on this frame.
465 
466  All coordinates are divided by ``bin``, as is right and proper for overlaying on a binned image
467  """
468  coaddWcs = exposure.getWcs()
469  catalog = exposure.getInfo().getCoaddInputs().ccds
470 
471  offset = lsst.geom.PointD() - lsst.geom.PointD(exposure.getXY0())
472 
473  display = _getDisplayFromDisplayOrFrame(display, frame)
474 
475  with display.Buffering():
476  for record in catalog:
477  ccdBox = lsst.geom.Box2D(record.getBBox())
478  ccdCorners = ccdBox.getCorners()
479  coaddCorners = [coaddWcs.skyToPixel(record.getWcs().pixelToSky(point)) + offset
480  for point in ccdCorners]
481  display.line([(coaddCorners[i].getX()/bin, coaddCorners[i].getY()/bin)
482  for i in range(-1, 4)], ctype=ctype)
def drawCoaddInputs(exposure, frame=None, ctype=None, bin=1, display="deferToFrame")
Definition: utils.py:461
def setBackground(self, background)
Definition: utils.py:261
A floating-point coordinate rectangle geometry.
Definition: Box.h:413
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:392
def getBBox(self, ix, iy=None)
Definition: utils.py:287
def drawLabels(self, labels=None, display="deferToFrame", frame=None)
Definition: utils.py:305
def append(self, image, label=None, ctype=None)
Definition: utils.py:135
table::Key< int > type
Definition: Detector.cc:163
def setGutter(self, gutter)
Definition: utils.py:256
def setMode(self, mode)
Definition: utils.py:266
def __init__(self, gutter=3, background=0, mode="square")
Definition: utils.py:121
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects...
def makeMosaic(self, images=None, display="deferToFrame", mode=None, background=None, title="")
Definition: utils.py:157
An integer coordinate rectangle.
Definition: Box.h:55
def drawBBox(bbox, borderWidth=0.0, origin=None, display="deferToFrame", ctype=None, bin=1, frame=None)
Definition: utils.py:349