Skip to content

Commit

Permalink
🚧 working server plotting in new tab
Browse files Browse the repository at this point in the history
notebook is not ideal as it is not inline and you have to "interrupt kernel" to close server thread

May have to wait for bokeh/bokeh#3461 to be resolved.
  • Loading branch information
caryan committed Jul 7, 2016
1 parent 45226f7 commit 2445895
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 71 deletions.
51 changes: 51 additions & 0 deletions QGL/Plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@
import bokeh.plotting as bk
import numpy as np

import threading
import subprocess
import psutil
import os
import sys

from . import config

def output_notebook():
Expand Down Expand Up @@ -88,3 +94,48 @@ def plot_waveforms(waveforms, figTitle = ''):
def show(seq):
waveforms = build_waveforms(seq)
plot_waveforms(waveforms)

def in_notebook():
return True
# From http://stackoverflow.com/questions/15411967/how-can-i-check-if-code-is-executed-in-the-ipython-notebook
try:
cfg = get_ipython().config
if cfg['IPKernelApp']['parent_appname'] == 'ipython-notebook':
return True
else:
return False
except NameError:
return False

class BokehServerThread(threading.Thread):
def __init__(self):
super(BokehServerThread, self).__init__()
self.daemon = True
self.run_in_notebook = in_notebook()

def __del__(self):
self.join()

def __del__(self):
self.join()

def run(self):
args = []
if 'win32' in sys.platform:
args.append("bokeh.bat")
else:
args.append("bokeh")
args.append("serve")
if self.run_in_notebook:
args.append("--allow-websocket-origin=localhost:8888")
self.p = subprocess.Popen(args, env=os.environ.copy())

def join(self, timeout=None):
if self.p:
print("Killing bokeh server thread {}".format(self.p.pid))
for child_proc in psutil.Process(self.p.pid).children():
print("Killing child process {}".format(child_proc.pid))
child_proc.kill()
self.p.kill()
self.p = None
super(BokehServerThread, self).join(timeout=timeout)
147 changes: 76 additions & 71 deletions QGL/PulseSequencePlotter.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,26 @@
limitations under the License.
'''

import time
import os.path
from importlib import import_module
from bokeh.io import vform
from bokeh.models import CustomJS, ColumnDataSource, Slider
from bokeh.plotting import Figure, show
import pkgutil
from collections import OrderedDict

from jinja2 import Template
import numpy as np

from bokeh.layouts import column
from bokeh.models import Slider
from bokeh.plotting import Figure
from bokeh.client import push_session
from bokeh.io import curdoc, output_notebook, output_server

from . import config
from . import ChannelLibrary

from . import drivers
import pkgutil

from .Plotting import BokehServerThread, in_notebook

def all_zero_seqs(seqs):
return all([np.allclose([_[1] for _ in seq], 0) for seq in seqs])
Expand Down Expand Up @@ -67,92 +73,91 @@ def resolve_translator(filename, translators):
return t
raise NameError("No translator found to open the given file %s", filename)

def plot_pulse_files(fileNames):
'''
plot_pulse_files(fileNames)
def plot_pulse_files(h5_files):
"""
Plots a list of h5 sequence files with an interactive slider.
"""

Helper function to plot a list of AWG files. A JS slider allows choice of sequence number.
'''
#If we only go one filename turn it into a list
if isinstance(fileNames, str):
fileNames = [fileNames]
bokeh_thread = BokehServerThread()
bokeh_thread.start()
time.sleep(1) #make sure server is finish launching
output_server()
curdoc().clear()
# output_notebook()

dataDict = {}
lineNames, num_seqs = extract_waveforms(dataDict, fileNames)
#If we only got one filename turn it into a list
if isinstance(h5_files, str):
h5_files = [h5_files]

localname = os.path.split(fileNames[0])[1]
sequencename = localname.split('-')[0]
wfs = extract_waveforms(h5_files)
num_seqs = max([len(_) for _ in wfs.values()])

all_data = ColumnDataSource(data=dataDict)
plot = Figure(title=sequencename, plot_width=1000)
plot.background_fill_color = config.plotBackground
if config.gridColor:
plot.xgrid.grid_line_color = config.gridColor
plot.ygrid.grid_line_color = config.gridColor
filename = os.path.split(h5_files[0])[1]
seq_name = filename.split('-')[0]

# Colobrewer2 qualitative Set1 (http://colorbrewer2.org)
colours = [
"#e41a1c",
"#377eb8",
"#4daf4a",
"#984ea3",
"#ff7f00",
"#ffff33",
"#a65628",
"#f781bf",
"#999999"
]
fig = Figure(title=seq_name, plot_width=800)
fig.background_fill_color = config.plotBackground
if config.gridColor:
fig.xgrid.grid_line_color = config.gridColor
fig.ygrid.grid_line_color = config.gridColor

js_sources = {}
js_sources["all_data"] = all_data
for ct,k in enumerate(lineNames):
k_ = k.replace("-", "_")
line = plot.line(dataDict[k_+"_x_1"], dataDict[k_+"_y_1"], color=colours[ct%len(colours)], line_width=2, legend=k)
js_sources[k_] = line.data_source
num_lines = len(wfs.keys())
#for some reason the qualitative maps aren't in bokeh.palettes
# see https://github.com/bokeh/bokeh/issues/4758
brewer_set3 = ['#8dd3c7','#ffffb3','#bebada','#fb8072','#80b1d3','#fdb462','#b3de69','#fccde5','#d9d9d9','#bc80bd','#ccebc5','#ffed6f']
colours = list(np.resize(brewer_set3, num_lines))

code_template = Template("""
var seq_num = cb_obj.get('value');
console.log(seq_num)
var all_data = all_data.get('data');
{% for line in lineNames %}
{{line}}.set('data', {'x':all_data['{{line}}_x_'.concat(seq_num.toString())], 'y':all_data['{{line}}_y_'.concat(seq_num.toString())]} );
{% endfor %}
console.log("Got here!")
""")
lines = []
for colour, (k,v) in zip(colours, wfs.items()):
lines.append( fig.line(v[0]["x"], v[0]["y"], color=colour, legend=k, line_width=2) )

callback = CustomJS(args=js_sources, code=code_template.render(lineNames=[l.replace("-","_") for l in lineNames]))
def callback(attr, old, new):
for line, wf in zip(lines, wfs.values()):
new_xy = {"x":wf[new-1]["x"], "y":wf[new-1]["y"]}
line.data_source.data = new_xy

slider = Slider(start=1, end=num_seqs, value=1, step=1, title="Sequence", callback=callback)
slider = Slider(start=1, end=num_seqs, value=1, step=1, title="Sequence")
slider.on_change("value", callback)

layout = vform(slider, plot)
session = push_session(curdoc())
session.show(column([slider, fig]))
session.loop_until_closed()

show(layout)
def extract_waveforms(h5_files, nameDecorator=''):
"""
Extracts a dictionary mapping channel names to lists of dictionaries (keys "x" and "y") lines
def extract_waveforms(dataDict, fileNames, nameDecorator=''):
lineNames = []
num_seqs = 0
for fileName in sorted(fileNames):
Lines are shifted along the y axis to prevent overlap
"""
wfs = OrderedDict()

y_shift = 0
for file_name in sorted(h5_files):
# Assume a naming convention path/to/file/SequenceName-AWGName.h5
AWGName = (os.path.split(os.path.splitext(fileName)[0])[1]).split('-')[1]
AWGName = (os.path.split(os.path.splitext(file_name)[0])[1]).split('-')[1]
# Strip any _ suffix
if '_' in AWGName:
AWGName = AWGName[:AWGName.index('_')]

translator = resolve_translator(fileName, translators)
wfs = translator.read_sequence_file(fileName)
translator = resolve_translator(file_name, translators)
ta_pairs = translator.read_sequence_file(file_name)

for (k,seqs) in sorted(wfs.items()):
for (k,seqs) in sorted(ta_pairs.items()):
if all_zero_seqs(seqs):
continue
num_seqs = max(num_seqs, len(seqs))
lineNames.append(AWGName + nameDecorator + '-' + k)
k_ = lineNames[-1].replace("-", "_")

line_name = AWGName + nameDecorator + '-' + k
y_shift += 2
wfs[line_name] = []
for ct,seq in enumerate(seqs):
# Convert from time amplitude pairs to x,y lines with points at start and beginnning to prevent interpolation
dataDict[k_+"_x_{:d}".format(ct+1)] = np.tile( np.cumsum([0] + [_[0] for _ in seq]), (2,1)).flatten(order="F")[1:-1]
dataDict[k_+"_y_{:d}".format(ct+1)] = np.tile( [_[1] for _ in seq], (2,1)).flatten(order="F") + 2*(len(lineNames)-1)
return lineNames, num_seqs
wfs[line_name].append(
{
# Convert from time amplitude pairs to x,y lines with points at start and beginnning to prevent interpolation
"x": np.tile( np.cumsum([0] + [_[0] for _ in seq]), (2,1)).flatten(order="F")[1:-1],
"y": np.tile( [_[1] for _ in seq], (2,1)).flatten(order="F") + y_shift
}
)
return wfs

def plot_pulse_files_compare(fileNames1, fileNames2):
'''
Expand All @@ -173,10 +178,10 @@ def plot_pulse_files_compare(fileNames1, fileNames2):
num_seqs = max(num_seqs1, num_seqs2)

localname = os.path.split(fileNames1[0])[1]
sequencename = localname.split('-')[0]
seq_name = localname.split('-')[0]

all_data = ColumnDataSource(data=dataDict)
plot = Figure(title=sequencename, plot_width=1000)
plot = Figure(title=seq_name, plot_width=1000)
plot.background_fill_color = config.plotBackground
if config.gridColor:
plot.xgrid.grid_line_color = config.gridColor
Expand Down

0 comments on commit 2445895

Please sign in to comment.