24from socket
import gaierror
34from .footprints
import createFootprintsTable
39except ImportError
as e:
40 raise RuntimeError(
"Cannot import firefly_client: %s" % (e))
41from ws4py.client
import HandshakeError
47 Exception.__init__(self, str)
51 """Return the version of firefly_client in use, as a string"""
52 return firefly_client.__version__
56 """Device to talk to a firefly display"""
59 def __handleCallbacks(event):
60 if 'type' in event[
'data']:
61 if event[
'data'][
'type'] ==
'AREA_SELECT':
62 lsst.log.debug(
'*************area select')
63 pParams = {
'URL':
'http://web.ipac.caltech.edu/staff/roby/demo/wise-m51-band2.fits',
67 _fireflyClient.show_fits(fileOnServer=
None, plot_id=plot_id, additionalParams=pParams)
69 lsst.log.debug(
"Callback event info: {}".format(event))
71 data = dict((_.split(
'=')
for _
in event.get(
'data', {}).split(
'&')))
72 if data.get(
'type') ==
"POINT":
73 lsst.log.debug(
"Event Received: %s" % data.get(
'id'))
75 def __init__(self, display, verbose=False, url=None,
76 name=None, *args, **kwargs):
77 virtualDevice.DisplayImpl.__init__(self, display, verbose)
80 print(
"Opening firefly device %s" % (self.display.frame
if self.display
else "[None]"))
83 if not _fireflyClient:
86 html_file = kwargs.get(
'html_file',
87 os.environ.get(
'FIREFLY_HTML',
'slate.html'))
89 if ((
'fireflyLabExtension' in os.environ)
and
90 (
'fireflyURLLab' in os.environ)):
91 url = os.environ[
'fireflyURLLab']
92 start_tab = kwargs.get(
'start_tab',
True)
93 start_browser_tab = kwargs.get(
'start_browser_tab',
False)
94 if (name
is None)
and (
'fireflyChannelLab' in os.environ):
95 name = os.environ[
'fireflyChannelLab']
96 elif 'FIREFLY_URL' in os.environ:
97 url = os.environ[
'FIREFLY_URL']
99 raise RuntimeError(
'Cannot determine url from environment; you must pass url')
101 token = kwargs.get(
'token',
102 os.environ.get(
'ACCESS_TOKEN',
None))
107 print(
'Starting Jupyterlab client')
108 _fireflyClient = firefly_client.FireflyClient.make_lab_client(
109 start_tab=
True, start_browser_tab=start_browser_tab,
110 html_file=kwargs.get(
'html_file'), verbose=verbose,
115 print(
'Starting vanilla client')
116 _fireflyClient = firefly_client.FireflyClient.make_client(
117 url=url, html_file=html_file, launch_browser=
True,
118 channel_override=name, verbose=verbose,
121 except (HandshakeError, gaierror)
as e:
122 raise RuntimeError(
"Unable to connect to %s: %s" % (url
or '', e))
126 except Exception
as e:
127 raise RuntimeError(
"Cannot add listener. Browser must be connected" +
129 (_fireflyClient.get_firefly_url(), e))
138 self.
_url = _fireflyClient.get_firefly_url()
148 return "lsstRegions%s" % self.display.frame
if self.display
else "None"
151 """Delete the current image in the Firefly viewer
153 self._client.dispatch(action_type='ImagePlotCntlr.deletePlotView',
154 payload=dict(plotId=str(self.display.frame)))
156 def _mtv(self, image, mask=None, wcs=None, title=""):
157 """Display an Image and/or Mask on a Firefly display
160 title = str(self.display.frame)
163 print(
'displaying image')
166 with tempfile.NamedTemporaryFile()
as fd:
167 afwDisplay.writeFitsImage(fd.name, image, wcs, title)
173 viewer_id = (
'image-' + str(_fireflyClient.render_tree_id) +
'-' +
175 except AttributeError:
176 viewer_id =
'image-' + str(self.
frame)
177 extraParams = dict(Title=title,
179 PredefinedOverlayIds=
' ',
185 extraParams[
'InitZoomLevel'] = self.
_lastZoom
186 extraParams[
'ZoomType'] =
'LEVEL'
188 extraParams[
'InitialCenterPosition'] =
'{0:.3f};{1:.3f};PIXEL'.format(
193 ret = _fireflyClient.show_fits(self.
_fireflyFitsID, plot_id=str(self.display.frame),
196 if not ret[
"success"]:
197 raise RuntimeError(
"Display of image failed")
201 print(
'displaying mask')
202 with tempfile.NamedTemporaryFile()
as fdm:
203 afwDisplay.writeFitsImage(fdm.name, mask, wcs, title)
208 maskPlaneDict = mask.getMaskPlaneDict()
209 for k, v
in maskPlaneDict.items():
214 if (((1 << self.
_maskDict[k]) & usedPlanes)
and
218 _fireflyClient.add_mask(bit_number=self.
_maskDict[k],
220 plot_id=str(self.display.frame),
230 """Remove mask layers"""
232 _fireflyClient.remove_mask(plot_id=str(self.display.frame), mask_id=k)
235 def _buffer(self, enable=True):
236 """!Enable or disable buffering of writes to the display
237 param enable True or False,
as appropriate
242 """!Flush any I/O buffers
248 print(
"Flushing %d regions" % len(self.
_regions))
252 _fireflyClient.add_region_data(region_data=self.
_regions, plot_id=str(self.display.frame),
263 """Called when the device is closed"""
265 print(
"Closing firefly device %s" % (self.display.frame
if self.display
else "[None]"))
266 if _fireflyClient
is not None:
267 _fireflyClient.disconnect()
268 _fireflyClient.session.close()
270 def _dot(self, symb, c, r, size, ctype, fontFamily="helvetica", textAngle=None):
271 """Draw a symbol onto the specified DS9 frame at (col,row) = (c,r) [0-based coordinates]
277 @:Mxx,Mxy,Myy Draw an ellipse with moments (Mxx, Mxy, Myy) (argument size
is ignored)
278 An object derived
from afwGeom.ellipses.BaseCore Draw the ellipse (argument size
is ignored)
279 Any other value
is interpreted
as a string to be drawn. Strings obey the fontFamily (which may be extended
280 with other characteristics, e.g.
"times bold italic". Text will be drawn rotated by textAngle (textAngle
281 is ignored otherwise).
283 N.b. objects derived
from BaseCore include Axes
and Quadrupole.
285 self._uploadTextData(ds9Regions.dot(symb, c, r, size, ctype, fontFamily, textAngle))
288 """Connect the points, a list of (col,row)
289 Ctype is the name of a colour (e.g.
'red')
"""
294 """Erase all overlays on the image"""
298 _fireflyClient.delete_region_layer(self.
_regionLayerId, plot_id=str(self.display.frame))
301 if func != interface.noop_callback:
303 status = _fireflyClient.add_extension(
'POINT' if False else 'AREA_SELECT', title=what,
304 plot_id=str(self.display.frame),
306 if not status[
'success']:
308 except Exception
as e:
309 raise RuntimeError(
"Cannot set callback. Browser must be (re)opened " +
311 (_fireflyClient.url_bw,
312 _fireflyClient.channel, e))
315 """Return an event generated by a keypress or mouse click
317 ev = interface.Event("q")
320 print(
"virtual[%s]._getEvent() -> %s" % (self.display.frame, ev))
327 def _scale(self, algorithm, min, max, unit=None, *args, **kwargs):
328 """Scale the image stretch and limits
333 stretch algorithm, e.g. 'linear',
'log',
'loglog',
'equal',
'squared',
334 'sqrt',
'asinh', powerlaw_gamma
'
335 min : `float` or `str`
336 lower limit,
or 'minmax' for full range,
or 'zscale'
337 max : `float`
or `str`
338 upper limit; overrriden
if min
is 'minmax' or 'zscale'
340 unit
for min
and max.
'percent',
'absolute',
'sigma'.
341 if not specified, min
and max are presumed to be
in 'absolute' units.
343 *args, **kwargs : additional position
and keyword arguments.
344 The options are shown below:
346 **Q** : `float`, optional
347 The asinh softening parameter
for asinh stretch.
348 Use Q=0
for linear stretch, increase Q to make brighter features visible.
349 When
not specified
or None, Q
is calculated by Firefly to use full color range.
351 The gamma value
for power law gamma stretch (default 2.0)
352 **zscale_contrast** : `int`, optional
353 Contrast parameter
in percent
for zscale algorithm (default 25)
354 **zscale_samples** : `int`, optional
355 Number of samples
for zscale algorithm (default 600)
356 **zscale_samples_perline** : `int`, optional
357 Number of samples per line
for zscale algorithm (default 120)
359 stretch_algorithms = ('linear',
'log',
'loglog',
'equal',
'squared',
'sqrt',
360 'asinh',
'powerlaw_gamma')
361 interval_methods = (
'percent',
'maxmin',
'absolute',
'zscale',
'sigma')
367 algorithm = dict((a.lower(), a)
for a
in stretch_algorithms).get(algorithm.lower(), algorithm)
369 if algorithm
not in stretch_algorithms:
370 raise FireflyError(
'Algorithm %s is invalid; please choose one of "%s"' %
371 (algorithm,
'", "'.join(stretch_algorithms)))
378 kwargs[
'asinh_q_value'] = kwargs[
'Q']
381 if 'gamma' in kwargs:
382 kwargs[
'gamma_value'] = kwargs[
'gamma']
386 interval_type =
'percent'
389 elif min ==
'zscale':
390 interval_type =
'zscale'
397 units = (
'percent',
'absolute',
'sigma')
398 if unit
not in units:
399 raise FireflyError(
'Unit %s is invalid; please choose one of "%s"' % (unit,
'", "'.join(units)))
402 interval_type =
'sigma'
403 elif unit ==
'absolute' and interval_type
is None:
404 interval_type =
'absolute'
405 elif unit ==
'percent':
406 interval_type =
'percent'
412 if interval_type
not in interval_methods:
413 raise FireflyError(
'Interval method %s is invalid' % interval_type)
416 if interval_type !=
'zscale':
417 rval = _fireflyClient.set_stretch(str(self.display.frame), stype=interval_type,
418 algorithm=algorithm, lower_value=min,
419 upper_value=max, **kwargs)
421 if 'zscale_contrast' not in kwargs:
422 kwargs[
'zscale_contrast'] = 25
423 if 'zscale_samples' not in kwargs:
424 kwargs[
'zscale_samples'] = 600
425 if 'zscale_samples_perline' not in kwargs:
426 kwargs[
'zscale_samples_perline'] = 120
427 rval = _fireflyClient.set_stretch(str(self.display.frame), stype=
'zscale',
428 algorithm=algorithm, **kwargs)
430 if 'rv_string' in rval:
434 """Specify mask transparency (percent); or None to not set it when loading masks"""
435 if maskName
is not None:
436 masklist = [maskName]
438 masklist =
set(self.
_maskIds +
list(self.display._defaultMaskPlaneColor.keys()))
441 _fireflyClient.dispatch(action_type=
'ImagePlotCntlr.overlayPlotChangeAttributes',
442 payload={
'plotId': str(self.display.frame),
444 'attributes': {
'opacity': 1.0 - transparency/100.},
448 """Return the current mask's transparency"""
455 """Specify mask color """
456 _fireflyClient.remove_mask(plot_id=str(self.display.frame),
459 if (color.lower() !=
'ignore'):
460 _fireflyClient.add_mask(bit_number=self.
_maskDict[maskName],
462 plot_id=str(self.display.frame),
464 color=self.display.getMaskPlaneColor(maskName),
468 """Show the requested window"""
469 if self.
_client.render_tree_id
is not None:
474 localbrowser, url = _fireflyClient.launch_browser(verbose=self.verbose)
475 if not localbrowser
and not self.verbose:
476 _fireflyClient.display_url()
483 """Zoom display by specified amount
488 zoom level in screen pixels per image pixel
491 _fireflyClient.set_zoom(plot_id=str(self.display.frame), factor=zoomfac)
494 """Pan to specified pixel coordinates
499 column and row
in units of pixels (zero-based convention,
500 with the xy0 already subtracted off)
502 self._lastPan = [colc+0.5, rowc+0.5]
504 _fireflyClient.set_pan(plot_id=str(self.display.frame), x=colc, y=rowc)
509 """Get the instance of FireflyClient for this display
513 `firefly_client.FireflyClient`
514 Instance of FireflyClient used by this display
519 """Reinitialize the viewer
524 """Reset the layout of the Firefly Slate browser
526 Clears the display and adds Slate cells to display image
in upper left,
527 plot area
in upper right,
and plots stretch across the bottom
531 tables_cell_id =
'tables-' + str(_fireflyClient.render_tree_id)
532 except AttributeError:
533 tables_cell_id =
'tables'
534 self.
_client.add_cell(row=2, col=0, width=4, height=2, element_type=
'tables',
535 cell_id=tables_cell_id)
537 image_cell_id = (
'image-' + str(_fireflyClient.render_tree_id) +
'-' +
539 except AttributeError:
540 image_cell_id =
'image-' + str(self.
frame)
541 self.
_client.add_cell(row=0, col=0, width=2, height=3, element_type=
'images',
542 cell_id=image_cell_id)
544 plots_cell_id =
'plots-' + str(_fireflyClient.render_tree_id)
545 except AttributeError:
546 plots_cell_id =
'plots'
547 self.
_client.add_cell(row=0, col=2, width=2, height=3, element_type=
'xyPlots',
548 cell_id=plots_cell_id)
551 highlightColor='cyan', selectColor=
'orange',
552 style=
'fill', layerString=
'detection footprints ',
553 titleString=
'catalog footprints '):
554 """Overlay outlines of footprints from a catalog
556 Overlay outlines of LSST footprints from the input catalog. The colors
557 and style can be specified
as parameters,
and the base color
and style
558 can be changed
in the Firefly browser user interface.
563 Source catalog
from which to display footprints.
565 Color
for footprints overlay. Colors can be specified
as a name
566 like
'cyan' or afwDisplay.RED;
as an rgb value such
as
567 'rgb(80,100,220)';
or as rgb plus alpha (transparency) such
568 as 'rgba('74,144,226,0.60)
'.
569 highlightColor : `str`
570 Color for highlighted footprints
572 Color
for selected footprints
573 style : {
'fill',
'outline'}
574 Style of footprints display, filled
or outline
576 Column at which to insert the
"family_id" and "category" columns
578 Name of footprints layer string, to concatenate
with the frame
579 Re-using the layer_string will overwrite the previous table
and
582 Title of catalog, to concatenate
with the frame
584 footprintTable = createFootprintsTable(catalog)
585 with BytesIO()
as fd:
586 footprintTable.to_xml(fd)
587 tableval = self.
_client.upload_data(fd,
'UNKNOWN')
588 self.
_client.overlay_footprints(footprint_file=tableval,
589 title=titleString + str(self.display.frame),
590 footprint_layer_id=layerString + str(self.display.frame),
591 plot_id=str(self.display.frame),
593 highlightColor=highlightColor,
594 selectColor=selectColor,
_drawLines(self, points, ctype)
_getMaskTransparency(self, maskName)
_setCallback(self, what, func)
_dot(self, symb, c, r, size, ctype, fontFamily="helvetica", textAngle=None)
_setMaskTransparency(self, transparency, maskName)
_uploadTextData(self, regions)
__init__(self, display, verbose=False, url=None, name=None, *args, **kwargs)
_flush(self)
Flush any I/O buffers.
overlayFootprints(self, catalog, color='rgba(74, 144, 226, 0.60)', highlightColor='cyan', selectColor='orange', style='fill', layerString='detection footprints ', titleString='catalog footprints ')
_mtv(self, image, mask=None, wcs=None, title="")
_scale(self, algorithm, min, max, unit=None, *args, **kwargs)
_setMaskPlaneColor(self, maskName, color)
daf::base::PropertyList * list
daf::base::PropertySet * set
Statistics makeStatistics(lsst::afw::image::Image< Pixel > const &img, lsst::afw::image::Mask< image::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl=StatisticsControl())
Handle a watered-down front-end to the constructor (no variance)