holoviz / holoviews

With Holoviews, your data visualizes itself.
https://holoviews.org
BSD 3-Clause "New" or "Revised" License
2.71k stars 404 forks source link

Redim the X and Y range of an Overlay #3608

Closed CorBer closed 5 years ago

CorBer commented 5 years ago

Hi,

I have create a plot combining 3 different elements(box, points, image) and can zoom in/out, pan etc using the default functionality from Bokeh ( hv.extension('bokeh'). But I cant seem to find a way where I can directly control the zoom of the whole plot. This is a snippet of my code, i am creating a global variable called plot which (I thought) would allow me to control the X and Y ranges. I was thinking that I could use plot.redim.range(x=(xx,xx)) but that does not do anything.

def loadimg(file):
    global plot, box, point_stream, img, bounds
    img=load_image(file)
    data=load_csv(file)
    # Declare a Bounds stream and DynamicMap to get box_select geometry and draw it
    box = streams.BoundsXY(source=img, bounds=(0,0,0,0))
    bounds = hv.DynamicMap(lambda bounds: hv.Bounds(bounds), streams=[box]).opts(color='white', line_width=2)
    points = hv.Points(data, vdims=['color','label']).redim.range(x=(0,xdim), y=(0,ydim)).opts( color='color')
    point_stream = streams.PointDraw(data=points.columns(), num_objects=count+20, source=points, empty_value='XX')

    plot=((img*points * bounds).opts(
            opts.Image(cmap='viridis', hooks=[hook], width=800, height=600),
            opts.Points(tools=['hover'],marker='+',  fill_color='color', fill_alpha=0.1, line_width=2,  size=10 ),
          ))

    display(plot.opts(norm=dict(framewise=True)),)

interact(loadimg ,file=imgs)
jbednar commented 5 years ago

image

CorBer commented 5 years ago

Hello James,

Thanks for chiming in, unfortunately this does not help at all ... I tried to use plot=plot.options(xlim=(2500, 2900), ylim=(1000, 1200), clone=False) but the plot does not change. I guess it is due to the fact that the plot is part of an interactive setup. Just a case of ... trying something that is not very much the default approach. Thanks again for answering.

regards Cor

jbednar commented 5 years ago

That FAQ is addressing why you didn't find redim.range to do anything. Above, you don't need "plot=" if you do clone=False, and in any case clone=False is the default for .opts, so just use that one for setting options.

CorBer commented 5 years ago

Well I first tried without clone and nothing happened also ... I thought this would be a very simple thing to do ... just set the plotting range for X and Y programmatically ... turns out to be something that was not ment to be done.

jbednar commented 5 years ago

Don't worry; setting the plotting range programatically is very common indeed; I'm just pointing out a few issues I see immediately, without having time on a Sunday to look at your full example...

CorBer commented 5 years ago

No problem, I am enjoying the fact that jupyter notebooks and the libraries give me the freedom to build stuff using python I have not been able to do before. I hope you can provide more advice during the week

CorBer commented 5 years ago

Found a way forward based on code from http://holoviews.org/user_guide/Responding_to_Events.html

%%opts Image {+framewise}
# Styles and plot options used in this user guide

ls = np.linspace(0, 10, 200)
xx, yy = np.meshgrid(ls, ls)

XY = Stream.define('limits',xl=0.1,yl=0.3,xh=0.4,yh=0.6)

def img(xl,yl,xh,yh):
    extents=(xl,yl,xh,yh)
    return hv.Image(np.sin(xx)*np.cos(yy), extents=extents)

dmap = hv.DynamicMap(img, streams=[XY()])

dmap

after this I can call dmap.event(xl=0,yl=0,xh=1,yh=1) and it will zoom to that area !

CorBer commented 5 years ago

Now I only need change the way I use interact probably

CorBer commented 5 years ago

Unfortunately this does not work as easy as I was hoping. When using several elements in a plot (inside the img_routine and combined using * at the end, the moment I add my markers to the plot the call to dmap.event does not update the plot anymore.

CorBer commented 5 years ago

So I am back to my previous code, I am sharing it below together with a screenshot after starting the code. To make clear what my issue is: the code allows the user to draw a box using the box-edit function from the bokeh-menu. What I then want to do is save that box (allready implemented) and from that moment directly allow the user to zoom to this section of the plot the moment the user presses a button (not implemented yet) ideally without reloading the whole graph.

regards Cor

import numpy as np
import pandas as pd
from ipywidgets import interact
from ipywidgets import HBox, Button,  Output
import holoviews as hv
from holoviews import opts
from holoviews import streams
import imageio
import os
import csv

hv.extension('bokeh')
#setup debugger
debug_view = Output(layout={'border': '1px solid black'})

#load filenames
path = '.'
imgs = list(filter(lambda fn:fn.lower().endswith('.jpg'), os.listdir(path)))

xdim,ydim=0,0

##############################################    LABELS
dummy_data=list()
lbls=['N','T','L','R','F','G','H','J','1','2','3','4','5','6','7','8','9']
# routine to give markers a default colour based on the labels
def lbl2col(csv_label):
   col='black' #wrong labeled will be shown in black
   if csv_label=='N':
       col='red'
   if csv_label=='T':
       col='lightgreen'
   if csv_label in ['L','R']:
       col='yellow'
   if csv_label in ['F','G']:
       col='lightblue'
   if csv_label in ['H','J']:
       col='orange'
   if csv_label in ['1','2','3','4','5','6','7','8','9']:
       col='white'     
   return col
##############################################    LOAD BOX
def load_box(file):
    global xdim,ydim, xmax,xmin,ymax,ymin, dummy_data
    filecsv=str(file).split('.')[0]+'.box'
    try:
        with open(filecsv) as csv_file:
            csv_reader = csv.reader(csv_file)
            next(csv_reader, None) #skip the CSVheader
            #load the box Xmin, Ymin, Xmax, Ymax
            val=next(csv_reader,None)
            xmin=int(float(val[0]))
            val=next(csv_reader,None)
            ymax=ydim-int(float(val[0]))

            val=next(csv_reader,None)
            xmax=int(float(val[0]))
            val=next(csv_reader,None)
            ymin=ydim-int(float(val[0]))

    except:
        xmin,ymin=0,0
        xmax,ymax=xdim,ydim

    dummy_data=list()    
    yheight=(ymax-ymin )/20  #20 steps
    y=ydim-ymin
    x=xmin
    for csv_label in lbls:
       colr=lbl2col(csv_label)
       y=y-yheight
       dummy_data.append([x,y,colr,csv_label])

##############################################    LOAD CSV
@debug_view.capture()
def load_csv(file):
    import csv
    #filecsv='DJI_0002.csv'
    #create the filename and try to load the data
    filecsv=str(file).split('.')[0]+'.csv'
    #print(filecsv
    data=list()
    count=0
    xmin,ymin=xdim,ydim
    xmax,ymax=0,0
    try:
        with open(filecsv) as csv_file:
            csv_reader = csv.reader(csv_file)
            next(csv_reader, None)  # skip the header
            moredata=True
            while moredata:
                try:
                    csvdata=next(csv_reader,None)
                    csv_x=int(float(csvdata[0]))
                    csv_y=int(float(csvdata[1]))
                    #keep track of the margins
                    if csv_x>0:
                        if csv_y>0:
                            if csv_x<xmin:
                                xmin=csv_x
                            if csv_x>xmax:
                                xmax=csv_x
                            if csv_y<ymin:
                                ymin=csv_y
                            if csv_y>ymax:
                                ymax=csv_y

                    csv_label=csvdata[3]
                    col=lbl2col(csv_label)
                    data.append([csv_x,csv_y,col,csv_label])
                    count=count+1
                except:
                  moredata=False  
    except:
        print('error in csv')
        xmin,ymin=0,0
        xmax,ymax=xdim,ydim
        data=dummy_data #fallback to dummy

    return data    

##############################################    SET BOUNDS
def setimagebounds():
    global xmax,xmin,ymax,ymin
    width=(xmax-xmin) #prepare for zooming out
    height=(ymax-ymin)
    ratio=xdim/ydim # original image ratio 
    #keep centre, keep image ratio and zoom 20% out
    if xmin>0:
        h_width=(height*ratio/2)*1.2
        h_height=(height/2)*1.2
    else:
        h_width=height*ratio/2
        h_height=height/2

    centerX=(width)*0.5+xmin
    centerY=(height)*0.5+ymin

    xmax=centerX+h_width
    ymax=centerY+h_height
    xmin=centerX-h_width
    ymin=centerY-h_height

def hook(plot, element):
    global xmin, ymin, xmax, ymax
    plot.handles['x_range'].start = xmin
    plot.handles['x_range'].end = xmax
    plot.handles['y_range'].start = ydim-ymax
    plot.handles['y_range'].end = ydim-ymin
    #plot.handles['xaxis'].visible=False
    #plot.handles['yaxis'].visible=False

##############################################    LOAD_IMAGE

def load_image(file):
    global img, table,points,xdim,ydim
    img = imageio.imread(file)
    nimg = np.asarray(img)
    ydim, xdim, chans = nimg.shape
    img=hv.Image(nimg[::1], bounds=(0,0,xdim,ydim))
    return img

data=list()
lastfile=''
labelcount=15
##############################################    INTERACTIVE LOADIMAGE
@debug_view.capture(clear_output=True)
def I_loadimg(file):
    global lastfile
    lastfile=file
    global plot, box, point_stream, img, bounds

    img=load_image(file)
    load_box(file)
    data=load_csv(file)
    # Declare a Bounds stream and DynamicMap to get box_select geometry and draw it
    box = streams.BoundsXY(source=img, bounds=(0,0,0,0))
    bounds = hv.DynamicMap(lambda bounds: hv.Bounds(bounds), streams=[box]).opts(color='white', line_width=2)

    setimagebounds()

    points = hv.Points(data, vdims=['color','label']).redim.range(x=(0,xdim), y=(0,ydim)).opts( color='color')
    point_stream = streams.PointDraw(data=points.columns(), num_objects=labelcount+20, source=points, empty_value='XX')

    plot=((img*points * bounds).opts(
            opts.Image(cmap='viridis', hooks=[hook], width=800, height=600),
            opts.Points(tools=['hover'],marker='x',  fill_color='color', fill_alpha=0.1, line_width=2,  size=10 ),
          ))

    display(plot.opts(framewise=True))

##############################################    GUI

button = Button(description="Save CSV")

@debug_view.capture()
def on_button_clicked(b):
  df=pd.DataFrame(point_stream.element.data)
  df=df[['x','y','color','label']]
  fname=lastfile.split('.')[0]+'.csv'
  df.to_csv(fname, index=False)
  button.description='Saved CSV'
button.on_click(on_button_clicked)

Zbutton = Button(description="Save ZoomBox")

@debug_view.capture()
def on_Zbutton_clicked(b):
  Zbutton.description='Saved Zoom'
  df=pd.DataFrame(box.bounds)
  fname=lastfile.split('.')[0]+'.box'
  df.to_csv(fname, index=False)
Zbutton.on_click(on_Zbutton_clicked)

display(HBox([button,Zbutton]))
display(debug_view)    
interact(I_loadimg ,file=imgs)

image

CorBer commented 5 years ago

Closing, did not find a solution

github-actions[bot] commented 3 weeks ago

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.