33 import matplotlib.pyplot
as pyplot
34 import matplotlib.cbook
35 import matplotlib.colors
as mpColors
36 from matplotlib.blocking_input
import BlockingInput
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 98 def __init__(self, display, verbose=False,
99 interpretMaskBits=True, mtvOrigin=afwImage.PARENT, fastMaskDisplay=True,
100 reopenPlot=False, *args, **kwargs):
102 Initialise a matplotlib display 104 @param fastMaskDisplay If True, only show the first bitplane 105 that's set in each pixel 106 (e.g. if (SATURATED & DETECTED), ignore 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) 115 virtualDevice.DisplayImpl.__init__(self, display, verbose)
118 pyplot.close(display.frame)
119 self.
_figure = pyplot.figure(display.frame)
128 self.
__alpha = unicodedata.lookup(
"GREEK SMALL LETTER alpha")
129 self.
__delta = unicodedata.lookup(
"GREEK SMALL LETTER delta")
144 warnings.filterwarnings(
"ignore", category=matplotlib.cbook.mplDeprecation)
147 """!Close the display, cleaning up any allocated resources""" 151 self.
_figure.gca().format_coord =
None 154 """Put the plot at the top of the window stacking order""" 157 self.
_figure.canvas._tkcanvas._root().lift()
158 except AttributeError:
162 self.
_figure.canvas.manager.window.raise_()
163 except AttributeError:
168 except AttributeError:
175 """Defer to figure.savefig()""" 179 """Show (or hide) the colour bar""" 184 def wait(self, prompt="[c(ontinue) p(db)] :
", allowPdb=True): 185 """Wait for keyboard input 189 @param allowPdb `bool` 190 If true, entering a 'p' or 'pdb' puts you into pdb 192 Returns the string you entered 194 Useful when plotting from a programme that exits such as a processCcd 195 Any key except 'p' continues; 'p' puts you into pdb (unless allowPdb 200 if allowPdb
and s
in (
"p",
"pdb"):
210 def _setMaskTransparency(self, transparency, maskplane):
211 """Specify mask transparency (percent)""" 215 def _getMaskTransparency(self, maskplane=None):
216 """Return the current mask transparency""" 219 def _mtv(self, image, mask=None, wcs=None, title=""):
220 """Display an Image and/or Mask on a matplotlib display 222 title = str(title)
if title
else "" 238 self.
_i_mtv(image, wcs, title,
False)
242 self.
_i_mtv(mask, wcs, title,
True)
249 def format_coord(x, y, wcs=self._wcs, x0=self._xy0[0], y0=self._xy0[1],
250 origin=afwImage.PARENT, bbox=self._image.getBBox(afwImage.PARENT)):
252 fmt =
'(%1.2f, %1.2f)' 256 msg = (fmt +
"L") % (x - x0, y - y0)
262 ra, dec = wcs.pixelToSky(x, y)
263 msg +=
r" (%s, %s): (%9.4f, %9.4f)" % (self.
__alpha, self.
__delta, ra, dec)
265 msg +=
' %1.3f' % (self.
_image[col, row])
267 val = self.
_mask[col, row]
269 msg +=
" [%s]" % self.
_mask.interpret(val)
275 ax.format_coord = format_coord
278 from matplotlib.image
import AxesImage
279 for a
in ax.mouseover_set:
280 if isinstance(a, AxesImage):
281 a.get_cursor_data =
lambda ev:
None 284 self.
_figure.canvas.draw_idle()
286 def _i_mtv(self, data, wcs, title, isMask):
287 """Internal routine to display an Image or Mask on a DS9 display""" 289 title = str(title)
if title
else "" 290 dataArr = data.getArray()
293 maskPlanes = data.getMaskPlaneDict()
294 nMaskPlanes =
max(maskPlanes.values()) + 1
297 for key
in maskPlanes:
298 planes[maskPlanes[key]] = key
300 planeList = range(nMaskPlanes)
302 maskArr = np.zeros_like(dataArr, dtype=np.int32)
304 colorNames = [
'black']
305 colorGenerator = self.display.maskColorGenerator(omitBW=
True)
310 color =
next(colorGenerator)
311 elif color.lower() == afwDisplay.IGNORE:
314 colorNames.append(color)
323 colors = mpColors.to_rgba_array(colorNames)
325 colors[0][alphaChannel] = 0.0
326 for i, p
in enumerate(planeList):
327 if colorNames[i + 1] ==
'black':
332 colors[i + 1][alphaChannel] = alpha
334 cmap = mpColors.ListedColormap(colors)
335 norm = mpColors.NoNorm()
341 bbox = data.getBBox()
342 extent = (bbox.getBeginX() - 0.5, bbox.getEndX() - 0.5,
343 bbox.getBeginY() - 0.5, bbox.getEndY() - 0.5)
345 with pyplot.rc_context(dict(interactive=
False)):
347 for i, p
in reversed(
list(enumerate(planeList))):
348 if colors[i + 1][alphaChannel] == 0:
351 bitIsSet = (dataArr & (1 << p)) != 0
352 if bitIsSet.sum() == 0:
355 maskArr[bitIsSet] = i + 1
358 ax.imshow(maskArr, origin=
'lower', interpolation=
'nearest',
359 extent=extent, cmap=cmap, norm=norm)
363 ax.imshow(maskArr, origin=
'lower', interpolation=
'nearest',
364 extent=extent, cmap=cmap, norm=norm)
366 mappable = ax.imshow(dataArr, origin=
'lower', interpolation=
'nearest',
367 extent=extent, cmap=cmap, norm=norm)
370 self.
_figure.canvas.draw_idle()
372 def _i_setImage(self, image, mask=None, wcs=None):
373 """Save the current image, mask, wcs, and XY0""" 381 self._width, self.
_height = 0, 0
385 self.
_xcen = 0.5*self._width
388 def _setImageColormap(self, cmap):
389 """Set the colormap used for the image 391 cmap should be either the name of an attribute of pyplot.cm or an 392 mpColors.Colormap (e.g. "gray" or pyplot.cm.gray) 395 if not isinstance(cmap, mpColors.Colormap):
396 cmap = getattr(pyplot.cm, cmap)
404 def _buffer(self, enable=True):
415 """Erase the display""" 424 zoomfac = self._zoomfac
428 self._mtv(self._image, mask=self._mask, wcs=self._wcs, title=self._title)
434 self._figure.canvas.draw_idle()
436 def _dot(self, symb, c, r, size, ctype,
437 fontFamily="helvetica", textAngle=None):
438 """Draw a symbol at (col,row) = (c,r) [0-based coordinates] 444 @:Mxx,Mxy,Myy Draw an ellipse with moments 445 (Mxx, Mxy, Myy) (argument size is ignored) 446 An afwGeom.ellipses.Axes Draw the ellipse (argument size is 449 Any other value is interpreted as a string to be drawn. Strings obey the 450 fontFamily (which may be extended with other characteristics, e.g. 451 "times bold italic". Text will be drawn rotated by textAngle 452 (textAngle is ignored otherwise). 455 ctype = afwDisplay.GREEN
457 axis = self._figure.gca()
460 if isinstance(symb, afwGeom.ellipses.Axes):
461 from matplotlib.patches
import Ellipse
466 axis.add_artist(Ellipse((c + x0, r + y0), height=2*symb.getA(), width=2*symb.getB(),
467 angle=90.0 + math.degrees(symb.getTheta()),
468 edgecolor=
mapCtype(ctype), facecolor=
'none'))
470 from matplotlib.patches
import CirclePolygon
as Circle
472 axis.add_artist(Circle((c + x0, r + y0), radius=size, color=
mapCtype(ctype), fill=
False))
474 from matplotlib.lines
import Line2D
476 for ds9Cmd
in ds9Regions.dot(symb, c + x0, r + y0, size, fontFamily=
"helvetica", textAngle=
None):
477 tmp = ds9Cmd.split(
'#')
478 cmd = tmp.pop(0).split()
480 cmd, args = cmd[0], cmd[1:]
483 args = np.array(args).astype(float) - 1.0
485 x = np.empty(len(args)//2)
487 i = np.arange(len(args), dtype=int)
491 axis.add_line(Line2D(x, y, color=
mapCtype(ctype)))
493 x, y = np.array(args[0:2]).astype(float) - 1.0
494 axis.text(x, y, symb, color=
mapCtype(ctype),
495 horizontalalignment=
'center', verticalalignment=
'center')
497 raise RuntimeError(ds9Cmd)
499 def _drawLines(self, points, ctype):
500 """Connect the points, a list of (col,row) 501 Ctype is the name of a colour (e.g. 'red')""" 503 from matplotlib.lines
import Line2D
506 ctype = afwDisplay.GREEN
508 points = np.array(points)
509 x = points[:, 0] + self._xy0[0]
510 y = points[:, 1] + self._xy0[1]
512 self._figure.gca().add_line(Line2D(x, y, color=
mapCtype(ctype)))
514 def _scale(self, algorithm, minval, maxval, unit, *args, **kwargs):
518 self._scaleArgs[
'algorithm'] = algorithm
519 self._scaleArgs[
'minval'] = minval
520 self._scaleArgs[
'maxval'] = maxval
521 self._scaleArgs[
'unit'] = unit
522 self._scaleArgs[
'args'] = args
523 self._scaleArgs[
'kwargs'] = kwargs
526 self._i_scale(algorithm, minval, maxval, unit, *args, **kwargs)
527 except (AttributeError, RuntimeError):
531 def _i_scale(self, algorithm, minval, maxval, unit, *args, **kwargs):
532 if minval ==
"minmax":
533 if self._image
is None:
534 raise RuntimeError(
"You may only use minmax if an image is loaded into the display")
537 minval = stats.getValue(afwMath.MIN)
538 maxval = stats.getValue(afwMath.MAX)
540 if algorithm
is None:
541 self._normalize =
None 542 elif algorithm ==
"asinh":
543 if minval ==
"zscale":
544 if self._image
is None:
545 raise RuntimeError(
"You may only use zscale if an image is loaded into the display")
547 self._normalize = AsinhZScaleNormalize(image=self._image, Q=kwargs.get(
"Q", 8.0))
549 self._normalize = AsinhNormalize(minimum=minval,
550 dataRange=maxval - minval, Q=kwargs.get(
"Q", 8.0))
551 elif algorithm ==
"linear":
552 if minval ==
"zscale":
553 if self._image
is None:
554 raise RuntimeError(
"You may only use zscale if an image is loaded into the display")
556 self._normalize = ZScaleNormalize(image=self._image,
557 nSamples=kwargs.get(
"nSamples", 1000),
558 contrast=kwargs.get(
"contrast", 0.25))
560 self._normalize = LinearNormalize(minimum=minval, maximum=maxval)
562 raise RuntimeError(
"Unsupported stretch algorithm \"%s\"" % algorithm)
567 def _zoom(self, zoomfac):
568 """Zoom by specified amount""" 570 self._zoomfac = zoomfac
574 size =
min(self._width, self._height)
575 if size < self._zoomfac:
577 xmin, xmax = self._xcen + x0 + size/self._zoomfac*np.array([-1, 1])
578 ymin, ymax = self._ycen + y0 + size/self._zoomfac*np.array([-1, 1])
580 ax = self._figure.gca()
582 tb = self._figure.canvas.toolbar
586 ax.set_xlim(xmin, xmax)
587 ax.set_ylim(ymin, ymax)
588 ax.set_aspect(
'equal',
'datalim')
590 self._figure.canvas.draw_idle()
592 def _pan(self, colc, rowc):
593 """Pan to (colc, rowc)""" 598 self._zoom(self._zoomfac)
600 def _getEvent(self, timeout=-1):
601 """Listen for a key press, returning (key, x, y)""" 603 mpBackend = matplotlib.get_backend()
604 if mpBackend
not in interactiveBackends:
605 print(
"The %s matplotlib backend doesn't support display._getEvent()" %
606 (matplotlib.get_backend(),), file=sys.stderr)
607 return interface.Event(
'q')
609 blocking_input = BlockingKeyInput(self._figure)
610 return blocking_input(timeout=timeout)
617 Callable class to retrieve a single keyboard click 620 r"""Create a BlockingKeyInput 622 \param fig The figure to monitor for keyboard events 624 BlockingInput.__init__(self, fig=fig, eventslist=(
'key_press_event',))
628 Return the event containing the key and (x, y) 631 event = self.events[-1]
636 self.
ev = interface.Event(event.key, event.xdata, event.ydata)
640 Blocking call to retrieve a single key click 641 Returns key or None if timeout 645 BlockingInput.__call__(self, n=1, timeout=timeout)
653 """Class to support stretches for mtv()""" 657 Return a MaskedArray with value mapped to [0, 255] 659 @param value Input pixel value or array to be mapped 661 if isinstance(value, np.ndarray):
666 data = data - self.mapping.minimum[0]
667 return ma.array(data*self.mapping.mapIntensityToUint8(data)/255.0)
671 """Provide an asinh stretch for mtv()""" 673 """Initialise an object able to carry out an asinh mapping 675 @param minimum Minimum pixel value (default: 0) 676 @param dataRange Range of values for stretch if Q=0; roughly the 677 linear part (default: 1) 678 @param Q Softening parameter (default: 8) 680 See Lupton et al., PASP 116, 133 682 Normalize.__init__(self)
685 self.
mapping = afwRgb.AsinhMapping(minimum, dataRange, Q)
689 """Provide an asinh stretch using zscale to set limits for mtv()""" 691 """Initialise an object able to carry out an asinh mapping 693 @param image image to use estimate minimum and dataRange using zscale 695 @param Q Softening parameter (default: 8) 697 See Lupton et al., PASP 116, 133 699 Normalize.__init__(self)
702 self.
mapping = afwRgb.AsinhZScaleMapping(image, Q)
706 """Provide a zscale stretch for mtv()""" 707 def __init__(self, image=None, nSamples=1000, contrast=0.25):
708 """Initialise an object able to carry out a zscale mapping 710 @param image to be used to estimate the stretch 711 @param nSamples Number of data points to use (default: 1000) 712 @param contrast Control the range of pixels to display around the 713 median (default: 0.25) 716 Normalize.__init__(self)
719 self.
mapping = afwRgb.ZScaleMapping(image, nSamples, contrast)
723 """Provide a linear stretch for mtv()""" 725 """Initialise an object able to carry out a linear mapping 727 @param minimum Minimum value to display 728 @param maximum Maximum value to display 731 Normalize.__init__(self)
734 self.
mapping = afwRgb.LinearMapping(minimum, maximum)
def __init__(self, minimum=0, maximum=1)
def _i_mtv(self, data, wcs, title, isMask)
def __init__(self, display, verbose=False, interpretMaskBits=True, mtvOrigin=afwImage.PARENT, fastMaskDisplay=True, reopenPlot=False, args, kwargs)
def _i_scale(self, algorithm, minval, maxval, unit, args, kwargs)
def savefig(self, args, kwargs)
Statistics makeStatistics(lsst::afw::math::MaskedVector< EntryT > const &mv, std::vector< WeightPixel > const &vweights, int const flags, StatisticsControl const &sctrl=StatisticsControl())
The makeStatistics() overload to handle lsst::afw::math::MaskedVector<>
def __init__(self, image=None, Q=8)
def getMaskPlaneColor(name, frame=None)
def _i_setImage(self, image, mask=None, wcs=None)
def wait(self, prompt="[c(ontinue) p(db)] :", allowPdb=True)
def __init__(self, image=None, nSamples=1000, contrast=0.25)
def show_colorbar(self, show=True)
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects...
def _getMaskTransparency(self, maskplane=None)
def __init__(self, minimum=0, dataRange=1, Q=8)
def __call__(self, value, clip=None)
daf::base::PropertyList * list