import matplotlib
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
from matplotlib.figure import Figure
from scipy.signal import savgol_filter
import scipy.sparse as sparse
from scipy.sparse.linalg import spsolve # for smoothing and baseline
import Tkinter as tk  # python 2.7 syntax
from Tkinter import *          
import tkMessageBox
import tkFileDialog
import sys
import os
import oceanoptics    # spectrometer critical - Python 2.7 only
import time
import threading
import csv
import datetime


# WARNING !
#
# This program is prototype written on Win10 for Python 2.7 and RedTide 650 spectrometer, but may support other models (check oceanoptics lib). 
# Intended for Raman spectroscopy on DIY setup
#
# If you have timeout issues with usb comm when sample_interval_sec is above 1 sec, try following:
# _DEFAULT_TIMEOUT variable must be increased in your python 2.7 directory
# --->>  Lib\site-packages\usb\core.py for example from default 1000 (1s) to 65000 (65s for very long sample intervals) 

class Application(tk.Frame):


## init (runs only once)

    def __init__(self, master=None, sample_interval_sec=1.0, smoothing=1, averaging=1, laser=532, smoothness=10e4, asymmetry= 0.05, raw=False,
                 lower=None, upper=None):  # Default start up values                                 
                
        tk.Frame.__init__(self,master)

        self.running = False
        
        self.sample_interval_ms = int(sample_interval_sec*1000) # oceanoptics
        self.smoothing = int(smoothing) 
        self._sample_n = 0 
        self.raw = bool(raw) # oceanoptics
        self.spectrometer = oceanoptics.get_a_random_spectrometer() # oceanoptics
        self.spectrometer.integration_time(time_sec=(sample_interval_sec * 0.8))
        self.wl = self.spectrometer.wavelengths() # oceanoptics
        self.sp = self.spectrometer.intensities() # oceanoptics
        self.exsp = self.sp

        self.laser = laser 
        
        # Wavelenght -> Raman calculation
        self.laserarray = np.full(shape=2022, fill_value=laser, dtype=float)
        self.rsla = (1/self.laserarray)
        self.rswl = (1/self.wl)
        self.rs = 10e6*((self.rsla)-(self.rswl))
        self.ramanactive = True

        self.sp = np.zeros((len(self.sp), int(averaging)))

        # plot setup
        fig, plot1 = plt.subplots()    
        fig.set_size_inches(16, 9, forward=True)                                                     #graph size                
        fig.subplots_adjust(left=0.07, bottom=0.07, right=0.97, top=0.95, wspace=0.20, hspace=0.20)  #fitting  
        plot1.set(xlabel='Raman shift (cm-1)', ylabel='Intensity',title='Intensity vs. Raman shift') #captions
        plot1.grid()                                                                                 #plot grid
        self.plot1 = plot1

        canvas=FigureCanvasTkAgg(fig,master=root)  
        canvas.get_tk_widget().place(x=20, y=60)   
        canvas.draw()

        # plot toolbar
        toolbar = NavigationToolbar2Tk(canvas, root)       
        canvas.get_tk_widget().place(x=280, y=30)   
        toolbar.update()
        self.canvas = canvas

        # dark spectra indicator
        self.dcanvas = Canvas(master=root, width=19, height=19)
        self.dlight = self.dcanvas.create_rectangle(0, 0, 20, 20, outline='red', fill='red')
        self.dcanvas.place(x=150, y=329)

        self.dark = None
        self.darkEntry = None
        self.applyD = False
        self.progress = 1
        self.spf = self.sp
        self.subtractedbase = False

        self.line, = plot1.plot(self.rs, self.sp[:,0]) 


## Buttons 

        self.buttonramanmode=tk.Button(master=root, text="Set Raman mode", command=self.ramanmode, width = 26)
        self.buttonramanmode.place(x=15, y=60)     
        self.buttonramanmode.config(background='#b5cb9f')

        self.buttonwlmode=tk.Button(master=root, text="Wavelenght mode", command=self.wlmode, width = 26)
        self.buttonwlmode.place(x=15, y=100)
       
        self.buttonLive=tk.Button(master=root, text="Live feed", fg='red', command=lambda: self.run(canvas, plot1), height = 3, width = 15, background='#f0f0f0')
        self.buttonLive.place(x=15, y=160)

        self.buttonStop=tk.Button(master=root, text="Stop feed", fg='red', command=self.stop, height = 3, width = 15)
        self.buttonStop.place(x=15, y=230)

        self.buttonSingle=tk.Button(master=root, text="Single capture", command=lambda: self.autoclicker(canvas,plot1), height = 3, width = 15, activebackground='#ffc20a')
        self.buttonSingle.place(x=145, y=160)

        self.buttonUse=tk.Button(master=root, text="Use", command=self.specConfig, width = 15)
        self.buttonUse.place(x=15, y=540)

        self.buttonDark=tk.Button(master=root, bg='black', fg='white', text="Capture dark", command=self.captureDark, width = 10)
        self.buttonDark.place(x=177, y=328)

        self.buttonApply=tk.Button(master=root, text="Apply", command=self.applyDark, width = 10)
        self.buttonApply.place(x=177, y=358)

        self.buttonRel=tk.Button(master=root, text="Release", command=self.releaseDark, width = 10)
        self.buttonRel.place(x=177, y=398)

        self.buttonSetbase=tk.Button(master=root, text="Set baseline", command=self.baseline, width = 15)
        self.buttonSetbase.place(x=15, y=710)

        self.buttonSubtract=tk.Button(master=root, text="Subtract", command=self.baselinesub, width = 15)
        self.buttonSubtract.place(x=139, y=710)

        self.buttonExport=tk.Button(master=root, text="Export as .csv", command=self.exportcsv, width = 18)
        self.buttonExport.place(x=70, y=830)

        self.buttonImport=tk.Button(master=root, text="Import .csv", command=self.importcsv, width = 18)
        self.buttonImport.place(x=70, y=860)

        self.buttonClr=tk.Button(master=root, text="Close .csv", command=self.closeimport, width = 18)
        self.buttonClr.place(x=70, y=900)


## Entry fields

        self.Exlaser=Entry(master=root, width=5)
        self.Exlaser.insert(0, 532)
        self.Exlaser.place(x=170, y=31)

        self.Averaging=Entry(master=root, width=15)
        self.Averaging.insert(0, 1)
        self.Averaging.place(x=15, y=360)

        self.Smoothing=Entry(master=root, width=15)
        self.Smoothing.insert(0, 1)
        self.Smoothing.place(x=15, y=430)

        self.Integrace=Entry(master=root, width=15)
        self.Integrace.insert(0, 1.0)
        self.Integrace.place(x=15, y=500)

        self.Smootlam=Entry(master=root, width=15)
        self.Smootlam.insert(0, 10e4)
        self.Smootlam.place(x=156, y=650)

        self.Asymmetry=Entry(master=root, width=15)
        self.Asymmetry.insert(0, 0.05)
        self.Asymmetry.place(x=156, y=680)


## Labels

        self.labelText3 = StringVar() #define label text 
        self.labelText3.set("Excitation wavelenght: ") 
        label0 = Label(root, textvariable=self.labelText3, width=20)
        label0.place(x=15, y=30)

       
        self.labelText1 = StringVar() 
        actAveraging = "Averaging: " + str(self.Averaging.get())
        self.labelText1.set(actAveraging) 
        label1 = Label(root, textvariable=self.labelText1, width=15)
        label1.place(x=15, y=330)

        self.labelText2 = StringVar() 
        actSmoothing = "Smoothing: " + str(self.Smoothing.get())
        self.labelText2.set(actSmoothing) 
        label2 = Label(root, textvariable=self.labelText2, width=15)
        label2.place(x=15, y=400)

        self.labelText3 = StringVar()
        actTime = "Int. time (s): " + str(self.Integrace.get())
        self.labelText3.set(actTime)
        label3 = Label(root, textvariable=self.labelText3, width=15)
        label3.place(x=15, y=470)

        self.labelText4 = StringVar() 
        self.actProgress = str(self.progress) + " of " + str(self.Averaging.get())
        self.labelText4.set(self.actProgress)
        label4 = Label(root, textvariable=self.labelText4, width=15)
        label4.place(x=146, y=230)

        self.samplechange = int(self.Averaging.get()) #for change detect logic
        self.smoothingchange = int(self.Smoothing.get())
        self.timechange = float(self.Integrace.get())

        self.labelText5 = StringVar() #define label text 
        self.labelText5.set("Baseline removal section: ") 
        label5 = Label(root, textvariable=self.labelText5, width=33)
        label5.place(x=15, y=620)

        self.labelText5 = StringVar() #define label text 
        self.labelText5.set("smoothness lambda: ") 
        label5 = Label(root, textvariable=self.labelText5, width=18)
        label5.place(x=15, y=650)

        self.labelText5 = StringVar() #define label text 
        self.labelText5.set("asymmetry p: ") 
        label5 = Label(root, textvariable=self.labelText5, width=18)
        label5.place(x=15, y=680)


        self.plot(canvas,plot1)


## Get data from entry fields
  
    def specConfig(self, sample_interval_sec=1.0, smoothing=1, averaging=1, raw=False,
                 lower=None, upper=None):

        if (self.applyD == True) and ((self.samplechange != int(self.Averaging.get())) 
            or (self.smoothingchange != int(self.Smoothing.get())) or (self.timechange != float(self.Integrace.get()))):    
            
            self.dcanvas.itemconfig(self.dlight, outline='blue', fill='blue')

        self.averaging = int(self.Averaging.get())
        actAveraging = "Averaging: " + str(self.Averaging.get())
        self.labelText1.set(actAveraging)
        self.samplechange = int(self.Averaging.get())

        self.smoothing = int(self.Smoothing.get())
        actSmoothing = "Smoothing: " + str(self.Smoothing.get())
        self.labelText2.set(actSmoothing)
        if self.smoothingchange != int(self.Smoothing.get()):
            self.replot()
        self.smoothingchange = int(self.Smoothing.get())

        self.sample_interval_sec = float(self.Integrace.get())
        actTime = "Int. time (s): " + str(self.Integrace.get())
        self.labelText3.set(actTime)
        self.timechange = float(self.Integrace.get())

        self.sample_interval_ms = int(self.sample_interval_sec*1000)
        self._sample_n = 0
        self.raw = bool(raw)
        self.spectrometer.integration_time(time_sec=(self.sample_interval_sec * 0.8))

        self.sp = np.zeros((len(self.sp), int(self.averaging)))

        self.actProgress = str(self.progress) + " of " + str(self.Averaging.get())
        self.labelText4.set(self.actProgress)

        if ((int(self.Smoothing.get()) % 2) == 0) and (self.Smoothing.get() > 1):
            tkMessageBox.showinfo("Error", "Smoothing must have odd value!")

        if int(self.Averaging.get()) > 1:
                self.buttonLive["state"] = "disabled"
                self.buttonStop["state"] = "disabled"
        else:
                self.buttonLive["state"] = "normal"
                self.buttonStop["state"] = "normal"

        


## Set Raman mode (default)

    def ramanmode(self):     
        self.laser = int(self.Exlaser.get())
        self.laserarray = np.full(shape=2022, fill_value=self.laser, dtype=float)
        self.rsla = (1/self.laserarray)
        self.rswl = (1/self.wl)
        self.rs = 10e6*((self.rsla)-(self.rswl))
        plt.cla()
        self.line, = self.plot1.plot(self.rs, self.exsp)
        self.plot1.set(xlabel='Raman shift (cm-1)', ylabel='Intensity',title='Intensity vs. Raman shift')
        self.plot1.relim()
        self.plot1.autoscale_view(False, False, True)
        self.plot1.grid()
        self.canvas.draw()
        self.ramanactive = True
        self.buttonramanmode.config(background='#b5cb9f')
        self.buttonwlmode.config(background='#f0f0f0')


## Set wavelenght mode

    def wlmode(self):  
        plt.cla()
        self.line, = self.plot1.plot(self.wl, self.exsp)
        self.plot1.set(xlabel='Wavelenght (nm)', ylabel='Intensity',title='Intensity vs. Wavelenght')
        self.plot1.relim()
        self.plot1.autoscale_view(False, False, True)
        self.plot1.grid()
        self.canvas.draw()
        self.ramanactive = False
        self.buttonramanmode.config(background='#f0f0f0')
        self.buttonwlmode.config(background='#b5cb9f')


## Threading for Live run

    def run(self, canvas, plot1):
        self.buttonLive.config(background="#ffc20a")
        if  self.running == False:
            self.running = True
            self.t1 = threading.Thread(target=lambda: self.live(canvas,plot1))
            self.t1.daemon = True
            self.t1.start()
        else:
            return


## Stop Live

    def stop(self):
        self.buttonLive.config(background='#f0f0f0')
        self.running = False


## Dark current logic

    def captureDark(self):
        self.dark = self.darkEntry
        self.applyD = False
        self.dcanvas.itemconfig(self.dlight, outline='#ffc20a', fill='#ffc20a')
    
    def applyDark(self):
        if self.dark is not None:
            self.dcanvas.itemconfig(self.dlight, outline='green', fill='green')
            self.applyD = True

    def releaseDark(self):
        self.dark = None
        self.darkEntry = None
        self.applyD = False
        self.dcanvas.itemconfig(self.dlight, outline='red', fill='red')


## Averaging autoclicker

    def autoclicker(self,canvas,plot1):
     
        for i in range(0,int(self.Averaging.get()),1):

            self.plot(canvas,plot1)
            self.progress = i+1
            self.actProgress = str(self.progress) + " of " + str(self.Averaging.get())
            self.labelText4.set(self.actProgress)
            
            if int(self.progress) > 1 and int(self.progress) < int(self.Averaging.get()):
                self.buttonSingle.config(bg = "#ffc20a")
            else:
                self.buttonSingle.config(bg = "#f0f0f0")
            root.update_idletasks()

## Replot after smoothing change

    def replot(self):

        if self.smoothing > 1:
            n = self.smoothing
            sp = savgol_filter(self.replot1, n, 2)
            self.line.set_ydata(sp)
            self.plot1.relim()
            self.plot1.autoscale_view(False, False, True)
            self.canvas.draw()

            self.darkEntry = sp
            self.exsp = sp
        else:
            pass
            
## Single capture     
 
    def plot(self,canvas,plot1):

        self.subtractedbase = False

        while True:

    # -> redraw on new spectrum
    # -> average over self.sample_n spectra
    # -> smooth if self.smoothing

    # remark:
    # > smoothing can be done after averaging

    # get spectrum
            self.replot1 = None

            sp = np.array(self.spectrometer.intensities(raw=self.raw, only_valid_pixels=True))

            self.sp[:,self._sample_n] = sp
            self._sample_n += 1
            self._sample_n %= self.sp.shape[1]
            if self._sample_n != 0: # do not draw or average
                return
    # average!
            sp = np.mean(self.sp, axis=1)
            
            if self.dark is not None and self.applyD == True:
                sp = np.subtract(sp, self.dark)

            if self.dark is not None and self.applyD == False:
                self.applyD = False
                self.dcanvas.itemconfig(self.dlight, outline='#ffc20a', fill='#ffc20a')

            self.replot1 = sp

            if self.smoothing > 1:
                n = self.smoothing
                sp = savgol_filter(sp, n, 2)
            
            self.line.set_ydata(sp)

            self.darkEntry = sp
            self.exsp = sp

            plot1.relim()
            plot1.autoscale_view(False, False, True)
            canvas.draw()
            
            return True
                  

## Live capture 

    def live(self,canvas,plot1):

        self.subtractedbase = False
         
        while self.running:
            
            self.replot1 = None

            sp = np.array(self.spectrometer.intensities(raw=self.raw))

            self.sp[:,self._sample_n] = sp
            self._sample_n += 1
            self._sample_n %= self.sp.shape[1]
            if self._sample_n != 0: 
                return

            sp = np.mean(self.sp, axis=1)

            if self.dark is not None and self.applyD == True:
                sp = np.subtract(sp, self.dark)

            if self.dark is not None and self.applyD == False:
                self.applyD = False
                self.dcanvas.itemconfig(self.dlight, outline='#ffc20a', fill='#ffc20a')

            self.replot1 = sp

            if self.smoothing > 1:
                n = self.smoothing
                sp = savgol_filter(sp, n, 2)

            self.line.set_ydata(sp)

            self.darkEntry = sp
            self.exsp = sp
            
            plot1.relim()
            plot1.autoscale_view(False, False, True)
            canvas.draw()
    


## Get and calculate baseline

    def baseline(self, smoothness=10e4, asymmetry= 0.05):

        self.smoothness = float(self.Smootlam.get())
        self.asymmetry = float(self.Asymmetry.get())

        if self.ramanactive == True:
            niter = 10

            L = len(self.exsp)
            D = sparse.csc_matrix(np.diff(np.eye(L), 2))
            w = np.ones(L)

            for i in xrange(niter):
                W = sparse.spdiags(w, 0, L, L)
                Z = W + self.smoothness * D.dot(D.transpose())
                z = spsolve(Z, w*self.exsp)
                w = self.asymmetry * (self.exsp > z) + (1-self.asymmetry) * (self.exsp < z)  # z is calculated baseline

            self.z = z
            self.plot1.plot(self.rs,z) 
            self.canvas.draw()

        else:
            niter = 10

            L = len(self.exsp)
            D = sparse.csc_matrix(np.diff(np.eye(L), 2))
            w = np.ones(L)

            for i in xrange(niter):
                W = sparse.spdiags(w, 0, L, L)
                Z = W + self.smoothness * D.dot(D.transpose())
                z = spsolve(Z, w*self.exsp)
                w = self.asymmetry * (self.exsp > z) + (1-self.asymmetry) * (self.exsp < z)  # z is calculated baseline

            self.z = z
            self.plot1.plot(self.wl,z) 
            self.canvas.draw()


## Subtract baseline

    def baselinesub(self):

            self.spf = np.subtract(self.exsp, self.z)

            if self.ramanactive == True:
                if self.smoothing > 1:
                    n = self.smoothing
                    self.spf = savgol_filter(self.spf, n, 2)

                plt.cla()
                self.line, = self.plot1.plot(self.rs, self.spf)
                self.plot1.set(xlabel='Raman shift (cm-1)', ylabel='Intensity',title='Intensity vs. Raman shift')
                self.plot1.relim()
                self.plot1.autoscale_view(False, False, True)
                self.plot1.grid()
                self.canvas.draw()
                
            else:
                if self.smoothing > 1:
                    n = self.smoothing
                    self.spf = savgol_filter(self.spf, n, 2)

                plt.cla()
                self.line, = self.plot1.plot(self.wl, self.spf)
                self.plot1.set(xlabel='Wavelenght (nm)', ylabel='Intensity',title='Intensity vs. Wavelenght')
                self.plot1.relim()
                self.plot1.autoscale_view(False, False, True)
                self.plot1.grid()
                self.canvas.draw()
            
            self.subtractedbase = True

            


## .cvs export

    def exportcsv(self):
        if self.subtractedbase == True:
            self.exsp = self.spf
        directory = tkFileDialog.askdirectory(initialdir="/",  title='Please select a directory') #ask for directory
        date = datetime.datetime.now().strftime("%m%d%H%M%S") #get date and time
        extension = ".csv"      #join name_date.extension in choosen path
        mergename = "spectra_"+ date + extension
        mergepath = os.path.join(directory, mergename)  #create file for writing
        csvfile = open(mergepath, "wb")   #get and write data
        if self.ramanactive == True:
            dataX = (self.rs)
            dataY = (self.exsp)
        else:
            dataX = (self.wl)
            dataY = (self.exsp)
        writer = csv.writer(csvfile)
        for w in range(2022):
            writer.writerow([dataX[w], dataY[w]])
        csvfile.close()
  

## .cvs import

    def importcsv(self):
        importfile = tkFileDialog.askopenfilename(initialdir="/",  title='Please select file')
        irs = []
        isp = []

        with open(importfile, 'r') as csvfile:
            plots= csv.reader(csvfile, delimiter=',')
            for row in plots:
                irs.append(float(row[0]))
                isp.append(float(row[1]))

        self.plot1.plot(irs,isp) 
        self.canvas.draw()


## Close .cvs

    def closeimport(self):
        plt.cla()
        if self.ramanactive == True:
            self.line, = self.plot1.plot(self.rs, self.exsp)
            self.plot1.set(xlabel='Raman shift (cm-1)', ylabel='Intensity',title='Intensity vs. Raman shift')
            self.plot1.relim()
            self.plot1.autoscale_view(False, False, True)
            self.plot1.grid()
            self.canvas.draw()
        else:
            self.line, = self.plot1.plot(self.wl, self.exsp)
            self.plot1.set(xlabel='Wavelenght (nm)', ylabel='Intensity',title='Intensity vs. Wavelenght')
            self.plot1.relim()
            self.plot1.autoscale_view(False, False, True)
            self.plot1.grid()
            self.canvas.draw()

## Main window 

root=tk.Tk()
root.title("RaSpec")
root.geometry("1280x720")
root['background']='#424d5c' 

def properExit(): # Stops freezing when Tkinter window is closed with frame [X] button
    app.quit()
root.protocol('WM_DELETE_WINDOW', properExit) 

app=Application(master=root)

app.mainloop()





