pyviz panel as deployable app

see https://panel.pyviz.org/getting_started/index.html


In [201]:
import os
from importlib import reload
import numpy as np
import pandas as pd
from epitopepredict import plotting, base, peptutils, web, config
from epitopepredict import __version__
from IPython.display import HTML
import panel as pn
import panel.widgets as pnw
helppage = 'http://epitopepredict.readthedocs.io/en/latest/webapp.html'
css_file=(os.path.join(base.module_path,'static/custom.css'))
js_files = {'sortable': os.path.join(base.module_path,'static/sorttable.js')}
css=''.join(open(css_file,'r').readlines())
pn.extension(raw_css=[css])
pn.config.js_files=js_files



In [202]:
t = pn.widgets.TextInput()
m = pn.pane.Markdown("")
    
def callback(target, event):   
    target.object = '##' + event.new
  
t.link(m, callbacks={'value': callback})
t.value="hello"
pn.Row(t,m)


Out[202]:

In [203]:
html="""
    <script type="text/javascript" src="%s"></script> 
""" %os.path.join(base.module_path,'static/sorttable.js')
df=preds[0].data
res=df[:20].to_html(classes=['sortable'])
div = '<div class="scrollingArea">%s</div>' %res
pn.Column(pn.pane.HTML(html),pn.pane.HTML(div))


Out[203]:

dashboard


In [ ]:
def predictions_dashboard(path):
    """Dashboard for viewing results from epitopepredict runs."""
    
    #folder_input = pn.widgets.TextInput(name='path', value='../zaire_test', width=400,width_policy='fit')
    #reload_btn = pn.widgets.Button(name='reload',width=100,button_type='primary')    
    
    names = web.get_file_lists(path)
    if names is None:
        return
    preds = web.get_predictors(path,name=names[0])
    print (preds)
    seqname = pnw.Select(name='name', value=names[0], options=names)
    cutoff_slider = pnw.FloatSlider(name='cutoff', value=.95,start=.75,end=.99,step=0.01)
    cutoff_method = pnw.Select(name='cutoff method', value='default', options=['default','rank'])
    n_select = pnw.FloatSlider(name='n',value=1,start=1,end=8,step=1)
    plot_select = pnw.Select(name='plot view', value='tracks', options=['tracks', 'sequence'])
    table_select = pnw.Select(name='table view', value='promiscuous', options=['promiscuous','binders'])
    colorseq_box = pnw.Checkbox(name='color sequences', value=False)
    
    header = pn.pane.Markdown('__total sequences: %s__' %len(names), css_classes=['main'])
    tables = pn.Tabs(width=900)
    plot = pn.pane.Bokeh(width=800)
    debug = pn.pane.Markdown('test',style={'font-size': '10pt','background-color':'yellow'})
    summary_plot = pn.pane.Bokeh()
    summary_table_tabs = pn.Tabs()
    recalc_button = pnw.Button(name='recalculate',width=200)
    
    def update_banner():
        """Update the banner"""
        
        fullpath = os.path.abspath(path)
        banner = pn.Row(pn.pane.Markdown('<h4>epitopepredict: %s</h4> [help](%s) version %s' %(fullpath,helppage,__version__),
                                         css_classes=['divheader'],
                                         sizing_mode='stretch_width'))
        return banner
    
    def update_header(target, event):
        names = web.get_file_lists(event.new)
        target.object = "_total sequences: %s_" %str(len(names))        
        return
    
    def callback_getpath(event):
        path = os.path.getcwd()
        folder.value = path

    def update_plot(preds,name,cutoff,n,kind):
        """Plot data view"""

        if kind == 'tracks':
            p = plotting.bokeh_plot_tracks(preds,name=name,cutoff=cutoff,n=n,width=1000,title=name)           
            plot.object = p
        elif kind == 'sequence':
            p = bokeh_plot_sequence(preds,name=name,cutoff=cutoff,n=n,width=1000,
                                             title=name,color_sequence=colorseq_box.value)
            plot.object = p
        return p

    def update_tables(preds,name,n):
        """Tabular views of results"""

        P = preds[0]
        view = table_select.value        
        tables.clear()
        for P in preds:
            if view == 'promiscuous':
                df = P.promiscuous_binders(n=n,name=name)
            else:
                df = P.get_binders(name=name)
            res = df.to_html(classes="tinytable sortable")
            div = '<div class="scrollingArea">%s</div>' %res
            tables.append((P.name, div))
            #tables.append((P.name,pn.pane.HTML('<p>hddsadsadsasda</p>',width=700))) 
        return 

    def update(event):
        """Update all elements"""

        name = seqname.value
        n = n_select.value
        cutoff = cutoff_slider.value
        kind = plot_select.value
        debug.object = name
        preds = web.get_predictors(path,name=name)
        update_plot(preds,name=name,cutoff=cutoff,n=n,kind=kind)
        update_tables(preds,name,n)
        return
    
    def update_summary(path):
        """Summary info for folder"""
        
        data = web.get_summary_tables(path)
        df = pd.concat(data, sort=True).reset_index()
        #plot = plotting.bokeh_summary_plot(df)   
        #summary_plot.object = plot        
        summary_table_tabs.clear()
        a = web.aggregate_summary(data)
        div = web.get_scrollable_table(a)
        summary_table_tabs.append(('all', div))
        names = list(data.keys())
        for n in names:
            df = data[n]
            res = df.to_html(classes="tinytable sortable")
            div = '<div class="scrollingArea">%s</div>' %res
            summary_table_tabs.append((n, div))
        return
        
    @pn.depends(seqname.param.value,n_select.param.value)
    def download_link(name,n):
        if preds is None:
            return 
        df = preds[0].promiscuous_binders(n=n,name=name)
        df.to_csv()
        return pn.Pane(HTML('<a>download</a>'),width=700)

    info = pn.pane.Markdown(web.get_readme())
    banner = update_banner()
    update_summary(path)
    #reload_btn.param.watch(load_predictors, 'clicks')
    #reload_btn.param.trigger()
    seqname.param.watch(update, 'value')  
    cutoff_slider.param.watch(update, 'value')
    n_select.param.watch(update, 'value')
    table_select.param.watch(update, 'value')
    plot_select.param.watch(update, 'value')
    seqname.param.trigger('options', 'value')

    top = pn.Row(header)#,download_link)
    left = pn.Column(plot,tables,margin=10,sizing_mode='stretch_width')
    right = pn.Column(seqname,cutoff_slider,cutoff_method,n_select,plot_select,
                      table_select,colorseq_box,css_classes=['widget-box'],width=200)
    center = pn.Row(left,right)
    #bottom = pn.Row(table)
    main = pn.Column(top,center)
    summarypane = pn.Column(recalc_button,(pn.Row(summary_table_tabs)))
    tabs = pn.Tabs(('summary', summarypane),('sequence',main),('about',info))
    #tabs.append()
    app = pn.Column(banner,tabs, sizing_mode='stretch_width')
    return app

reload(web)
reload(plotting)
path = '../zaire_test/'
#path= '../results_malaria/'
app = predictions_dashboard(path)
app

In [304]:
path='../zaire_test'
#path='../results_malaria'
names = web.get_file_lists(path)
name=names[5]
preds = web.get_predictors(path,name=name)
preds


Out[304]:
[tepitope predictor with results in 1 sequences,
 netmhcpan predictor with results in 1 sequences,
 mhcflurry predictor with results in 1 sequences]

In [314]:
def bokeh_plot_sequence(preds, name=None, n=2, cutoff=.95, cutoff_method='default',
                        width=1000, color_sequence=False, title=''):
    """Plot sequence view of binders """

    from bokeh.plotting import figure
    from bokeh.models import ColumnDataSource, LinearAxis, Grid, Range1d, Text, Rect, CustomJS, Slider, RangeSlider, FactorRange
    from bokeh.layouts import gridplot, column
    
    colors = []    
    seqs = []
    text = []
    alleles = []
    ylabels = []
    pcolors = plotting.get_bokeh_colors()
    
    for P in preds:
        print (P.name)
        df = P.data
        #get sequence from results dataframe
        seq = base.sequence_from_peptides(df)
        l = base.get_length(df)
        b = P.get_binders(name=name, cutoff=cutoff, cutoff_method=cutoff_method)
        pb = P.promiscuous_binders(name=name, cutoff=cutoff, n=n, cutoff_method=cutoff_method)
        b = b[b.pos.isin(pb.pos)] #only promiscuous
        
        grps = b.groupby('allele')
        al = list(grps.groups)        
        alleles.extend(al)
        ylabels.extend([P.name+' '+i for i in al])
        currseq=[seq for i in al]
        seqs.extend(currseq)
        t = [i for s in currseq for i in s]          
        text.extend(t)
        print (len(seqs),len(text))
        for a in al:
            pos=[]
            f = list(b[b.allele==a].pos)         
            for i in f: 
                pos.extend(np.arange(i,i+l))
            if color_sequence is True:
                c = plotting.get_sequence_colors(seq)
            else:
                c = ['white' for i in seq]

            for i in pos:
                c[i] = pcolors[P.name]               
            colors.extend(c)
        
    #put into columndatasource for plotting
    N = len(seqs[0])
    S = len(alleles)  
    x = np.arange(1, N+1)
    y = np.arange(0,S,1)
    xx, yy = np.meshgrid(x, y)
    gx = xx.ravel()
    gy = yy.flatten()    
    recty = gy+.5
    
    source = ColumnDataSource(dict(x=gx, y=gy, recty=recty, text=text, colors=colors))
    plot_height = len(seqs)*15+60
    x_range = Range1d(0,N+1, bounds='auto')
    L=100
    if len(seq)<100:
        L=len(seq)
    view_range = (0,L)
    viewlen = view_range[1]-view_range[0]
    
    fontsize="8.5pt"
    tools="xpan, reset, save"
    p = figure(title=title, plot_width=width, plot_height=plot_height, x_range=view_range, y_range=ylabels, tools=tools,
               min_border=0, sizing_mode='stretch_both', lod_factor=10,  lod_threshold=1000)
    seqtext = Text(x="x", y="y", text="text", text_align='center',text_color="black", 
                 text_font="monospace", text_font_size=fontsize)    
    rects = Rect(x="x", y="recty", width=1, height=1, fill_color="colors", line_color='gray', fill_alpha=0.6)
        
    p.add_glyph(source, rects)    
    p.add_glyph(source, seqtext)
    p.xaxis.major_label_text_font_style = "bold"
    p.grid.visible = False
    p.toolbar.logo = None

    #preview view (no text)
    p1 = figure(title=None, plot_width=width, plot_height=S*3+5, x_range=x_range, y_range=(0,S), tools=[],
                    min_border=0, sizing_mode='stretch_width', lod_factor=10, lod_threshold=10)
    rects = Rect(x="x", y="recty",  width=1, height=1, fill_color="colors", line_color=None, fill_alpha=0.6)
    previewrect = Rect(x=viewlen/2,y=S/2, width=viewlen, height=S*.99, line_color='darkblue', fill_color=None)
    p1.add_glyph(source, rects)
    p1.add_glyph(source, previewrect)    
    p1.yaxis.visible = False
    p1.grid.visible = False    
    p1.toolbar_location = None
    
    #callback for slider move
    jscode="""        
        var start = cb_obj.value[0];  
        var end = cb_obj.value[1];        
        x_range.setv({"start": start, "end": end})
        rect.width = end-start;
        rect.x = start+rect.width/2;
        var fac = rect.width/width;
        console.log(fac);
        if (fac>=.14) { fontsize = 0;}  
        else { fontsize = 8.5; }
        text.text_font_size=fontsize+"pt";
    """
    callback = CustomJS(
        args=dict(x_range=p.x_range,rect=previewrect,text=seqtext,width=p.plot_width), code=jscode)
    slider = RangeSlider (start=0, end=N, value=(0,L), step=10, callback_policy="throttle")
    slider.js_on_change('value_throttled', callback)
    
    #callback for plot drag
    jscode="""        
        start = parseInt(range.start);
        end = parseInt(range.end);
        slider.value[0] = start;
        rect.width = end-start;
        rect.x = start+rect.width/2;
    """
    p.x_range.callback = CustomJS(args=dict(slider=slider, range=p.x_range, rect=previewrect),
                                  code=jscode)
    
    p = gridplot([[p1],[p],[slider]], toolbar_location="below", merge_tools=False)
    return p

reload(plotting)
p = bokeh_plot_sequence(preds, n=2, title=name)#, color_sequence=True)
pn.pane.Bokeh(p, width=1400)


tepitope
11 3267
netmhcpan
14 4158
mhcflurry
17 5049
Out[314]:

single summary table?


In [ ]:
data = web.get_summary_tables(path)
def aggregate_summary(data):
    X = pd.concat(data).reset_index().rename(columns={'level_0':'predictor'})
    a = pd.pivot_table(X,index=['locus_tag','length'],columns=['predictor'],values=['clusters','binders','binder_density']).reset_index()
    a = a.fillna('-')
    return a

a = aggregate_summary(data)
print (a)

In [ ]:
def summ_plot(df, height=400):
    
    from bokeh.models import ColumnDataSource, LinearColorMapper    
    from bokeh.plotting import figure
    from bokeh.models.glyphs import Text, Rect, Circle    
    from bokeh.transform import transform
    
    source = ColumnDataSource(data=df)
    colors = ["#75968f", "#a5bab7", "#c9d9d3", "#e2e2e2", "#dfccce", "#ddb7b1", "#cc7878", "#933b41", "#550b1d"]
    mapper = LinearColorMapper(palette=colors, low=df.binders.min(), high=df.binders.max())
    text_props = {"text_align": "left", "text_baseline": "middle", "text_font": "18"}
    
    tools = "pan,wheel_zoom,hover,reset,save"
    prednames = list(df.predictor.unique())
    names = list(df.name.unique())
    p = figure(plot_height=height,tools=tools,x_range=prednames,y_range=names)
    hm = Rect(x='predictor', y='name',fill_color=transform('binders', mapper), width=1, height=1)
    r = Text(x='predictor', y="name", text="top_peptide", **text_props)               
    p.add_glyph(source, hm)
    p.add_glyph(source, r)
    p.toolbar.logo = None
    return p

X = pd.concat(data).reset_index().rename(columns={'level_0':'predictor'})
print (X[:2])
p=summ_plot(X)
#pn.pane.Bokeh(p)

In [174]:
reload(plotting)
p = plotting.bokeh_plot_tracks(preds, width=1000, height=400, cutoff=.95, title='test')
pn.pane.Bokeh(p)


Out[174]:

In [ ]:
def bokeh_test(n=20,height=400):

    from bokeh.models import ColumnDataSource
    from bokeh.plotting import figure
    from bokeh.models.glyphs import Text, Rect, Circle
    data = {'x_values': np.random.random(n),
            'y_values': np.random.random(n)}
    source = ColumnDataSource(data=data)
    tools = "pan,wheel_zoom,hover,tap,reset,save"
    p = figure(plot_height=height,tools=tools)
    #c = Circle(x='x_values', y='y_values', radius=.02, line_color='black', fill_color='blue', fill_alpha=.6)
    c=p.circle(x='x_values', y='y_values', radius=.02, line_color='black', fill_color='blue', fill_alpha=.6, source=source)
    #p.add_glyph(source, c)
    return p

reload(plotting)
#s = pn.pane.Str('aas')
s = pnw.IntSlider(start=1,end=10,value=1)
p = bokeh_test(n=50)

pp = pn.panel(p)

#s.jslink(c.glyph, value='line_width')
#pp.link(s, callbacks={'xrange.start': callback})
#pp.jslink(s, **{'x': 'value'})
#p.js_on_event('tap', code)
pn.Row(s, pp)

template for app


In [31]:
template = """
{% extends base %}

<!-- goes in body -->
{% block postamble %}
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
<link rel="stylesheet" href="/home/damien/gitprojects/epitopepredict/epitopepredict/static/custom.css">
{% endblock %}

<!-- goes in body -->
{% block contents %}
<h1>epitopepredict app</h1>
<p>This a test.</p>
<br>
<div>
  <div class="row">
    <div class="col-sm">
      {{ embed(roots.A) }}
    </div>
  </div>
</div>
{% endblock %}
"""

In [32]:
tmpl = pn.Template(template)
tmpl.add_panel('A', app)
tmpl.servable()


Out[32]:

In [ ]: