Interactive demo of precision-recall metric
Load libraries¶
In [ ]:
%reload_ext autoreload
%autoreload 2
%matplotlib inline
In [ ]:
from IPython.core.interactiveshell import InteractiveShell
# InteractiveShell.ast_node_interactivity = "all"
InteractiveShell.ast_node_interactivity = 'last_expr'
In [ ]:
import os,sys
import re
sys.dont_write_bytecode = True
In [ ]:
import pandas as pd
# import geopandas as gpd
# from sklearn.externals import joblib
import numpy as np
import matplotlib.pyplot as plt
import holoviews as hv
from holoviews import opts, dim
hv.extension('bokeh', 'matplotlib')
hv.output.info()
from pprint import pprint
from pathlib import Path
import pdb
In [ ]:
this_nb_path = Path(os.getcwd())
ROOT = this_nb_path.parent.parent
SCRIPTS = ROOT/'scripts'
paths2add = [this_nb_path, SCRIPTS]
print("Project root: ", str(ROOT))
print("this nb path: ", str(this_nb_path))
print('Scripts folder: ', str(SCRIPTS))
for p in paths2add:
if str(p) not in sys.path:
sys.path.insert(0, str(p))
print(str(p), "added to the path\n")
print(sys.path)
In [ ]:
# Load custom helper libraries
import output_helpers as oh
# import convert_helpers as ch
# import viz_helpers as vh
Set data path¶
Sample datasets¶
In [ ]:
DATA = Path("/home/hayley/Data_Spacenet/")
# Sample dataset
sample_dir = Path("/home/hayley/Data_Spacenet/SpaceNet_Roads_Sample/")
sample_root_dirs = [sample_dir/ city for city in ["AOI_2_Vegas_Roads_Sample",
"AOI_3_Paris_Roads_Sample",
"AOI_4_Shanghai_Roads_Sample",
"AOI_5_Khartoum_Roads_Sample"]
]
# Original big rgb(16), rgb8bits, mask (uint)
sample_rgb_dirs = [root/"RGB-PanSharpen" for root in sample_root_dirs]
sample_rgb8_dirs = [root/"RGB-PanSharpen-8bits" for root in sample_root_dirs]
sample_mask_dirs = [root/"Mask" for root in sample_root_dirs]
# Cropped 100x100 tif image tiles
sample_rgb_tile_dirs = list(map(get_crop_dir, sample_rgb_dirs))
sample_rgb8_tile_dirs = list(map(get_crop_dir, sample_rgb8_dirs))
sample_mask_tile_dirs = list(map(get_crop_dir, sample_mask_dirs))
# for d in sample_mask_tile_dirs:
# nprint(d, d.exists())
sample_road_vec_dirs = [root/"geojson/spacenetroads" for root in sample_root_dirs]
sample_buffer_vec_dirs = [root/"geojson/buffer" for root in sample_root_dirs]
Training datasets¶
In [ ]:
vegas_root = Path("/home/hayley/Data_Spacenet/AOI_2_Vegas_Roads_Train/")
paris_root = Path("/home/hayley/Data_Spacenet/AOI_3_Paris_Roads_Train/")
shanghai_root = Path("/home/hayley/Data_Spacenet/AOI_4_Shanghai_Roads_Train/")
k_root = Path("/home/hayley/Data_Spacenet/AOI_5_Khartoum_Roads_Train/")
train_root_dirs = [vegas_root, paris_root, shanghai_root, k_root]
# Original big rasters: rgb(16), rgb8bits, mask (uint)
train_rgb_dirs = [root/"RGB-PanSharpen" for root in train_root_dirs]
train_rgb8_dirs = [root/"RGB-PanSharpen-8bits" for root in train_root_dirs]
train_mask_dirs = [root/"Mask" for root in train_root_dirs]
# Cropped 100x100 tif image tiles
train_rgb_tile_dirs = list(map(get_crop_dir, train_rgb_dirs))
train_rgb8_tile_dirs = list(map(get_crop_dir, train_rgb8_dirs))
train_mask_tile_dirs = list(map(get_crop_dir, train_mask_dirs))
# vector file dirs
train_road_vec_dirs = [root/"geojson/spacenetroads" for root in train_root_dirs]
train_buffer_vec_dirs = [root/"geojson/buffer" for root in train_root_dirs]
Simple sample datasets¶
In [ ]:
RGB8_DIR = Path('/home/hayley/Data_Spacenet/SpaceNet_Roads_Sample/'
'AOI_2_Vegas_Roads_Sample/RGB-PanSharpen-8bits/')
RGB16_DIR = Path('/home/hayley/Data_Spacenet/SpaceNet_Roads_Sample/'
'AOI_2_Vegas_Roads_Sample/RGB-PanSharpen/')
MASK_DIR = Path('/home/hayley/Data_Spacenet/SpaceNet_Roads_Sample/'
'AOI_2_Vegas_Roads_Sample/Mask/')
CROP8_DIR = RGB8_DIR.parent/"RGB-PanSharpen-8bits-Crop"
CROP16_DIR = RGB16_DIR.parent/"RGB-PanSharpen-Crop"
In [ ]:
RGB8_FILES = [f for f in RGB8_DIR.iterdir() if f.suffix == '.tif']
RGB16_FILES = [f for f in RGB16_DIR.iterdir() if f.suffix == '.tif']
MASK_FILES = [f for f in MASK_DIR.iterdir() if f.suffix == '.tif']
print(len(RGB8_FILES), len(MASK_FILES))
In [ ]:
RGB8_CROP_DIR = RGB8_DIR.parent / 'RGB-PanSharpen-8bits-Crop'
RGB8_CROP_DIR.mkdir(exist_ok=True)
In [ ]:
# check if the simple image loading works
im = cv2.imread(str(RGB8_FILES[0]), -1)
In [ ]:
mask = cv2.imread(str(MASK_FILES[0]),-1)
nprint("im shape: ", im.shape)
nprint('mask shape: ', mask.shape)
plt.imshow(im)
In [ ]:
# Create train, dev, test indices
Precision-Recall¶
- Set holoviews display defaults
- style mapping
dim
is a function that can be applyed to a specific dimension of thehv.Elment
object. For example,dim('y').categorize( {0: 'circle', 1:'square'}).apply(points)
In [ ]:
%opts Scatter (marker='o', size=10, alpha=0.6) [aspect='equal', tools=['hover'], height=600]
In [ ]:
%opts Points(marker='o', size=10, alpha=0.6) [aspect='equal', tools=['hover'], height=600]
In [ ]:
%opts HLine (line_dash='dashed', color='k') [tools=['hover']]
In [ ]:
%opts VLine (line_dash='solid', color='k') [tools=['hover']]
- Dummy example
In [ ]:
np.random.seed(0)
scores = sorted(np.random.normal(0.5, 0.5, size=15), reverse=True)
scores = np.clip(scores, 0., 1.); pprint(list(scores))
labels = np.r_[ 1, 1, 1, 0, 1, 1, 0, 0, 1,0,0,0,1,0,0]; print(len(labels))
df = pd.DataFrame({'score': scores,
'y': labels})
thresh = 0.5
df['yhat'] = df['score'] > thresh
display(df)
# data = np.array(0.99, 0.88, 0.81
Compute precision and recall given the data¶
In [ ]:
cond1 = df['y']==1
cond2 = df['yhat']==1
tp = df[cond1&cond2]
fp = df[~cond1&cond2]
tn = df[~cond1&~cond2]
fn = df[cond1&~cond2]
display(tp)
display(fp)
display(tn)
display(fn)
In [ ]:
# def get_precision_recall(df, thresh):
def get_precision_recall(thresh):
assert 'y' in df.columns and 'score' in df.columns
df['yhat'] = df['score'] > thresh
cond1 = df['y']==1
cond2 = df['yhat']==1
tp = len(df[cond1&cond2])
fp = len(df[~cond1&cond2])
tn = len(df[~cond1&~cond2])
fn = len(df[cond1&~cond2])
precision = tp / (tp + fp)
recall = tp / (tp + fn)
return precision, recall
In [ ]:
def test_get_precision_recall():
thresh = 0.5
display(df)
print('threshold: ', thresh)
# p,r = get_precision_recall(df, thresh)
p,r = get_precision_recall(thresh)
print('p,r: ', p, r )
test_get_precision_recall()
Visualize the data points and threshold line¶
In [ ]:
# df.columns has 'score', 'y', 'yhat'
scatter = hv.Scatter(df, kdims=['y', 'score'],vdims='yhat')
scatter_style = dict(
color=dim('y').categorize({0:'red', 1:'blue'}),
# marker=dim('yhat').categorize({False:'circle',True:'cross'}),
xlim=(-1,2),
ylim=(-0., 1.1),
aspect='equal'
)
# class division helper line
vline = hv.VLine(0.5).opts(line_width=1)
# static background showing the datapoints
static_scatter = scatter.opts(**scatter_style) *vline
In [ ]:
# Version1: static threshold line
thresh_line = hv.HLine(thresh)
line_style = dict(
color='k',
line_dash='dashed'
)
static_scatter * thresh_line.opts(**line_style)
Scatter plot of the dataset generating function¶
In [ ]:
In [ ]:
def get_sorted_labels(n, r_ones=0.5, r_mixzone=0.2, p_flips=None,
verbose=False):
"""
Returns a list of ones and zeros of length `n`.
Args:
- n (int): number of labels (ones and zeros)
- r_ones (float): percentage of ones before any mixing ('r' for ratio)
- r_mix (float): mixing percentage. n*r_mix number of points in the middle
will be randomly permuted
- p_flips (iterable): probability of fliping 1->0 and 0->1 in the non-mixing zone
- default is None in which case no (random) flip will occur
Returns:
- labels (list): a list of ones and zeros of which `r_ones` percent is ones
"""
n1 = int(np.float(n*r_ones))
n0 = n - n1
labels = np.r_[np.ones(n1), np.zeros(n0)]
# mixing ones and zeors in the middle zone
n_mix = int(np.float(n*r_mixzone))
n1_mix = n_mix//2
n0_mix = n_mix - n1_mix
n1_pure = n1 - n1_mix
n0_pure = n0 - n0_mix
assert n1_pure >=0 and n0_pure>=0
if verbose:
print('number of mixing: ', n_mix)
print('number of pure ones and zeors: ', n1_pure, n0_pure)
print('label before mixing: ', labels)
# mix labels in the middle zone
mid = labels[n1_pure:n1_pure+n_mix] #same as labels[n1-n1_mix: n1+n0_mix]
if verbose:
print('mid before mixing: ', mid)
labels[n1_pure:n1_pure+n_mix] = np.random.permutation(mid)
if verbose:
print('mid after mixing: ', mid)
# flip high and low zones
if p_flips is not None and len(p_flips) == 2:
p_flip1, p_flip0 = p_flips
# randomly choose which indices to flip
n1_flipped = int(n1_pure*p_flip1)
n0_flipped = int(n0_pure*p_flip0)
inds_flip1 = np.random.randint(n1_pure, size=n1_flipped)
inds_flip0 = -np.random.randint(1,n0_pure+1, size=n0_flipped)
# flip
if verbose:
print('before flip1: ', labels[:n1_pure] )
print('before flip0: ', labels[-n0_pure:])
labels[inds_flip1] = 0
labels[inds_flip0] = 1
if verbose:
print('after flip1: ', labels[:n1_pure] )
print('after flip0: ', labels[-n0_pure:])
return labels
def test_get_sorted_labels():
n = 10; r_ones = 0.5;r_mixzone=0.5
print(get_sorted_labels(n,r_ones, r_mixzone,verbose=True))
def test_get_sorted_labels_2():
n = 20; r_ones = 0.5;r_mixzone=0.5
p_flips = [0.5,0.5]
print(get_sorted_labels(n,r_ones, r_mixzone, p_flips,verbose=True))
test_get_sorted_labels()
# test_get_sorted_labels_2()
In [ ]:
def get_data(n, r_ones, r_mixzone, p_flips, seed=0):
np.random.seed(seed)
sorted_scores = sorted(np.random.normal(0.5, 0.5, size=n), reverse=True) #large->small
sorted_scores = np.clip(sorted_scores, 0., 1.)
sorted_labels = get_sorted_labels(n, r_ones, r_mixzone, p_flips)
df = pd.DataFrame({'score': sorted_scores,
'y': sorted_labels})
# print(sorted_labels)
# print(list(sorted_scores))
# display(df)
return df
def test_get_data():
n = 10; seed = 0
r_ones = 0.5; r_mixzone = 0; p_flips = (0.2, 0.2)
display(get_data(n, r_ones, r_mixzone, p_flips, seed))
test_get_data()
In [ ]:
def get_tline(t, style=dict()):
return hv.HLine(t).opts(**style)
def get_scatter(df, style=None):
scatter = hv.Scatter(df, kdims=['y','score'], vdims='y')
if style is None:
style = dict(
color=dim('y').categorize({0:'red', 1:'blue'}),
xlim=(-1, 2),
ylim=(-0.1, 1.1),
aspect='equal'
)
return scatter.opts(**style)
def get_table(n, r_ones=0.5, r_mixzone=0., p_flips=None, seed=0):
df = get_data(n, r_ones, r_mixzone, p_flips, seed)
def get_data_layout(n, r_ones=0.5, r_mixzone=0., p_flips=None, seed=0, thresh=0.0):
"""
Args:
- n (int): number of points in the dataset
- seed (int): numpy random seed
Returns:
- hv.Layout object with the datapoints scatter object and vline at 0.5
"""
df = get_data(n, r_ones, r_mixzone, p_flips, seed)
# display(df)
# df.columns has 'score', 'y'
scatter = hv.Scatter(df, kdims=['y', 'score'], vdims='y')
scatter_style = dict(
color=dim('y').categorize({0:'red', 1:'blue'}),
xlim=(-1, 2),
ylim=(-0.1, 1.1),
aspect='equal'
)
# class division helper line
vline = hv.VLine(0.5).opts(line_width=1)
# threshold line
tline = get_tline(thresh)
# dataframe table (temp -- todo: remove?)
table = hv.Table(df.sample(n=min(50,len(df))), kdims=['score','y'])
# static background showing the datapoints
temp = scatter.opts(**scatter_style) * vline *tline
# layout = scatter.opts(**scatter_style) * vline *tline + table.opts(width=200)
return temp #layout
def test_get_data_layout():
n = 20
r_ones = 0.5
r_mixzone = 0.2
p_flips = [0.1,0.1]
seed = 10
layout = get_data_layout(n, r_ones, r_mixzone, p_flips, seed)
display(layout)
test_get_data_layout()
In [ ]:
# dmap of the data scatter plot
dmap_data = hv.DynamicMap(
lambda n,r_ones, r_mixzone, seed, thresh: get_data_layout(n, r_ones, r_mixzone, p_flips=[0.1,0.1], seed=seed, thresh=thresh),
kdims=['n', 'r_ones', 'r_mixzone', 'seed', 'thresh']
)
dmap_data.redim.range(n=(10,20),
r_ones=(0.5,1.),
r_mixzone=(0., 0.5),
seed=(0,10),
thresh=(0.,1.0)
)
Make the threhold line dynamic/interactive¶
Now let's use a dynamicmap to make a dynamic threshold line.
Recall the hv.DynamicMap constructor format
hv.DynamicMap
(callable
, kdims
=list of argnames to the callable
)
where callable
is a function that returns a hv object
In [ ]:
def get_tline(t, style=dict()):
return hv.HLine(t).opts(**style)
dmapgen_thresh = hv.DynamicMap(get_tline, kdims='t')
dmap_thresh = dmapgen_thresh.redim(t=dict(range=(0.,1.),
step=0.001)
)
dmap_thresh
In [ ]:
# Now let's combine the static data table represention with this dynamicmap fo the threshold lines
static_scatter * dmap_thresh
In [ ]:
# Now with dynamic df scatter and dynamic thresh line
dmap_scatter * dmap_thresh
Now, let's add another graph object that shows the precision-recall in the 2D space as we vary the threshold value
In [ ]:
def pr_crosshair(t):
crosshair_style = dict(color='r',
line_dash='solid')
p,r = get_precision_recall(t)
layout = (hv.VLine(p).opts(**crosshair_style) * hv.HLine(r).opts(**crosshair_style))
print('precision: ', p, 'recall: ', r)
# xaxis for p, yaxis for r
return layout.redim.label(x='precision', y='recall')
In [ ]:
pr_crosshair(0.3)
In [ ]:
dmapgen_pr = hv.DynamicMap(pr_crosshair, kdims='t')
dmap_pr = dmapgen_pr.redim.range(t=(0.,1.))
dmap_pr
Putting the static scatter plot, dynamic threshold hline and pr plot together
In [ ]:
((static_scatter * dmap_thresh) + dmap_pr).cols(1)
In [ ]:
l = (hv.VLine(1) * hv.HLine(0.5))
print(l.dimensions)
l2 = l.redim.label(x='precision', y='recall')
l2
HoloMaps¶
In [ ]:
from scipy.stats import beta
def beta_dist(a, b):
rv = beta(a, b)
xs = np.linspace(0,1,100)
ys = rv.pdf(xs)
return hv.Curve((xs, ys))
In [ ]:
beta_dist(1,2)
- One parameter space
In [ ]:
alphas = [0.1, 0.2, 0.3, 0.4, 1, 2, 5,10]
b = 1.
beta_dict = {a: beta_dist(a, b) for a in alphas}
print(len(beta_dict))
In [ ]:
# numpy floating point error handling: from printing to actual warning
# https://stackoverflow.com/a/15934081
print('current floating point handle setting: ')
print(np.geterr())
# set all errors to warning
np.seterr(all='warn')
import warnings
hmap = hv.HoloMap(beta_dict, kdims='alpha');
with warnings.catch_warnings():
# warnings.filterwarnings('error')
# try:
# display(hmap)
# except RuntimeWarning:
# pdb.set_trace()
# print('raised!')
# Alternatively, ignore the runtime warning
warnings.simplefilter('ignore')
display(hmap)
- Two parameters
Use a tuple as the key of the dictionary
In [ ]:
betas = [0.1, 0.3, 0.6, 0.9, 1.2, 1.5, 3]
beta_dict_2d = {(a,b): beta_dist(a,b) for a in alphas for b in betas}
hmap_2d = hv.HoloMap(beta_dict_2d, kdims=['a', 'b'])
with warnings.catch_warnings():
warnings.simplefilter('ignore')
display(hmap_2d.opts(ylim=(0,10)))
In [ ]:
curve = hv.Curve((list(range(10)), np.ones(10)))
print(curve)
print(curve.kdims, curve.vdims)
curve
In [ ]:
#`redim` method
curve2 = curve.redim(x='newx')
print(curve2)
print(curve2.data is curve.data)
curve2.select(newx=(3,6))
In [ ]:
print(thresh_line)
HoloMap of HLines¶
Another example with simple horizontal line. This can be used for the threshold line visualization for my precision-recall exploration.
In [ ]:
t_list = np.linspace(0,1,20)
hline_dict = {t: hv.HLine(t) for t in t_list}
hmap = hv.HoloMap(hline_dict, kdims='thresh')
hmap
Great!
DynamicMap¶
- a wrapper around a callable that returns HoloViews objects
- can be seen as a generalization of HoloMap (which holds a dictionary of elements)
- beta_dist(a,b) -> hv.Curve
In [ ]:
b = 1.
hmap = hv.HoloMap( {a: beta_dist(a,b) for a in alphas},
kdims='a')
In [ ]:
tdim = hv.Dimension(
In [ ]:
dmap_line = hv.DynamicMap(thresh_line, kdims='t')
# can realize the dmap using `redim` method
# `redim` always returns a new object
## 1. Set the range only:
dmap_line.redim.range(t=(0.,2.)) #make sure to use float
# http://holoviews.org/reference/apps/bokeh/sine.html
## 2. To control the stepsize explicitly, use the `redim` and
# dim_name as the key and value as a setting dictionary:
dmap_line.redim(t=dict(range=(-1.,1.),
step=1.0)
)
In [ ]:
hv.output.info()
In [ ]:
dmap_line.redim.range(
In [ ]:
dmap = hv.DynamicMap( beta_dist, kdims=['a','b'] )
dmap[1,2] + dmap.select(a=0.1, b=2.2)
In [ ]:
def thresh_line(t, dx=5, dy=2.5):
# print(t-dy, t+dy)
return hv.HLine(t)#.opts(xlim=(-dx,dx), ylim=(t-dy, t+dy))
In [ ]:
## Clone behavior
hv.extension('bokeh')
t0 = thresh_line(0.5)
t1 = t0.opts(color='r', clone=True)
t2 = t0.options(color='g')
display(t0+t1+t2)
t1 is t0
t2 is t0
In [ ]:
## current backend
hv.extension('bokeh')
hv.output.info()
H, W = 600,600
opts.defaults(
opts.Curve(height=H, width=W, tools=['hover']),
opts.HLine(height=H, width=W, tools=['hover']),
opts.VLine(height=H, width=W, tools=['hover']),
opts.Table(height=H, width=W),
opts.Image(height=H, width=W, tools=['hover'])
)
In [ ]:
xs = np.linspace(0,2,100)
ys = xs**2-1
curve = hv.Curve((xs,ys))
y10 = hv.HLine(10).opts(color='g')
# display(curve * y10)
# display( (curve *y10).opts(ylim=(-1,11)) ) #works okay on the container object
# display(y10)
# display(y10.opts(ylim=(-1,11))) # doesn't work. inconsistent
Streams¶
hv.Stream objects have two functions
- send updated values to its
subscribers
(eg. the HoloMap it was used for construction
In [ ]:
pt = (2., 2.)
pts = ( (0.5, 0.5), (0., 1.0)) # a tuple of xs and ys
pts = [(0.5, 0.5), (0., 1.0)] # a list of points
s = hv.Scatter((pts)) #default kdims are named 'x' and 'y'
In [ ]:
s = hv.Scatter((pts), 'x1', 'x2').opts(xlim=(-2,2), ylim=(-2,2))
print(s.kdims, s.vdims)
In [ ]:
XStream = Stream.define(name='x_stream', x=0.0) #can drop `name=`
YStream = Stream.define(name='y_stream', y=0.0)
xstream = XStream()
ystream = YStream()
print(xstream.get_param_values())
print(ystream.get_param_values())
xstream.event(x=3)
print(xstream.get_param_values())
In [ ]:
dmap = hv.DynamicMap(lambda x, y: hv.Scatter((x,y)), streams=[xstream, ystream])
dmap
In [ ]:
curve = hv.Curve((xs, ys))
subcurve = curve.iloc[0:4].opts(color='r')
curve * subcurve
In [ ]:
#e.iloc[bool_indices]
#eg: e.iloc[some_dimname < threshold]
print(curve.iloc[xs<3].data)
print(curve.iloc[ys<3].data)
print(curve.iloc[(xs<5 and ys<8)].data)
In [ ]:
# can send updates through streamobj
XYStream = Stream.define(name='xy_stream', xy=(1.0, 1.0))
xystream = XYStream()
print(xystream.get_param_values())
In [ ]:
temp = xystream.rename(xy='data')
print(temp)
# print(xystream.rename(xy='data').get_param_values())
In [ ]:
dmap = hv.DynamicMap(hv.Scatter, streams=[temp])
# Or, equivalently in one line
dmap = hv.DynamicMap(hv.Scatter, streams = [XYStream().rename(xy='data')])
dmap
In [ ]:
# send updated values to the dmap
# Note that they use proper parameter name for the caller object
# - temp's parameter name is `xy`, so we must pass in `xy` when calling the event method of `temp` object
# - dmap's `callable` is `hv.Scatter` whose first argument is `data`. So we must pass in `data` as the name of the parameter
# when calling the `event` method of the dmap object
temp.event(xy=(0.5,0.5))
dmap.event(data=(0.5,0))
- can "stream" (i.e. receive and transport/pipe) data from its
source
(eg. the first DynamicMap we pass it to) ```python
```
In [ ]:
from holoviews.streams import Stream, param
In [ ]:
# Define a new stream class, Thresh, with a numertic `thresh` parameter that defaults to zero.
Thresh = Stream.define('Thresh', thresh=0.0)
hv.help(Thresh)
In [ ]:
# Make a new instance
thresh = Thresh()
print('This Thresh instance has parameter:', thresh.contents)
In [ ]:
# Make a new instance with specific value instead of the default
thresh = Thresh(thresh=0.5)
print(thresh.contents)
In [ ]:
def get_hline(y):
return hv.HLine(y)
dmap = hv.DynamicMap(get_hline, streams=[Thresh(thresh=0.5).rename(thresh='y')])
dmap
In [ ]:
# A recommended way to update the value of dmap via changing the stream object
dmap.event(y=0.8)
In [ ]:
# Another way to push the new value to the dmap through stream object
dmap.streams[0].event(thresh=0.2)
In [ ]:
# can we make a stream of data frame object?
#set up
from holoviews.streams import Buffer
example = pd.DataFrame({'score': [], 'y': []},
columns = ['score', 'y'])
print(example)
In [ ]:
dfstream = Buffer(example, length=1000, index=False)
table_dmap = hv.DynamicMap(hv.Table, streams=[dfstream])
scatter_dmap = hv.DynamicMap(lambda data: hv.Scatter(data,
kdims=['y','score'],
vdims='y').opts(**scatter_style, width=600),
streams=[dfstream])
In [ ]:
def get_scatter(data):
scatter = hv.Scatter(data, kdims=['y', 'score'], vdims='y')
scatter_style = dict(
color=dim('y').categorize({0:'red', 1:'blue'}),
xlim=(-1, 2),
ylim=(-0.1, 1.1),
aspect='equal',
width=600
)
# class division helper line
vline = hv.VLine(0.5).opts(line_width=1)
return scatter.opts(**scatter_style) * vline
# better
scatter_dmap = hv.DynamicMap(get_scatter, streams=[dfstream])
In [ ]:
display(table_dmap + scatter_dmap )
In [ ]:
#push data (in pd.DataFrame format) through the buffer stream
n = 20
r_ones = 0.5
r_mixzone = 0
p_flips = [0.1, 0.1]
for i in range(10):
time.sleep(1)
seed = i
dfstream.clear()
dfstream.send( get_data(n,r_ones,r_mixzone, p_flips,seed))
In [ ]:
# Now let's add the threshold stream that can push threshold value
Thresh = Stream.define('Thresh', t=0.0)
tstream = Thresh()
thresh_dmap = hv.DynamicMap(get_tline, streams=[tstream])
thresh_dmap
In [ ]:
tstream.event(t=0.3)
In [ ]:
display(table_dmap+scatter_dmap*thresh_dmap)
In [ ]:
tstream.event(t=0.8)
Let's now link a PointerXY source to the VLine (thresh_line)¶
In [ ]:
xstream = streams.PointerX()
xstream.get_param_values()
In [ ]:
xstream.rename(x='
In [ ]:
In [ ]:
Linked stream¶
- to be able to move the threshold line on the plot directly
Story so far
- Stream is a class. We can define a new class using
Stream.define(namestr, param_name=default_val)
syntax hv.DynamicMap
can be constructed with stream objects passed in- The resulting dmap is a
subscriber
of the stream instance, ie. it listens to the events from the stream object. When any of the stream object's parameter's value changes, the stream object fires up and passes along the updated value to dmap, and dmap appropriately matches the stream obj's parameter name with its callable function's argname and updates the argument's value with the new value from the stream object
- The resulting dmap is a
- Linked stream introduces another functionality to Stream: now Stream objects can listen to its sources (often
Element
type)- Upon creation of hv.DynamicMap objects with a stream obj passed in, the dynamicmap object automatically is linked as a source to the passed-in stream object.
When an event happens on the dmap object, now the stream object receives data from the dynamicmap object (which is the
source
of the data) Let's take a look at this new functionality.
- Upon creation of hv.DynamicMap objects with a stream obj passed in, the dynamicmap object automatically is linked as a source to the passed-in stream object.
When an event happens on the dmap object, now the stream object receives data from the dynamicmap object (which is the
In [ ]:
from holoviews import streams
In [ ]:
pointer = streams.PointerXY(x=1,y=1)
print(pointer)
print(pointer.contents) #parameters
print(pointer.source) #linked sources
In [ ]:
dmap = hv.DynamicMap(lambda x,y: hv.Points([(x,y)]), kdims=['x','y']
# streams = [pointer]
)
print(dmap.data) #currently no data
# dmap.redim.range(x=(0,10), y=(0,10))
In [ ]:
dmap = hv.DynamicMap(lambda x,y: hv.Points([(x,y)]),
streams=[pointer])
# automatically links the dmap to the stream object
# that was passed in during dmap construction
print(pointer.source)
print(pointer.source is dmap)
print(dmap.streams, id(dmap.streams), dmap.streams[0] is pointer)
print(id(pointer))
In [ ]:
# dmap
dmap.opts(size=10)
- [x] todo: the mouse is not tracked but the pointer stream object's value is correctly being updates
In [ ]:
# hv.Div(1)
div_dmap = hv.DynamicMap(lambda x,y: hv.Div( f'<p>x,y: {x},{y}</p>' ),
streams=[pointer])
In [ ]:
table_dmap = hv.DynamicMap(lambda x,y: hv.Table(dict(col1=x,col2=y)).opts(height=50),
streams=[pointer])
table_dmap
In [ ]:
table_dmap.event(x=10,y=-0.5)
In [ ]:
pointer.event(x=0.5, y=0.8)
In [ ]:
pointer.get_param_values()
In [ ]:
def subscriber_div (**kwargs):
x = kwargs['x']
y = kwargs['y']
# print(x,y)
return hv.Div( f'<p>x,y: {x},{y}</p>' ).opts(height=20)
pointer.add_subscriber(subscriber_div)
In [ ]:
print(pointer.x, pointer.y)
In [ ]:
pointer.event(x=10, y=-10)
In [ ]:
subscriber_div(x=10, y=10)
In [ ]:
%opts Table [height=100,width=300]
In [ ]:
hv.Table(([1],[2]), ['x','y'])
In [ ]:
hv.Table(([1,2],[3,4]), ['x','y'])
In [ ]:
# hv.Table({'x': [1], 'y':[2]})#doesn't work
df = pd.DataFrame({'x': [1], 'y':[2]});display(df)
display(hv.Table(df))
In [ ]:
def get_table(x,y):
df = df = pd.DataFrame({'x': [x], 'y':[y]})
return hv.Table(df)
table_dmap2 = hv.DynamicMap(get_table, streams=[pointer])
In [ ]:
table_dmap2
In [ ]:
table_dmap2.event(x=10)
In [ ]:
pointer.event(x=0)
Demo of the Linked Stream to illustrate:¶
- Linked stream obj
stream
gets subscriber callables todmap
dmap
is automatically registered as the source (in case of the passed stream object is of Linked Stream instance)
In [ ]:
px = streams.PointerX()
print(px.get_param_values())
print('n subscribers before dmap construction',len(px.subscribers)) # this lists only the user-added subscriber callbacks
# print(px._callbacks)
# print("num linked callbacks: ", len(px._callbacks))
cb1 = lambda x: print("x: ", x)
px.add_subscriber(cb1)
print(px.subscribers)
print(px.source)
px.event(x=10)
In [ ]:
def gen_point(x):
# pdb.set_trace()
print("x received:", x)
return hv.Points([(x,0.5)])
In [ ]:
p_dmap = hv.DynamicMap(gen_point,
streams= [px]
)
In [ ]:
p_dmap
In [ ]:
px.event(x=1)
In [ ]:
# print(id(px.source))
print(px.source == p_dmap)
In [ ]:
dmap.event(x=2)
Load the downloaded image¶
In [ ]:
import rasterio as rio
In [ ]:
ds = rio.open(
In [ ]:
In [ ]:
In [ ]:
In [ ]:
In [ ]:
In [ ]: