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