LSSTApplications  19.0.0-10-g920eed2,19.0.0-11-g48a0200+2,19.0.0-18-gfc4e62b+17,19.0.0-2-g3b2f90d+2,19.0.0-2-gd671419+5,19.0.0-20-g5a5a17ab+15,19.0.0-21-g2644856+18,19.0.0-23-g84eeccb+6,19.0.0-24-g878c510+5,19.0.0-25-g6c8df7140,19.0.0-25-gb330496+5,19.0.0-3-g2b32d65+5,19.0.0-3-g8227491+16,19.0.0-3-g9c54d0d+16,19.0.0-3-gca68e65+12,19.0.0-3-gcfc5f51+5,19.0.0-3-ge110943+15,19.0.0-3-ge74d124,19.0.0-3-gfe04aa6+16,19.0.0-30-g9c3fd16+6,19.0.0-4-g06f5963+5,19.0.0-4-g3d16501+18,19.0.0-4-g4a9c019+5,19.0.0-4-g5a8b323,19.0.0-4-g66397f0+1,19.0.0-4-g8278b9b+1,19.0.0-4-g8557e14,19.0.0-4-g8964aba+17,19.0.0-4-ge404a01+16,19.0.0-5-g40f3a5a,19.0.0-5-g4db63b3,19.0.0-5-gfb03ce7+17,19.0.0-6-gbaebbfb+16,19.0.0-61-gec4c6e08+6,19.0.0-7-g039c0b5+16,19.0.0-7-gbea9075+4,19.0.0-7-gc567de5+17,19.0.0-70-g334bf3e+1,19.0.0-9-g463f923+16,b.20.0.x-g5487ab2134,v20.0.0.rc1
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(f"display == {display} doesn't support .frame")
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  f"You have already appended {len(self.images)} images to this Mosaic")
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(f"Unknown mosaicing mode: {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(f"Attempt to mosaic images of type {type(mosaic)} which don't support set")
232 
233  for i in range(len(images)):
234  smosaic = mosaic.Factory(
235  mosaic, self.getBBox(i%nx, i//nx), afwImage.LOCAL)
236  im = images[i]
237 
238  if smosaic.getDimensions() != im.getDimensions(): # im is smaller than smosaic
239  llc = lsst.geom.PointI((smosaic.getWidth() - im.getWidth())//2,
240  (smosaic.getHeight() - im.getHeight())//2)
241  smosaic = smosaic.Factory(smosaic, lsst.geom.Box2I(
242  llc, im.getDimensions()), afwImage.LOCAL)
243 
244  smosaic[:] = im
245 
246  display = _getDisplayFromDisplayOrFrame(display)
247  if display:
248  display.mtv(mosaic, title=title)
249 
250  if images == self.images:
251  self.drawLabels(display=display)
252 
253  return mosaic
254 
255  def setGutter(self, gutter):
256  """Set the number of pixels between panels in a mosaic
257  """
258  self.gutter = gutter
259 
260  def setBackground(self, background):
261  """Set the value in the gutters
262  """
263  self.background = background
264 
265  def setMode(self, mode):
266  """Set mosaicing mode.
267 
268  Parameters
269  ----------
270  mode : {"square", "x", "y"}
271  Valid options:
272 
273  square
274  Make mosaic as square as possible
275  x
276  Make mosaic one image high
277  y
278  Make mosaic one image wide
279  """
280 
281  if mode not in ("square", "x", "y"):
282  raise RuntimeError(f"Unknown mosaicing mode: {mode}")
283 
284  self.mode = mode
285 
286  def getBBox(self, ix, iy=None):
287  """Get the BBox for a panel
288 
289  Parameters
290  ----------
291  ix : `int`
292  If ``iy`` is not `None`, this is the x coordinate of the panel.
293  If ``iy`` is `None`, this is the number of the panel.
294  iy : `int`, optional
295  The y coordinate of the panel.
296  """
297 
298  if iy is None:
299  ix, iy = ix % self.nx, ix//self.nx
300 
301  return lsst.geom.Box2I(lsst.geom.PointI(ix*(self.xsize + self.gutter), iy*(self.ysize + self.gutter)),
302  lsst.geom.ExtentI(self.xsize, self.ysize))
303 
304  def drawLabels(self, labels=None, display="deferToFrame", frame=None):
305  """Draw the list labels at the corners of each panel.
306 
307  Notes
308  -----
309  If labels is None, use the ones specified by ``Mosaic.append()``
310  """
311 
312  if not labels:
313  labels = self.labels
314 
315  if not labels:
316  return
317 
318  if len(labels) != self.nImage:
319  raise RuntimeError(f"You provided {len(labels)} labels for {self.nImage} panels")
320 
321  display = _getDisplayFromDisplayOrFrame(display, frame)
322  if not display:
323  return
324 
325  with display.Buffering():
326  for i in range(len(labels)):
327  if labels[i]:
328  label, ctype = labels[i], None
329  try:
330  label, ctype = label
331  except Exception:
332  pass
333 
334  if not label:
335  continue
336 
337  display.dot(str(label), self.getBBox(i).getMinX(),
338  self.getBBox(i).getMinY(), ctype=ctype)
339 
340  @property
341  def nImage(self):
342  """Number of images
343  """
344  return len(self.images)
345 
346 
347 def drawBBox(bbox, borderWidth=0.0, origin=None, display="deferToFrame", ctype=None, bin=1, frame=None):
348  """Draw a bounding box on a display frame with the specified ctype.
349 
350  Parameters
351  ----------
352  bbox : `lsst.geom.Box2I` or `lsst.geom.Box2D`
353  The box to draw
354  borderWidth : `float`
355  Include this many pixels
356  origin
357  If specified, the box is shifted by ``origin``
358  display : `str`
359  ctype : `str`
360  The desired color, either e.g. `lsst.afw.display.RED` or a color name known to X11
361  bin : `int`
362  All BBox coordinates are divided by bin, as is right and proper for overlaying on a binned image
363  frame
364  """
365  x0, y0 = bbox.getMinX(), bbox.getMinY()
366  x1, y1 = bbox.getMaxX(), bbox.getMaxY()
367 
368  if origin:
369  x0 += origin[0]
370  x1 += origin[0]
371  y0 += origin[1]
372  y1 += origin[1]
373 
374  x0 /= bin
375  y0 /= bin
376  x1 /= bin
377  y1 /= bin
378  borderWidth /= bin
379 
380  display = _getDisplayFromDisplayOrFrame(display, frame)
381  display.line([(x0 - borderWidth, y0 - borderWidth),
382  (x0 - borderWidth, y1 + borderWidth),
383  (x1 + borderWidth, y1 + borderWidth),
384  (x1 + borderWidth, y0 - borderWidth),
385  (x0 - borderWidth, y0 - borderWidth),
386  ], ctype=ctype)
387 
388 
389 def drawFootprint(foot, borderWidth=0.5, origin=None, XY0=None, frame=None, ctype=None, bin=1,
390  peaks=False, symb="+", size=0.4, ctypePeak=None, display="deferToFrame"):
391  """Draw an `lsst.afw.detection.Footprint` on a display frame with the specified ctype.
392 
393  Parameters
394  ----------
395  foot : `lsst.afw.detection.Footprint`
396  borderWidth : `float`
397  Include an extra borderWidth pixels
398  origin
399  If ``origin`` is present, it's arithmetically added to the Footprint
400  XY0
401  if ``XY0`` is present is subtracted from the Footprint
402  frame
403  ctype : `str`
404  The desired color, either e.g. `lsst.afw.display.RED` or a color name known to X11
405  bin : `int`
406  All Footprint coordinates are divided by bin, as is right and proper
407  for overlaying on a binned image
408  peaks : `bool`
409  If peaks is `True`, also show the object's Peaks using the specified
410  ``symb`` and ``size`` and ``ctypePeak``
411  symb : `str`
412  size : `float`
413  ctypePeak : `str`
414  The desired color for peaks, either e.g. `lsst.afw.display.RED` or a color name known to X11
415  display : `str`
416  """
417 
418  if XY0:
419  if origin:
420  raise RuntimeError("You may not specify both origin and XY0")
421  origin = (-XY0[0], -XY0[1])
422 
423  display = _getDisplayFromDisplayOrFrame(display, frame)
424  with display.Buffering():
425  borderWidth /= bin
426  for s in foot.getSpans():
427  y, x0, x1 = s.getY(), s.getX0(), s.getX1()
428 
429  if origin:
430  x0 += origin[0]
431  x1 += origin[0]
432  y += origin[1]
433 
434  x0 /= bin
435  x1 /= bin
436  y /= bin
437 
438  display.line([(x0 - borderWidth, y - borderWidth),
439  (x0 - borderWidth, y + borderWidth),
440  (x1 + borderWidth, y + borderWidth),
441  (x1 + borderWidth, y - borderWidth),
442  (x0 - borderWidth, y - borderWidth),
443  ], ctype=ctype)
444 
445  if peaks:
446  for p in foot.getPeaks():
447  x, y = p.getIx(), p.getIy()
448 
449  if origin:
450  x += origin[0]
451  y += origin[1]
452 
453  x /= bin
454  y /= bin
455 
456  display.dot(symb, x, y, size=size, ctype=ctypePeak)
457 
458 
459 def drawCoaddInputs(exposure, frame=None, ctype=None, bin=1, display="deferToFrame"):
460  """Draw the bounding boxes of input exposures to a coadd on a display
461  frame with the specified ctype, assuming ``display.mtv()`` has already been
462  called on the given exposure on this frame.
463 
464  All coordinates are divided by ``bin``, as is right and proper for overlaying on a binned image
465  """
466  coaddWcs = exposure.getWcs()
467  catalog = exposure.getInfo().getCoaddInputs().ccds
468 
469  offset = lsst.geom.PointD() - lsst.geom.PointD(exposure.getXY0())
470 
471  display = _getDisplayFromDisplayOrFrame(display, frame)
472 
473  with display.Buffering():
474  for record in catalog:
475  ccdBox = lsst.geom.Box2D(record.getBBox())
476  ccdCorners = ccdBox.getCorners()
477  coaddCorners = [coaddWcs.skyToPixel(record.getWcs().pixelToSky(point)) + offset
478  for point in ccdCorners]
479  display.line([(coaddCorners[i].getX()/bin, coaddCorners[i].getY()/bin)
480  for i in range(-1, 4)], ctype=ctype)
lsst::afw.display.utils.drawCoaddInputs
def drawCoaddInputs(exposure, frame=None, ctype=None, bin=1, display="deferToFrame")
Definition: utils.py:459
lsst::afw::image
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects.
Definition: imageAlgorithm.dox:1
lsst::afw.display.utils.Mosaic.getBBox
def getBBox(self, ix, iy=None)
Definition: utils.py:286
lsst::afw.display.utils.Mosaic.labels
labels
Definition: utils.py:133
lsst::afw.display.utils.drawFootprint
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:389
lsst::afw.display
Definition: __init__.py:1
lsst::afw.display.utils.Mosaic.setMode
def setMode(self, mode)
Definition: utils.py:265
lsst::afw.display.utils.Mosaic.makeMosaic
def makeMosaic(self, images=None, display="deferToFrame", mode=None, background=None, title="")
Definition: utils.py:156
lsst::afw.display.utils.Mosaic.nImage
nImage
Definition: utils.py:178
lsst::afw.display.utils.Mosaic.ny
ny
Definition: utils.py:221
lsst::afw.display.utils.Mosaic.drawLabels
def drawLabels(self, labels=None, display="deferToFrame", frame=None)
Definition: utils.py:304
lsst::afw.display.utils.Mosaic.background
background
Definition: utils.py:123
lsst::afw.display.utils.Mosaic.setGutter
def setGutter(self, gutter)
Definition: utils.py:255
lsst::afw.display.utils.Mosaic.ysize
ysize
Definition: utils.py:126
lsst::geom
Definition: geomOperators.dox:4
lsst::afw.display.utils.Mosaic.mode
mode
Definition: utils.py:284
lsst::afw::image.slicing.Factory
Factory
Definition: slicing.py:252
lsst::afw.display.utils.drawBBox
def drawBBox(bbox, borderWidth=0.0, origin=None, display="deferToFrame", ctype=None, bin=1, frame=None)
Definition: utils.py:347
lsst::geom::Point< int, 2 >
lsst::geom::Box2I
An integer coordinate rectangle.
Definition: Box.h:55
lsst::geom::Box2D
A floating-point coordinate rectangle geometry.
Definition: Box.h:413
lsst::afw.display.utils.Mosaic.__init__
def __init__(self, gutter=3, background=0, mode="square")
Definition: utils.py:121
lsst::afw.display.utils.Mosaic.images
images
Definition: utils.py:132
lsst::afw.display.utils.Mosaic.setBackground
def setBackground(self, background)
Definition: utils.py:260
lsst::geom::Extent< int, 2 >
lsst::afw.display.utils.Mosaic.gutter
gutter
Definition: utils.py:122
lsst::afw.display.utils.Mosaic.reset
def reset(self)
Definition: utils.py:130
lsst::afw.display.utils.Mosaic.append
def append(self, image, label=None, ctype=None)
Definition: utils.py:135
lsst::afw.display.utils.Mosaic
Definition: utils.py:71
lsst::afw.display.utils.Mosaic.xsize
xsize
Definition: utils.py:125