27 from __future__
import absolute_import, division, print_function
34 import matplotlib.pyplot
as pyplot
35 import matplotlib.cbook
36 import matplotlib.colors
as mpColors
37 from matplotlib.blocking_input
import BlockingInput
60 interactiveBackends = [
69 afwDisplay.GREEN :
"#00FF00",
73 """Map the ctype to a potentially different ctype 75 Specifically, if matplotlibCtypes[ctype] exists, use it instead 77 This is used e.g. to map "green" to a brighter shade 79 return matplotlibCtypes[ctype]
if ctype
in matplotlibCtypes
else ctype
83 """Provide a matplotlib backend for afwDisplay 85 Recommended backends in notebooks are: 97 Apparently only qt supports Display.interact(); the list of interactive backends 98 is given by lsst.display.matplotlib.interactiveBackends 100 def __init__(self, display, verbose=False,
101 interpretMaskBits=True, mtvOrigin=afwImage.PARENT, fastMaskDisplay=True,
102 reopenPlot=False, *args, **kwargs):
104 Initialise a matplotlib display 106 @param fastMaskDisplay If True, only show the first bitplane that's set in each pixel 107 (e.g. if (SATURATED & DETECTED), ignore 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) 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")
143 warnings.filterwarnings(
"ignore", category=matplotlib.cbook.mplDeprecation)
146 """!Close the display, cleaning up any allocated resources""" 150 self.
_figure.gca().format_coord =
None 153 """Put the plot at the top of the window stacking order""" 156 self.
_figure.canvas._tkcanvas._root().lift()
157 except AttributeError:
161 self.
_figure.canvas.manager.window.raise_()
162 except AttributeError:
167 except AttributeError:
174 """Defer to figure.savefig()""" 178 """Show (or hide) the colour bar""" 183 def wait(self, prompt="[c(ontinue) p(db)] :
", allowPdb=True): 184 """Wait for keyboard input 188 @param allowPdb `bool` 189 If true, entering a 'p' or 'pdb' puts you into pdb 191 Returns the string you entered 193 Useful when plotting from a programme that exits such as a processCcd 194 Any key except 'p' continues; 'p' puts you into pdb (unless allowPdb is False) 198 if allowPdb
and s
in (
"p",
"pdb"):
208 def _setMaskTransparency(self, transparency, maskplane):
209 """Specify mask transparency (percent)""" 213 def _getMaskTransparency(self, maskplane=None):
214 """Return the current mask transparency""" 217 def _mtv(self, image, mask=None, wcs=None, title=""):
218 """Display an Image and/or Mask on a matplotlib display 220 title = str(title)
if title
else "" 235 self.
_i_mtv(image, wcs, title,
False)
239 self.
_i_mtv(mask, wcs, title,
True)
246 def format_coord(x, y, wcs=self._wcs, x0=self._xy0[0], y0=self._xy0[1],
247 origin=afwImage.PARENT, bbox=self._image.getBBox(afwImage.PARENT)):
249 fmt =
'(%1.2f, %1.2f)' 253 msg = (fmt +
"L") % (x - x0, y - y0)
259 ra, dec = wcs.pixelToSky(x, y)
260 msg +=
r" (%s, %s): (%9.4f, %9.4f)" % (self.
__alpha, self.
__delta, ra, dec)
262 msg +=
' %1.3f' % (self.
_image[col, row])
264 val = self.
_mask[col, row]
266 msg +=
" [%s]" % self.
_mask.interpret(val)
272 ax.format_coord = format_coord
274 from matplotlib.image
import AxesImage
275 for a
in ax.mouseover_set:
276 if isinstance(a, AxesImage):
277 a.get_cursor_data =
lambda ev:
None 280 self.
_figure.canvas.draw_idle()
282 def _i_mtv(self, data, wcs, title, isMask):
283 """Internal routine to display an Image or Mask on a DS9 display""" 285 title = str(title)
if title
else "" 286 dataArr = data.getArray()
289 maskPlanes = data.getMaskPlaneDict()
290 nMaskPlanes =
max(maskPlanes.values()) + 1
293 for key
in maskPlanes:
294 planes[maskPlanes[key]] = key
296 planeList = range(nMaskPlanes)
298 maskArr = np.zeros_like(dataArr, dtype=np.int32)
300 colorNames = [
'black']
301 colorGenerator = self.display.maskColorGenerator(omitBW=
True)
306 color =
next(colorGenerator)
307 elif color.lower() == afwDisplay.IGNORE:
310 colorNames.append(color)
318 colors = mpColors.to_rgba_array(colorNames)
320 colors[0][alphaChannel] = 0.0
321 for i, p
in enumerate(planeList):
322 if colorNames[i + 1] ==
'black':
327 colors[i + 1][alphaChannel] = alpha
329 cmap = mpColors.ListedColormap(colors)
330 norm = mpColors.NoNorm()
336 bbox = data.getBBox()
337 extent = (bbox.getBeginX() - 0.5, bbox.getEndX() - 0.5,
338 bbox.getBeginY() - 0.5, bbox.getEndY() - 0.5)
340 with pyplot.rc_context(dict(interactive=
False)):
342 for i, p
in reversed(
list(enumerate(planeList))):
343 if colors[i + 1][alphaChannel] == 0:
346 bitIsSet = (dataArr & (1 << p)) != 0
347 if bitIsSet.sum() == 0:
350 maskArr[bitIsSet] = i + 1
353 ax.imshow(maskArr, origin=
'lower', interpolation=
'nearest',
354 extent=extent, cmap=cmap, norm=norm)
358 ax.imshow(maskArr, origin=
'lower', interpolation=
'nearest',
359 extent=extent, cmap=cmap, norm=norm)
361 mappable = ax.imshow(dataArr, origin=
'lower', interpolation=
'nearest',
362 extent=extent, cmap=cmap, norm=norm)
365 self.
_figure.canvas.draw_idle()
367 def _i_setImage(self, image, mask=None, wcs=None):
368 """Save the current image, mask, wcs, and XY0""" 376 self._width, self.
_height = 0, 0
380 self.
_xcen = 0.5*self._width
383 def _setImageColormap(self, cmap):
384 """Set the colormap used for the image 386 cmap should be either the name of an attribute of pyplot.cm or an mpColors.Colormap 387 (e.g. "gray" or pyplot.cm.gray) 390 if not isinstance(cmap, mpColors.Colormap):
391 cmap = getattr(pyplot.cm, cmap)
399 def _buffer(self, enable=True):
410 """Erase the display""" 419 zoomfac = self._zoomfac
423 self._mtv(self._image, mask=self._mask, wcs=self._wcs, title=self._title)
429 self._figure.canvas.draw_idle()
431 def _dot(self, symb, c, r, size, ctype,
432 fontFamily="helvetica", textAngle=None):
433 """Draw a symbol at (col,row) = (c,r) [0-based coordinates] 439 @:Mxx,Mxy,Myy Draw an ellipse with moments (Mxx, Mxy, Myy) (argument size is ignored) 440 An afwGeom.ellipses.Axes Draw the ellipse (argument size is ignored) 441 Any other value is interpreted as a string to be drawn. Strings obey the fontFamily (which may be extended 442 with other characteristics, e.g. "times bold italic". Text will be drawn rotated by textAngle 443 (textAngle is ignored otherwise). 446 ctype = afwDisplay.GREEN
448 axis = self._figure.gca()
451 if isinstance(symb, afwGeom.ellipses.Axes):
452 from matplotlib.patches
import Ellipse
456 axis.add_artist(Ellipse((c + x0, r + y0), height=2*symb.getA(), width=2*symb.getB(),
457 angle=90.0 + math.degrees(symb.getTheta()),
458 edgecolor=
mapCtype(ctype), facecolor=
'none'))
460 from matplotlib.patches
import CirclePolygon
as Circle
462 axis.add_artist(Circle((c + x0, r + y0), radius=size, color=
mapCtype(ctype), fill=
False))
464 from matplotlib.lines
import Line2D
466 for ds9Cmd
in ds9Regions.dot(symb, c + x0, r + y0, size, fontFamily=
"helvetica", textAngle=
None):
467 tmp = ds9Cmd.split(
'#')
468 cmd = tmp.pop(0).split()
469 comment = tmp.pop(0)
if tmp
else "" 471 cmd, args = cmd[0], cmd[1:]
474 args = np.array(args).astype(float) - 1.0
476 x = np.empty(len(args)//2)
478 i = np.arange(len(args), dtype=int)
482 axis.add_line(Line2D(x, y, color=
mapCtype(ctype)))
484 x, y = np.array(args[0:2]).astype(float) - 1.0
485 axis.text(x, y, symb, color=
mapCtype(ctype),
486 horizontalalignment=
'center', verticalalignment=
'center')
488 raise RuntimeError(ds9Cmd)
490 def _drawLines(self, points, ctype):
491 """Connect the points, a list of (col,row) 492 Ctype is the name of a colour (e.g. 'red')""" 494 from matplotlib.lines
import Line2D
497 ctype = afwDisplay.GREEN
499 points = np.array(points)
500 x = points[:, 0] + self._xy0[0]
501 y = points[:, 1] + self._xy0[1]
503 self._figure.gca().add_line(Line2D(x, y, color=
mapCtype(ctype)))
505 def _scale(self, algorithm, minval, maxval, unit, *args, **kwargs):
509 self._scaleArgs[
'algorithm'] = algorithm
510 self._scaleArgs[
'minval'] = minval
511 self._scaleArgs[
'maxval'] = maxval
512 self._scaleArgs[
'unit'] = unit
513 self._scaleArgs[
'args'] = args
514 self._scaleArgs[
'kwargs'] = kwargs
517 self._i_scale(algorithm, minval, maxval, unit, *args, **kwargs)
518 except (AttributeError, RuntimeError):
522 def _i_scale(self, algorithm, minval, maxval, unit, *args, **kwargs):
523 if minval ==
"minmax":
524 if self._image
is None:
525 raise RuntimeError(
"You may only use minmax if an image is loaded into the display")
528 minval = stats.getValue(afwMath.MIN)
529 maxval = stats.getValue(afwMath.MAX)
531 if algorithm
is None:
532 self._normalize =
None 533 elif algorithm ==
"asinh":
534 if minval ==
"zscale":
535 if self._image
is None:
536 raise RuntimeError(
"You may only use zscale if an image is loaded into the display")
538 self._normalize = AsinhZScaleNormalize(image=self._image, Q=kwargs.get(
"Q", 8.0))
540 self._normalize = AsinhNormalize(minimum=minval,
541 dataRange=maxval - minval, Q=kwargs.get(
"Q", 8.0))
542 elif algorithm ==
"linear":
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 = ZScaleNormalize(image=self._image,
548 nSamples=kwargs.get(
"nSamples", 1000),
549 contrast=kwargs.get(
"contrast", 0.25))
551 self._normalize = LinearNormalize(minimum=minval, maximum=maxval)
553 raise RuntimeError(
"Unsupported stretch algorithm \"%s\"" % algorithm)
558 def _zoom(self, zoomfac):
559 """Zoom by specified amount""" 561 self._zoomfac = zoomfac
565 size =
min(self._width, self._height)
566 if size < self._zoomfac:
568 xmin, xmax = self._xcen + x0 + size/self._zoomfac*np.array([-1, 1])
569 ymin, ymax = self._ycen + y0 + size/self._zoomfac*np.array([-1, 1])
571 ax = self._figure.gca()
573 tb = self._figure.canvas.toolbar
577 ax.set_xlim(xmin, xmax)
578 ax.set_ylim(ymin, ymax)
579 ax.set_aspect(
'equal',
'datalim')
581 self._figure.canvas.draw_idle()
583 def _pan(self, colc, rowc):
584 """Pan to (colc, rowc)""" 589 self._zoom(self._zoomfac)
591 def _getEvent(self, timeout=-1):
592 """Listen for a key press, returning (key, x, y)""" 594 mpBackend = matplotlib.get_backend()
595 if mpBackend
not in interactiveBackends:
596 print(
"The %s matplotlib backend doesn't support display._getEvent()" %
597 (matplotlib.get_backend(),), file=sys.stderr)
598 return interface.Event(
'q')
600 blocking_input = BlockingKeyInput(self._figure)
601 return blocking_input(timeout=timeout)
608 Callable class to retrieve a single keyboard click 611 r"""Create a BlockingKeyInput 613 \param fig The figure to monitor for keyboard events 615 BlockingInput.__init__(self, fig=fig, eventslist=(
'key_press_event',))
619 Return the event containing the key and (x, y) 622 event = self.events[-1]
627 self.
ev = interface.Event(event.key, event.xdata, event.ydata)
631 Blocking call to retrieve a single key click 632 Returns key or None if timeout 636 BlockingInput.__call__(self, n=1, timeout=timeout)
644 """Class to support stretches for mtv()""" 648 Return a MaskedArray with value mapped to [0, 255] 650 @param value Input pixel value or array to be mapped 652 if isinstance(value, np.ndarray):
657 data = data - self.mapping.minimum[0]
658 return ma.array(data*self.mapping.mapIntensityToUint8(data)/255.0)
662 """Provide an asinh stretch for mtv()""" 664 """Initialise an object able to carry out an asinh mapping 666 @param minimum Minimum pixel value (default: 0) 667 @param dataRange Range of values for stretch if Q=0; roughly the linear part (default: 1) 668 @param Q Softening parameter (default: 8) 670 See Lupton et al., PASP 116, 133 672 Normalize.__init__(self)
675 self.
mapping = afwRgb.AsinhMapping(minimum, dataRange, Q)
679 """Provide an asinh stretch using zscale to set limits for mtv()""" 681 """Initialise an object able to carry out an asinh mapping 683 @param image image to use estimate minimum and dataRange using zscale (see AsinhNormalize) 684 @param Q Softening parameter (default: 8) 686 See Lupton et al., PASP 116, 133 688 Normalize.__init__(self)
691 self.
mapping = afwRgb.AsinhZScaleMapping(image, Q)
695 """Provide a zscale stretch for mtv()""" 696 def __init__(self, image=None, nSamples=1000, contrast=0.25):
697 """Initialise an object able to carry out a zscale mapping 699 @param image to be used to estimate the stretch 700 @param nSamples Number of data points to use (default: 1000) 701 @param contrast Control the range of pixels to display around the median (default: 0.25) 704 Normalize.__init__(self)
707 self.
mapping = afwRgb.ZScaleMapping(image, nSamples, contrast)
711 """Provide a linear stretch for mtv()""" 713 """Initialise an object able to carry out a linear mapping 715 @param minimum Minimum value to display 716 @param maximum Maximum value to display 719 Normalize.__init__(self)
722 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