Small Simplicity

Understanding Intelligence from Computational Perspective

Sep 01, 2018

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

  1. Set holoviews display defaults
    • style mapping
    • dim is a function that can be applyed to a specific dimension of the hv.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']]
  1. 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)
  1. 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)
  1. 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

  1. 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)) 
  1. 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)
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
  • 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.
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:

  1. Linked stream obj stream gets subscriber callables to dmap
  2. 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 [ ]: