lsst.afw g7b64926daa+e08cd4597e
Loading...
Searching...
No Matches
interface.py
Go to the documentation of this file.
1# This file is part of afw.
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__ = [
23 "WHITE", "BLACK", "RED", "GREEN", "BLUE", "CYAN", "MAGENTA", "YELLOW", "ORANGE", "IGNORE",
24 "Display", "Event", "noop_callback", "h_callback",
25 "setDefaultBackend", "getDefaultBackend",
26 "setDefaultFrame", "getDefaultFrame", "incrDefaultFrame",
27 "setDefaultMaskTransparency", "setDefaultMaskPlaneColor",
28 "getDisplay", "delAllDisplays",
29]
30
31import logging
32import re
33import sys
34import importlib
35import lsst.afw.geom as afwGeom
36import lsst.afw.image as afwImage
37
38from ._images_compat import normalize_lsst_images
39
40logger = logging.getLogger(__name__)
41
42# Symbolic names for mask/line colors. N.b. ds9 supports any X11 color for masks
43WHITE = "white"
44BLACK = "black"
45RED = "red"
46GREEN = "green"
47BLUE = "blue"
48CYAN = "cyan"
49MAGENTA = "magenta"
50YELLOW = "yellow"
51ORANGE = "orange"
52IGNORE = "ignore"
53
54
55def _makeDisplayImpl(display, backend, *args, **kwargs):
56 """Return the ``DisplayImpl`` for the named backend
57
58 Parameters
59 ----------
60 display : `str`
61 Name of device. Should be importable, either absolutely or relative to lsst.display
62 backend : `str`
63 The desired backend
64 *args
65 Arguments passed to DisplayImpl.__init__
66 *kwargs
67 Keywords arguments passed to DisplayImpl.__init__
68
69 Examples
70 --------
71 E.g.
72
73 .. code-block:: py
74
75 import lsst.afw.display as afwDisplay
76 display = afwDisplay.Display(backend="ds9")
77
78 would call
79
80 .. code-block:: py
81
82 _makeDisplayImpl(..., "ds9", 1)
83
84 and import the ds9 implementation of ``DisplayImpl`` from `lsst.display.ds9`
85 """
86 _disp = None
87 exc = None
88 candidateBackends = (f"lsst.display.{backend}", backend, f".{backend}", f"lsst.afw.display.{backend}")
89 for dt in candidateBackends:
90 exc = None
91 # only specify the root package if we are not doing an absolute import
92 impargs = {}
93 if dt.startswith("."):
94 impargs["package"] = "lsst.display"
95 try:
96 _disp = importlib.import_module(dt, **impargs)
97 # If _disp doesn't have a DisplayImpl attribute, we probably
98 # picked up an irrelevant module due to a name collision
99 if hasattr(_disp, "DisplayImpl"):
100 break
101 else:
102 _disp = None
103 except (ImportError, SystemError) as e:
104 # SystemError can be raised in Python 3.5 if a relative import
105 # is attempted when the root package, lsst.display, does not exist.
106 # Copy the exception into outer scope
107 exc = e
108
109 if not _disp or not hasattr(_disp.DisplayImpl, "_show"):
110 # If available, re-use the final exception from above
111 e = ImportError(f"Could not load the requested backend: {backend} "
112 f"(tried {', '.join(candidateBackends)}, but none worked).")
113 if exc is not None:
114 raise e from exc
115 else:
116 raise e
117
118 if display:
119 _impl = _disp.DisplayImpl(display, *args, **kwargs)
120 if not hasattr(_impl, "frame"):
121 _impl.frame = display.frame
122
123 return _impl
124 else:
125 return True
126
127
129 """Create an object able to display images and overplot glyphs.
130
131 Parameters
132 ----------
133 frame
134 An identifier for the display.
135 backend : `str`
136 The backend to use (defaults to value set by setDefaultBackend()).
137 **kwargs
138 Arguments to pass to the backend.
139 """
140 _displays = {}
141 _defaultBackend = None
142 _defaultFrame = 0
143 _defaultMaskPlaneColor = dict(
144 BAD=RED,
145 CR=MAGENTA,
146 COSMIC_RAY=MAGENTA,
147 EDGE=YELLOW,
148 DETECTION_EDGE=YELLOW,
149 INTERPOLATED=GREEN,
150 SATURATED=GREEN,
151 DETECTED=BLUE,
152 DETECTED_NEGATIVE=CYAN,
153 SUSPECT=YELLOW,
154 NO_DATA=ORANGE,
155 # deprecated names
156 INTRP=GREEN,
157 SAT=GREEN,
158 )
159 _defaultMaskTransparency = {}
160 _defaultImageColormap = "gray"
161
162 def __init__(self, frame=None, backend=None, **kwargs):
163 if frame is None:
164 frame = getDefaultFrame()
165
166 if backend is None:
167 if Display._defaultBackend is None:
168 try:
169 setDefaultBackend("ds9")
170 except RuntimeError:
171 setDefaultBackend("virtualDevice")
172
173 backend = Display._defaultBackend
174
175 self.frame = frame
176 self._impl = _makeDisplayImpl(self, backend, **kwargs)
177 self.name = backend
178
179 self._xy0 = None # displayed data's XY0
180 self.setMaskTransparency(Display._defaultMaskTransparency)
182 self.setMaskPlaneColor(Display._defaultMaskPlaneColor)
183 self.setImageColormap(Display._defaultImageColormap)
184
185 self._callbacks = {}
186
187 for ik in range(ord('a'), ord('z') + 1):
188 k = f"{ik:c}"
189 self.setCallback(k, noRaise=True)
190 self.setCallback(k.upper(), noRaise=True)
191
192 for k in ('Return', 'Shift_L', 'Shift_R'):
193 self.setCallback(k)
194
195 for k in ('q', 'Escape'):
196 self.setCallback(k, lambda k, x, y: True)
197
198 def _h_callback(k, x, y):
199 h_callback(k, x, y)
200
201 for k in sorted(self._callbacks.keys()):
202 doc = self._callbacks[k].__doc__
203 print(" %-6s %s" % (k, doc.split("\n")[0] if doc else "???"))
204
205 self.setCallback('h', _h_callback)
206
207 Display._displays[frame] = self
208
209 def __enter__(self):
210 """Support for python's with statement.
211 """
212 return self
213
214 def __exit__(self, *args):
215 """Support for python's with statement.
216 """
217 self.close()
218
219 def __del__(self):
220 self.close()
221
222 def __getattr__(self, name):
223 """Return the attribute of ``self._impl``, or ``._impl`` if it is
224 requested.
225
226 Parameters:
227 -----------
228 name : `str`
229 name of the attribute requested.
230
231 Returns:
232 --------
233 attribute : `object`
234 the attribute of self._impl for the requested name.
235 """
236
237 if name == '_impl':
238 return object.__getattr__(self, name)
239
240 if not (hasattr(self, "_impl") and self._impl):
241 raise AttributeError("Device has no _impl attached")
242
243 try:
244 return getattr(self._impl, name)
245 except AttributeError:
246 raise AttributeError(
247 f"Device {self.name} has no attribute \"{name}\"")
248
249 def close(self):
250 if getattr(self, "_impl", None) is not None:
251 self._impl._close()
252 del self._impl
253 self._impl = None
254
255 if self.frame in Display._displays:
256 del Display._displays[self.frame]
257
258 @property
259 def verbose(self):
260 """The backend's verbosity.
261 """
262 return self._impl.verbose
263
264 @verbose.setter
265 def verbose(self, value):
266 if self._impl:
267 self._impl.verbose = value
268
269 def __str__(self):
270 return f"Display[{self.frame}]"
271
272 # Handle Displays, including the default one (the frame to use when a user specifies None)
273
274 @staticmethod
275 def setDefaultBackend(backend):
276 try:
277 _makeDisplayImpl(None, backend)
278 except Exception as e:
279 raise RuntimeError(
280 f"Unable to set backend to {backend}: \"{e}\"")
281
282 Display._defaultBackend = backend
283
284 @staticmethod
286 return Display._defaultBackend
287
288 @staticmethod
289 def setDefaultFrame(frame=0):
290 """Set the default frame for display.
291 """
292 Display._defaultFrame = frame
293
294 @staticmethod
296 """Get the default frame for display.
297 """
298 return Display._defaultFrame
299
300 @staticmethod
302 """Increment the default frame for display.
303 """
304 Display._defaultFrame += 1
305 return Display._defaultFrame
306
307 @staticmethod
308 def setDefaultMaskTransparency(maskPlaneTransparency={}):
309 if hasattr(maskPlaneTransparency, "copy"):
310 maskPlaneTransparency = maskPlaneTransparency.copy()
311
312 Display._defaultMaskTransparency = maskPlaneTransparency
313
314 @staticmethod
315 def setDefaultMaskPlaneColor(name=None, color=None):
316 """Set the default mapping from mask plane names to colors.
317
318 Parameters
319 ----------
320 name : `str` or `dict`
321 Name of mask plane, or a dict mapping names to colors
322 If name is `None`, use the hard-coded default dictionary.
323 color
324 Desired color, or `None` if name is a dict.
325 """
326
327 if name is None:
328 name = Display._defaultMaskPlaneColor
329
330 if isinstance(name, dict):
331 assert color is None
332 for k, v in name.items():
334 return
335 # Set the individual color values
336 Display._defaultMaskPlaneColor[name] = color
337
338 @staticmethod
340 """Set the default colormap for images.
341
342 Parameters
343 ----------
344 cmap : `str`
345 Name of colormap, as interpreted by the backend.
346
347 Notes
348 -----
349 The only colormaps that all backends are required to honor
350 (if they pay any attention to setImageColormap) are "gray" and "grey".
351 """
352
353 Display._defaultImageColormap = cmap
354
355 def setImageColormap(self, cmap):
356 """Set the colormap to use for images.
357
358 Parameters
359 ----------
360 cmap : `str`
361 Name of colormap, as interpreted by the backend.
362
363 Notes
364 -----
365 The only colormaps that all backends are required to honor
366 (if they pay any attention to setImageColormap) are "gray" and "grey".
367 """
368
369 self._impl._setImageColormap(cmap)
370
371 @staticmethod
372 def getDisplay(frame=None, backend=None, create=True, verbose=False, **kwargs):
373 """Return a specific `Display`, creating it if need be.
374
375 Parameters
376 ----------
377 frame
378 The desired frame (`None` => use defaultFrame
379 (see `~Display.setDefaultFrame`)).
380 backend : `str`
381 create the specified frame using this backend (or the default if
382 `None`) if it doesn't already exist. If ``backend == ""``, it's an
383 error to specify a non-existent ``frame``.
384 create : `bool`
385 create the display if it doesn't already exist.
386 verbose : `bool`
387 Allow backend to be chatty.
388 **kwargs
389 keyword arguments passed to `Display` constructor.
390 """
391
392 if frame is None:
393 frame = Display._defaultFrame
394
395 if frame not in Display._displays:
396 if backend == "":
397 raise RuntimeError(f"Frame {frame} does not exist")
398
399 Display._displays[frame] = Display(
400 frame, backend, verbose=verbose, **kwargs)
401
402 Display._displays[frame].verbose = verbose
403 return Display._displays[frame]
404
405 @staticmethod
407 """Delete and close all known displays.
408 """
409 for disp in list(Display._displays.values()):
410 disp.close()
411 Display._displays = {}
412
413 def maskColorGenerator(self, omitBW=True):
414 """A generator for "standard" colors.
415
416 Parameters
417 ----------
418 omitBW : `bool`
419 Don't include `BLACK` and `WHITE`.
420
421 Examples
422 --------
423
424 .. code-block:: py
425
426 colorGenerator = interface.maskColorGenerator(omitBW=True)
427 for p in planeList:
428 print(p, next(colorGenerator))
429 """
430 _maskColors = [WHITE, BLACK, RED, GREEN,
431 BLUE, CYAN, MAGENTA, YELLOW, ORANGE]
432
433 i = -1
434 while True:
435 i += 1
436 color = _maskColors[i%len(_maskColors)]
437 if omitBW and color in (BLACK, WHITE):
438 continue
439
440 yield color
441
442 def setMaskPlaneColor(self, name, color=None):
443 """Request that mask plane name be displayed as color.
444
445 Parameters
446 ----------
447 name : `str` or `dict`
448 Name of mask plane or a dictionary of name -> colorName.
449 color : `str`
450 The name of the color to use (must be `None` if ``name`` is a
451 `dict`).
452
453 Colors may be specified as any X11-compliant string (e.g.
454 `"orchid"`), or by one of the following constants in
455 `lsst.afw.display` : `BLACK`, `WHITE`, `RED`, `BLUE`,
456 `GREEN`, `CYAN`, `MAGENTA`, `YELLOW`.
457
458 If the color is "ignore" (or `IGNORE`) then that mask plane is not
459 displayed.
460
461 The advantage of using the symbolic names is that the python
462 interpreter can detect typos.
463 """
464 if isinstance(name, dict):
465 assert color is None
466 for k, v in name.items():
467 self.setMaskPlaneColor(k, v)
468 return
469
470 self._maskPlaneColors[name] = color
471
472 def getMaskPlaneColor(self, name=None):
473 """Return the color associated with the specified mask plane name.
474
475 Parameters
476 ----------
477 name : `str`
478 Desired mask plane; if `None`, return entire dict.
479 """
480 if name is None:
481 return self._maskPlaneColors
482 else:
483 color = self._maskPlaneColors.get(name)
484
485 if color is None:
486 color = self._defaultMaskPlaneColor.get(name)
487
488 return color
489
490 def setMaskTransparency(self, transparency=None, name=None):
491 """Specify display's mask transparency (percent); or `None` to not set
492 it when loading masks.
493 """
494 if isinstance(transparency, dict):
495 assert name is None
496 for k, v in transparency.items():
497 self.setMaskTransparency(v, k)
498 return
499
500 if transparency is not None and (transparency < 0 or transparency > 100):
501 print(
502 "Mask transparency should be in the range [0, 100]; clipping", file=sys.stderr)
503 if transparency < 0:
504 transparency = 0
505 else:
506 transparency = 100
507
508 if transparency is not None:
509 self._impl._setMaskTransparency(transparency, name)
510
511 def getMaskTransparency(self, name=None):
512 """Return the current display's mask transparency.
513 """
514 return self._impl._getMaskTransparency(name)
515
516 def show(self):
517 """Uniconify and Raise display.
518
519 Notes
520 -----
521 Throws an exception if frame doesn't exit.
522 """
523 return self._impl._show()
524
525 def __addMissingMaskPlanes(self, mask):
526 """Assign colours to any missing mask planes found in mask.
527 """
528 maskPlanes = mask.getMaskPlaneDict()
529 nMaskPlanes = max(maskPlanes.values()) + 1
530
531 # Build inverse dictionary from mask plane index to name.
532 planes = {}
533 for key in maskPlanes:
534 planes[maskPlanes[key]] = key
535
536 colorGenerator = self.display.maskColorGenerator(omitBW=True)
537 for p in range(nMaskPlanes):
538 name = planes[p] # ordered by plane index
539 if name not in self._defaultMaskPlaneColor:
540 self.setDefaultMaskPlaneColor(name, next(colorGenerator))
541
542 def image(self, data, title="", wcs=None, metadata=None):
543 """Display an image on a display, with semi-transparent masks
544 overlaid, if available.
545
546 Parameters
547 ----------
548 data : `lsst.afw.image.Exposure` or `lsst.afw.image.MaskedImage` or `lsst.afw.image.Image`
549 Image to display; Exposure and MaskedImage will show transparent
550 mask planes. `lsst.images` objects (`~lsst.images.MaskedImage`
551 and its subclasses such as `~lsst.images.VisitImage` and
552 `~lsst.images.cells.CellCoadd`, `~lsst.images.Image`, and
553 `~lsst.images.Mask`) are converted to the equivalent afw types,
554 using any attached ``sky_projection`` as the WCS.
555 title : `str`, optional
556 Title for the display window.
557 wcs : `lsst.afw.geom.SkyWcs`, optional
558 World Coordinate System to align an `~lsst.afw.image.MaskedImage`
559 or `~lsst.afw.image.Image` to; raise an exception if ``data``
560 is an `~lsst.afw.image.Exposure`.
561 metadata : `lsst.daf.base.PropertySet`, optional
562 Additional FITS metadata to be sent to display.
563
564 Raises
565 ------
566 RuntimeError
567 Raised if an Exposure, or an ``lsst.images`` object with its own
568 ``sky_projection``, is passed when the ``wcs`` kwarg is also
569 non-None.
570 TypeError
571 Raised if data is an incompatible type.
572 """
573 data, wcs = normalize_lsst_images(data, wcs)
574
575 if hasattr(data, "getXY0"):
576 self._xy0 = data.getXY0()
577 else:
578 self._xy0 = None
579
580 # It's an Exposure; display the MaskedImage with the WCS
581 if isinstance(data, afwImage.Exposure):
582 if wcs:
583 raise RuntimeError("You may not specify a wcs with an Exposure")
584 data, wcs, metadata = data.getMaskedImage(), data.wcs, data.metadata
585 # it's a DecoratedImage; display it
586 elif isinstance(data, afwImage.DecoratedImage):
587 try:
588 wcs = afwGeom.makeSkyWcs(data.getMetadata())
589 except TypeError:
590 wcs = None
591 data = data.image
592
593 self._xy0 = data.getXY0() # DecoratedImage doesn't have getXY0()
594
595 if isinstance(data, afwImage.Image): # it's an Image; display it
596 self._impl._mtv(data, None, wcs, title, metadata)
597 # It's a Mask; display it, bitplane by bitplane.
598 elif isinstance(data, afwImage.Mask):
599 self.__addMissingMaskPlanes(data)
600 # Some displays can't display a Mask without an image; so display
601 # an Image too, with pixel values set to the mask.
602 self._impl._mtv(afwImage.ImageI(data.array), data, wcs, title, metadata)
603 # It's a MaskedImage; display Image and overlay Mask.
604 elif isinstance(data, afwImage.MaskedImage):
605 self.__addMissingMaskPlanes(data.mask)
606 self._impl._mtv(data.image, data.mask, wcs, title, metadata)
607 else:
608 raise TypeError(f"Unsupported type {data!r}")
609
610 def mtv(self, data, title="", wcs=None, metadata=None):
611 """Display an image on a display, with semi-transparent masks
612 overlaid, if available.
613
614 Notes
615 -----
616 Historical note: the name "mtv" comes from Jim Gunn's forth imageprocessing
617 system, Mirella (named after Mirella Freni); The "m" stands for Mirella.
618 """
619 self.image(data, title, wcs, metadata)
620
622 """Context manager for buffering repeated display commands.
623 """
624 def __init__(self, _impl):
625 self._impl = _impl
626
627 def __enter__(self):
628 self._impl._buffer(True)
629
630 def __exit__(self, *args):
631 self._impl._buffer(False)
632 self._impl._flush()
633
634 def Buffering(self):
635 """Return a context manager that will buffer repeated display
636 commands, to e.g. speed up displaying points.
637
638 Examples
639 --------
640 .. code-block:: py
641
642 with display.Buffering():
643 display.dot("+", xc, yc)
644 """
645 return self._Buffering(self._impl)
646
647 def flush(self):
648 """Flush any buffering that may be provided by the backend.
649 """
650 self._impl._flush()
651
652 def erase(self):
653 """Erase the specified display frame.
654 """
655 self._impl._erase()
656
657 def centroids(self, catalog, *, symbol="o", **kwargs):
658 """Draw the sources from a catalog at their pixel centroid positions
659 as given by `~lsst.afw.table.Catalog.getX()` and
660 `~lsst.afw.table.Catalog.getY()`.
661
662 See `dot` for an explanation of ``symbol`` and available args/kwargs,
663 which are passed to `dot`.
664
665 Parameters
666 ----------
667 catalog : `lsst.afw.table.Catalog`
668 Catalog to display centroids for. Must have valid `slot_Centroid`.
669 """
670 if not catalog.getCentroidSlot().isValid():
671 raise RuntimeError("Catalog must have a valid `slot_Centroid` defined to get X/Y positions.")
672
673 with self.Buffering():
674 for pt in catalog:
675 self.dot(symbol, pt.getX(), pt.getY(), **kwargs)
676
677 def dot(self, symb, c, r, size=2, ctype=None, origin=afwImage.PARENT, **kwargs):
678 """Draw a symbol onto the specified display frame.
679
680 Parameters
681 ----------
682 symb
683 Possible values are:
684
685 ``"+"``
686 Draw a +
687 ``"x"``
688 Draw an x
689 ``"*"``
690 Draw a *
691 ``"o"``
692 Draw a circle
693 ``"@:Mxx,Mxy,Myy"``
694 Draw an ellipse with moments (Mxx, Mxy, Myy) (argument size is ignored)
695 `lsst.afw.geom.ellipses.BaseCore`
696 Draw the ellipse (argument size is ignored). N.b. objects
697 derived from `~lsst.afw.geom.ellipses.BaseCore` include
698 `~lsst.afw.geom.ellipses.Axes` and `~lsst.afw.geom.ellipses.Quadrupole`.
699 Any other value
700 Interpreted as a string to be drawn.
701 c, r : `float`
702 The column and row where the symbol is drawn [0-based coordinates].
703 size : `int`
704 Size of symbol, in pixels.
705 ctype : `str`
706 The desired color, either e.g. `lsst.afw.display.RED` or a color name known to X11
707 origin : `lsst.afw.image.ImageOrigin`
708 Coordinate system for the given positions.
709 **kwargs
710 Extra keyword arguments to backend.
711 """
712 if isinstance(symb, int):
713 symb = f"{symb:d}"
714
715 if origin == afwImage.PARENT and self._xy0 is not None:
716 x0, y0 = self._xy0
717 r -= y0
718 c -= x0
719
720 if isinstance(symb, afwGeom.ellipses.BaseCore) or re.search(r"^@:", symb):
721 try:
722 mat = re.search(r"^@:([^,]+),([^,]+),([^,]+)", symb)
723 except TypeError:
724 pass
725 else:
726 if mat:
727 mxx, mxy, myy = [float(_) for _ in mat.groups()]
728 symb = afwGeom.Quadrupole(mxx, myy, mxy)
729
730 symb = afwGeom.ellipses.Axes(symb)
731
732 self._impl._dot(symb, c, r, size, ctype, **kwargs)
733
734 def line(self, points, origin=afwImage.PARENT, symbs=False, ctype=None, size=0.5):
735 """Draw a set of symbols or connect points
736
737 Parameters
738 ----------
739 points : `list`
740 A list of (col, row)
741 origin : `lsst.afw.image.ImageOrigin`
742 Coordinate system for the given positions.
743 symbs : `bool` or sequence
744 If ``symbs`` is `True`, draw points at the specified points using
745 the desired symbol, otherwise connect the dots.
746
747 If ``symbs`` supports indexing (which includes a string -- caveat
748 emptor) the elements are used to label the points.
749 ctype : `str`
750 ``ctype`` is the name of a color (e.g. 'red').
751 size : `float`
752 Size of points to create if `symbs` is passed.
753 """
754 if symbs:
755 try:
756 symbs[1]
757 except TypeError:
758 symbs = len(points)*list(symbs)
759
760 for i, xy in enumerate(points):
761 self.dot(symbs[i], *xy, size=size, ctype=ctype)
762 else:
763 if len(points) > 0:
764 if origin == afwImage.PARENT and self._xy0 is not None:
765 x0, y0 = self._xy0
766 _points = list(points) # make a mutable copy
767 for i, p in enumerate(points):
768 _points[i] = (p[0] - x0, p[1] - y0)
769 points = _points
770
771 self._impl._drawLines(points, ctype)
772
773 def scale(self, algorithm, min, max=None, unit=None, **kwargs):
774 """Set the range of the scaling from DN in the image to the image
775 display.
776
777 Parameters
778 ----------
779 algorithm : `str`
780 Desired scaling (e.g. "linear" or "asinh").
781 min
782 Minimum value, or "minmax" or "zscale".
783 max
784 Maximum value (must be `None` for minmax|zscale).
785 unit
786 Units for min and max (e.g. Percent, Absolute, Sigma; `None` if
787 min==minmax|zscale).
788 **kwargs
789 Optional keyword arguments to the backend.
790 """
791 if min in ("minmax", "zscale"):
792 assert max is None, f"You may not specify \"{min}\" and max"
793 assert unit is None, f"You may not specify \"{min}\" and unit"
794 elif max is None:
795 raise RuntimeError("Please specify max")
796
797 self._impl._scale(algorithm, min, max, unit, **kwargs)
798
799 def zoom(self, zoomfac=None, colc=None, rowc=None, origin=afwImage.PARENT):
800 """Zoom frame by specified amount, optionally panning also
801 """
802 if (rowc and colc is None) or (colc and rowc is None):
803 raise RuntimeError(
804 "Please specify row and column center to pan about")
805
806 if rowc is not None:
807 if origin == afwImage.PARENT and self._xy0 is not None:
808 x0, y0 = self._xy0
809 colc -= x0
810 rowc -= y0
811
812 self._impl._pan(colc, rowc)
813
814 if zoomfac is None and rowc is None:
815 zoomfac = 2
816
817 if zoomfac is not None:
818 self._impl._zoom(zoomfac)
819
820 def pan(self, colc=None, rowc=None, origin=afwImage.PARENT):
821 """Pan to a location.
822
823 Parameters
824 ----------
825 colc, rowc
826 Coordinates to pan to.
827 origin : `lsst.afw.image.ImageOrigin`
828 Coordinate system for the given positions.
829
830 See also
831 --------
832 Display.zoom
833 """
834 self.zoom(None, colc, rowc, origin)
835
836 def interact(self):
837 """Enter an interactive loop, listening for key presses or equivalent
838 UI actions in the display and firing callbacks.
839
840 Exit with ``q``, ``CR``, ``ESC``, or any equivalent UI action provided
841 in the display. The loop may also be exited by returning `True` from a
842 user-provided callback function.
843 """
844 interactFinished = False
845
846 while not interactFinished:
847 ev = self._impl._getEvent()
848 if not ev:
849 continue
850 k, x, y = ev.k, ev.x, ev.y # for now
851
852 if k not in self._callbacks:
853 logger.warning("No callback registered for %s", k)
854 else:
855 try:
856 interactFinished = self._callbacks[k](k, x, y)
857 except Exception:
858 logger.exception(
859 "Display._callbacks['%s'](%s,%s,%s) failed.", k, x, y)
860
861 def alignImages(self, match_type="", lock_match=True):
862 """Align and optionally lock the orientation of the images being
863 displayed.
864
865 Parameters
866 ----------
867 match_type : `str`, optional
868 Match type to use to align the images; see the interface-specific
869 docs for what values to use.
870 lock_match : `bool`, optional
871 Whether to lock the alignment. Panning/zooming in one image will
872 preserve the alignment in other images.
873 """
874 try:
875 self._impl._alignImages(match_type, lock_match)
876 except AttributeError:
877 raise NotImplementedError("This feature only available on some backends.")
878
879 def setCallback(self, k, func=None, noRaise=False):
880 """Set the callback for a key.
881
882 Backend displays may provide an equivalent graphical UI action, but
883 must make the associated key letter visible in the UI in some way.
884
885 Parameters
886 ----------
887 k : `str`
888 The key to assign the callback to.
889 func : callable
890 The callback assigned to ``k``.
891 noRaise : `bool`
892 Do not raise if ``k`` is already in use.
893
894 Returns
895 -------
896 oldFunc : callable
897 The callback previously assigned to ``k``.
898 """
899
900 if k in "f":
901 if noRaise:
902 return
903 raise RuntimeError(
904 f"Key '{k}' is already in use by display, so I can't add a callback for it")
905
906 ofunc = self._callbacks.get(k)
907 self._callbacks[k] = func if func else noop_callback
908
909 self._impl._setCallback(k, self._callbacks[k])
910
911 return ofunc
912
913 def getActiveCallbackKeys(self, onlyActive=True):
914 """Return all callback keys
915
916 Parameters
917 ----------
918 onlyActive : `bool`
919 If `True` only return keys that do something
920 """
921 return sorted([k for k, func in self._callbacks.items() if
922 not (onlyActive and func == noop_callback)])
923
924
925# Callbacks for display events
926
927
928class Event:
929 """A class to handle events such as key presses in image display windows.
930 """
931
932 def __init__(self, k, x=float('nan'), y=float('nan')):
933 self.k = k
934 self.x = x
935 self.y = y
936
937 def __str__(self):
938 return f"{self.k} ({self.x:.2f}, {self.y:.2f}"
939
940
941# Default fallback function
942
943
944def noop_callback(k, x, y):
945 """Callback function
946
947 Parameters
948 ----------
949 key
950 x
951 y
952 """
953 return False
954
955
956def h_callback(k, x, y):
957 print("Enter q or <ESC> to leave interactive mode, h for this help, or a letter to fire a callback")
958 return False
959
960# Handle Displays, including the default one (the frame to use when a user specifies None)
961# If the default frame is None, image display is disabled
962
963
964def setDefaultBackend(backend):
965 Display.setDefaultBackend(backend)
966
967
969 return Display.getDefaultBackend()
970
971
972def setDefaultFrame(frame=0):
973 return Display.setDefaultFrame(frame)
974
975
977 """Get the default frame for display.
978 """
979 return Display.getDefaultFrame()
980
981
983 """Increment the default frame for display.
984 """
985 return Display.incrDefaultFrame()
986
987
988def setDefaultMaskTransparency(maskPlaneTransparency={}):
989 return Display.setDefaultMaskTransparency(maskPlaneTransparency)
990
991
992def setDefaultMaskPlaneColor(name=None, color=None):
993 """Set the default mapping from mask plane names to colors.
994
995 Parameters
996 ----------
997 name : `str` or `dict`
998 Name of mask plane, or a dict mapping names to colors.
999 If ``name`` is `None`, use the hard-coded default dictionary.
1000 color : `str`
1001 Desired color, or `None` if ``name`` is a dict.
1002 """
1003
1004 return Display.setDefaultMaskPlaneColor(name, color)
1005
1006
1007def getDisplay(frame=None, backend=None, create=True, verbose=False, **kwargs):
1008 """Return a specific `Display`, creating it if need be.
1009
1010 Parameters
1011 ----------
1012 frame
1013 Desired frame (`None` => use defaultFrame (see `setDefaultFrame`)).
1014 backend : `str`
1015 Create the specified frame using this backend (or the default if
1016 `None`) if it doesn't already exist. If ``backend == ""``, it's an
1017 error to specify a non-existent ``frame``.
1018 create : `bool`
1019 Create the display if it doesn't already exist.
1020 verbose : `bool`
1021 Allow backend to be chatty.
1022 **kwargs
1023 Keyword arguments passed to `Display` constructor.
1024
1025 See also
1026 --------
1027 Display.getDisplay
1028 """
1029
1030 return Display.getDisplay(frame, backend, create, verbose, **kwargs)
1031
1032
1034 """Delete and close all known displays.
1035 """
1036 return Display.delAllDisplays()
image(self, data, title="", wcs=None, metadata=None)
Definition interface.py:542
alignImages(self, match_type="", lock_match=True)
Definition interface.py:861
setMaskTransparency(self, transparency=None, name=None)
Definition interface.py:490
getDisplay(frame=None, backend=None, create=True, verbose=False, **kwargs)
Definition interface.py:372
getActiveCallbackKeys(self, onlyActive=True)
Definition interface.py:913
dot(self, symb, c, r, size=2, ctype=None, origin=afwImage.PARENT, **kwargs)
Definition interface.py:677
line(self, points, origin=afwImage.PARENT, symbs=False, ctype=None, size=0.5)
Definition interface.py:734
scale(self, algorithm, min, max=None, unit=None, **kwargs)
Definition interface.py:773
__init__(self, frame=None, backend=None, **kwargs)
Definition interface.py:162
setDefaultMaskTransparency(maskPlaneTransparency={})
Definition interface.py:308
pan(self, colc=None, rowc=None, origin=afwImage.PARENT)
Definition interface.py:820
centroids(self, catalog, *, symbol="o", **kwargs)
Definition interface.py:657
setCallback(self, k, func=None, noRaise=False)
Definition interface.py:879
setMaskPlaneColor(self, name, color=None)
Definition interface.py:442
setDefaultMaskPlaneColor(name=None, color=None)
Definition interface.py:315
maskColorGenerator(self, omitBW=True)
Definition interface.py:413
mtv(self, data, title="", wcs=None, metadata=None)
Definition interface.py:610
zoom(self, zoomfac=None, colc=None, rowc=None, origin=afwImage.PARENT)
Definition interface.py:799
__init__(self, k, x=float('nan'), y=float('nan'))
Definition interface.py:932
A container for an Image and its associated metadata.
Definition Image.h:406
A class to contain the data, WCS, and other information needed to describe an image of the sky.
Definition Exposure.h:72
A class to represent a 2-dimensional array of pixels.
Definition Image.h:51
Represent a 2-dimensional array of bitmask pixels.
Definition Mask.h:82
A class to manipulate images, masks, and variance as a single object.
Definition MaskedImage.h:74
setDefaultMaskTransparency(maskPlaneTransparency={})
Definition interface.py:988
getDisplay(frame=None, backend=None, create=True, verbose=False, **kwargs)
setDefaultMaskPlaneColor(name=None, color=None)
Definition interface.py:992
_makeDisplayImpl(display, backend, *args, **kwargs)
Definition interface.py:55