Source code for gazelib.visualization.common

# -*- coding: utf-8 -*-
import bokeh.plotting as plotting
from . import utils
import gazelib.preprocessing as gpre

# For printing environments
import yaml

# Custom HTML templates are required by custom HTML such as tables.
# The following setup is needed for custom HTML with bokeh.
# See http://bokeh.pydata.org/en/0.10.0/docs/user_guide/embed.html
# See http://bokeh.pydata.org/en/latest/docs/reference/embed.html
# For FileSystemLoader, see https://gist.github.com/wrunk/1317933
from bokeh.resources import CDN
from bokeh.embed import file_html
from jinja2 import Environment, FileSystemLoader
from jinja2.exceptions import TemplateNotFound
import os


[docs]def render_path(common, output_html_filepath, title='Path'): ''' Create a HTML-based visualization of the gaze path. ''' lxs = common.get_stream_values('gazelib/gaze/left_eye_x_relative') lys = common.get_stream_values('gazelib/gaze/left_eye_y_relative') rxs = common.get_stream_values('gazelib/gaze/right_eye_x_relative') rys = common.get_stream_values('gazelib/gaze/right_eye_y_relative') p = plotting.figure(title=title, x_axis_label='X', y_axis_label='Y', plot_width=640, plot_height=480, x_range=(-0.2, 1.2), y_range=(-0.2, 1.2)) # Render screen rectangle p.quad(left=[0.0], right=[1.0], top=[0.0], bottom=[1.0], line_width=1, line_color='black', fill_alpha=0) # Visualize only valid points. # We do that by visualizing valid subpaths def validator(x): return x[0] is not None and x[1] is not None lpaths = utils.get_valid_sublists(zip(lxs, lys), validator) rpaths = utils.get_valid_sublists(zip(rxs, rys), validator) for valid_path in lpaths: valid_xs = list(map(lambda x: x[0], valid_path)) valid_ys = list(map(lambda x: x[1], valid_path)) p.cross(valid_xs, valid_ys, size=10, line_color='blue') p.line(valid_xs, valid_ys, line_width=1, line_color='blue') for valid_path in rpaths: valid_xs = list(map(lambda x: x[0], valid_path)) valid_ys = list(map(lambda x: x[1], valid_path)) p.cross(valid_xs, valid_ys, size=10, line_color='red') p.line(valid_xs, valid_ys, line_width=1, line_color='red') # Red as Right # Display start points. lfirst = lpaths[0][0] rfirst = rpaths[0][0] # Width and height in x_axis and y_axis units. p.oval(x=lfirst[0], y=lfirst[1], width=0.17, height=0.2, line_color='blue', fill_alpha=0) p.oval(x=rfirst[0], y=rfirst[1], width=0.17, height=0.2, line_color='red', fill_alpha=0) plotting.output_file(output_html_filepath, title) plotting.save(p)
[docs]def render_path_for_each_event(common, event_tag, output_html_filepath): pass
[docs]def render_overview(common, output_html_filepath, title='Overview', emphasize_gaps=False): ''' Create HTML-based visualization from all streams and events. Does not understand stream or event semantics like gaze paths for example. Can be slow if streams contain large number of gaps. Parameters: common: A CommonV1 object output_html_filepath: a filepath as string title: HTML page title as string emphasize_gaps: if False, do not show red gaps. Makes it quicker. ''' # Collect figures together figs = [] def to_ms(micros): return int(round(micros / 1000)) def pick_color(d): ''' We visually discriminate original and derived data. Parameters: d: event or stream object. Return color string ''' if 'derived' in d: return 'blue' return 'black' ######### # Environment constants ######### # Build dict env_names = common.list_environment_names() envs = {name: common.get_environment(name) for name in env_names} # Make human readable # yaml.safe_dump instad of yaml.dump to avoid annoying unicode tags # See http://stackoverflow.com/a/1950399/638546 env_yaml = yaml.safe_dump(envs, default_flow_style=False) env_html = '<pre>' + env_yaml + '</pre>' ######### # Streams ######### stream_names = sorted(common.list_stream_names()) for stream_name in stream_names: stream = common.get_stream(stream_name) x = list(map(to_ms, common.get_timeline(stream['timeline']))) y = stream['values'] color = pick_color(stream) fig = plotting.figure(title=stream_name, x_axis_label='time (ms)', plot_width=1000, plot_height=300, toolbar_location=None) if emphasize_gaps: # Emphasize gaps with red. Slow if data very gapped. # Get valid substreams sl = utils.get_valid_sublists_2d(x, y) for xs, ys in sl: fig.line(xs, ys, line_color=color) # Emphasize gaps with red line. # Loop pairwise, fill gaps. # TODO Optimize. Currently very slow. for i in range(len(sl) - 1): xs0, ys0 = sl[i] xs1, ys1 = sl[i + 1] # Extrapolate from last known value x0 = xs0[-1] x1 = xs1[0] y = ys0[-1] fig.line(x=[x0, x1], y=[y, y], line_width=1, line_color='red') else: # Fill gaps and draw single line. # This should be much faster. yy = gpre.fill_gaps(y) fig.line(x=x, y=yy, line_width=1, line_color=color) figs.append(fig) ######## # Events ######## # Create a row for each event. X is time. evs = common.list_events() # Visualize in start time, then duration order. # Recent topmost, hence minus. If start time same, longest topmost. def order_key(ev): t0 = -ev['range'][0] dur = ev['range'][1] + t0 return (t0, dur) evs = sorted(evs, key=order_key) # Plot height: it is dependent on number of events. # With one event, we want it still be visible, thus the constant. plot_height = (100 + 50 * len(evs)) fig = plotting.figure(title='Events', y_range=(-1, len(evs)), plot_width=1000, plot_height=plot_height, x_axis_label='time (ms)', toolbar_location=None) # Hide event indices and draw custom text instead. fig.yaxis.visible = None def get_text_position(x0, x1, minx, maxx): '''Change text position based on event's horizontal position so that the rightmost text do not go over the right edge.''' # Event mean, normalized to range c = (x0 + x1) / 2 - minx # Max range r = maxx - minx if c < 2 * r / 3: return (x0, 'left') # Center align was not good because text formed # visually annoying justification. # if c < 2 * r / 3: # return ((x0 + x1) / 2, 'center') return (x1, 'right') # Limit for horizontal coordinates. Helps to position text. evs_minx = to_ms(evs[-1]['range'][0]) evs_maxx = to_ms(evs[0]['range'][1]) for i, ev in enumerate(evs): t0 = to_ms(ev['range'][0]) t1 = to_ms(ev['range'][1]) color = pick_color(ev) x, align = get_text_position(t0, t1, evs_minx, evs_maxx) fig.text(text=[', '.join(ev['tags'])], y=[i + 0.1], x=[x], text_align=align, text_color=color) fig.line(x=[t0, t1], y=[i, i], line_width=6, line_color=color) # Mark where events start and end. fig.line(x=[t0, t0], y=[i - 0.1, i + 0.1], line_width=2, line_color=color) fig.line(x=[t1, t1], y=[i - 0.1, i + 0.1], line_width=2, line_color=color) figs.append(fig) # Make human readable evs_rev = list(reversed(evs)) evs_yaml = yaml.safe_dump(evs_rev, default_flow_style=False) evs_html = '<pre>' + evs_yaml + '</pre>' ########### # Render HTML ########## this_dir = os.path.dirname(os.path.abspath(__file__)) template_dir = os.path.join(this_dir, 'templates') jinja2loader = FileSystemLoader(template_dir) jinja2env = Environment(loader=jinja2loader) try: overview_template = jinja2env.get_template('overview.html') except TemplateNotFound as ex: print('Tried template dir: ' + template_dir) raise ex html = file_html(figs, CDN, title=title, template=overview_template, template_variables={'environment': env_html, 'events': evs_html}) # Save as html file. with open(output_html_filepath, 'w') as f: f.write(html)