22__all__ = [
"Ds9Error",
"getXpaAccessPoint",
"ds9Version",
"Buffer",
23 "selectFrame",
"ds9Cmd",
"initDS9",
"Ds9Event",
"DisplayImpl"]
34import lsst.afw.display.interface
as interface
35import lsst.afw.display.virtualDevice
as virtualDevice
36import lsst.afw.display.ds9Regions
as ds9Regions
39 from .
import xpa
as xpa
40except ImportError
as e:
41 print(f
"Cannot import xpa: {e}", file=sys.stderr)
43import lsst.afw.display
as afwDisplay
44import lsst.afw.math
as afwMath
53 """Represents an error communicating with DS9.
60 _maskTransparency =
None
64 """Parse XPA_PORT if set and return an identifier to send DS9 commands.
69 xpaAccessPoint : `str`
70 Either a reference to the local host with the configured port, or the
75 If you don't have XPA_PORT set, the usual xpans tricks will be played
76 when we return ``"ds9"``.
78 xpa_port = os.environ.get(
"XPA_PORT")
80 mat = re.search(
r"^DS9:ds9\s+(\d+)\s+(\d+)", xpa_port)
82 port1, port2 = mat.groups()
84 return f
"127.0.0.1:{port1}"
86 print(f
"Failed to parse XPA_PORT={xpa_port}", file=sys.stderr)
92 """Get the version of DS9 in use.
97 Version of DS9 in use.
100 v =
ds9Cmd(
"about", get=
True)
101 return v.splitlines()[1].split()[1]
102 except Exception
as e:
103 print(f
"Error reading version: {e}", file=sys.stderr)
111 XPA_SZ_LINE = 4096 - 100
114 """Buffer to control sending commands to DS9.
118 The usual usage pattern is:
120 >>> with ds9.Buffering():
121 ... # bunches of ds9.{dot,line} commands
123 ... # bunches more ds9.{dot,line} commands
133 def set(self, size, silent=True):
134 """Set the ds9 buffer size to size.
139 Size of buffer. Requesting a negative size provides the
140 largest possible buffer given bugs in xpa.
141 silent : `bool`, optional
142 Do not print error messages (default `True`).
145 size = XPA_SZ_LINE - 5
147 if size > XPA_SZ_LINE:
148 print(
"xpa silently hardcodes a limit of %d for buffer sizes (you asked for %d) " %
149 (XPA_SZ_LINE, size), file=sys.stderr)
158 self.
flush(silent=silent)
161 """Get the current DS9 buffer size.
171 """Replace current DS9 command buffer size.
175 size : `int`, optional
176 Size of buffer. A negative value sets the largest possible
183 self.
flush(silent=
True)
185 self.
set(size, silent=
True)
188 """Switch back to the previous command buffer size.
194 self.
flush(silent=
True)
200 """Flush the pending commands.
204 silent : `bool`, optional
205 Do not print error messages.
207 ds9Cmd(flush=
True, silent=silent)
213 """Convert integer frame number to DS9 command syntax.
224 return f
"frame {frame}"
227def ds9Cmd(cmd=None, trap=True, flush=False, silent=True, frame=None, get=False):
228 """Issue a DS9 command, raising errors as appropriate.
232 cmd : `str`, optional
234 trap : `bool`, optional
236 flush : `bool`, optional
238 silent : `bool`, optional
239 Do not print trapped error messages.
240 frame : `int`, optional
241 Frame number on which to execute command.
242 get : `bool`, optional
247 if frame
is not None:
248 cmd = f
"{selectFrame(frame)};{cmd}"
255 if cmdBuffer._lenCommands + len(cmd) > XPA_SZ_LINE - 5:
256 ds9Cmd(flush=
True, silent=silent)
258 cmdBuffer._commands +=
";" + cmd
259 cmdBuffer._lenCommands += 1 + len(cmd)
261 if flush
or cmdBuffer._lenCommands >= cmdBuffer._getSize():
262 cmd = (cmdBuffer._commands +
"\n")
263 cmdBuffer._commands =
""
264 cmdBuffer._lenCommands = 0
278 raise Ds9Error(f
"XPA: {e}, ({cmd})")
280 print(f
"Caught ds9 exception processing command \"{cmd}\": {e}", file=sys.stderr)
288 execDs9 : `bool`, optional
289 If DS9 is not running, attempt to execute it.
293 ds9Cmd(
"iconify no; raise",
False)
301 needShow = (int(v1) <= 4)
304 except Ds9Error
as e:
305 if not re.search(
'xpa', os.environ[
'PATH']):
306 raise Ds9Error(
'You need the xpa binaries in your path to use ds9 with python')
311 if not shutil.which(
"ds9"):
312 raise NameError(
"ds9 doesn't appear to be on your path")
313 if "DISPLAY" not in os.environ:
314 raise RuntimeError(
"$DISPLAY isn't set, so I won't be able to start ds9 for you")
316 print(f
"ds9 doesn't appear to be running ({e}), I'll try to exec it for you")
324 print(
"waiting for ds9...\r", end=
"")
337 """An event generated by a mouse or key click on the display.
341 interface.Event.__init__(self, k, x, y)
345 """Virtual device display implementation.
348 def __init__(self, display, verbose=False, *args, **kwargs):
349 virtualDevice.DisplayImpl.__init__(self, display, verbose)
352 """Called when the device is closed.
357 """Specify DS9's mask transparency.
362 Percent transparency.
363 maskplane : `NoneType`
364 If `None`, transparency is enabled. Otherwise, this parameter is
367 if maskplane
is not None:
368 print(f
"ds9 is unable to set transparency for individual maskplanes ({maskplane})",
371 ds9Cmd(f
"mask transparency {transparency}", frame=self.display.frame)
374 """Return the current DS9's mask transparency.
379 This parameter does nothing.
382 return float(
ds9Cmd(
"mask transparency", get=
True))
385 """Uniconify and raise DS9.
389 Raises if ``self.display.frame`` doesn't exist.
391 ds9Cmd(
"raise", trap=
False, frame=self.display.frame)
393 def _mtv(self, image, mask=None, wcs=None, title="", metadata=None):
394 """Display an Image and/or Mask on a DS9 display.
398 image : subclass of `lsst.afw.image.Image`
400 mask : subclass of `lsst.afw.image.Mask`, optional
402 wcs : `lsst.afw.geom.SkyWcs`, optional
404 title : `str`, optional
406 metadata : `lsst.daf.base`, optional
414 print(
"waiting for ds9...\r", end=
"")
428 _i_mtv(image, wcs, title,
False, metadata=metadata)
431 maskPlanes = mask.getMaskPlaneDict()
432 nMaskPlanes = max(maskPlanes.values()) + 1
435 for key
in maskPlanes:
436 planes[maskPlanes[key]] = key
438 planeList = range(nMaskPlanes)
439 usedPlanes = int(afwMath.makeStatistics(mask, afwMath.SUM).getValue())
440 mask1 = mask.Factory(mask.getBBox())
442 colorGenerator = self.display.maskColorGenerator(omitBW=
True)
447 if not ((1 << p) & usedPlanes):
453 color = self.display.getMaskPlaneColor(pname)
456 color = next(colorGenerator)
457 elif color.lower() ==
"ignore":
460 ds9Cmd(f
"mask color {color}")
461 _i_mtv(mask1, wcs, title,
True, metadata=metadata)
467 """Push and pop buffer size.
471 enable : `bool`, optional
472 If `True` (default), push size; else pop it.
485 """Erase all regions in current frame.
487 ds9Cmd(
"regions delete all", flush=
True, frame=self.display.frame)
489 def _dot(self, symb, c, r, size, ctype, fontFamily="helvetica", textAngle=None):
490 """Draw a symbol onto the specified DS9 frame.
494 symb : `str`, or subclass of `lsst.afw.geom.ellipses.BaseCore`
495 Symbol to be drawn. Possible values are:
497 - ``"+"``: Draw a "+"
498 - ``"x"``: Draw an "x"
499 - ``"*"``: Draw a "*"
500 - ``"o"``: Draw a circle
501 - ``"@:Mxx,Mxy,Myy"``: Draw an ellipse with moments (Mxx, Mxy,
502 Myy);(the ``size`` parameter is ignored)
503 - An object derived from `lsst.afw.geom.ellipses.BaseCore`: Draw
504 the ellipse (argument size is ignored)
506 Any other value is interpreted as a string to be drawn.
508 Column to draw symbol [0-based coordinates].
510 Row to draw symbol [0-based coordinates].
514 the name of a colour (e.g. ``"red"``)
515 fontFamily : `str`, optional
516 String font. May be extended with other characteristics,
517 e.g. ``"times bold italic"``.
518 textAngle: `float`, optional
519 Text will be drawn rotated by ``textAngle``.
523 Objects derived from `lsst.afw.geom.ellipses.BaseCore` include
524 `~lsst.afw.geom.ellipses.Axes` and `lsst.afw.geom.ellipses.Quadrupole`.
527 for region
in ds9Regions.dot(symb, c, r, size, ctype, fontFamily, textAngle):
528 cmd += f
'regions command {{{region}}}; '
533 """Connect the points.
537 points : `list` of (`int`, `int`)
538 A list of points specified as (col, row).
540 The name of a colour (e.g. ``"red"``).
543 for region
in ds9Regions.drawLines(points, ctype):
544 cmd += f
'regions command {{{region}}}; '
548 def _scale(self, algorithm, min, max, unit, *args, **kwargs):
549 """Set image color scale.
553 algorithm : {``"linear"``, ``"log"``, ``"pow"``, ``"sqrt"``, ``"squared"``, ``"asinh"``, ``"sinh"``, ``"histequ"``} # noqa: E501
554 Scaling algorithm. May be any value supported by DS9.
556 Minimum value for scale.
558 Maximum value for scale.
567 ds9Cmd(f
"scale {algorithm}", frame=self.display.frame)
569 if min
in (
"minmax",
"zscale"):
570 ds9Cmd(f
"scale mode {min}")
573 print(f
"ds9: ignoring scale unit {unit}")
575 ds9Cmd(f
"scale limits {min:g} {max:g}", frame=self.display.frame)
581 """Zoom frame by specified amount.
589 cmd += f
"zoom to {zoomfac}; "
599 Physical column to which to pan.
601 Physical row to which to pan.
605 cmd += f
"pan to {colc + 1:g} {rowc + 1:g} physical; "
610 """Listen for a key press on a frame in DS9 and return an event.
615 Event with (key, x, y).
617 vals =
ds9Cmd(
"imexam key coordinate", get=
True).split()
618 if vals[0] ==
"XPA$ERROR":
619 if vals[1:4] == [
'unknown',
'option',
'"-state"']:
622 print(
"Error return from imexam:",
" ".join(vals), file=sys.stderr)
640 haveGzip =
not os.system(
"gzip < /dev/null > /dev/null 2>&1")
643def _i_mtv(data, wcs, title, isMask, metadata):
644 """Internal routine to display an image or a mask on a DS9 display.
648 data : Subclass of `lsst.afw.image.Image` or `lsst.afw.image.Mask`
650 wcs : `lsst.afw.geom.SkyWcs`
656 metadata : `lsst.daf.base.PropertySet`
659 title = str(title)
if title
else ""
662 xpa_cmd = f
"xpaset {getXpaAccessPoint()} fits mask"
666 if data.getArray().dtype == np.uint16:
669 xpa_cmd = f
"xpaset {getXpaAccessPoint()} fits"
672 xpa_cmd =
"gzip | " + xpa_cmd
674 with subprocess.Popen(xpa_cmd, stdin=subprocess.PIPE, shell=
True)
as pfd:
675 ds9Cmd(flush=
True, silent=
True)
676 afwDisplay.writeFitsImage(pfd, data, wcs, title, metadata)
set(self, size, silent=True)
_getMaskTransparency(self, maskplane)
__init__(self, display, verbose=False, *args, **kwargs)
_drawLines(self, points, ctype)
_buffer(self, enable=True)
_scale(self, algorithm, min, max, unit, *args, **kwargs)
_dot(self, symb, c, r, size, ctype, fontFamily="helvetica", textAngle=None)
_setMaskTransparency(self, transparency, maskplane)
_mtv(self, image, mask=None, wcs=None, title="", metadata=None)
ds9Cmd(cmd=None, trap=True, flush=False, silent=True, frame=None, get=False)
_i_mtv(data, wcs, title, isMask, metadata)