Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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 

26import lsst.geom 

27import lsst.afw.image as afwImage 

28 

29__all__ = ( 

30 "Mosaic", 

31 "drawBBox", "drawFootprint", "drawCoaddInputs", 

32) 

33 

34 

35def _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 

71class 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 

347def 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 

389def 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 

459def 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)