code: #import tkinter as tk
  #from tkinter import filedialog
  from bokeh.layouts import gridplot, column
  from bokeh.models import ColumnDataSource,CustomJS, LassoSelectTool, BoxSelectTool, Slider, Select, RadioButtonGroup, Button, TextInput, HoverTool, TapTool
  from bokeh.models.annotations import Title
  from bokeh.plotting import figure, show, output_file, reset_output
  from bokeh.application.handlers import FunctionHandler
  from bokeh.application import Application
  from bokeh.server.server import Server
  #from __future__ import division
  from bokeh.io import curdoc   
  import numpy as np
  import pandas as pd
  from sklearn.decomposition import PCA
  import umap
  import math
  from operator import add
  
  # This stops the GUI window opening  
  #root = tk.Tk()
  #root.withdraw()
  
  
  #root.iconify()
  #root.update()
  #root.deiconify()
  #root.mainloop()
  
  # change data path to file path when ready
  
  def load_data():
      curdoc().clear()
  	
      button_1 = Button(label="Load data")
      button_1.on_click(load_data)
      button_2 = Button(label="Advanced options")
      button_2.on_click(Advanced_options)
  
  
  
  
      curdoc().add_root(button_1)
      curdoc().add_root(button_2)
  	
      
      datapath=filedialog.askopenfilename() 
      
  
  	
      global Dsource, Csource, Isource, Ssource, data_new_masked, data3, CX2, CY2, data, YY, XX,SelectedIon, mzlabs
      
      
      mz=pd.read_csv(datapath,sep='\t',skiprows=(0,1,2),header=None, nrows=1)
      
  	
      mz=mz.drop(columns=[0,1,2])
      #lastmz=mz.columns[-1]
      #mz=mz.drop(columns=[lastmz-1,lastmz])
  
          
      data = pd.read_csv(datapath,sep='\t',skiprows=(0,1,2,3),header=None)
      Xpixels=data[1].tolist()
      Ypixels=data[2].tolist()
  
      last=data.columns[-1]
      data = data.drop(data.columns[[0, 1, 2,last-1,last]], axis=1)
  
      ScanNum=data.index.tolist()
  
      TotalScan=len(ScanNum)
  
      
      mzlabs=mz.loc[0].values.tolist()
      
      data.columns=mzlabs
  
      
  
      data = data.reindex(sorted(data.columns), axis=1)
      mzlabs.sort()
  
      peakNum=len(mzlabs)
      
  
  
  # Work out pixel dimensions- need to do try/ catch here 
  
      a=Xpixels[1]
      Ypix=Xpixels.count(a)
  
      Xpix=np.round(TotalScan/Ypix)
  
      print(Ypix)
      print(Xpix)
  
  # Make sure Ypix * Xpix = total pix 
  
  # Do sum normalisation.. this will have multiple options 
  
  # This is not the real sum normalisation 
      
      
  
    #  data_new = data.div(data.sum(axis=1), axis=0)
  
  # This is a crude method for clearing background pixels based on lipid vs non-lipid 
  
      #low1=int(peakNum/2)
      #low2=int(peakNum/2)
      #high2=int(peakNum)
      
      low1=min(range(len(mzlabs)), key=lambda x:abs(mzlabs[x]-50))
      high1=min(range(len(mzlabs)), key=lambda x:abs(mzlabs[x]-200))
      low2=min(range(len(mzlabs)), key=lambda x:abs(mzlabs[x]-750))
      high2=min(range(len(mzlabs)), key=lambda x:abs(mzlabs[x]-900))
      
      
      D1=data.iloc[:,low1:high1]
      D2=data.iloc[:,low2:high2]
      D1s = D1.sum(axis=1)
      D1s=D1s+1
      D2s = D2.sum(axis=1)
  
      Ratio=D2s/D1s
  
      Ratio.tolist()
      
      del D1,D1s,D2,D2s
  
  # This may be possible to do with only one copy of the data 
  
  
      data2=data
      data2.loc[Ratio<2,:]=0
  
      data3=data2.loc[~(data2==0).all(axis=1)]
      del data2
      data_new_masked = data3.div(data3.sum(axis=1), axis=0)
      data_new_masked=data_new_masked.fillna(0)
  
  # Do PCA data reduction 
      
     
  
  
  
  #Data_reduced=PCA(n_components=10).fit_transform(data_new)
      Data_reduced=PCA(n_components=10).fit_transform(data_new_masked)
  
  # Perform the UMAP - these paramaters will be adjustable
  
  
      reducer = umap.UMAP(n_neighbors=10,min_dist=0.1,n_components=2,metric='euclidean')
  
      embedding = reducer.fit_transform(Data_reduced)
  
  # This can be replaced using the Xpix Ypix from above 
  
  
      YY=int(Ypix)
      XX=int(Xpix)
  
      CX=[]
      for y in range(YY):
          for x in range(XX):
              CX.append(x)
          
      CY=[]
  
      for y in range(YY):
          for x in range(XX):
              CY.append(y)
  		
  		
  		
      idx=data3.index
  
      CX2 = [CX[i] for i in idx]
      CY2 = [CY[i] for i in idx]
  	
  #    CX2=reverse(CX2)
  #    CY2=reverse(CY2)
  
      #CX2=CX2[::-1]
      #CY2=CY2[::-1]
  	
  	
  # This defines the UMAP output as columns for the plotting tools 
  
      x2=embedding[:, 0]
      y2=embedding[:, 1]    
  
  
      x3=x2-np.min(x2)
      y3=y2-np.min(y2)
  
      scannum= np.arange(0,TotalScan).tolist()
      spectra=scannum
  
      spectra2 = [spectra[i] for i in idx]
  
      ColX=(x3/np.max(x3))*255
      ColY=(y3/np.max(y3))*255
      CV1 = ["#%02x%02x%02x" % (int(r), int(g), 0) for r, g in zip(ColX, ColY)]
      CV2 = ["#%02x%02x%02x" % (0, int(r), int(g)) for r, g in zip(ColX, ColY)]
      CV3 = ["#%02x%02x%02x" % (int(r), 0, int(g)) for r, g in zip(ColX, ColY)]
  
  
  # Create the data sources required 
  
      Mean1=np.mean(data3) #.iloc[1,:] 
  
      Blank=[0]*len(CX2)
      BlankMap = ["#%02x%02x%02x" % (0, 0, 0) for r in(ColX)]
  
      CompData=Mean1/Mean1
      Ssource = ColumnDataSource(data=dict(x=mzlabs,y=Mean1))
      Dsource = ColumnDataSource(data=dict(x=x2, y=y2, cordsX=CX2,cordsY=CY2,CV=CV1,spectra=spectra2))
      Csource = ColumnDataSource(data=dict(x=mzlabs,Region1=Mean1,Region2=Mean1,y=CompData))
      Isource = ColumnDataSource(data=dict(cordsX=CX2,cordsY=CY2,Region1=Blank,Region2=Blank,Map=Blank))
  
  
  
  # Set up the plot region (need to define min and max for right plot) 
      TOOLS="lasso_select, box_select,pan,wheel_zoom,box_zoom,reset"
  
  
      Right=figure(title="UMAP output",plot_width=500,plot_height=500,x_range=[-15,15], y_range=[-15,15],tools=TOOLS)
  
      Left=figure(plot_width=500,plot_height=500,title=None,x_range=[0,XX], y_range=[0,YY],tools=TOOLS)
      Left.axis.visible = False
      Results=figure(plot_width=500,plot_height=400,title=None,x_range=[0,1200],tools="pan,wheel_zoom,box_zoom,reset,tap",x_axis_label='m/z',y_axis_label='log2 fold change')
      Spectrum=figure(plot_width=500,plot_height=400,title=None,x_range=[0,1200],x_axis_label='m/z',y_axis_label='mean intensity')
      SelectedIon=figure(plot_width=300,plot_height=300,title="Selected ion image",title_location = "below",x_range=[0,XX], y_range=[0,YY])
      SelectedIon.axis.visible = False
      Regions=figure(plot_width=200,plot_height=200,title=None,x_range=[0,Xpix], y_range=[0,Ypix],align="center")
      Regions.axis.visible = False
  
  
      Results.add_tools(HoverTool(
          tooltips = [
              ("m/z", "@x"),
              ("fold change", "@y"),
          ],
          mode='mouse',
          point_policy='snap_to_data'
      ))
  
  # Populate the initial plots 
      r=Right.scatter(x='x',y='y',fill_color='CV',line_color=None,source=Dsource,radius=0.1)
      Left.square(x='cordsX',y='cordsY',fill_color='CV',line_color=None,alpha=1,size=5, source=Dsource)
  #Spectrum.line(x='x',y='y',source=Ssource)
  #Results.line(x='x',y='y',source=Csource)
      Spectrum.vbar(x='x',top='y',source=Ssource,width=0.5)
      Results.vbar(x='x',top='y',source=Csource,width=0.5)
      Regions.square(x='cordsX',y='cordsY',fill_color='Map',line_color=None,alpha=1,size=5, source=Isource)
  
      callback = CustomJS(args=dict(renderer=r), code="""
          renderer.glyph.radius = cb_obj.value;
      """)
  
  
      slider1 = Slider(start=0.01, end=1, step=0.01, value=0.1,title='circle size')
      slider1.js_on_change('value', callback)
  
      #text = TextInput(title="title", value='Insert experiment name')
  
      button_group = RadioButtonGroup(labels=["Red Green", "Red Blue", "Blue Green"], active=0)
      select = Select(title="Option:", value="foo", options=["Sum normalisation", "No normalisation", "Mean normalisation", "Median normalisation"])
      button_1 = Button(label="Load data")
      button_2 = Button(label="Reset data")
      button_3 = Button(label="Select region 1")
      button_4 = Button(label="Select region 2")
      button_5 = Button(label="Compare")
      button_6 = Button(label="Output report")
  	# These are the list of actions possible 
  
      #text.on_change('value', update_title) 
      button_1.on_click(load_data)
      button_3.on_click(Region_1)
      button_4.on_click(Region_2)
      button_5.on_click(Compare_data)
      button_6.on_click(Output)
      Dsource.selected.on_change('indices', update_data)
  #taptool = Results.select(type=TapTool)
      Csource.selected.on_change('indices',create_ion_map)
  
  
      
      p = gridplot([[Left,Right,column(slider1,select, button_3,button_4,Regions,button_5,button_6)],[Spectrum,Results,SelectedIon]])
      
  
      curdoc().add_root(p)
      #curdoc().title = "Data Plotter"
  
  def Region_1():
      #Csource.data['Region1']=Mean2
      Region1=Dsource.selected.indices
      Mean2=np.mean(data_new_masked.iloc[Region1,:])
      Csource.data['Region1']=Mean2
      
      Mask1=[0]*len(CX2)
      for i in Region1:
          Mask1[i]=255
      Isource.data['Region1']=Mask1
      Mask2=Isource.data['Region2']
      Map = ["#%02x%02x%02x" % (int(r), 0, int(b)) for r, b in zip(Mask1, Mask2)]
      Isource.data['Map']=Map
      
  
  def Region_2():
      Region2=Dsource.selected.indices
      Mean3=np.mean(data_new_masked.iloc[Region2,:])
      Csource.data['Region2']=Mean3
      #
      Mask2=[0]*len(CX2)
      
      for i in Region2:
          Mask2[i]=255
      Isource.data['Region2']=Mask2
      Mask1=Isource.data['Region1']
      Map = ["#%02x%02x%02x" % (int(r), 0, int(b)) for r, b in zip(Mask1, Mask2)]
      Isource.data['Map']=Map
  
  
  	
  def Compare_data():
      # Open a new tab and show results
      Mean2=Csource.data['Region1']
      Mean3=Csource.data['Region2']
      Compare=np.log2(Mean2/Mean3)
      Csource.data['y']=Compare
      
      
  
  def update_title(attrname, old, new):
      Right.title.text = text.value
      
     
  
      
  def update_data(attrname, old, new):
     
      scan=Dsource.selected.indices
      Mean1=np.mean(data3.iloc[scan,:])
      Ssource.data['y']=Mean1
  	
  def create_ion_map(attrname, old, new):
      mass=Csource.selected.indices[0] # Need to limit this to only one value 
      X=data.iloc[:,mass].values.tolist()
      X[X == math.inf] = 0
      X2=np.reshape(X,[YY,XX])
      X2=np.flipud(X2)
      SelectedIon.image(image=[X2], x=0, y=0, dw=XX, dh=YY, palette="Spectral11")
      ma1=mzlabs[mass]
      ma2=str(ma1)
      SelectedIon.title.text='m/z ' + ma2
  	
      #SelectedIon.title.text=data.columns[mass]
      SelectedIon.title.align = "center"
      #SelectedIon.title_location = "below"
  
  	
  	
  def main(curdoc):
  
      curdoc.add_root(button_1)
      curdoc.add_root(button_2)
  	#doc.title = "MSI explore"
  
  
  #curdoc().add_root(button_1)
  	
  	#doc.add_root(row(inputs, plot, width=800))
      
  #button_1 = Button(label="Load data")
  #button_1.on_click(load_data)
  
  
  
  #curdoc().add_root(button_1)
  
  def Output():
      Compare=Csource.data['y']
      Compare2=pd.DataFrame(mzlabs,Compare)
      Compare2=Compare2.sort_index(axis=0, level=None, ascending=False) 
      pp=len(Compare2)
      Compare3=Compare2.iloc[0:50, :]
      Compare4=Compare2.iloc[pp-50:pp,:]
      Compare3.to_csv('20201103_red.csv',sep=",",header=False)
      Compare4.to_csv('20201103_blue.csv',sep=",",header=False)
  
  	
  	
  	
  def Advanced_options():
      print('There will be things here')
  
  
  button_1 = Button(label="Load data")
  button_1.on_click(load_data)
  
  button_2 = Button(label="Advanced options")
  button_2.on_click(Advanced_options)
  
  app = Application(FunctionHandler(main))
  server = Server({'/': app}, port=0)
  
  server.start()
  server.show('/')
  
  # Outside the notebook ioloop needs to be started
  from tornado.ioloop import IOLoop
  loop = IOLoop.current()
  loop.start()
  
apis: sklearn.decomposition.PCA