23 __all__ = [
"Ds9Error",
"getXpaAccessPoint",
"ds9Version",
"Buffer",
24 "selectFrame",
"ds9Cmd",
"initDS9",
"Ds9Event",
"DisplayImpl"]
33 import lsst.afw.display.interface
as interface
34 import lsst.afw.display.virtualDevice
as virtualDevice
35 import lsst.afw.display.ds9Regions
as ds9Regions
38 from .
import xpa
as xpa
39 except ImportError
as e:
40 print(
"Cannot import xpa: %s" % (e), file=sys.stderr)
42 import lsst.afw.display.displayLib
as displayLib
43 import lsst.afw.math
as afwMath
54 """Some problem talking to ds9""" 60 _maskTransparency =
None 64 """Parse XPA_PORT and send return an identifier to send ds9 commands there, instead of "ds9" 65 If you don't have XPA_PORT set, the usual xpans tricks will be played when we return "ds9". 67 xpa_port = os.environ.get(
"XPA_PORT")
69 mat = re.search(
r"^DS9:ds9\s+(\d+)\s+(\d+)", xpa_port)
71 port1, port2 = mat.groups()
73 return "127.0.0.1:%s" % (port1)
75 print(
"Failed to parse XPA_PORT=%s" % xpa_port, file=sys.stderr)
81 """Return the version of ds9 in use, as a string""" 83 v =
ds9Cmd(
"about", get=
True)
84 return v.splitlines()[1].split()[1]
85 except Exception
as e:
86 print(
"Error reading version: %s" % e, file=sys.stderr)
93 XPA_SZ_LINE = 4096 - 100
96 """Control buffering the sending of commands to ds9; 97 annoying but necessary for anything resembling performance 99 The usual usage pattern (from a module importing this file, ds9.py) is: 101 with ds9.Buffering(): 102 # bunches of ds9.{dot,line} commands 104 # bunches more ds9.{dot,line} commands 108 """Create a command buffer, with a maximum depth of size""" 115 def set(self, size, silent=True):
116 """Set the ds9 buffer size to size""" 118 size = XPA_SZ_LINE - 5
120 if size > XPA_SZ_LINE:
121 print(
"xpa silently hardcodes a limit of %d for buffer sizes (you asked for %d) " %
122 (XPA_SZ_LINE, size), file=sys.stderr)
131 self.
flush(silent=silent)
134 """Get the current ds9 buffer size""" 138 """Replace current ds9 command buffer size with size (see also popSize) 139 @param: Size of buffer (-1: largest possible given bugs in xpa)""" 140 self.
flush(silent=
True)
142 self.
set(size, silent=
True)
145 """Switch back to the previous command buffer size (see also pushSize)""" 146 self.
flush(silent=
True)
152 """Flush the pending commands""" 153 ds9Cmd(flush=
True, silent=silent)
159 return "frame %d" % (frame)
162 def ds9Cmd(cmd=None, trap=True, flush=False, silent=True, frame=None, get=False):
163 """Issue a ds9 command, raising errors as appropriate""" 167 if frame
is not None:
175 if cmdBuffer._lenCommands + len(cmd) > XPA_SZ_LINE - 5:
176 ds9Cmd(flush=
True, silent=silent)
178 cmdBuffer._commands +=
";" + cmd
179 cmdBuffer._lenCommands += 1 + len(cmd)
181 if flush
or cmdBuffer._lenCommands >= cmdBuffer._getSize():
182 cmd = (cmdBuffer._commands +
"\n")
183 cmdBuffer._commands =
"" 184 cmdBuffer._lenCommands = 0
198 raise Ds9Error(
"XPA: %s, (%s)" % (e, cmd))
200 print(
"Caught ds9 exception processing command \"%s\": %s" % (cmd, e), file=sys.stderr)
206 ds9Cmd(
"iconify no; raise",
False)
214 needShow = (int(v1) <= 4)
217 except Ds9Error
as e:
218 if not re.search(
'xpa', os.environ[
'PATH']):
219 raise Ds9Error(
'You need the xpa binaries in your path to use ds9 with python')
224 import distutils.spawn
225 if not distutils.spawn.find_executable(
"ds9"):
226 raise NameError(
"ds9 doesn't appear to be on your path")
227 if "DISPLAY" not in os.environ:
228 raise RuntimeError(
"$DISPLAY isn't set, so I won't be able to start ds9 for you")
230 print(
"ds9 doesn't appear to be running (%s), I'll try to exec it for you" % e)
238 print(
"waiting for ds9...\r", end=
"")
251 """An event generated by a mouse or key click on the display""" 254 interface.Event.__init__(self, k, x, y)
259 def __init__(self, display, verbose=False, *args, **kwargs):
260 virtualDevice.DisplayImpl.__init__(self, display, verbose)
263 """Called when the device is closed""" 266 def _setMaskTransparency(self, transparency, maskplane):
267 """Specify ds9's mask transparency (percent); or None to not set it when loading masks""" 268 if maskplane
is not None:
269 print(
"ds9 is unable to set transparency for individual maskplanes" % maskplane,
272 ds9Cmd(
"mask transparency %d" % transparency, frame=self.display.frame)
274 def _getMaskTransparency(self, maskplane):
275 """Return the current ds9's mask transparency""" 278 return float(
ds9Cmd(
"mask transparency", get=
True))
281 """Uniconify and Raise ds9. N.b. throws an exception if frame doesn't exit""" 282 ds9Cmd(
"raise", trap=
False, frame=self.display.frame)
284 def _mtv(self, image, mask=None, wcs=None, title=""):
285 """Display an Image and/or Mask on a DS9 display 292 print(
"waiting for ds9...\r", end=
"")
306 _i_mtv(image, wcs, title,
False)
309 maskPlanes = mask.getMaskPlaneDict()
310 nMaskPlanes = max(maskPlanes.values()) + 1
313 for key
in maskPlanes:
314 planes[maskPlanes[key]] = key
316 planeList = range(nMaskPlanes)
317 usedPlanes = int(afwMath.makeStatistics(mask, afwMath.SUM).getValue())
318 mask1 = mask.Factory(mask.getDimensions())
320 colorGenerator = self.display.maskColorGenerator(omitBW=
True)
325 if not ((1 << p) & usedPlanes):
331 color = self.display.getMaskPlaneColor(pname)
334 color = next(colorGenerator)
335 elif color.lower() ==
"ignore":
338 ds9Cmd(
"mask color %s" % color)
339 _i_mtv(mask1, wcs, title,
True)
344 def _buffer(self, enable=True):
354 """Erase the specified DS9 frame""" 355 ds9Cmd(
"regions delete all", flush=
True, frame=self.display.frame)
357 def _dot(self, symb, c, r, size, ctype, fontFamily="helvetica", textAngle=None):
358 """Draw a symbol onto the specified DS9 frame at (col,row) = (c,r) [0-based coordinates] 364 @:Mxx,Mxy,Myy Draw an ellipse with moments (Mxx, Mxy, Myy) (argument size is ignored) 365 An object derived from afwGeom.ellipses.BaseCore Draw the ellipse (argument size is ignored) 366 Any other value is interpreted as a string to be drawn. Strings obey the fontFamily (which may be extended 367 with other characteristics, e.g. "times bold italic". Text will be drawn rotated by textAngle 368 (textAngle is ignored otherwise). 370 N.b. objects derived from BaseCore include Axes and Quadrupole. 373 for region
in ds9Regions.dot(symb, c, r, size, ctype, fontFamily, textAngle):
374 cmd +=
'regions command {%s}; ' % region
378 def _drawLines(self, points, ctype):
379 """Connect the points, a list of (col,row) 380 Ctype is the name of a colour (e.g. 'red')""" 383 for region
in ds9Regions.drawLines(points, ctype):
384 cmd +=
'regions command {%s}; ' % region
391 def _scale(self, algorithm, min, max, unit, *args, **kwargs):
393 ds9Cmd(
"scale %s" % algorithm, frame=self.display.frame)
395 if min
in (
"minmax",
"zscale"):
396 ds9Cmd(
"scale mode %s" % (min))
399 print(
"ds9: ignoring scale unit %s" % unit)
401 ds9Cmd(
"scale limits %g %g" % (min, max), frame=self.display.frame)
406 def _zoom(self, zoomfac):
407 """Zoom frame by specified amount""" 410 cmd +=
"zoom to %d; " % zoomfac
414 def _pan(self, colc, rowc):
415 """Pan frame to (colc, rowc)""" 419 cmd +=
"pan to %g %g physical; " % (colc + 1, rowc + 1)
424 """Listen for a key press on frame in ds9, returning (key, x, y)""" 426 vals =
ds9Cmd(
"imexam key coordinate", get=
True).split()
427 if vals[0] ==
"XPA$ERROR":
428 if vals[1:4] == [
'unknown',
'option',
'"-state"']:
431 print(
"Error return from imexam:",
" ".join(vals), file=sys.stderr)
449 haveGzip =
not os.system(
"gzip < /dev/null > /dev/null 2>&1")
452 def _i_mtv(data, wcs, title, isMask):
453 """Internal routine to display an Image or Mask on a DS9 display""" 455 title = str(title)
if title
else "" 463 if data.getArray().dtype == np.uint16:
469 xpa_cmd =
"gzip | " + xpa_cmd
471 pfd = os.popen(xpa_cmd,
"w")
473 pfd = open(
"foo.fits",
"w")
475 ds9Cmd(flush=
True, silent=
True)
478 displayLib.writeFitsImage(pfd.fileno(), data, wcs, title)
479 except Exception
as e:
497 definedCallbacks =
True 499 for k
in (
'XPA$ERROR',):
500 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)