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
41 import lsst.afw.display.interface
as interface
42 import lsst.afw.display.virtualDevice
as virtualDevice
43 import lsst.afw.display.ds9Regions
as ds9Regions
46 from .
import xpa
as xpa
47 except ImportError
as e:
48 print(
"Cannot import xpa: %s" % (e), file=sys.stderr)
50 import lsst.afw.display.displayLib
as displayLib
51 import lsst.afw.math
as afwMath
62 """Some problem talking to ds9""" 67 _maskTransparency =
None 73 """Parse XPA_PORT and send return an identifier to send ds9 commands there, instead of "ds9" 74 If you don't have XPA_PORT set, the usual xpans tricks will be played when we return "ds9". 76 xpa_port = os.environ.get(
"XPA_PORT")
78 mat = re.search(
r"^DS9:ds9\s+(\d+)\s+(\d+)", xpa_port)
80 port1, port2 = mat.groups()
82 return "127.0.0.1:%s" % (port1)
84 print(
"Failed to parse XPA_PORT=%s" % xpa_port, file=sys.stderr)
90 """Return the version of ds9 in use, as a string""" 92 v =
ds9Cmd(
"about", get=
True)
93 return v.splitlines()[1].split()[1]
94 except Exception
as e:
95 print(
"Error reading version: %s" % e, file=sys.stderr)
101 XPA_SZ_LINE = 4096 - 100
104 """Control buffering the sending of commands to ds9; 105 annoying but necessary for anything resembling performance 107 The usual usage pattern (from a module importing this file, ds9.py) is: 109 with ds9.Buffering(): 110 # bunches of ds9.{dot,line} commands 112 # bunches more ds9.{dot,line} commands 116 """Create a command buffer, with a maximum depth of size""" 123 def set(self, size, silent=True):
124 """Set the ds9 buffer size to size""" 126 size = XPA_SZ_LINE - 5
128 if size > XPA_SZ_LINE:
129 print (
"xpa silently hardcodes a limit of %d for buffer sizes (you asked for %d) " %
130 (XPA_SZ_LINE, size), file=sys.stderr)
139 self.
flush(silent=silent)
142 """Get the current ds9 buffer size""" 146 """Replace current ds9 command buffer size with size (see also popSize) 147 @param: Size of buffer (-1: largest possible given bugs in xpa)""" 148 self.
flush(silent=
True)
150 self.
set(size, silent=
True)
153 """Switch back to the previous command buffer size (see also pushSize)""" 154 self.
flush(silent=
True)
160 """Flush the pending commands""" 161 ds9Cmd(flush=
True, silent=silent)
167 return "frame %d" % (frame)
170 def ds9Cmd(cmd=None, trap=True, flush=False, silent=True, frame=None, get=False):
171 """Issue a ds9 command, raising errors as appropriate""" 175 if frame
is not None:
182 if cmdBuffer._lenCommands + len(cmd) > XPA_SZ_LINE - 5:
183 ds9Cmd(flush=
True, silent=silent)
185 cmdBuffer._commands +=
";" + cmd
186 cmdBuffer._lenCommands += 1 + len(cmd)
188 if flush
or cmdBuffer._lenCommands >= cmdBuffer._getSize():
189 cmd = (cmdBuffer._commands +
"\n")
190 cmdBuffer._commands =
"" 191 cmdBuffer._lenCommands = 0
205 raise Ds9Error(
"XPA: %s, (%s)" % (e, cmd))
207 print(
"Caught ds9 exception processing command \"%s\": %s" % (cmd, e), file=sys.stderr)
213 ds9Cmd(
"iconify no; raise",
False)
221 needShow = (int(v1) <= 4)
224 except Ds9Error
as e:
225 if not re.search(
'xpa', os.environ[
'PATH']):
226 raise Ds9Error(
'You need the xpa binaries in your path to use ds9 with python')
231 import distutils.spawn
232 if not distutils.spawn.find_executable(
"ds9"):
233 raise NameError(
"ds9 doesn't appear to be on your path")
234 if not "DISPLAY" in os.environ:
235 raise RuntimeError(
"$DISPLAY isn't set, so I won't be able to start ds9 for you")
237 print(
"ds9 doesn't appear to be running (%s), I'll try to exec it for you" % e)
245 print(
"waiting for ds9...\r", end=
"")
258 """An event generated by a mouse or key click on the display""" 261 interface.Event.__init__(self, k, x, y)
268 def __init__(self, display, verbose=False, *args, **kwargs):
269 virtualDevice.DisplayImpl.__init__(self, display, verbose)
272 """Called when the device is closed""" 275 def _setMaskTransparency(self, transparency, maskplane):
276 """Specify ds9's mask transparency (percent); or None to not set it when loading masks""" 277 if maskplane !=
None:
278 print(
"ds9 is unable to set transparency for individual maskplanes" % maskplane,
281 ds9Cmd(
"mask transparency %d" % transparency, frame=self.display.frame)
283 def _getMaskTransparency(self, maskplane):
284 """Return the current ds9's mask transparency""" 287 return float(
ds9Cmd(
"mask transparency", get=
True))
290 """Uniconify and Raise ds9. N.b. throws an exception if frame doesn't exit""" 291 ds9Cmd(
"raise", trap=
False, frame=self.display.frame)
293 def _mtv(self, image, mask=None, wcs=None, title=""):
294 """Display an Image and/or Mask on a DS9 display 301 print(
"waiting for ds9...\r", end=
"")
315 _i_mtv(image, wcs, title,
False)
318 maskPlanes = mask.getMaskPlaneDict()
319 nMaskPlanes = max(maskPlanes.values()) + 1
322 for key
in maskPlanes:
323 planes[maskPlanes[key]] = key
325 planeList = range(nMaskPlanes)
326 usedPlanes = long(afwMath.makeStatistics(mask, afwMath.SUM).getValue())
327 mask1 = mask.Factory(mask.getDimensions())
329 colorGenerator = self.display.maskColorGenerator(omitBW=
True)
334 if not ((1 << p) & usedPlanes):
340 color = self.display.getMaskPlaneColor(pname)
343 color = next(colorGenerator)
344 elif color.lower() ==
"ignore":
347 ds9Cmd(
"mask color %s" % color)
348 _i_mtv(mask1, wcs, title,
True)
353 def _buffer(self, enable=True):
363 """Erase the specified DS9 frame""" 364 ds9Cmd(
"regions delete all", flush=
True, frame=self.display.frame)
366 def _dot(self, symb, c, r, size, ctype, fontFamily="helvetica", textAngle=None):
367 """Draw a symbol onto the specified DS9 frame at (col,row) = (c,r) [0-based coordinates] 373 @:Mxx,Mxy,Myy Draw an ellipse with moments (Mxx, Mxy, Myy) (argument size is ignored) 374 An object derived from afwGeom.ellipses.BaseCore Draw the ellipse (argument size is ignored) 375 Any other value is interpreted as a string to be drawn. Strings obey the fontFamily (which may be extended 376 with other characteristics, e.g. "times bold italic". Text will be drawn rotated by textAngle (textAngle is 379 N.b. objects derived from BaseCore include Axes and Quadrupole. 382 for region
in ds9Regions.dot(symb, c, r, size, ctype, fontFamily, textAngle):
383 cmd +=
'regions command {%s}; ' % region
387 def _drawLines(self, points, ctype):
388 """Connect the points, a list of (col,row) 389 Ctype is the name of a colour (e.g. 'red')""" 392 for region
in ds9Regions.drawLines(points, ctype):
393 cmd +=
'regions command {%s}; ' % region
400 def _scale(self, algorithm, min, max, unit, *args, **kwargs):
402 ds9Cmd(
"scale %s" % algorithm, frame=self.display.frame)
404 if min
in (
"minmax",
"zscale"):
405 ds9Cmd(
"scale mode %s" % (min))
408 print(
"ds9: ignoring scale unit %s" % unit)
410 ds9Cmd(
"scale limits %g %g" % (min, max), frame=self.display.frame)
415 def _zoom(self, zoomfac):
416 """Zoom frame by specified amount""" 419 cmd +=
"zoom to %d; " % zoomfac
423 def _pan(self, colc, rowc):
424 """Pan frame to (colc, rowc)""" 427 cmd +=
"pan to %g %g physical; " % (colc + 1, rowc + 1)
432 """Listen for a key press on frame in ds9, returning (key, x, y)""" 434 vals =
ds9Cmd(
"imexam key coordinate", get=
True).split()
435 if vals[0] ==
"XPA$ERROR":
436 if vals[1:4] == [
'unknown',
'option',
'"-state"']:
439 print(
"Error return from imexam:",
" ".join(vals), file=sys.stderr)
457 haveGzip =
not os.system(
"gzip < /dev/null > /dev/null 2>&1")
460 def _i_mtv(data, wcs, title, isMask):
461 """Internal routine to display an Image or Mask on a DS9 display""" 463 title = str(title)
if title
else "" 470 if data.getArray().dtype == np.uint16:
476 xpa_cmd =
"gzip | " + xpa_cmd
478 pfd = os.popen(xpa_cmd,
"w")
480 pfd =
file(
"foo.fits",
"w")
482 ds9Cmd(flush=
True, silent=
True)
485 displayLib.writeFitsImage(pfd.fileno(), data, wcs, title)
486 except Exception
as e:
503 definedCallbacks =
True 505 for k
in (
'XPA$ERROR',):
506 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)