Loading [MathJax]/extensions/tex2jax.js
LSST Applications g04dff08e69+fafbcb10e2,g0d33ba9806+e09a96fa4e,g0fba68d861+cc01b48236,g1e78f5e6d3+fb95f9dda6,g1ec0fe41b4+f536777771,g1fd858c14a+ae46bc2a71,g35bb328faa+fcb1d3bbc8,g4af146b050+dd94f3aad7,g4d2262a081+7ee6f976aa,g53246c7159+fcb1d3bbc8,g5a012ec0e7+b20b785ecb,g60b5630c4e+e09a96fa4e,g6273192d42+bf8cfc5e62,g67b6fd64d1+4086c0989b,g78460c75b0+2f9a1b4bcd,g786e29fd12+cf7ec2a62a,g7b71ed6315+fcb1d3bbc8,g87b7deb4dc+831c06c8fc,g8852436030+54b48a5987,g89139ef638+4086c0989b,g9125e01d80+fcb1d3bbc8,g94187f82dc+e09a96fa4e,g989de1cb63+4086c0989b,g9f33ca652e+64be6d9d51,g9f7030ddb1+d11454dffd,ga2b97cdc51+e09a96fa4e,gabe3b4be73+1e0a283bba,gabf8522325+fa80ff7197,gb1101e3267+23605820ec,gb58c049af0+f03b321e39,gb89ab40317+4086c0989b,gcf25f946ba+54b48a5987,gd6cbbdb0b4+af3c3595f5,gd9a9a58781+fcb1d3bbc8,gde0f65d7ad+15f2daff9d,ge278dab8ac+d65b3c2b70,ge410e46f29+4086c0989b,gf67bdafdda+4086c0989b,v29.0.0.rc5
LSST Data Management Base Package
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
astrowidgets.py
Go to the documentation of this file.
1# This file is part of display_astrowidgets.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
21
22__all__ = ["AstroWidgetsVersion", "DisplayImpl"]
23
24import sys
25import tempfile
26import warnings
27from astropy.table import Table
28from astropy.io.fits.verify import VerifyWarning
29from astropy.wcs import FITSFixedWarning
30
31import lsst.afw.display.interface as interface
32import lsst.afw.display.virtualDevice as virtualDevice
33import lsst.afw.display.ds9Regions as ds9Regions
34from lsst.afw.display import writeFitsImage
35
36try:
37 from ginga.misc.log import get_logger
38 from ginga.util.io import io_fits
39 haveGinga = True
40except ImportError:
41 import logging
42 logging.getLogger("lsst.afw.display.astrowidgets").warning("Cannot import ginga libraries.")
43 haveGinga = False
44
45
46try:
47 import astrowidgets
48 haveAstrowidgets = True
49except ImportError:
50 haveAstrowidgets = False
51
52try:
53 _maskTransparency
54except NameError:
55 _maskTransparency = None
56
57
59 """Get the version of Astrowidgets in use.
60
61 Returns
62 -------
63 version : `str`
64 Version of DS9 in use.
65 """
66 return astrowidgets.__version__
67
68
70 """An event generated by a mouse or key click on the display"""
71
72 def __int__(self, k, x, y):
73 interface.Event.__init__(self, k, x, y)
74
75
77 """Virtual device display implementation.
78
79 Parameters
80 ----------
81 display : `lsst.afw.display.virtualDevice.DisplayImpl`
82 Display object to connect to.
83 dims : `tuple` [`int`, `int`], optional
84 Dimensions of the viewer window.
85 use_opencv : `bool`, optional
86 Should openCV be used to speed drawing?
87 verbose : `bool`, optional
88 Increase log verbosity?
89 """
90 markerDict = {'+': 'plus', 'x': 'cross', '.': 'circle', '*': 'circle', 'o': 'circle'}
91
92 def __init__(self, display, dims=None, use_opencv=False, verbose=False, *args, **kwargs):
93 virtualDevice.DisplayImpl.__init__(self, display, verbose)
94 if dims is None:
95 width, height = 1024, 768
96 else:
97 width, height = dims
98 if haveGinga:
99 self.logger = get_logger("ginga", log_stderr=True, level=40)
100 else:
101 self.logger = None
102 self._viewer = astrowidgets.ImageWidget(image_width=width, image_height=height,
103 use_opencv=use_opencv, logger=self.logger)
105 self._callbackDict = dict()
106
107 # We want to display the IW, but ginga has all the handles
108 self._gingaViewer = self._viewer._viewer
109
110 bd = self._gingaViewer.get_bindings()
111 bd.enable_all(True)
112 self._canvas = self._viewer.canvas
113 self._canvas.enable_draw(False)
115 self._redraw = True
116
117 def embed(self):
118 """Attach this display to the output of the current cell."""
119 return self._viewer
120
121 def get_viewer(self):
122 """Return the ginga viewer"""
123 return self._viewer
124
125 def show_color_bar(self, show=True):
126 """Show (or hide) the colour bar.
127
128 Parameters
129 ----------
130 show : `bool`, optional
131 Should the color bar be shown?
132 """
133 self._gingaViewer.show_color_bar(show)
134
135 def show_pan_mark(self, show=True, color='red'):
136 """Show (or hide) the pan mark.
137
138 Parameters
139 ----------
140 show : `bool`, optional
141 Should the pan marker be shown?
142 color : `str`, optional
143 What color should the pan mark be?
144 """
145 self._gingaViewer.show_pan_mark(show, color)
146
147 def _setMaskTransparency(self, transparency, maskplane=None):
148 """Specify mask transparency (percent); or None to not set it when loading masks.
149
150 Parameters
151 ----------
152 transparency : `float`
153 Transparency of the masks in percent (0-100).
154 maskplane : `str`, optional
155 Unsupported option to only change the transparency of
156 certain masks.
157 """
158 if maskplane is not None:
159 print("display_astrowidgets is not yet able to set transparency for individual maskplanes" % maskplane, # noqa E501
160 file=sys.stderr)
161 return
162
163 self._maskTransparency = 0.01*transparency
164
165 def _getMaskTransparency(self, maskplane=None):
166 """Return the current mask transparency."""
167 return self._maskTransparency
168
169 def _mtv(self, image, mask=None, wcs=None, title="", metadata=None):
170 """Display an Image and/or Mask on a ginga display
171
172 Parameters
173 ----------
174 image : `lsst.afw.image.Image` or `lsst.afw.image.Exposure`
175 Image to display.
176 mask : `lsst.afw.image.Mask`, optional
177 Mask to use, if the input does not contain one.
178 wcs : `ginga.util.wcsmod.wcs_astropy`
179 WCS to use, if the input does not contain one.
180 title : `str`, optional
181 Title that will be stored in OBJECT header of FITS file.
182 metadata : `lsst.daf.base.PropertyList`, optional
183 FITS header information that might be used.
184 """
185 self._erase()
186 self._canvas.delete_all_objects()
187 self._buffer()
188 if haveGinga:
189 with tempfile.NamedTemporaryFile() as fd:
190 writeFitsImage(fd.name, image, wcs, title, metadata=metadata)
191 fd.flush()
192 # Astropy complains a lot about things we do not care about.
193 with warnings.catch_warnings():
194 warnings.simplefilter("ignore", VerifyWarning)
195 warnings.simplefilter("ignore", FITSFixedWarning)
196 Aimage = io_fits.load_file(fd.name)
197 self._gingaViewer.set_image(Aimage)
198
199 if mask:
200 maskColorFromName = {'BAD': 'red',
201 'SAT': 'green',
202 'INTRP': 'green',
203 'CR': 'magenta',
204 'EDGE': 'yellow',
205 'DETECTED': 'blue',
206 'DETECTED_NEGATIVE': 'cyan',
207 'SUSPECT': 'yellow',
208 'NO_DATA': 'orange',
209 'CROSSTALK': None,
210 'UNMASKEDNAN': None}
211 maskDict = dict()
212 for plane, bit in mask.getMaskPlaneDict().items():
213 color = maskColorFromName.get(plane, None)
214 if color:
215 maskDict[1 << bit] = color
216 # This value of 0.9 is pretty thick for the alpha.
217 self.overlay_mask(mask, maskDict,
218 self._maskTransparency)
219 self._buffer(enable=False)
220 self._flush()
221
222 def overlay_mask(self, maskImage, maskDict, maskAlpha):
223 """Draw mask onto the image display.
224
225 Parameters
226 ----------
227 maskImage : `lsst.afw.image.Mask`
228 Mask to display.
229 maskDict : `dict` [`str`, `str`]
230 Dictionary of mask plane names to colors.
231 maskAlpha : `float`
232 Transparency to display the mask.
233 """
234 import numpy as np
235 from ginga.RGBImage import RGBImage
236 from ginga import colors
237
238 maskArray = maskImage.getArray()
239 height, width = maskArray.shape
240 maskRGBA = np.zeros((height, width, 4), dtype=np.uint8)
241 nSet = np.zeros_like(maskArray, dtype=np.uint8)
242
243 for maskValue, maskColor in maskDict.items():
244 r, g, b = colors.lookup_color(maskColor)
245 isSet = (maskArray & maskValue) != 0
246 if (isSet == 0).all():
247 continue
248
249 maskRGBA[:, :, 0][isSet] = 255 * r
250 maskRGBA[:, :, 1][isSet] = 255 * g
251 maskRGBA[:, :, 2][isSet] = 255 * b
252
253 nSet[isSet] += 1
254
255 maskRGBA[:, :, 3][nSet == 0] = 0
256 maskRGBA[:, :, 3][nSet != 0] = 255 * maskAlpha
257
258 nSet[nSet == 0] = 1
259 for C in (0, 1, 2):
260 maskRGBA[:, :, C] //= nSet
261
262 rgb_img = RGBImage(data_np=maskRGBA)
263 Image = self._viewer.canvas.get_draw_class('image')
264 maskImageRGBA = Image(0, 0, rgb_img)
265
266 if "mask_overlay" in self._gingaViewer.canvas.get_tags():
267 self._gingaViewer.canvas.delete_object_by_tag("mask_overlay")
268 self._gingaViewer.canvas.add(maskImageRGBA, tag="mask_overlay")
269
270 def _buffer(self, enable=True):
271 self._redraw = not enable
272
273 def _flush(self):
274 self._gingaViewer.redraw(whence=3)
275
276 def _erase(self):
277 """Erase the display"""
278 self._canvas.delete_all_objects()
279
280 def _dot(self, symb, c, r, size, ctype, fontFamily="helvetica", textAngle=None, label='_dot'):
281 """Draw a symbol at (col,row) = (c,r) [0-based coordinates].
282
283 Parameters
284 ----------
285 symb : `str`
286 Symbol to draw. Should be one of '+', 'x', '*', 'o', '.'.
287 c : `int`
288 Image column for dot center (0-based coordinates).
289 r : `int`
290 Image row for dot center (0-based coordinate).
291 size : `int`
292 Size of dot.
293 fontFamily : `str`, optional
294 Font to use for text symbols.
295 textAngle : `float`, optional
296 Text rotation angle.
297 label : `str`, optional
298 Label to store this dot in the internal list.
299 """
300 dataTable = Table([{'x': c, 'y': r}])
301 if symb in '+x*.o':
302 self._viewer.marker = {'type': self.markerDict[symb], 'color': ctype, 'radius': size}
303 self._viewer.add_markers(dataTable, marker_name=label)
304 self._flush()
305 else:
306 Line = self._canvas.get_draw_class('line')
307 Text = self._canvas.get_draw_class('text')
308
309 for ds9Cmd in ds9Regions.dot(symb, c, r, size, fontFamily="helvetica", textAngle=None):
310 tmp = ds9Cmd.split('#')
311 cmd = tmp.pop(0).split()
312 comment = tmp.pop(0) if tmp else ""
313
314 cmd, args = cmd[0], cmd[1:]
315 if cmd == "line":
316 self._gingaViewer.canvas.add(Line(*[float(p) - 1 for p in args], color=ctype),
317 redraw=self._redraw)
318 elif cmd == "text":
319 x, y = [float(p) - 1 for p in args[0:2]]
320 self._gingaViewer.canvas.add(Text(x, y, symb, color=ctype), redraw=self._redraw)
321 else:
322 raise RuntimeError(ds9Cmd)
323 if comment:
324 print(comment)
325
326 def _drawLines(self, points, ctype):
327 """Connect the points, a list of (col,row).
328
329 Parameters
330 ----------
331 points : `list` [`tuple` [`int`, `int`]]
332 Points to connect with lines.
333 ctype : `str`
334 Color to use.
335 """
336 Line = self._gingaViewer.canvas.get_draw_class('line')
337 p0 = points[0]
338 for p in points[1:]:
339 self._gingaViewer.canvas.add(Line(p0[0], p0[1], p[0], p[1], color=ctype), redraw=self._redraw)
340 p0 = p
341
342 def beginMarking(self, symb='+', ctype='cyan', size=10, label='interactive'):
343 """Begin interactive mark adding.
344
345 Parameters
346 ----------
347 symb : `str`, optional
348 Symbol to use. Should be one of '+', 'x', '*', 'o', '.'.
349 ctype : `str`, optional
350 Color of markers.
351 size : `float`, optional
352 Size of marker.
353 label : `str`
354 Label to store this marker in the internal list.
355 """
356 self._viewer.start_marking(marker_name=label,
357 marker={'type': self.markerDict[symb], 'color': ctype, 'radius': size})
358
359 def endMarking(self):
360 """End interactive mark adding."""
361 self._viewer.stop_marking()
362
363 def getMarkers(self, label='interactive'):
364 """Get list of markers.
365
366 Parameters
367 ----------
368 label : `str`, optional
369 Marker label to return.
370
371 Returns
372 -------
373 table : `astropy.table.Table`
374 Table of markers with the given label.
375 """
376 return self._viewer.get_markers(marker_name=label)
377
378 def clearMarkers(self, label=None):
379 """Clear markers.
380
381 Parameters
382 ----------
383 label : `str`, optional
384 Marker label to clear. If None, all markers are cleared.
385 """
386 if label:
387 self._viewer.remove_markers(label)
388 else:
389 self._viewer.reset_markers()
390
391 def linkMarkers(self, ctype='brown', label='interactive'):
392 """Connect markers with lines.
393
394 Parameters
395 ----------
396 ctype : `str`, optional
397 Color to draw the lines.
398 label : `str`, optional
399 Marker label to connect. Lines are drawn in the order
400 found in the table.
401 """
402 Line = self._gingaViewer.canvas.get_draw_class('line')
403 table = self._viewer.get_markers(marker_name=label)
404
405 x0, y0 = (0, 0)
406 for rowCount, (x, y) in enumerate(table.iterrows('x', 'y')):
407 if rowCount != 0:
408 self._gingaViewer.canvas.add(Line(x0, y0, x, y, color=ctype), redraw=self._redraw)
409 x0 = x
410 y0 = y
411
412 def clearLines(self):
413 """Remove all lines from the display."""
414 self._gingaViewer.canvas.deleteObjects(list(self._gingaViewer.canvas.get_objects_by_kind('line')))
415
416 def _scale(self, algorithm, min, max, unit, *args, **kwargs):
417 """Set greyscale values.
418
419 Parameters
420 ----------
421 algorithm : `str`
422 Image scaling algorithm to use.
423 min : `float` or `str`
424 Minimum value to set to black. If a string, should be one of 'zscale' or 'minmax'.
425 max : `float`
426 Maximum value to set to white.
427 unit : `str`
428 Scaling units. This is ignored.
429 """
430 self._gingaViewer.set_color_map('gray')
431 self._gingaViewer.set_color_algorithm(algorithm)
432
433 if min == "zscale":
434 self._gingaViewer.set_autocut_params('zscale', contrast=0.25)
435 self._gingaViewer.auto_levels()
436 elif min == "minmax":
437 self._gingaViewer.set_autocut_params('minmax')
438 self._gingaViewer.auto_levels()
439 else:
440 if unit:
441 print("ginga: ignoring scale unit %s" % unit, file=sys.stderr)
442
443 self._gingaViewer.cut_levels(min, max)
444
445 def _show(self):
446 """Show the requested display.
447
448 In this case, embed it in the notebook (equivalent to
449 Display.get_viewer().show(); see also
450 Display.get_viewer().embed() N.b. These command *must* be the
451 last entry in their cell
452 """
453 return self._gingaViewer.show()
454
455 #
456 # Zoom and Pan
457 #
458 def _zoom(self, zoomfac):
459 """Zoom by specified amount
460
461 Parameters
462 ----------
463 zoomfac : `float`
464 Zoom factor to use.
465 """
466 self._gingaViewer.scale_to(zoomfac, zoomfac)
467
468 def _pan(self, colc, rowc):
469 """Pan to (colc, rowc)
470
471 Parameters
472 ----------
473 colc : `int`
474 Column to center in viewer (0-based coordinate).
475 rowc : `int`
476 Row to center in viewer (0-based coordinate).
477 """
478 self._gingaViewer.set_pan(colc, rowc)
479
480 def _getEvent(self):
481 """Listen for a key press on a frame in DS9 and return an event.
482
483 Returns
484 -------
485 event : `Ds9Event`
486 Event with (key, x, y).
487 """
488 pass
__init__(self, display, dims=None, use_opencv=False, verbose=False, *args, **kwargs)