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",
38from ._images_compat
import normalize_lsst_images
40logger = logging.getLogger(__name__)
56 """Return the ``DisplayImpl`` for the named backend
61 Name of device. Should be importable, either absolutely or relative to lsst.display
65 Arguments passed to DisplayImpl.__init__
67 Keywords arguments passed to DisplayImpl.__init__
75 import lsst.afw.display as afwDisplay
76 display = afwDisplay.Display(backend="ds9")
82 _makeDisplayImpl(..., "ds9", 1)
84 and import the ds9 implementation of ``DisplayImpl`` from `lsst.display.ds9`
88 candidateBackends = (f
"lsst.display.{backend}", backend, f
".{backend}", f
"lsst.afw.display.{backend}")
89 for dt
in candidateBackends:
93 if dt.startswith(
"."):
94 impargs[
"package"] =
"lsst.display"
96 _disp = importlib.import_module(dt, **impargs)
99 if hasattr(_disp,
"DisplayImpl"):
103 except (ImportError, SystemError)
as e:
109 if not _disp
or not hasattr(_disp.DisplayImpl,
"_show"):
111 e = ImportError(f
"Could not load the requested backend: {backend} "
112 f
"(tried {', '.join(candidateBackends)}, but none worked).")
119 _impl = _disp.DisplayImpl(display, *args, **kwargs)
120 if not hasattr(_impl,
"frame"):
121 _impl.frame = display.frame
129 """Create an object able to display images and overplot glyphs.
134 An identifier for the display.
136 The backend to use (defaults to value set by setDefaultBackend()).
138 Arguments to pass to the backend.
141 _defaultBackend =
None
143 _defaultMaskPlaneColor = dict(
148 DETECTION_EDGE=YELLOW,
152 DETECTED_NEGATIVE=CYAN,
159 _defaultMaskTransparency = {}
160 _defaultImageColormap =
"gray"
162 def __init__(self, frame=None, backend=None, **kwargs):
167 if Display._defaultBackend
is None:
173 backend = Display._defaultBackend
187 for ik
in range(ord(
'a'), ord(
'z') + 1):
192 for k
in (
'Return',
'Shift_L',
'Shift_R'):
195 for k
in (
'q',
'Escape'):
198 def _h_callback(k, x, y):
203 print(
" %-6s %s" % (k, doc.split(
"\n")[0]
if doc
else "???"))
207 Display._displays[frame] = self
210 """Support for python's with statement.
215 """Support for python's with statement.
223 """Return the attribute of ``self._impl``, or ``._impl`` if it is
229 name of the attribute requested.
234 the attribute of self._impl for the requested name.
238 return object.__getattr__(self, name)
240 if not (hasattr(self,
"_impl")
and self.
_impl):
241 raise AttributeError(
"Device has no _impl attached")
244 return getattr(self.
_impl, name)
245 except AttributeError:
246 raise AttributeError(
247 f
"Device {self.name} has no attribute \"{name}\"")
250 if getattr(self,
"_impl",
None)
is not None:
255 if self.
frame in Display._displays:
256 del Display._displays[self.
frame]
260 """The backend's verbosity.
262 return self.
_impl.verbose
267 self.
_impl.verbose = value
270 return f
"Display[{self.frame}]"
278 except Exception
as e:
280 f
"Unable to set backend to {backend}: \"{e}\"")
282 Display._defaultBackend = backend
286 return Display._defaultBackend
290 """Set the default frame for display.
292 Display._defaultFrame = frame
296 """Get the default frame for display.
298 return Display._defaultFrame
302 """Increment the default frame for display.
304 Display._defaultFrame += 1
305 return Display._defaultFrame
309 if hasattr(maskPlaneTransparency,
"copy"):
310 maskPlaneTransparency = maskPlaneTransparency.copy()
312 Display._defaultMaskTransparency = maskPlaneTransparency
316 """Set the default mapping from mask plane names to colors.
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.
324 Desired color, or `None` if name is a dict.
328 name = Display._defaultMaskPlaneColor
330 if isinstance(name, dict):
332 for k, v
in name.items():
336 Display._defaultMaskPlaneColor[name] = color
340 """Set the default colormap for images.
345 Name of colormap, as interpreted by the backend.
349 The only colormaps that all backends are required to honor
350 (if they pay any attention to setImageColormap) are "gray" and "grey".
353 Display._defaultImageColormap = cmap
356 """Set the colormap to use for images.
361 Name of colormap, as interpreted by the backend.
365 The only colormaps that all backends are required to honor
366 (if they pay any attention to setImageColormap) are "gray" and "grey".
369 self.
_impl._setImageColormap(cmap)
372 def getDisplay(frame=None, backend=None, create=True, verbose=False, **kwargs):
373 """Return a specific `Display`, creating it if need be.
378 The desired frame (`None` => use defaultFrame
379 (see `~Display.setDefaultFrame`)).
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``.
385 create the display if it doesn't already exist.
387 Allow backend to be chatty.
389 keyword arguments passed to `Display` constructor.
393 frame = Display._defaultFrame
395 if frame
not in Display._displays:
397 raise RuntimeError(f
"Frame {frame} does not exist")
399 Display._displays[frame] =
Display(
400 frame, backend, verbose=verbose, **kwargs)
402 Display._displays[frame].verbose = verbose
403 return Display._displays[frame]
407 """Delete and close all known displays.
409 for disp
in list(Display._displays.values()):
411 Display._displays = {}
414 """A generator for "standard" colors.
419 Don't include `BLACK` and `WHITE`.
426 colorGenerator = interface.maskColorGenerator(omitBW=True)
428 print(p, next(colorGenerator))
430 _maskColors = [WHITE, BLACK, RED, GREEN,
431 BLUE, CYAN, MAGENTA, YELLOW, ORANGE]
436 color = _maskColors[i%len(_maskColors)]
437 if omitBW
and color
in (BLACK, WHITE):
443 """Request that mask plane name be displayed as color.
447 name : `str` or `dict`
448 Name of mask plane or a dictionary of name -> colorName.
450 The name of the color to use (must be `None` if ``name`` is a
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`.
458 If the color is "ignore" (or `IGNORE`) then that mask plane is not
461 The advantage of using the symbolic names is that the python
462 interpreter can detect typos.
464 if isinstance(name, dict):
466 for k, v
in name.items():
473 """Return the color associated with the specified mask plane name.
478 Desired mask plane; if `None`, return entire dict.
491 """Specify display's mask transparency (percent); or `None` to not set
492 it when loading masks.
494 if isinstance(transparency, dict):
496 for k, v
in transparency.items():
500 if transparency
is not None and (transparency < 0
or transparency > 100):
502 "Mask transparency should be in the range [0, 100]; clipping", file=sys.stderr)
508 if transparency
is not None:
509 self.
_impl._setMaskTransparency(transparency, name)
512 """Return the current display's mask transparency.
514 return self.
_impl._getMaskTransparency(name)
517 """Uniconify and Raise display.
521 Throws an exception if frame doesn't exit.
523 return self.
_impl._show()
525 def __addMissingMaskPlanes(self, mask):
526 """Assign colours to any missing mask planes found in mask.
528 maskPlanes = mask.getMaskPlaneDict()
529 nMaskPlanes = max(maskPlanes.values()) + 1
533 for key
in maskPlanes:
534 planes[maskPlanes[key]] = key
537 for p
in range(nMaskPlanes):
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.
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.
567 Raised if an Exposure, or an ``lsst.images`` object with its own
568 ``sky_projection``, is passed when the ``wcs`` kwarg is also
571 Raised if data is an incompatible type.
573 data, wcs = normalize_lsst_images(data, wcs)
575 if hasattr(data,
"getXY0"):
576 self.
_xy0 = data.getXY0()
583 raise RuntimeError(
"You may not specify a wcs with an Exposure")
584 data, wcs, metadata = data.getMaskedImage(), data.wcs, data.metadata
588 wcs = afwGeom.makeSkyWcs(data.getMetadata())
593 self.
_xy0 = data.getXY0()
596 self.
_impl._mtv(data,
None, wcs, title, metadata)
602 self.
_impl._mtv(afwImage.ImageI(data.array), data, wcs, title, metadata)
606 self.
_impl._mtv(data.image, data.mask, wcs, title, metadata)
608 raise TypeError(f
"Unsupported type {data!r}")
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.
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.
619 self.
image(data, title, wcs, metadata)
622 """Context manager for buffering repeated display commands.
628 self.
_impl._buffer(
True)
631 self.
_impl._buffer(
False)
635 """Return a context manager that will buffer repeated display
636 commands, to e.g. speed up displaying points.
642 with display.Buffering():
643 display.dot("+", xc, yc)
648 """Flush any buffering that may be provided by the backend.
653 """Erase the specified display frame.
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()`.
662 See `dot` for an explanation of ``symbol`` and available args/kwargs,
663 which are passed to `dot`.
667 catalog : `lsst.afw.table.Catalog`
668 Catalog to display centroids for. Must have valid `slot_Centroid`.
670 if not catalog.getCentroidSlot().isValid():
671 raise RuntimeError(
"Catalog must have a valid `slot_Centroid` defined to get X/Y positions.")
675 self.
dot(symbol, pt.getX(), pt.getY(), **kwargs)
677 def dot(self, symb, c, r, size=2, ctype=None, origin=afwImage.PARENT, **kwargs):
678 """Draw a symbol onto the specified display frame.
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`.
700 Interpreted as a string to be drawn.
702 The column and row where the symbol is drawn [0-based coordinates].
704 Size of symbol, in pixels.
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.
710 Extra keyword arguments to backend.
712 if isinstance(symb, int):
715 if origin == afwImage.PARENT
and self.
_xy0 is not None:
720 if isinstance(symb, afwGeom.ellipses.BaseCore)
or re.search(
r"^@:", symb):
722 mat = re.search(
r"^@:([^,]+),([^,]+),([^,]+)", symb)
727 mxx, mxy, myy = [float(_)
for _
in mat.groups()]
728 symb = afwGeom.Quadrupole(mxx, myy, mxy)
730 symb = afwGeom.ellipses.Axes(symb)
732 self.
_impl._dot(symb, c, r, size, ctype, **kwargs)
734 def line(self, points, origin=afwImage.PARENT, symbs=False, ctype=None, size=0.5):
735 """Draw a set of symbols or connect points
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.
747 If ``symbs`` supports indexing (which includes a string -- caveat
748 emptor) the elements are used to label the points.
750 ``ctype`` is the name of a color (e.g. 'red').
752 Size of points to create if `symbs` is passed.
758 symbs = len(points)*list(symbs)
760 for i, xy
in enumerate(points):
761 self.
dot(symbs[i], *xy, size=size, ctype=ctype)
764 if origin == afwImage.PARENT
and self.
_xy0 is not None:
766 _points = list(points)
767 for i, p
in enumerate(points):
768 _points[i] = (p[0] - x0, p[1] - y0)
771 self.
_impl._drawLines(points, ctype)
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
780 Desired scaling (e.g. "linear" or "asinh").
782 Minimum value, or "minmax" or "zscale".
784 Maximum value (must be `None` for minmax|zscale).
786 Units for min and max (e.g. Percent, Absolute, Sigma; `None` if
789 Optional keyword arguments to the backend.
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"
795 raise RuntimeError(
"Please specify max")
797 self.
_impl._scale(algorithm, min, max, unit, **kwargs)
799 def zoom(self, zoomfac=None, colc=None, rowc=None, origin=afwImage.PARENT):
800 """Zoom frame by specified amount, optionally panning also
802 if (rowc
and colc
is None)
or (colc
and rowc
is None):
804 "Please specify row and column center to pan about")
807 if origin == afwImage.PARENT
and self.
_xy0 is not None:
812 self.
_impl._pan(colc, rowc)
814 if zoomfac
is None and rowc
is None:
817 if zoomfac
is not None:
818 self.
_impl._zoom(zoomfac)
820 def pan(self, colc=None, rowc=None, origin=afwImage.PARENT):
821 """Pan to a location.
826 Coordinates to pan to.
827 origin : `lsst.afw.image.ImageOrigin`
828 Coordinate system for the given positions.
834 self.
zoom(
None, colc, rowc, origin)
837 """Enter an interactive loop, listening for key presses or equivalent
838 UI actions in the display and firing callbacks.
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.
844 interactFinished =
False
846 while not interactFinished:
847 ev = self.
_impl._getEvent()
850 k, x, y = ev.k, ev.x, ev.y
853 logger.warning(
"No callback registered for %s", k)
856 interactFinished = self.
_callbacks[k](k, x, y)
859 "Display._callbacks['%s'](%s,%s,%s) failed.", k, x, y)
862 """Align and optionally lock the orientation of the images being
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.
875 self.
_impl._alignImages(match_type, lock_match)
876 except AttributeError:
877 raise NotImplementedError(
"This feature only available on some backends.")
880 """Set the callback for a key.
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.
888 The key to assign the callback to.
890 The callback assigned to ``k``.
892 Do not raise if ``k`` is already in use.
897 The callback previously assigned to ``k``.
904 f
"Key '{k}' is already in use by display, so I can't add a callback for it")
907 self.
_callbacks[k] = func
if func
else noop_callback
914 """Return all callback keys
919 If `True` only return keys that do something
921 return sorted([k
for k, func
in self.
_callbacks.items()
if
922 not (onlyActive
and func == noop_callback)])
929 """A class to handle events such as key presses in image display windows.
932 def __init__(self, k, x=float(
'nan'), y=float(
'nan')):
938 return f
"{self.k} ({self.x:.2f}, {self.y:.2f}"
957 print(
"Enter q or <ESC> to leave interactive mode, h for this help, or a letter to fire a callback")
965 Display.setDefaultBackend(backend)
969 return Display.getDefaultBackend()
973 return Display.setDefaultFrame(frame)
977 """Get the default frame for display.
979 return Display.getDefaultFrame()
983 """Increment the default frame for display.
985 return Display.incrDefaultFrame()
989 return Display.setDefaultMaskTransparency(maskPlaneTransparency)
993 """Set the default mapping from mask plane names to colors.
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.
1001 Desired color, or `None` if ``name`` is a dict.
1004 return Display.setDefaultMaskPlaneColor(name, color)
1007def getDisplay(frame=None, backend=None, create=True, verbose=False, **kwargs):
1008 """Return a specific `Display`, creating it if need be.
1013 Desired frame (`None` => use defaultFrame (see `setDefaultFrame`)).
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``.
1019 Create the display if it doesn't already exist.
1021 Allow backend to be chatty.
1023 Keyword arguments passed to `Display` constructor.
1030 return Display.getDisplay(frame, backend, create, verbose, **kwargs)
1034 """Delete and close all known displays.
1036 return Display.delAllDisplays()
image(self, data, title="", wcs=None, metadata=None)
alignImages(self, match_type="", lock_match=True)
setMaskTransparency(self, transparency=None, name=None)
getDisplay(frame=None, backend=None, create=True, verbose=False, **kwargs)
getActiveCallbackKeys(self, onlyActive=True)
setDefaultBackend(backend)
dot(self, symb, c, r, size=2, ctype=None, origin=afwImage.PARENT, **kwargs)
getMaskPlaneColor(self, name=None)
setDefaultImageColormap(cmap)
line(self, points, origin=afwImage.PARENT, symbs=False, ctype=None, size=0.5)
scale(self, algorithm, min, max=None, unit=None, **kwargs)
__init__(self, frame=None, backend=None, **kwargs)
setDefaultMaskTransparency(maskPlaneTransparency={})
pan(self, colc=None, rowc=None, origin=afwImage.PARENT)
centroids(self, catalog, *, symbol="o", **kwargs)
getMaskTransparency(self, name=None)
setCallback(self, k, func=None, noRaise=False)
setMaskPlaneColor(self, name, color=None)
setDefaultMaskPlaneColor(name=None, color=None)
__addMissingMaskPlanes(self, mask)
maskColorGenerator(self, omitBW=True)
mtv(self, data, title="", wcs=None, metadata=None)
zoom(self, zoomfac=None, colc=None, rowc=None, origin=afwImage.PARENT)
setImageColormap(self, cmap)
__init__(self, k, x=float('nan'), y=float('nan'))
A container for an Image and its associated metadata.
A class to contain the data, WCS, and other information needed to describe an image of the sky.
A class to represent a 2-dimensional array of pixels.
Represent a 2-dimensional array of bitmask pixels.
A class to manipulate images, masks, and variance as a single object.
setDefaultMaskTransparency(maskPlaneTransparency={})
getDisplay(frame=None, backend=None, create=True, verbose=False, **kwargs)
setDefaultMaskPlaneColor(name=None, color=None)
_makeDisplayImpl(display, backend, *args, **kwargs)
setDefaultBackend(backend)