Coverage for python/lsst/afw/display/interface.py: 27%
350 statements
« prev ^ index » next coverage.py v6.4.4, created at 2022-08-19 12:16 -0700
« prev ^ index » next coverage.py v6.4.4, created at 2022-08-19 12:16 -0700
2# This file is part of afw.
3#
4# Developed for the LSST Data Management System.
5# This product includes software developed by the LSST Project
6# (https://www.lsst.org).
7# See the COPYRIGHT file at the top-level directory of this distribution
8# for details of code ownership.
9#
10# This program is free software: you can redistribute it and/or modify
11# it under the terms of the GNU General Public License as published by
12# the Free Software Foundation, either version 3 of the License, or
13# (at your option) any later version.
14#
15# This program is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18# GNU General Public License for more details.
19#
20# You should have received a copy of the GNU General Public License
21# along with this program. If not, see <https://www.gnu.org/licenses/>.
23__all__ = [
24 "WHITE", "BLACK", "RED", "GREEN", "BLUE", "CYAN", "MAGENTA", "YELLOW", "ORANGE", "IGNORE",
25 "Display", "Event", "noop_callback", "h_callback",
26 "setDefaultBackend", "getDefaultBackend",
27 "setDefaultFrame", "getDefaultFrame", "incrDefaultFrame",
28 "setDefaultMaskTransparency", "setDefaultMaskPlaneColor",
29 "getDisplay", "delAllDisplays",
30]
32import re
33import sys
34import importlib
35import lsst.afw.geom as afwGeom
36import lsst.afw.image as afwImage
37import lsst.log
39logger = lsst.log.Log.getLogger(__name__)
41#
42# Symbolic names for mask/line colors. N.b. ds9 supports any X11 color for masks
43#
44WHITE = "white"
45BLACK = "black"
46RED = "red"
47GREEN = "green"
48BLUE = "blue"
49CYAN = "cyan"
50MAGENTA = "magenta"
51YELLOW = "yellow"
52ORANGE = "orange"
53IGNORE = "ignore"
56def _makeDisplayImpl(display, backend, *args, **kwargs):
57 """Return the ``DisplayImpl`` for the named backend
59 Parameters
60 ----------
61 display : `str`
62 Name of device. Should be importable, either absolutely or relative to lsst.display
63 backend : `str`
64 The desired backend
65 *args
66 Arguments passed to DisplayImpl.__init__
67 *kwargs
68 Keywords arguments passed to DisplayImpl.__init__
70 Examples
71 --------
72 E.g.
73 .. code-block:: py
75 import lsst.afw.display as afwDisplay
76 display = afwDisplay.Display(display=1, backend="ds9")
77 would call
78 .. code-block:: py
80 _makeDisplayImpl(..., "ds9", 1)
81 and import the ds9 implementation of ``DisplayImpl`` from `lsst.display.ds9`
82 """
83 _disp = None
84 exc = None
85 for dt in (f"lsst.display.{backend}", backend, f".{backend}", f"lsst.afw.display.{backend}"):
86 exc = None
87 # only specify the root package if we are not doing an absolute import
88 impargs = {}
89 if dt.startswith("."):
90 impargs["package"] = "lsst.display"
91 try:
92 _disp = importlib.import_module(dt, **impargs)
93 break
94 except (ImportError, SystemError) as e:
95 # SystemError can be raised in Python 3.5 if a relative import
96 # is attempted when the root package, lsst.display, does not exist.
97 # Copy the exception into outer scope
98 exc = e
100 if not _disp or not hasattr(_disp.DisplayImpl, "_show"): 100 ↛ 108line 100 didn't jump to line 108, because the condition on line 100 was never false
101 if exc is not None: 101 ↛ 105line 101 didn't jump to line 105, because the condition on line 101 was never false
102 # re-raise the final exception
103 raise exc
104 else:
105 raise ImportError(
106 "Could not load the requested backend: {}".format(backend))
108 if display:
109 _impl = _disp.DisplayImpl(display, *args, **kwargs)
110 if not hasattr(_impl, "frame"):
111 _impl.frame = display.frame
113 return _impl
114 else:
115 return True
118class Display:
119 """Create an object able to display images and overplot glyphs
121 Parameters
122 ----------
123 frame
124 An identifier for the display
125 backend : `str`
126 The backend to use (defaults to value set by setDefaultBackend())
127 *args
128 Arguments to pass to the backend
129 **kwargs
130 Arguments to pass to the backend
131 """
132 _displays = {}
133 _defaultBackend = None
134 _defaultFrame = 0
135 _defaultMaskPlaneColor = dict(
136 BAD=RED,
137 CR=MAGENTA,
138 EDGE=YELLOW,
139 INTERPOLATED=GREEN,
140 SATURATED=GREEN,
141 DETECTED=BLUE,
142 DETECTED_NEGATIVE=CYAN,
143 SUSPECT=YELLOW,
144 NO_DATA=ORANGE,
145 # deprecated names
146 INTRP=GREEN,
147 SAT=GREEN,
148 )
149 _defaultMaskTransparency = {}
150 _defaultImageColormap = "gray"
152 def __init__(self, frame=None, backend=None, *args, **kwargs):
153 if frame is None:
154 frame = getDefaultFrame()
156 if backend is None:
157 if Display._defaultBackend is None:
158 try:
159 setDefaultBackend("ds9")
160 except RuntimeError:
161 setDefaultBackend("virtualDevice")
163 backend = Display._defaultBackend
165 self.frame = frame
166 self._impl = _makeDisplayImpl(self, backend, *args, **kwargs)
167 self.name = backend
169 self._xy0 = None # displayed data's XY0
170 self.setMaskTransparency(Display._defaultMaskTransparency)
171 self._maskPlaneColors = {}
172 self.setMaskPlaneColor(Display._defaultMaskPlaneColor)
173 self.setImageColormap(Display._defaultImageColormap)
175 self._callbacks = {}
177 for ik in range(ord('a'), ord('z') + 1):
178 k = f"{ik:c}"
179 self.setCallback(k, noRaise=True)
180 self.setCallback(k.upper(), noRaise=True)
182 for k in ('Return', 'Shift_L', 'Shift_R'):
183 self.setCallback(k)
185 for k in ('q', 'Escape'):
186 self.setCallback(k, lambda k, x, y: True)
188 def _h_callback(k, x, y):
189 h_callback(k, x, y)
191 for k in sorted(self._callbacks.keys()):
192 doc = self._callbacks[k].__doc__
193 print(" %-6s %s" % (k, doc.split("\n")[0] if doc else "???"))
195 self.setCallback('h', _h_callback)
197 Display._displays[frame] = self
199 def __enter__(self):
200 """Support for python's with statement
201 """
202 return self
204 def __exit__(self, *args):
205 """Support for python's with statement
206 """
207 self.close()
209 def __del__(self):
210 self.close()
212 def __getattr__(self, name):
213 """Return the attribute of ``self._impl``, or ``._impl`` if it is requested
215 Parameters:
216 -----------
217 name : `str`
218 name of the attribute requested
220 Returns:
221 --------
222 attribute : `object`
223 the attribute of self._impl for the requested name
224 """
226 if name == '_impl':
227 return object.__getattr__(self, name)
229 if not (hasattr(self, "_impl") and self._impl):
230 raise AttributeError("Device has no _impl attached")
232 try:
233 return getattr(self._impl, name)
234 except AttributeError:
235 raise AttributeError(
236 f"Device {self.name} has no attribute \"{name}\"")
238 def close(self):
239 if getattr(self, "_impl", None) is not None:
240 self._impl._close()
241 del self._impl
242 self._impl = None
244 if self.frame in Display._displays:
245 del Display._displays[self.frame]
247 @property
248 def verbose(self):
249 """The backend's verbosity
250 """
251 return self._impl.verbose
253 @verbose.setter
254 def verbose(self, value):
255 if self._impl:
256 self._impl.verbose = value
258 def __str__(self):
259 return f"Display[{self.frame}]"
261 #
262 # Handle Displays, including the default one (the frame to use when a user specifies None)
263 #
264 @staticmethod
265 def setDefaultBackend(backend):
266 try:
267 _makeDisplayImpl(None, backend)
268 except Exception as e:
269 raise RuntimeError(
270 f"Unable to set backend to {backend}: \"{e}\"")
272 Display._defaultBackend = backend
274 @staticmethod
275 def getDefaultBackend():
276 return Display._defaultBackend
278 @staticmethod
279 def setDefaultFrame(frame=0):
280 """Set the default frame for display
281 """
282 Display._defaultFrame = frame
284 @staticmethod
285 def getDefaultFrame():
286 """Get the default frame for display
287 """
288 return Display._defaultFrame
290 @staticmethod
291 def incrDefaultFrame():
292 """Increment the default frame for display
293 """
294 Display._defaultFrame += 1
295 return Display._defaultFrame
297 @staticmethod
298 def setDefaultMaskTransparency(maskPlaneTransparency={}):
299 if hasattr(maskPlaneTransparency, "copy"): 299 ↛ 300line 299 didn't jump to line 300, because the condition on line 299 was never true
300 maskPlaneTransparency = maskPlaneTransparency.copy()
302 Display._defaultMaskTransparency = maskPlaneTransparency
304 @staticmethod
305 def setDefaultMaskPlaneColor(name=None, color=None):
306 """Set the default mapping from mask plane names to colors
308 Parameters
309 ----------
310 name : `str` or `dict`
311 name of mask plane, or a dict mapping names to colors
312 If name is `None`, use the hard-coded default dictionary
313 color
314 Desired color, or `None` if name is a dict
315 """
317 if name is None:
318 name = Display._defaultMaskPlaneColor
320 if isinstance(name, dict):
321 assert color is None
322 for k, v in name.items():
323 setDefaultMaskPlaneColor(k, v)
324 return
325 #
326 # Set the individual color values
327 #
328 Display._defaultMaskPlaneColor[name] = color
330 @staticmethod
331 def setDefaultImageColormap(cmap):
332 """Set the default colormap for images
334 Parameters
335 ----------
336 cmap : `str`
337 Name of colormap, as interpreted by the backend
339 Notes
340 -----
341 The only colormaps that all backends are required to honor
342 (if they pay any attention to setImageColormap) are "gray" and "grey"
343 """
345 Display._defaultImageColormap = cmap
347 def setImageColormap(self, cmap):
348 """Set the colormap to use for images
350 Parameters
351 ----------
352 cmap : `str`
353 Name of colormap, as interpreted by the backend
355 Notes
356 -----
357 The only colormaps that all backends are required to honor
358 (if they pay any attention to setImageColormap) are "gray" and "grey"
359 """
361 self._impl._setImageColormap(cmap)
363 @staticmethod
364 def getDisplay(frame=None, backend=None, create=True, verbose=False, *args, **kwargs):
365 """Return a specific `Display`, creating it if need be
367 Parameters
368 ----------
369 frame
370 The desired frame (`None` => use defaultFrame (see `~Display.setDefaultFrame`))
371 backend : `str`
372 create the specified frame using this backend (or the default if
373 `None`) if it doesn't already exist. If ``backend == ""``, it's an
374 error to specify a non-existent ``frame``.
375 create : `bool`
376 create the display if it doesn't already exist.
377 verbose : `bool`
378 Allow backend to be chatty
379 *args
380 arguments passed to `Display` constructor
381 **kwargs
382 keyword arguments passed to `Display` constructor
383 """
385 if frame is None:
386 frame = Display._defaultFrame
388 if frame not in Display._displays:
389 if backend == "":
390 raise RuntimeError(f"Frame {frame} does not exist")
392 Display._displays[frame] = Display(
393 frame, backend, verbose=verbose, *args, **kwargs)
395 Display._displays[frame].verbose = verbose
396 return Display._displays[frame]
398 @staticmethod
399 def delAllDisplays():
400 """Delete and close all known displays
401 """
402 for disp in list(Display._displays.values()):
403 disp.close()
404 Display._displays = {}
406 def maskColorGenerator(self, omitBW=True):
407 """A generator for "standard" colors
409 Parameters
410 ----------
411 omitBW : `bool`
412 Don't include `BLACK` and `WHITE`
414 Examples
415 --------
417 .. code-block:: py
419 colorGenerator = interface.maskColorGenerator(omitBW=True)
420 for p in planeList:
421 print p, next(colorGenerator)
422 """
423 _maskColors = [WHITE, BLACK, RED, GREEN,
424 BLUE, CYAN, MAGENTA, YELLOW, ORANGE]
426 i = -1
427 while True:
428 i += 1
429 color = _maskColors[i%len(_maskColors)]
430 if omitBW and color in (BLACK, WHITE):
431 continue
433 yield color
435 def setMaskPlaneColor(self, name, color=None):
436 """Request that mask plane name be displayed as color
438 Parameters
439 ----------
440 name : `str` or `dict`
441 Name of mask plane or a dictionary of name -> colorName
442 color : `str`
443 The name of the color to use (must be `None` if ``name`` is a `dict`)
445 Colors may be specified as any X11-compliant string (e.g. `"orchid"`), or by one
446 of the following constants in `lsst.afw.display` : `BLACK`, `WHITE`, `RED`, `BLUE`,
447 `GREEN`, `CYAN`, `MAGENTA`, `YELLOW`.
449 If the color is "ignore" (or `IGNORE`) then that mask plane is not displayed
451 The advantage of using the symbolic names is that the python interpreter can detect typos.
452 """
454 if isinstance(name, dict):
455 assert color is None
456 for k, v in name.items():
457 self.setMaskPlaneColor(k, v)
458 return
460 self._maskPlaneColors[name] = color
462 def getMaskPlaneColor(self, name=None):
463 """Return the color associated with the specified mask plane name
465 Parameters
466 ----------
467 name : `str`
468 Desired mask plane; if `None`, return entire dict
469 """
471 if name is None:
472 return self._maskPlaneColors
473 else:
474 return self._maskPlaneColors.get(name)
476 def setMaskTransparency(self, transparency=None, name=None):
477 """Specify display's mask transparency (percent); or `None` to not set it when loading masks
478 """
480 if isinstance(transparency, dict):
481 assert name is None
482 for k, v in transparency.items():
483 self.setMaskTransparency(v, k)
484 return
486 if transparency is not None and (transparency < 0 or transparency > 100):
487 print(
488 "Mask transparency should be in the range [0, 100]; clipping", file=sys.stderr)
489 if transparency < 0:
490 transparency = 0
491 else:
492 transparency = 100
494 if transparency is not None:
495 self._impl._setMaskTransparency(transparency, name)
497 def getMaskTransparency(self, name=None):
498 """Return the current display's mask transparency
499 """
501 return self._impl._getMaskTransparency(name)
503 def show(self):
504 """Uniconify and Raise display.
506 Notes
507 -----
508 Throws an exception if frame doesn't exit
509 """
510 return self._impl._show()
512 def mtv(self, data, title="", wcs=None):
513 """Display an `~lsst.afw.image.Image` or `~lsst.afw.image.Mask` on a display
515 Notes
516 -----
517 Historical note: the name "mtv" comes from Jim Gunn's forth imageprocessing
518 system, Mirella (named after Mirella Freni); The "m" stands for Mirella.
519 """
520 if hasattr(data, "getXY0"):
521 self._xy0 = data.getXY0()
522 else:
523 self._xy0 = None
525 # it's an Exposure; display the MaskedImage with the WCS
526 if isinstance(data, afwImage.Exposure):
527 if wcs:
528 raise RuntimeError(
529 "You may not specify a wcs with an Exposure")
530 data, wcs = data.getMaskedImage(), data.getWcs()
531 elif isinstance(data, afwImage.DecoratedImage): # it's a DecoratedImage; display it
532 try:
533 wcs = afwGeom.makeSkyWcs(data.getMetadata())
534 except TypeError:
535 wcs = None
536 data = data.image
538 self._xy0 = data.getXY0() # DecoratedImage doesn't have getXY0()
540 if isinstance(data, afwImage.Image): # it's an Image; display it
541 self._impl._mtv(data, None, wcs, title)
542 # it's a Mask; display it, bitplane by bitplane
543 elif isinstance(data, afwImage.Mask):
544 #
545 # Some displays can't display a Mask without an image; so display an Image too,
546 # with pixel values set to the mask
547 #
548 self._impl._mtv(afwImage.ImageI(data.getArray()), data, wcs, title)
549 # it's a MaskedImage; display Image and overlay Mask
550 elif isinstance(data, afwImage.MaskedImage):
551 self._impl._mtv(data.getImage(), data.getMask(), wcs, title)
552 else:
553 raise RuntimeError(f"Unsupported type {data!r}")
554 #
555 # Graphics commands
556 #
558 class _Buffering:
559 """A class intended to be used with python's with statement
560 """
562 def __init__(self, _impl):
563 self._impl = _impl
565 def __enter__(self):
566 self._impl._buffer(True)
568 def __exit__(self, *args):
569 self._impl._buffer(False)
570 self._impl._flush()
572 def Buffering(self):
573 """Return a class intended to be used with python's with statement
575 Examples
576 --------
577 .. code-block:: py
579 with display.Buffering():
580 display.dot("+", xc, yc)
581 """
582 return self._Buffering(self._impl)
584 def flush(self):
585 """Flush the buffers
586 """
587 self._impl._flush()
589 def erase(self):
590 """Erase the specified display frame
591 """
592 self._impl._erase()
594 def dot(self, symb, c, r, size=2, ctype=None, origin=afwImage.PARENT, *args, **kwargs):
595 """Draw a symbol onto the specified display frame
597 Parameters
598 ----------
599 symb
600 Possible values are:
602 ``"+"``
603 Draw a +
604 ``"x"``
605 Draw an x
606 ``"*"``
607 Draw a *
608 ``"o"``
609 Draw a circle
610 ``"@:Mxx,Mxy,Myy"``
611 Draw an ellipse with moments (Mxx, Mxy, Myy) (argument size is ignored)
612 `lsst.afw.geom.ellipses.BaseCore`
613 Draw the ellipse (argument size is ignored). N.b. objects
614 derived from `~lsst.afw.geom.ellipses.BaseCore` include
615 `~lsst.afw.geom.ellipses.Axes` and `~lsst.afw.geom.ellipses.Quadrupole`.
616 Any other value
617 Interpreted as a string to be drawn.
618 c, r
619 The column and row where the symbol is drawn [0-based coordinates]
620 size : `int`
621 Size of symbol, in pixels
622 ctype : `str`
623 The desired color, either e.g. `lsst.afw.display.RED` or a color name known to X11
624 origin : `lsst.afw.image.ImageOrigin`
625 Coordinate system for the given positions.
626 *args
627 Extra arguments to backend
628 **kwargs
629 Extra keyword arguments to backend
630 """
631 if isinstance(symb, int):
632 symb = f"{symb:d}"
634 if origin == afwImage.PARENT and self._xy0 is not None:
635 x0, y0 = self._xy0
636 r -= y0
637 c -= x0
639 if isinstance(symb, afwGeom.ellipses.BaseCore) or re.search(r"^@:", symb):
640 try:
641 mat = re.search(r"^@:([^,]+),([^,]+),([^,]+)", symb)
642 except TypeError:
643 pass
644 else:
645 if mat:
646 mxx, mxy, myy = [float(_) for _ in mat.groups()]
647 symb = afwGeom.Quadrupole(mxx, myy, mxy)
649 symb = afwGeom.ellipses.Axes(symb)
651 self._impl._dot(symb, c, r, size, ctype, **kwargs)
653 def line(self, points, origin=afwImage.PARENT, symbs=False, ctype=None, size=0.5):
654 """Draw a set of symbols or connect points
656 Parameters
657 ----------
658 points : `list`
659 a list of (col, row)
660 origin : `lsst.afw.image.ImageOrigin`
661 Coordinate system for the given positions.
662 symbs : `bool` or sequence
663 If ``symbs`` is `True`, draw points at the specified points using the desired symbol,
664 otherwise connect the dots.
666 If ``symbs`` supports indexing (which includes a string -- caveat emptor) the
667 elements are used to label the points
668 ctype : `str`
669 ``ctype`` is the name of a color (e.g. 'red')
670 size : `float`
671 """
672 if symbs:
673 try:
674 symbs[1]
675 except TypeError:
676 symbs = len(points)*list(symbs)
678 for i, xy in enumerate(points):
679 self.dot(symbs[i], *xy, size=size, ctype=ctype)
680 else:
681 if len(points) > 0:
682 if origin == afwImage.PARENT and self._xy0 is not None:
683 x0, y0 = self._xy0
684 _points = list(points) # make a mutable copy
685 for i, p in enumerate(points):
686 _points[i] = (p[0] - x0, p[1] - y0)
687 points = _points
689 self._impl._drawLines(points, ctype)
690 #
691 # Set gray scale
692 #
694 def scale(self, algorithm, min, max=None, unit=None, *args, **kwargs):
695 """Set the range of the scaling from DN in the image to the image display
697 Parameters
698 ----------
699 algorithm : `str`
700 Desired scaling (e.g. "linear" or "asinh")
701 min
702 Minimum value, or "minmax" or "zscale"
703 max
704 Maximum value (must be `None` for minmax|zscale)
705 unit
706 Units for min and max (e.g. Percent, Absolute, Sigma; `None` if min==minmax|zscale)
707 *args
708 Optional arguments to the backend
709 **kwargs
710 Optional keyword arguments to the backend
711 """
712 if min in ("minmax", "zscale"):
713 assert max is None, f"You may not specify \"{min}\" and max"
714 assert unit is None, f"You may not specify \"{min}\" and unit"
715 elif max is None:
716 raise RuntimeError("Please specify max")
718 self._impl._scale(algorithm, min, max, unit, *args, **kwargs)
719 #
720 # Zoom and Pan
721 #
723 def zoom(self, zoomfac=None, colc=None, rowc=None, origin=afwImage.PARENT):
724 """Zoom frame by specified amount, optionally panning also
725 """
727 if (rowc and colc is None) or (colc and rowc is None):
728 raise RuntimeError(
729 "Please specify row and column center to pan about")
731 if rowc is not None:
732 if origin == afwImage.PARENT and self._xy0 is not None:
733 x0, y0 = self._xy0
734 colc -= x0
735 rowc -= y0
737 self._impl._pan(colc, rowc)
739 if zoomfac is None and rowc is None:
740 zoomfac = 2
742 if zoomfac is not None:
743 self._impl._zoom(zoomfac)
745 def pan(self, colc=None, rowc=None, origin=afwImage.PARENT):
746 """Pan to a location
748 Parameters
749 ----------
750 colc, rowc
751 the coordinates to pan to
752 origin : `lsst.afw.image.ImageOrigin`
753 Coordinate system for the given positions.
755 See also
756 --------
757 Display.zoom
758 """
760 self.zoom(None, colc, rowc, origin)
762 def interact(self):
763 """Enter an interactive loop, listening for key presses in display and firing callbacks.
765 Exit with ``q``, ``CR``, ``ESC``, or any other callback function that returns a `True` value.
766 """
767 interactFinished = False
769 while not interactFinished:
770 ev = self._impl._getEvent()
771 if not ev:
772 continue
773 k, x, y = ev.k, ev.x, ev.y # for now
775 if k not in self._callbacks:
776 logger.warn("No callback registered for {0}".format(k))
777 else:
778 try:
779 interactFinished = self._callbacks[k](k, x, y)
780 except Exception as e:
781 logger.error(
782 "Display._callbacks['{0}']({0},{1},{2}) failed: {3}".format(k, x, y, e))
784 def setCallback(self, k, func=None, noRaise=False):
785 """Set the callback for a key
787 Parameters
788 ----------
789 k
790 The key to assign the callback to
791 func : callable
792 The callback assigned to ``k``
793 noRaise : `bool`
795 Returns
796 -------
797 oldFunc : callable
798 The callback previously assigned to ``k``.
799 """
801 if k in "f":
802 if noRaise:
803 return
804 raise RuntimeError(
805 f"Key '{k}' is already in use by display, so I can't add a callback for it")
807 ofunc = self._callbacks.get(k)
808 self._callbacks[k] = func if func else noop_callback
810 self._impl._setCallback(k, self._callbacks[k])
812 return ofunc
814 def getActiveCallbackKeys(self, onlyActive=True):
815 """Return all callback keys
817 Parameters
818 ----------
819 onlyActive : `bool`
820 If `True` only return keys that do something
821 """
823 return sorted([k for k, func in self._callbacks.items() if
824 not (onlyActive and func == noop_callback)])
826#
827# Callbacks for display events
828#
831class Event:
832 """A class to handle events such as key presses in image display windows
833 """
835 def __init__(self, k, x=float('nan'), y=float('nan')):
836 self.k = k
837 self.x = x
838 self.y = y
840 def __str__(self):
841 return f"{self.k} ({self.x:.2f}, {self.y:.2f}"
842#
843# Default fallback function
844#
847def noop_callback(k, x, y):
848 """Callback function
850 Parameters
851 ----------
852 key
853 x
854 y
855 """
856 return False
859def h_callback(k, x, y):
860 print("Enter q or <ESC> to leave interactive mode, h for this help, or a letter to fire a callback")
861 return False
863#
864# Handle Displays, including the default one (the frame to use when a user specifies None)
865#
866# If the default frame is None, image display is disabled
867#
870def setDefaultBackend(backend):
871 Display.setDefaultBackend(backend)
874def getDefaultBackend():
875 return Display.getDefaultBackend()
878def setDefaultFrame(frame=0):
879 return Display.setDefaultFrame(frame)
882def getDefaultFrame():
883 """Get the default frame for display
884 """
885 return Display.getDefaultFrame()
888def incrDefaultFrame():
889 """Increment the default frame for display
890 """
891 return Display.incrDefaultFrame()
894def setDefaultMaskTransparency(maskPlaneTransparency={}):
895 return Display.setDefaultMaskTransparency(maskPlaneTransparency)
898def setDefaultMaskPlaneColor(name=None, color=None):
899 """Set the default mapping from mask plane names to colors
901 Parameters
902 ----------
903 name : `str` or `dict`
904 name of mask plane, or a dict mapping names to colors.
905 If ``name`` is `None`, use the hard-coded default dictionary
906 color : `str`
907 Desired color, or `None` if ``name`` is a dict
908 """
910 return Display.setDefaultMaskPlaneColor(name, color)
913def getDisplay(frame=None, backend=None, create=True, verbose=False, *args, **kwargs):
914 """Return a specific `Display`, creating it if need be
916 Parameters
917 ----------
918 frame
919 The desired frame (`None` => use defaultFrame (see `setDefaultFrame`))
920 backend : `str`
921 Create the specified frame using this backend (or the default if
922 `None`) if it doesn't already exist. If ``backend == ""``, it's an
923 error to specify a non-existent ``frame``.
924 create : `bool`
925 Create the display if it doesn't already exist.
926 verbose : `bool`
927 Allow backend to be chatty
928 *args
929 arguments passed to `Display` constructor
930 **kwargs
931 keyword arguments passed to `Display` constructor
933 See also
934 --------
935 Display.getDisplay
936 """
938 return Display.getDisplay(frame, backend, create, verbose, *args, **kwargs)
941def delAllDisplays():
942 """Delete and close all known displays
943 """
944 return Display.delAllDisplays()