Last active
January 30, 2019 14:15
-
-
Save DancingQuanta/8ffb09971b0e80bcd8ed1690c1ea72b4 to your computer and use it in GitHub Desktop.
Pyviz app for drawing points on an image
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import xarray as xr | |
import numpy as np | |
import holoviews as hv | |
from holoviews.streams import PointDraw | |
from holoviews.plotting.links import DataLink | |
import panel as pn | |
import param | |
hv.extension('bokeh') | |
class DataLoader(param.Parameterized): | |
data = param.Parameter() | |
poi_coords = param.Dict({'x': [], 'y': []}) | |
generate_data = param.Action( | |
default=lambda self: self._generate_data(), | |
precedence=1, | |
doc="""Button to generate data.""") | |
def __init__(self, **params): | |
super().__init__(**params) | |
self._layout = pn.Column( | |
pn.Param(self, parameters=['generate_data'], show_name=False), | |
#pn.Spacer(height=200, width=200) | |
) | |
def _generate_data(self): | |
# Random data | |
x = np.arange(0, 10) | |
y = x | |
z = x | |
data = np.random.rand(len(x), len(y), len(z)) | |
self.data = xr.DataArray(data, | |
coords={'x': x, 'y': y, 'wavenumber': z}, | |
dims=['x', 'y', 'wavenumber'], | |
name='Signal') | |
def display_xarray(self): | |
msg = """``` | |
{} | |
``` | |
""".format(repr(self.data)) | |
self._layout.append(pn.pane.Markdown(msg)) | |
def panel(self): | |
return self._layout | |
class DataViewer(param.Parameterized): | |
data = param.Parameter() | |
poi_coords = param.Dict({'x': [], 'y': []}) | |
def point_draw_spatial_map(self, data): | |
# TODO: there will be a simpler way coming soon with last | |
# attribute soon to be added to holoviews | |
key = (self._spatial_map.callback.args | |
or tuple(d.values[0] for d in self._spatial_map.kdims)) | |
img = self._spatial_map[key] | |
closest = img.closest(list(zip(data['x'], data['y']))) | |
return hv.Points(closest) | |
def label_point(self, points): | |
# TODO: automatically calculate relative offsets | |
# TODO: React to changes in table | |
# Label points | |
text = np.arange(len(points)) | |
return hv.Labels(points.add_dimension( | |
'text', 0, text, vdim=True)).options( | |
xoffset=2, yoffset=2, fontsize=14, text_color='white') | |
def view_spatial_map(self): | |
# Setup image visualisation | |
# Plot image | |
self._spatial_map = hv.Dataset(self.data).to(hv.Image, ['x', 'y'], dynamic=True) | |
# Create a PointDraw stream for POI | |
self._poi_stream = PointDraw(data=self.poi_coords) | |
# Create a DynamicMap linked to a PointDraw stream using | |
# a function that snap points to center of a pixel | |
self._poi_points = hv.DynamicMap(self.point_draw_spatial_map, | |
streams=[self._poi_stream]) | |
# Initialise label of points | |
point_labels = self._poi_points.map(self.label_point, | |
hv.Points, | |
link_inputs=False) | |
# Link labels to points | |
DataLink(self._poi_points, point_labels) | |
# Compose plot | |
plot = self._spatial_map * self._poi_points * point_labels | |
return pn.panel(plot) | |
def view_poi_table(self): | |
# Create a table | |
self._poi_table = hv.Table( | |
self._poi_stream.data, ['x', 'y'] | |
).opts(height=500, width=200, editable=True) | |
# Link the data in points to table | |
DataLink(self._poi_points, self._poi_table) | |
return pn.panel(self._poi_table) | |
def panel(self): | |
spatial_map = self.view_spatial_map() | |
poi_table = self.view_poi_table() | |
return pn.Row( | |
spatial_map, | |
poi_table, | |
) | |
stages = [ | |
('Load Data', DataLoader), | |
('Browser', DataViewer), | |
] | |
pipeline = pn.pipeline.Pipeline(stages, debug=True) | |
# Change width of buttons | |
pipeline.layout[0][1]._widget_box.width = 100 | |
pipeline.layout[0][2]._widget_box.width = 100 | |
pipeline.layout.servable() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment