32import matplotlib.pyplot
as pyplot
33import matplotlib.cbook
34import matplotlib.colors
as mpColors
35from mpl_toolkits.axes_grid1
import make_axes_locatable
58 interactiveBackends = [
67 afwDisplay.GREEN:
"#00FF00",
71 """Map the ctype to a potentially different ctype
73 Specifically, if matplotlibCtypes[ctype] exists, use it instead
75 This
is used e.g. to map
"green" to a brighter shade
77 return matplotlibCtypes[ctype]
if ctype
in matplotlibCtypes
else ctype
81 """Provide a matplotlib backend for afwDisplay
83 Recommended backends in notebooks are:
95 Apparently only qt supports Display.interact(); the list of interactive
96 backends
is given by lsst.display.matplotlib.interactiveBackends
99 interpretMaskBits=True, mtvOrigin=afwImage.PARENT, fastMaskDisplay=False,
100 reopenPlot=False, useSexagesimal=False, dpi=None, *args, **kwargs):
102 Initialise a matplotlib display
104 @param fastMaskDisplay If
True only show the first bitplane that
's
106 (e.g.
if (SATURATED & DETECTED)
108 Not really what we want, but a bit faster
109 @param interpretMaskBits Interpret the mask value under the cursor
110 @param mtvOrigin Display pixel coordinates
with LOCAL origin
111 (bottom left == 0,0
not XY0)
112 @param reopenPlot If true, close the plot before opening it.
113 (useful
with e.g. %ipympl)
114 @param useSexagesimal If
True, display coordinates
in sexagesimal
115 E.g. hh:mm:ss.ss (default:
False)
116 May be changed by calling
117 display.useSexagesimal()
118 @param dpi Number of dpi (passed to pyplot.figure)
120 The `frame` argument to `Display` may be a matplotlib figure; this
122 fig, axes = plt.subplots(1, 2)
124 disp = afwDisplay.Display(fig)
125 disp.scale(
'asinh',
'zscale', Q=0.5)
127 for axis, exp
in zip(axes, exps):
131 if hasattr(display.frame,
"number"):
132 figure = display.frame
136 virtualDevice.DisplayImpl.__init__(self, display, verbose)
139 pyplot.close(display.frame)
141 if figure
is not None:
144 self.
_figure = pyplot.figure(display.frame, dpi=dpi)
157 self.
__alpha = unicodedata.lookup(
"GREEK SMALL LETTER alpha")
158 self.
__delta = unicodedata.lookup(
"GREEK SMALL LETTER delta")
171 """!Close the display, cleaning up any allocated resources"""
175 self.
_figure.gca().format_coord =
lambda x, y:
None
178 """Put the plot at the top of the window stacking order"""
181 self.
_figure.canvas._tkcanvas._root().lift()
182 except AttributeError:
186 self.
_figure.canvas.manager.window.raise_()
187 except AttributeError:
192 except AttributeError:
199 """Defer to figure.savefig()
204 Passed through to figure.savefig()
206 Passed through to figure.savefig()
210 def show_colorbar(self, show=True, where="right", axSize="5%", axPad=None, **kwargs):
211 """Show (or hide) the colour bar
216 Should I show the colour bar?
218 Location of colour bar: "right" or "bottom"
219 axSize : `float`
or `str`
220 Size of axes to hold the colour bar; fraction of current x-size
221 axPad : `float`
or `str`
222 Padding between axes
and colour bar; fraction of current x-size
224 Passed through to colorbar()
226 Passed through to colorbar()
228 We set the default padding to put the colourbar
in a reasonable
229 place
for roughly square plots, but you may need to fiddle
for
230 plots
with extreme axis ratios.
232 You can only configure the colorbar when it isn
't yet visible, but
233 as you can easily remove it this
is not in practice a difficulty.
238 orientationDict = dict(right=
"vertical", bottom=
"horizontal")
242 if where
in orientationDict:
243 orientation = orientationDict[where]
245 print(f
"Unknown location {where}; "
246 f
"please use one of {', '.join(orientationDict.keys())}")
249 axPad = 0.1
if orientation ==
"vertical" else 0.3
251 divider = make_axes_locatable(ax)
252 self.
_colorbar_ax = divider.append_axes(where, size=axSize, pad=axPad)
266 """Control the formatting coordinates as HH:MM:SS.ss
270 useSexagesimal : `bool`
271 Print coordinates as e.g. HH:MM:SS.ss iff
True
273 N.b. can also be set
in Display
's ctor
276 """Are we formatting coordinates as HH:MM:SS.ss?"""
279 def wait(self, prompt="[c(ontinue) p(db)] :
", allowPdb=True):
280 """Wait for keyboard input
287 If true, entering a 'p' or 'pdb' puts you into pdb
289 Returns the string you entered
291 Useful when plotting
from a programme that exits such
as a processCcd
292 Any key
except 'p' continues;
'p' puts you into pdb (unless
297 if allowPdb
and s
in (
"p",
"pdb"):
308 """Specify mask transparency (percent)"""
313 """Return the current mask transparency"""
316 def _mtv(self, image, mask=None, wcs=None, title=""):
317 """Display an Image and/or Mask on a matplotlib display
319 title = str(title) if title
else ""
336 self.
_i_mtv(image, wcs, title,
False)
339 self.
_i_mtv(mask, wcs, title,
True)
348 def format_coord(x, y, wcs=self._wcs, x0=self._xy0[0], y0=self._xy0[1],
349 origin=afwImage.PARENT, bbox=self._image.getBBox(afwImage.PARENT),
352 fmt =
'(%1.2f, %1.2f)'
356 msg = (fmt +
"L") % (x - x0, y - y0)
362 raDec = wcs.pixelToSky(x, y)
363 ra = raDec[0].asDegrees()
364 dec = raDec[1].asDegrees()
366 if _useSexagesimal[0]:
367 from astropy
import units
as u
368 from astropy.coordinates
import Angle
as apAngle
370 kwargs = dict(sep=
':', pad=
True, precision=2)
371 ra = apAngle(ra*u.deg).to_string(unit=u.hour, **kwargs)
372 dec = apAngle(dec*u.deg).to_string(unit=u.deg, **kwargs)
377 msg +=
r" (%s, %s): (%s, %s)" % (self.
__alpha, self.
__delta, ra, dec)
379 msg +=
' %1.3f' % (self.
_image[col, row])
381 val = self.
_mask[col, row]
383 msg +=
" [%s]" % self.
_mask.interpret(val)
389 ax.format_coord = format_coord
392 for a
in ax.get_images():
393 a.get_cursor_data =
lambda ev:
None
396 self.
_figure.canvas.draw_idle()
398 def _i_mtv(self, data, wcs, title, isMask):
399 """Internal routine to display an Image or Mask on a DS9 display"""
401 title = str(title)
if title
else ""
402 dataArr = data.getArray()
405 maskPlanes = data.getMaskPlaneDict()
406 nMaskPlanes =
max(maskPlanes.values()) + 1
409 for key
in maskPlanes:
410 planes[maskPlanes[key]] = key
412 planeList = range(nMaskPlanes)
414 maskArr = np.zeros_like(dataArr, dtype=np.int32)
416 colorNames = [
'black']
417 colorGenerator = self.display.maskColorGenerator(omitBW=
True)
419 color = self.display.getMaskPlaneColor(planes[p])
if p
in planes
else None
422 color = next(colorGenerator)
423 elif color.lower() == afwDisplay.IGNORE:
426 colorNames.append(color)
435 colors = mpColors.to_rgba_array(colorNames)
437 colors[0][alphaChannel] = 0.0
438 for i, p
in enumerate(planeList):
439 if colorNames[i + 1] ==
'black':
444 colors[i + 1][alphaChannel] = alpha
446 cmap = mpColors.ListedColormap(colors)
447 norm = mpColors.NoNorm()
453 bbox = data.getBBox()
454 extent = (bbox.getBeginX() - 0.5, bbox.getEndX() - 0.5,
455 bbox.getBeginY() - 0.5, bbox.getEndY() - 0.5)
457 with pyplot.rc_context(dict(interactive=
False)):
459 for i, p
in reversed(
list(enumerate(planeList))):
460 if colors[i + 1][alphaChannel] == 0:
463 bitIsSet = (dataArr & (1 << p)) != 0
464 if bitIsSet.sum() == 0:
467 maskArr[bitIsSet] = i + 1
470 ax.imshow(maskArr, origin=
'lower', interpolation=
'nearest',
471 extent=extent, cmap=cmap, norm=norm)
475 ax.imshow(maskArr, origin=
'lower', interpolation=
'nearest',
476 extent=extent, cmap=cmap, norm=norm)
484 mappable = ax.imshow(dataArr, origin=
'lower', interpolation=
'nearest',
485 extent=extent, cmap=cmap, norm=norm)
488 self.
_figure.canvas.draw_idle()
491 """Save the current image, mask, wcs, and XY0"""
507 """Set the colormap used for the image
509 cmap should be either the name of an attribute of pyplot.cm or an
510 mpColors.Colormap (e.g.
"gray" or pyplot.cm.gray)
513 if not isinstance(cmap, mpColors.Colormap):
514 cmap = getattr(pyplot.cm, cmap)
522 def _buffer(self, enable=True):
533 """Erase the display"""
535 for axis
in self._figure.axes:
539 self._figure.canvas.draw_idle()
541 def _dot(self, symb, c, r, size, ctype,
542 fontFamily="helvetica", textAngle=None):
543 """Draw a symbol at (col,row) = (c,r) [0-based coordinates]
549 @:Mxx,Mxy,Myy Draw an ellipse with moments
550 (Mxx, Mxy, Myy) (argument size
is ignored)
551 An afwGeom.ellipses.Axes Draw the ellipse (argument size
is
554 Any other value
is interpreted
as a string to be drawn. Strings obey the
555 fontFamily (which may be extended
with other characteristics, e.g.
556 "times bold italic". Text will be drawn rotated by textAngle
557 (textAngle
is ignored otherwise).
560 ctype = afwDisplay.GREEN
565 if isinstance(symb, afwGeom.ellipses.Axes):
566 from matplotlib.patches
import Ellipse
571 axis.add_artist(Ellipse((c + x0, r + y0), height=2*symb.getA(), width=2*symb.getB(),
572 angle=90.0 + math.degrees(symb.getTheta()),
573 edgecolor=
mapCtype(ctype), facecolor=
'none'))
575 from matplotlib.patches
import CirclePolygon
as Circle
577 axis.add_artist(Circle((c + x0, r + y0), radius=size, color=
mapCtype(ctype), fill=
False))
579 from matplotlib.lines
import Line2D
581 for ds9Cmd
in ds9Regions.dot(symb, c + x0, r + y0, size, fontFamily=
"helvetica", textAngle=
None):
582 tmp = ds9Cmd.split(
'#')
583 cmd = tmp.pop(0).split()
585 cmd, args = cmd[0], cmd[1:]
588 args = np.array(args).astype(float) - 1.0
590 x = np.empty(len(args)//2)
592 i = np.arange(len(args), dtype=int)
596 axis.add_line(Line2D(x, y, color=
mapCtype(ctype)))
598 x, y = np.array(args[0:2]).astype(float) - 1.0
599 axis.text(x, y, symb, color=
mapCtype(ctype),
600 horizontalalignment=
'center', verticalalignment=
'center')
602 raise RuntimeError(ds9Cmd)
605 """Connect the points, a list of (col,row)
606 Ctype is the name of a colour (e.g.
'red')
"""
608 from matplotlib.lines
import Line2D
611 ctype = afwDisplay.GREEN
613 points = np.array(points)
614 x = points[:, 0] + self.
_xy0[0]
615 y = points[:, 1] + self.
_xy0[1]
619 def _scale(self, algorithm, minval, maxval, unit, *args, **kwargs):
623 N.b. Supports extra arguments:
624 @param maskedPixels List of names of mask bits to ignore
625 E.g. [
"BAD",
"INTERP"].
626 A single name
is also supported
636 self.
_i_scale(algorithm, minval, maxval, unit, *args, **kwargs)
637 except (AttributeError, RuntimeError):
641 def _i_scale(self, algorithm, minval, maxval, unit, *args, **kwargs):
643 maskedPixels = kwargs.get(
"maskedPixels", [])
644 if isinstance(maskedPixels, str):
645 maskedPixels = [maskedPixels]
646 bitmask = afwImage.Mask.getPlaneBitMask(maskedPixels)
649 sctrl.setAndMask(bitmask)
651 if minval ==
"minmax":
653 raise RuntimeError(
"You may only use minmax if an image is loaded into the display")
657 minval = stats.getValue(afwMath.MIN)
658 maxval = stats.getValue(afwMath.MAX)
659 elif minval ==
"zscale":
661 print(
"scale(..., 'zscale', maskedPixels=...) is not yet implemented")
663 if algorithm
is None:
665 elif algorithm ==
"asinh":
666 if minval ==
"zscale":
668 raise RuntimeError(
"You may only use zscale if an image is loaded into the display")
673 dataRange=maxval - minval, Q=kwargs.get(
"Q", 8.0))
674 elif algorithm ==
"linear":
675 if minval ==
"zscale":
677 raise RuntimeError(
"You may only use zscale if an image is loaded into the display")
680 nSamples=kwargs.get(
"nSamples", 1000),
681 contrast=kwargs.get(
"contrast", 0.25))
685 raise RuntimeError(
"Unsupported stretch algorithm \"%s\"" % algorithm)
691 """Zoom by specified amount"""
703 xmin, xmax = self.
_xcen + x0 + size/self.
_zoomfac*np.array([-1, 1])
704 ymin, ymax = self.
_ycen + y0 + size/self.
_zoomfac*np.array([-1, 1])
708 tb = self.
_figure.canvas.toolbar
712 ax.set_xlim(xmin, xmax)
713 ax.set_ylim(ymin, ymax)
714 ax.set_aspect(
'equal',
'datalim')
716 self.
_figure.canvas.draw_idle()
719 """Pan to (colc, rowc)"""
727 """Listen for a key press, returning (key, x, y)"""
732 mpBackend = matplotlib.get_backend()
733 if mpBackend
not in interactiveBackends:
734 print(
"The %s matplotlib backend doesn't support display._getEvent()" %
735 (matplotlib.get_backend(),), file=sys.stderr)
736 return interface.Event(
'q')
743 def recordKeypress(keypress):
744 """Matplotlib callback to record keypress and unblock"""
746 event = interface.Event(keypress.key, keypress.xdata, keypress.ydata)
747 self.
_figure.canvas.stop_event_loop()
749 conn = self.
_figure.canvas.mpl_connect(
"key_press_event", recordKeypress)
751 self.
_figure.canvas.start_event_loop(timeout=timeout)
753 self.
_figure.canvas.mpl_disconnect(conn)
761 """Class to support stretches for mtv()"""
765 Return a MaskedArray with value mapped to [0, 255]
767 @param value Input pixel value
or array to be mapped
769 if isinstance(value, np.ndarray):
774 data = data - self.mapping.minimum[0]
775 return ma.array(data*self.mapping.mapIntensityToUint8(data)/255.0)
779 """Provide an asinh stretch for mtv()"""
781 """Initialise an object able to carry out an asinh mapping
783 @param minimum Minimum pixel value (default: 0)
784 @param dataRange Range of values
for stretch
if Q=0; roughly the
785 linear part (default: 1)
786 @param Q Softening parameter (default: 8)
788 See Lupton et al., PASP 116, 133
791 self.
mapping = afwRgb.AsinhMapping(minimum, dataRange, Q)
799 """Return an asinh mapping's minimum and maximum value, and Q
801 Regrettably this information is not preserved by AsinhMapping
802 so we have to reverse engineer it
806 Q = np.sinh((frac*self.
mapping._uint8Max)/self.
mapping._slope)/frac
807 dataRange = Q/self.
mapping._soften
810 return vmin, vmin + dataRange, Q
814 """Provide an asinh stretch using zscale to set limits for mtv()"""
816 """Initialise an object able to carry out an asinh mapping
818 @param image image to use estimate minimum
and dataRange using zscale
820 @param Q Softening parameter (default: 8)
822 See Lupton et al., PASP 116, 133
831 Normalize.__init__(self, vmin, vmax)
835 """Provide a zscale stretch for mtv()"""
836 def __init__(self, image=None, nSamples=1000, contrast=0.25):
837 """Initialise an object able to carry out a zscale mapping
839 @param image to be used to estimate the stretch
840 @param nSamples Number of data points to use (default: 1000)
841 @param contrast Control the range of pixels to display around the
842 median (default: 0.25)
846 self.
mapping = afwRgb.ZScaleMapping(image, nSamples, contrast)
852 """Provide a linear stretch for mtv()"""
854 """Initialise an object able to carry out a linear mapping
856 @param minimum Minimum value to display
857 @param maximum Maximum value to display
860 self.
mapping = afwRgb.LinearMapping(minimum, maximum)
Pass parameters to a Statistics object.
__init__(self, minimum=0, dataRange=1, Q=8)
__init__(self, image=None, Q=8)
wait(self, prompt="[c(ontinue) p(db)] :", allowPdb=True)
_getEvent(self, timeout=-1)
_close(self)
Close the display, cleaning up any allocated resources.
_drawLines(self, points, ctype)
_dot(self, symb, c, r, size, ctype, fontFamily="helvetica", textAngle=None)
savefig(self, *args, **kwargs)
_i_setImage(self, image, mask=None, wcs=None)
_mtv(self, image, mask=None, wcs=None, title="")
_scale(self, algorithm, minval, maxval, unit, *args, **kwargs)
_getMaskTransparency(self, maskplane=None)
_i_scale(self, algorithm, minval, maxval, unit, *args, **kwargs)
show_colorbar(self, show=True, where="right", axSize="5%", axPad=None, **kwargs)
useSexagesimal(self, useSexagesimal)
_i_mtv(self, data, wcs, title, isMask)
__init__(self, display, verbose=False, interpretMaskBits=True, mtvOrigin=afwImage.PARENT, fastMaskDisplay=False, reopenPlot=False, useSexagesimal=False, dpi=None, *args, **kwargs)
_setImageColormap(self, cmap)
_setMaskTransparency(self, transparency, maskplane)
__init__(self, minimum=0, maximum=1)
__call__(self, value, clip=None)
__init__(self, image=None, nSamples=1000, contrast=0.25)
daf::base::PropertyList * list
MaskedImage< ImagePixelT, MaskPixelT, VariancePixelT > * makeMaskedImage(typename std::shared_ptr< Image< ImagePixelT > > image, typename std::shared_ptr< Mask< MaskPixelT > > mask=Mask< MaskPixelT >(), typename std::shared_ptr< Image< VariancePixelT > > variance=Image< VariancePixelT >())
A function to return a MaskedImage of the correct type (cf.
Statistics makeStatistics(lsst::afw::image::Image< Pixel > const &img, lsst::afw::image::Mask< image::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl=StatisticsControl())
Handle a watered-down front-end to the constructor (no variance)