27 from __future__
import absolute_import, division, print_function
28 from builtins
import str
29 from builtins
import next
30 from builtins
import range
31 from builtins
import object
32 from past.builtins
import long
34 __all__ = [
"Ds9Error",
"getXpaAccessPoint",
"ds9Version",
"Buffer",
35 "selectFrame",
"ds9Cmd",
"initDS9",
"Ds9Event",
"DisplayImpl"]
44 import lsst.afw.display.interface
as interface
45 import lsst.afw.display.virtualDevice
as virtualDevice
46 import lsst.afw.display.ds9Regions
as ds9Regions
49 from .
import xpa
as xpa
50 except ImportError
as e:
51 print(
"Cannot import xpa: %s" % (e), file=sys.stderr)
53 import lsst.afw.display.displayLib
as displayLib
54 import lsst.afw.math
as afwMath
65 """Some problem talking to ds9""" 70 _maskTransparency =
None 76 """Parse XPA_PORT and send return an identifier to send ds9 commands there, instead of "ds9" 77 If you don't have XPA_PORT set, the usual xpans tricks will be played when we return "ds9". 79 xpa_port = os.environ.get(
"XPA_PORT")
81 mat = re.search(
r"^DS9:ds9\s+(\d+)\s+(\d+)", xpa_port)
83 port1, port2 = mat.groups()
85 return "127.0.0.1:%s" % (port1)
87 print(
"Failed to parse XPA_PORT=%s" % xpa_port, file=sys.stderr)
93 """Return the version of ds9 in use, as a string""" 95 v =
ds9Cmd(
"about", get=
True)
96 return v.splitlines()[1].split()[1]
97 except Exception
as e:
98 print(
"Error reading version: %s" % e, file=sys.stderr)
104 XPA_SZ_LINE = 4096 - 100
107 """Control buffering the sending of commands to ds9; 108 annoying but necessary for anything resembling performance 110 The usual usage pattern (from a module importing this file, ds9.py) is: 112 with ds9.Buffering(): 113 # bunches of ds9.{dot,line} commands 115 # bunches more ds9.{dot,line} commands 119 """Create a command buffer, with a maximum depth of size""" 126 def set(self, size, silent=True):
127 """Set the ds9 buffer size to size""" 129 size = XPA_SZ_LINE - 5
131 if size > XPA_SZ_LINE:
132 print (
"xpa silently hardcodes a limit of %d for buffer sizes (you asked for %d) " %
133 (XPA_SZ_LINE, size), file=sys.stderr)
142 self.
flush(silent=silent)
145 """Get the current ds9 buffer size""" 149 """Replace current ds9 command buffer size with size (see also popSize) 150 @param: Size of buffer (-1: largest possible given bugs in xpa)""" 151 self.
flush(silent=
True)
153 self.
set(size, silent=
True)
156 """Switch back to the previous command buffer size (see also pushSize)""" 157 self.
flush(silent=
True)
163 """Flush the pending commands""" 164 ds9Cmd(flush=
True, silent=silent)
170 return "frame %d" % (frame)
173 def ds9Cmd(cmd=None, trap=True, flush=False, silent=True, frame=None, get=False):
174 """Issue a ds9 command, raising errors as appropriate""" 178 if frame
is not None:
185 if cmdBuffer._lenCommands + len(cmd) > XPA_SZ_LINE - 5:
186 ds9Cmd(flush=
True, silent=silent)
188 cmdBuffer._commands +=
";" + cmd
189 cmdBuffer._lenCommands += 1 + len(cmd)
191 if flush
or cmdBuffer._lenCommands >= cmdBuffer._getSize():
192 cmd = (cmdBuffer._commands +
"\n")
193 cmdBuffer._commands =
"" 194 cmdBuffer._lenCommands = 0
208 raise Ds9Error(
"XPA: %s, (%s)" % (e, cmd))
210 print(
"Caught ds9 exception processing command \"%s\": %s" % (cmd, e), file=sys.stderr)
216 ds9Cmd(
"iconify no; raise",
False)
224 needShow = (int(v1) <= 4)
227 except Ds9Error
as e:
228 if not re.search(
'xpa', os.environ[
'PATH']):
229 raise Ds9Error(
'You need the xpa binaries in your path to use ds9 with python')
234 import distutils.spawn
235 if not distutils.spawn.find_executable(
"ds9"):
236 raise NameError(
"ds9 doesn't appear to be on your path")
237 if not "DISPLAY" in os.environ:
238 raise RuntimeError(
"$DISPLAY isn't set, so I won't be able to start ds9 for you")
240 print(
"ds9 doesn't appear to be running (%s), I'll try to exec it for you" % e)
248 print(
"waiting for ds9...\r", end=
"")
261 """An event generated by a mouse or key click on the display""" 264 interface.Event.__init__(self, k, x, y)
271 def __init__(self, display, verbose=False, *args, **kwargs):
272 virtualDevice.DisplayImpl.__init__(self, display, verbose)
275 """Called when the device is closed""" 278 def _setMaskTransparency(self, transparency, maskplane):
279 """Specify ds9's mask transparency (percent); or None to not set it when loading masks""" 280 if maskplane !=
None:
281 print(
"ds9 is unable to set transparency for individual maskplanes" % maskplane,
284 ds9Cmd(
"mask transparency %d" % transparency, frame=self.display.frame)
286 def _getMaskTransparency(self, maskplane):
287 """Return the current ds9's mask transparency""" 290 return float(
ds9Cmd(
"mask transparency", get=
True))
293 """Uniconify and Raise ds9. N.b. throws an exception if frame doesn't exit""" 294 ds9Cmd(
"raise", trap=
False, frame=self.display.frame)
296 def _mtv(self, image, mask=None, wcs=None, title=""):
297 """Display an Image and/or Mask on a DS9 display 304 print(
"waiting for ds9...\r", end=
"")
318 _i_mtv(image, wcs, title,
False)
321 maskPlanes = mask.getMaskPlaneDict()
322 nMaskPlanes = max(maskPlanes.values()) + 1
325 for key
in maskPlanes:
326 planes[maskPlanes[key]] = key
328 planeList = range(nMaskPlanes)
329 usedPlanes = long(afwMath.makeStatistics(mask, afwMath.SUM).getValue())
330 mask1 = mask.Factory(mask.getDimensions())
332 colorGenerator = self.display.maskColorGenerator(omitBW=
True)
337 if not ((1 << p) & usedPlanes):
343 color = self.display.getMaskPlaneColor(pname)
346 color = next(colorGenerator)
347 elif color.lower() ==
"ignore":
350 ds9Cmd(
"mask color %s" % color)
351 _i_mtv(mask1, wcs, title,
True)
356 def _buffer(self, enable=True):
366 """Erase the specified DS9 frame""" 367 ds9Cmd(
"regions delete all", flush=
True, frame=self.display.frame)
369 def _dot(self, symb, c, r, size, ctype, fontFamily="helvetica", textAngle=None):
370 """Draw a symbol onto the specified DS9 frame at (col,row) = (c,r) [0-based coordinates] 376 @:Mxx,Mxy,Myy Draw an ellipse with moments (Mxx, Mxy, Myy) (argument size is ignored) 377 An object derived from afwGeom.ellipses.BaseCore Draw the ellipse (argument size is ignored) 378 Any other value is interpreted as a string to be drawn. Strings obey the fontFamily (which may be extended 379 with other characteristics, e.g. "times bold italic". Text will be drawn rotated by textAngle (textAngle is 382 N.b. objects derived from BaseCore include Axes and Quadrupole. 385 for region
in ds9Regions.dot(symb, c, r, size, ctype, fontFamily, textAngle):
386 cmd +=
'regions command {%s}; ' % region
390 def _drawLines(self, points, ctype):
391 """Connect the points, a list of (col,row) 392 Ctype is the name of a colour (e.g. 'red')""" 395 for region
in ds9Regions.drawLines(points, ctype):
396 cmd +=
'regions command {%s}; ' % region
403 def _scale(self, algorithm, min, max, unit, *args, **kwargs):
405 ds9Cmd(
"scale %s" % algorithm, frame=self.display.frame)
407 if min
in (
"minmax",
"zscale"):
408 ds9Cmd(
"scale mode %s" % (min))
411 print(
"ds9: ignoring scale unit %s" % unit)
413 ds9Cmd(
"scale limits %g %g" % (min, max), frame=self.display.frame)
418 def _zoom(self, zoomfac):
419 """Zoom frame by specified amount""" 422 cmd +=
"zoom to %d; " % zoomfac
426 def _pan(self, colc, rowc):
427 """Pan frame to (colc, rowc)""" 430 cmd +=
"pan to %g %g physical; " % (colc + 1, rowc + 1)
435 """Listen for a key press on frame in ds9, returning (key, x, y)""" 437 vals =
ds9Cmd(
"imexam key coordinate", get=
True).split()
438 if vals[0] ==
"XPA$ERROR":
439 if vals[1:4] == [
'unknown',
'option',
'"-state"']:
442 print(
"Error return from imexam:",
" ".join(vals), file=sys.stderr)
460 haveGzip =
not os.system(
"gzip < /dev/null > /dev/null 2>&1")
463 def _i_mtv(data, wcs, title, isMask):
464 """Internal routine to display an Image or Mask on a DS9 display""" 466 title = str(title)
if title
else "" 473 if data.getArray().dtype == np.uint16:
479 xpa_cmd =
"gzip | " + xpa_cmd
481 pfd = os.popen(xpa_cmd,
"w")
483 pfd =
file(
"foo.fits",
"w")
485 ds9Cmd(flush=
True, silent=
True)
488 displayLib.writeFitsImage(pfd.fileno(), data, wcs, title)
489 except Exception
as e:
506 definedCallbacks =
True 508 for k
in (
'XPA$ERROR',):
509 interface.setCallback(k)
def initDS9(execDs9=True)
def set(self, size, silent=True)
def ds9Cmd(cmd=None, trap=True, flush=False, silent=True, frame=None, get=False)
def __init__(self, size=0)
def pushSize(self, size=-1)
def __init__(self, k, x, y)
def __init__(self, display, verbose=False, args, kwargs)
def flush(self, silent=True)