diff --git a/ivctrack/AniFrame.py b/ivctrack/AniFrame.py new file mode 100644 index 0000000..5db2615 --- /dev/null +++ b/ivctrack/AniFrame.py @@ -0,0 +1,215 @@ +# -*- coding: utf-8 -*- +'''This file implements a frame allowing to verify the tracking through an animation (which can be saved). +''' +__author__ = ' De Almeida Luis ' + +#------generic imports------ +from Tkinter import * +import tkFileDialog + +#------specific imports------ +import matplotlib +matplotlib.use('Tkagg') +from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg +import matplotlib.pyplot as plt +import matplotlib.cm as cm +import matplotlib.animation as anim + +#------ivctrack toolbox imports------ +from reader import ZipSource,Reader +from hdf5_read import get_hdf5_data + +class AniFrame(Frame): + """Frame displaying the results of the tracking as a video, and allowing to save it. Will NOT work if FFmpeg is not installed.""" + + def __init__(self,win,zip_filename): + Frame.__init__(self,win) + + self.feat=[] + self.data = [] + + #----------------------------------------------------GUI IMPLEMENTATION----------------------------------------- + + self.file_var=StringVar() + self.file_var.set('HDF5 File: ') + self.file_lbl=Label(self,textvariable=self.file_var) + self.file_lbl.grid(row=0,column=2,columnspan=2) + + self.file_browse_button=Button(self,text='Browse',command=self.ask_open_and_load_file) + self.file_browse_button.grid(row=0,column=4) + + self.soma_var=BooleanVar() + self.soma_check=Checkbutton(self,text='Soma',variable=self.soma_var) + self.soma_check.grid(column=0,row=1,columnspan=2) + + self.halo_var=BooleanVar() + self.halo_check=Checkbutton(self,text='Halo',variable=self.halo_var) + self.halo_check.grid(column=0,row=2,columnspan=2) + + self.trajectory_var=BooleanVar() + self.trajectory_check=Checkbutton(self,text='Trajectory',variable=self.trajectory_var) + self.trajectory_check.grid(column=0,row=3,columnspan=2) + + self.play_button = Button(master=self, text='Preview',command=lambda:(self.play())) + + self.save_button=Button(self,text='Save as..',command=lambda :(self.save())) + + self.dpi_lbl=Label(self,text='Resolution (DPI)') + self.var_dpi=IntVar() + self.var_dpi.set(200) + self.dpi_entry=Entry(self,textvariable=self.var_dpi) + + #-----Configuration of the tracking canvas------ + self.f=plt.figure() + self.a=self.f.add_subplot(111) + + self.frame_text=self.a.text(10,20,'') + + self.canvas = FigureCanvasTkAgg(self.f, master=self) + self.canvas.show() + self.canvas.get_tk_widget().grid(row=1,column=2,rowspan=3,columnspan=3) + + #------------------------------------------------------END------------------------------------------------------------- + + + #------import of the zip file & update of the canvas------ + self.datazip_filename = zip_filename + + self.reader = Reader(ZipSource(self.datazip_filename)) + self.bg=self.reader.getframe() + + self.im = self.a.imshow(self.bg,cmap=cm.gray) + + self.a.set_xlim(0,len(self.bg[0,:])) + self.a.set_ylim(len(self.bg[:,0]),0) + + #------MP4 writer------ + self.writer=anim.writers['ffmpeg'] + self.writer=self.writer(fps=5) + + + def ask_open_and_load_file(self): + self.file_opt={} + self.file_opt['filetypes'] = [('HDF5 file','.hdf5')] + self.file_opt['defaultextension'] ='.hdf5' + self.file_opt['title'] = 'Select a HDF5 file' + + self.hdf5_filename=tkFileDialog.askopenfilename(**self.file_opt) + self.file_var.set('HDF5 File: {}'.format(self.hdf5_filename)) + + self.feat,self.data=get_hdf5_data(self.hdf5_filename,fields=['center','halo','soma']) + self.halo=[] + self.soma=[] + self.trajectory=[] + + for k in range(len(self.data)): + self.halo.append(self.a.plot([],[],'o')) + self.soma.append(self.a.plot([],[])) + self.trajectory.append(self.a.plot([],[])) + + self.play_button.grid(row=4,column=1) + self.save_button.grid(row=4,column=0) + self.dpi_lbl.grid(column=2,row=4) + self.dpi_entry.grid(column=3,row=4) + + + def play(self): + + self.cell_ani = anim.FuncAnimation(fig=self.f, func=self.update_img,init_func=self.init_im,frames=self.reader.N(),blit=False) + self.canvas.show() + self.play_button.grid_forget() + + def save(self): + """Exports the animation to a MP4 file. + """ + self.file_opt={} + self.file_opt['filetypes'] = [('MP4 files','.mp4')] + self.file_opt['defaultextension'] ='.mp4' + self.file_opt['title'] = 'Save sequence as..' + + soma=self.soma_var.get() + halo=self.halo_var.get() + trajectory=self.trajectory_var.get() + + self.mp4_filename=tkFileDialog.asksaveasfilename(**self.file_opt) + + with self.writer.saving(self.f,self.mp4_filename,self.var_dpi.get()): + self.reader.rewind() + for i in range(self.reader.N()): + print "Grabing frame ",i + + self.bg=self.reader.getframe() + self.im.set_data(self.bg) + try: + self.reader.next() + except IndexError: + pass + if halo: + self.plot_halo(i) + if soma: + self.plot_soma(i) + if trajectory: + self.plot_trajectory(i) + + self.frame_text.set_text(i) + + self.writer.grab_frame() + print "Video correctly saved as ", self.mp4_filename + + + def update_img(self,frame): + + self.reader.moveto(frame) + self.bg=self.reader.getframe() + + self.im.set_data(self.bg) + + if self.halo_var.get(): + self.plot_halo(frame) + elif not self.halo_var.get(): + for k in range(len(self.data)): + self.halo[k][0].set_data([],[]) + + if self.soma_var.get(): + self.plot_soma(frame) + elif not self.soma_var.get(): + for k in range(len(self.data)): + self.soma[k][0].set_data([],[]) + + if self.trajectory_var.get(): + self.plot_trajectory(frame) + elif not self.trajectory_var.get(): + for k in range(len(self.data)): + self.trajectory[k][0].set_data([],[]) + + self.frame_text.set_text(frame) + + def init_im(self): + pass + + def plot_halo(self,i): + k=0 + for d in self.data: + t=d['halo'] + x=t[i,:,0] + y=t[i,:,1] + self.halo[k][0].set_data(x,y) + k+=1 + + def plot_soma(self,i): + k=0 + for d in self.data: + t=d['soma'] + x=t[i,:,0] + y=t[i,:,1] + self.soma[k][0].set_data(x,y) + k+=1 + + def plot_trajectory(self,i): + k=0 + for d in self.data: + t=d['center'] + x=t[0:i,0] + y=t[0:i,1] + self.trajectory[k][0].set_data(x,y) + k+=1 diff --git a/ivctrack/MainWindow.py b/ivctrack/MainWindow.py new file mode 100644 index 0000000..c1d6eff --- /dev/null +++ b/ivctrack/MainWindow.py @@ -0,0 +1,170 @@ +# -*- coding: utf-8 -*- +'''This file implements the menu frame of the GUI for the ivctrack module +''' +__author__ = ' De Almeida Luis ' + +#------generic imports------ +from Tkinter import * +import tkFileDialog + +#-------local imports------ +from TrackingFrame import TrackingFrame +from PlotFrame import PlotFrame +from AniFrame import AniFrame +from MeasFrameV3 import MeasFrameV3 + + + +class MainFrame(Frame): + + """Main menu frame : The user can either start a new tracking or view results from previous trackings + The .zip file which contains the image sequences is linked to the menu window and can only be modified from there""" + def __init__(self,win): + Frame.__init__(self,win,width=700,height=700) + self.pack() + + self.track_frame=None + self.plot_frame=None + self.meas_frame=None + self.ani_frame=None + + self.menu_buttons=[] + + self.datazip_filename="" + + self.file_opt={} + self.file_opt['filetypes'] = [('ZIP file','.zip')] + self.file_opt['defaultextension'] ='.zip' + self.file_opt['title'] = 'Select a zipped sequence file' + + + #----------------------------------------------------GUI IMPLEMENTATION----------------------------------------- + + self.track_button=Button(self,text='Start Tracking',command=lambda : self.track()) + self.track_button.pack(fill='both') + self.menu_buttons.append(self.track_button) + + self.plot_button=Button(self,text='Plot Results',command=lambda : self.plot()) + self.plot_button.pack(fill='both') + self.menu_buttons.append(self.plot_button) + + self.meas_button=Button(self,text='Measurements',command=lambda : self.measurements()) + self.meas_button.pack(fill='both') + self.menu_buttons.append(self.meas_button) + + self.ani_button=Button(self,text='Player',command=lambda : self.play()) + self.ani_button.pack(fill='both') + self.menu_buttons.append(self.ani_button) + + self.zip_var=StringVar() + self.change_zip_button=Button(self,textvariable=self.zip_var,command= lambda : self.change_zip()) + self.menu_buttons.append(self.change_zip_button) + + self.quit_button=Button(self,text='Quit',command=win.quit) + self.quit_button.pack(fill='both') + self.menu_buttons.append(self.quit_button) + + self.back_button=Button(self,text='Back',command = lambda : self.back()) + + #------------------------------------------------------END------------------------------------------------------------- + + + def back(self): + """Return to the menu window. """ + try : + self.track_frame.pack_forget() + self.track_frame=None + except: + pass + try : + self.plot_frame.pack_forget() + self.plot_frame=None + except: + pass + try : + self.meas_frame.pack_forget() + self.meas_frame=None + except: + pass + try: + self.ani_frame.pack_forget() + self.ani_frame=None + except: + pass + + self.back_button.pack_forget() + for button in self.menu_buttons: + button.pack(fill='both') + + def track(self): + """Method that transits from the menu to the tracking frame """ + if self.datazip_filename=="": + self.change_zip() + + self.track_frame=TrackingFrame(win,self.datazip_filename) + + self.track_frame.pack() + + for button in self.menu_buttons: + button.pack_forget() + + self.back_button.pack(side='bottom') + + def plot(self): + """Method that transits from the menu to the plotting frame """ + + if self.datazip_filename=="": + self.change_zip() + + self.plot_frame=PlotFrame(win,self.datazip_filename) + + self.plot_frame.pack() + + for button in self.menu_buttons: + button.pack_forget() + + self.back_button.pack(side='bottom') + + def measurements(self): + """Method that transits from the menu to the frame that displays multiple measures""" + + if self.datazip_filename=="": + self.change_zip() + self.meas_frame=MeasFrameV3(win,self.datazip_filename) + + self.meas_frame.pack() + + for button in self.menu_buttons: + button.pack_forget() + + self.back_button.pack(side='bottom') + + def play(self): + """Method that transits from the menu to the frame that plays the tracking and allows to save it to .mp4""" + if self.datazip_filename=="": + self.change_zip() + + self.ani_frame=AniFrame(win,self.datazip_filename) + + self.ani_frame.pack() + + for button in self.menu_buttons: + button.pack_forget() + + self.back_button.pack(side='bottom') + + def change_zip(self): + """Method that allows the user to change the current sequence ZIP file """ + self.datazip_filename=tkFileDialog.askopenfilename(**self.file_opt) + try: + self.zip_var.set("Change Zip File ({})".format(self.datazip_filename.rsplit('/')[-1])) + except AttributeError: + pass + + +if __name__== '__main__': + + win=Tk() + win.wm_title('IVCTrack GUI') + root=MainFrame(win) + root.mainloop() diff --git a/ivctrack/MeasFrameV3.py b/ivctrack/MeasFrameV3.py new file mode 100644 index 0000000..c7de311 --- /dev/null +++ b/ivctrack/MeasFrameV3.py @@ -0,0 +1,236 @@ +# -*- coding: utf-8 -*- +'''This file implements the frame displaying various measures related to the movement of the cells. +''' +__author__ = ' De Almeida Luis ' +#------generic imports------ +from Tkinter import * + +#------specfic imports------ +import matplotlib +matplotlib.use('Tkagg') +from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg +import matplotlib.pyplot as plt +import tkFileDialog +import matplotlib.cm as cm + +#------ivctrack toolbox imports------ +from hdf5_read import get_hdf5_data +from measurement import speed_feature_extraction,filter,scaling_exponent,relative_direction_distribution +from reader import ZipSource,Reader + + +class MeasFrameV3(Frame): + """Frame allowing a deeper trajectory analysis, based on the measurement module of the ivctrack toolbox. """ + + def __init__(self,win, zip_filename): + Frame.__init__(self,win,width=700,height=700) + + self.file_opt={} + self.file_opt['filetypes'] = [('HDF5 file','.hdf5')] + self.file_opt['defaultextension'] ='.hdf5' + self.file_opt['title'] = 'Select a HDF5 file' + + self.c_feat=[] + self.c_data = [] + + self.button_list=[] + + #----------------------------------------------------GUI IMPLEMENTATION----------------------------------------- + + self.file_var=StringVar() + self.file_var.set('HDF5 File: ') + self.file_lbl=Label(self,textvariable=self.file_var) + self.file_lbl.grid(row=0,column=1+0) + + self.file_browse_button=Button(self,text='Browse',command=self.ask_open_and_load_file) + self.file_browse_button.grid(row=0,column=1+1) + + #-----Configuration of the tracking canvas------ + self.f=plt.figure() + self.a=self.f.add_subplot(111) + self.canvas = FigureCanvasTkAgg(self.f, master=self) + self.canvas.get_tk_widget().grid(column=1+0,row=1,columnspan=2) + + #------------------------------------------------------END------------------------------------------------------------- + + #------import of the zip file & update of the canvas------ + self.datazip_filename=zip_filename + self.reader = Reader(ZipSource(self.datazip_filename)) + self.bg=self.reader.getframe() + + self.a.set_xlim(0,len(self.bg[0,:])) + self.a.set_ylim(len(self.bg[:,0]),0) + + self.a.imshow(self.bg,cmap=cm.gray) + + self.canvas.show() + + def ask_open_and_load_file(self): + + self.hdf5_filename=tkFileDialog.askopenfilename(**self.file_opt) + self.file_var.set('HDF5 File: {}'.format(self.hdf5_filename)) + + + self.c_feat,self.c_data=get_hdf5_data(self.hdf5_filename,fields=['center','halo','soma']) + feat_name, self.measures=speed_feature_extraction(self.c_data) + + + self.a.cla() + self.a.set_xlim(0,len(self.bg[0,:])) + self.a.set_ylim(len(self.bg[:,0]),0) + self.a.imshow(self.bg,cmap=cm.gray) + + for cell in self.c_data: + t=cell['halo'] + self.a.plot(t[0,:,0],t[0,:,1],'ko') + t=cell['soma'] + self.a.plot(t[0,:,0],t[0,:,1],'k') + self.a.set_xlabel('x') + self.a.set_ylabel('y') + + self.set_frame() + + self.canvas.show() + + def set_frame(self): + + self.nrcells=len(self.c_data) + + if (len(self.button_list)>0): + for button in self.button_list: + button.grid_forget() + del button + + self.features_list=[] + self.values_list=[] + self.units_list=[] + + self.button_rel_dir_list=[] + self.button_lin_fit_list=[] + + #----------------------------------------------------GUI IMPLEMENTATION----------------------------------------- + + self.file_lbl.grid(row=0,column=1+0,columnspan=2*self.nrcells-1) + self.file_browse_button.grid(row=0,column=1+2*self.nrcells-1) + self.canvas.get_tk_widget().grid(column=1+0,row=1,columnspan=2,rowspan=8) + + features=['Path Length', 'Average Speed', 'MRDO','Hull Surface','Hull Distance','Hull Density','Hull Relative Distance' ] + + for feat in features: + self.features_list.append(Label(self,text=feat)) + self.values_list.append(Label(self)) + + units=['px','px/frame','px/frame','px^2','px','/','/'] + + for unit in units: + self.units_list.append(Label(self,text=unit)) + + self.plotLinFit_button=Button(self,text='Linear Fitting Plot',command = self.plotLinFit) + self.plotRelDir_button=Button(self,text='Relative Direction Plot',command = self.plotRelDir) + + self.global_meas_button=Button(self,text='Global Measures',command=self.plotGlobMeas) + self.global_meas_button.grid(column=0,row=4) + + + self.cell_list=Listbox(self) + for cell in range(self.nrcells): + self.cell_list.insert('end','Cell {}'.format(cell+1)) + + self.cell_list.bind('',self.show_meas) + self.cell_list.bind('',self.show_meas) + + self.cell_list.grid(column=0,row=1,rowspan=3) + #------------------------------------------------------END------------------------------------------------------------- + + + def show_meas(self,event): + """Displays general measures of the selected cell. + """ + cell_nr=int(self.cell_list.get('active').rsplit(' ')[-1])-1 + + self.highlight_selected_cell(cell_nr) + + self.cell_lbl=Label(self,text='Cell {} ({},{})'.format(cell_nr+1,int(self.c_data[cell_nr]['center'][0,0]),int(self.c_data[cell_nr]['center'][0,1]))) + self.cell_lbl.grid(column=1+2,row=1,columnspan=2) + + for lbl in self.features_list : + lbl.grid(row=2+(self.features_list).index(lbl),column=1+2, sticky='W') + for lbl in self.units_list : + lbl.grid(row=2+(self.units_list).index(lbl),column=1+2+2, sticky='W') + + i=0 + for lbl in self.values_list : + lbl.config(text=round(self.measures[cell_nr][i],5)) + lbl.grid(row=2+(self.values_list).index(lbl),column=1+2+1, sticky='W') + i+=1 + + self.plotLinFit_button.grid(row=10,column=1+2) + self.plotRelDir_button.grid(row=10,column=1+2+1) + + + def plotRelDir(self): + """Executes the 'relative_directio_distribution' function of the measurement module frm the ivctrack toolbox. + """ + cell = int(self.cell_list.get('active').rsplit(' ')[-1])-1 + + self.highlight_selected_cell(cell) + + d=self.c_data[cell] + xy = d['center'] + fxy = filter(xy,sigma=1.) + R,V,Theta,Rtot,clip_dtheta,rho = relative_direction_distribution(fxy,verbose=True)#2nd figure (polar) + + def plotLinFit(self): + """Executes the 'scaling_exponent' function of the measurement module from the ivctrack toolbox. + """ + cell = int(self.cell_list.get('active').rsplit(' ')[-1])-1 + + self.highlight_selected_cell(cell) + + d=self.c_data[cell] + xy = d['center'] + fxy = filter(xy,sigma=1.) + se,se_err = scaling_exponent(fxy,verbose=True)#First figure(lin fit) + + def plotGlobMeas(self): + fig=plt.figure() + a_scat=fig.add_subplot(121) + a_hist=fig.add_subplot(122) + + a_scat.cla() + a_hist.cla() + + a_scat.scatter(self.measures[:,1],self.measures[:,3]) + a_scat.set_xlabel('avg speed') + a_scat.set_ylabel('hull surface') + + a_hist.hist(self.measures[:,1:4]) + a_hist.legend(['avg','mrdo','hull surface']) + + fig.show() + + def highlight_selected_cell(self,cell_nr): + """Highlights the cell selected in the listbox to allow a visual localisation of the studied cell. + """ + self.a.cla() + self.a.set_xlim(0,len(self.bg[0,:])) + self.a.set_ylim(len(self.bg[:,0]),0) + self.a.imshow(self.bg,cmap=cm.gray) + + i=0 + for cell in self.c_data: + if i==cell_nr: + t=cell['halo'] + self.a.plot(t[0,:,0],t[0,:,1],'wo') + t=cell['soma'] + self.a.plot(t[0,:,0],t[0,:,1],'w') + else: + t=cell['halo'] + self.a.plot(t[0,:,0],t[0,:,1],'ko') + t=cell['soma'] + self.a.plot(t[0,:,0],t[0,:,1],'k') + self.a.set_xlabel('x') + self.a.set_ylabel('y') + i+=1 + + self.canvas.show() diff --git a/ivctrack/PlotFrame.py b/ivctrack/PlotFrame.py new file mode 100644 index 0000000..26e3cd6 --- /dev/null +++ b/ivctrack/PlotFrame.py @@ -0,0 +1,189 @@ +# -*- coding: utf-8 -*- +'''This file implements the frame offering various tools to graphicaly observe the results of the analysis. +''' +__author__ = ' De Almeida Luis ' + +#------generic imports------ +from Tkinter import * +import tkFileDialog + +#------specific imports------ +import matplotlib +matplotlib.use('Tkagg') +from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg +import matplotlib.pyplot as plt +import matplotlib.cm as cm + +#------ivctrack toolbox imports------ +from hdf5_read import get_hdf5_data +from reader import ZipSource,Reader + + + + +class PlotFrame(Frame): + """Frame that allows the user to view the results of the tracking trough different kinds of graphic representations. """ + + def __init__(self,win,zip_filename): + Frame.__init__(self,win,width=700,height=700) + + self.frame=0 + self.file_opt={} + self.file_opt['filetypes'] = [('HDF5 file','.hdf5')] + self.file_opt['defaultextension'] ='.hdf5' + self.file_opt['title'] = 'Select a HDF5 file' + + self.feat=[] + self.data = [] + + #----------------------------------------------------GUI IMPLEMENTATION----------------------------------------- + self.file_var=StringVar() + self.file_var.set('HDF5 File: ') + self.file_lbl=Label(self,textvariable=self.file_var) + self.file_lbl.grid(row=0,column=1,columnspan=2) + + self.file_browse_button=Button(self,text='Browse',command=self.ask_open_and_load_file) + self.file_browse_button.grid(row=0,column=3) + + self.plot_to_do=StringVar() + self.plot_to_do.trace('w',self.plot) + + #------differen plot possibilities------ + self.xy_radbut=Radiobutton(self,text='X-Y Plot',variable=self.plot_to_do,value='xy') + self.xy_radbut.grid(column=0,row=1,sticky='W') + + self.xf_radbut=Radiobutton(self,text='X-Frame Plot',variable=self.plot_to_do,value='xf') + self.xf_radbut.grid(column=0,row=2,sticky='W') + + self.yf_radbut=Radiobutton(self,text='Y-Frame Plot',variable=self.plot_to_do,value='yf') + self.yf_radbut.grid(column=0,row=3,sticky='W') + + self.relxy_radbut=Radiobutton(self,text='relX-relY Plot',variable=self.plot_to_do,value='relxy') + self.relxy_radbut.grid(column=0,row=4,sticky='W') + + self.cell_shape_radbut=Radiobutton(self,text='Cell Shape',variable=self.plot_to_do,value='cellshape') + self.cell_shape_radbut.grid(column=0,row=5,sticky='W') + + self.next_frame_button=Button(self,text='Next',command=lambda : self.change_frame(1)) + self.prev_frame_button=Button(self,text='Previous',command=lambda : self.change_frame(-1)) + self.lbl=StringVar() + self.lbl.set('Frame {}'.format(self.frame+1)) + self.frame_lbl=Label(self,textvariable=self.lbl) + + #-----Configuration of the tracking canvas------ + self.f=plt.figure() + self.a=self.f.add_subplot(111) + + self.canvas = FigureCanvasTkAgg(self.f, master=self) + self.canvas.show() + self.canvas.get_tk_widget().grid(column=1,row=1,rowspan=5,columnspan=3) + + #------------------------------------------------------END------------------------------------------------------------- + + #------import of the zip file & update of the canvas------ + + self.datazip_filename=zip_filename + self.reader = Reader(ZipSource(self.datazip_filename)) + self.bgs = [] + for i in self.reader.range(): + self.bgs.append(self.reader.getframe()) + try: + self.reader.next() + except IndexError: + pass + + def plot(self,*args): + """Plots a graph according to the option selected. + """ + + self.f.clf() + self.a=self.f.add_subplot(111) + + self.next_frame_button.grid_forget() + self.prev_frame_button.grid_forget() + self.frame_lbl.grid_forget() + + legend_needed=True + leg=[] + cellnr=1 + for d in self.data: + t=d['center'] + leg.append('Cell nr {} ({},{})'.format(cellnr,int(t[0,0]),int(t[0,1]))) + cellnr+=1 + + if self.plot_to_do.get() =='xy': + for d in self.data: + t=d['center'] + self.a.plot(t[:,0],t[:,1]) + self.a.set_xlabel('x') + self.a.set_ylabel('y') + + elif self.plot_to_do.get() =='xf': + for d in self.data: + f=d['frames'] + t=d['center'] + self.a.plot(f,t[:,0]) + self.a.set_xlabel('frame') + self.a.set_ylabel('x') + + elif self.plot_to_do.get() =='yf': + for d in self.data: + f=d['frames'] + t=d['center'] + self.a.plot(f,t[:,1]) + self.a.set_xlabel('frame') + self.a.set_ylabel('y') + + elif self.plot_to_do.get() =='relxy': + for d in self.data : + t=d['center'] + self.a.plot(t[:,0]-t[0,0],t[:,1]-t[0,1]) + self.a.set_xlabel('$x_{rel}$') + self.a.set_ylabel('$y_{rel}$') + self.a.axvline(x=0,color='grey') + self.a.axhline(y=0,color='grey') + + elif self.plot_to_do.get() =='cellshape': + self.next_frame_button.grid(column=3,row=6) + self.prev_frame_button.grid(column=1,row=6) + self.frame_lbl.grid(column=2,row=6) + legend_needed=False + self.plot_cell_shapes() + + if legend_needed: + l=self.a.legend(leg) + l.draggable() + + self.canvas.show() + + def plot_cell_shapes(self): + """Plots the results of the tracking on the frames of the sequence. Allowing a visual verification of a good execution of the tracking algorithm. + """ + for d in self.data: + t=d['halo'] + self.a.plot(t[self.frame,:,0],t[self.frame,:,1],'o') + t=d['soma'] + self.a.plot(t[self.frame,:,0],t[self.frame,:,1]) + self.a.set_xlabel('x') + self.a.set_ylabel('y') + self.a.set_xlim(0,len(self.bgs[self.frame][0,:])) + self.a.set_ylim(len(self.bgs[self.frame][:,0]),0) + self.a.imshow(self.bgs[self.frame],cmap=cm.gray) + + def change_frame(self,A): + """Allows to slide through the sequence. + """ + if ( (A>0 and self.frame+A=0) ): + self.frame+=A + self.lbl.set('Frame {}'.format(self.frame+1)) + self.a.cla() + self.plot_cell_shapes() + self.canvas.show() + + + def ask_open_and_load_file(self): + + self.hdf5_filename=tkFileDialog.askopenfilename(**self.file_opt) + self.file_var.set('HDF5 File: {}'.format(self.hdf5_filename)) + + self.feat,self.data=get_hdf5_data(self.hdf5_filename,fields=['center','halo','soma']) diff --git a/ivctrack/README.rst b/ivctrack/README.rst new file mode 100644 index 0000000..60f1116 --- /dev/null +++ b/ivctrack/README.rst @@ -0,0 +1,57 @@ +GUI Tools for ivctrack Module +============================= + +This Python module adds a GUI to the ivctrack module, which is in-vitro cell tracking toolbox. + +This document gives additional info regarding the GUI. Please refer to https://github.com/odebeir/ivctrack for a description of the original toolbox. + +Dependencies +============ + +Ivctrack module and its dependencies + +FFmpeg for video recording +Download links can be found at http://www.ffmpeg.org/documentation.html + + +Getting started +=========== +Once the ivctrack module is correctly installed, execute the Mainwindow.py script. + +The Menu consists of 4 buttons : + Start Tracking + Plot Results + Measurements + Player + +The user should have downloaded the zip sequences described in the ivctrack README. None of the following can be done without a sequence to analyse. + +Start Tracking +-------------- +Allows you to interactively select the cells to track and start the analysis. +The output files are : + tracks.hdf5 : contains the results of the analysis + marks.csv : contains the marks of the analysed cells + features.csv : contains speed features deduced from the tracking + vid.mp4 : is a video of the tracking. Allows to verify the tracking + params.json : contains the parameters of the analysis + +Rem : The user can decide to only generate the tracks file by uncheking 'Create Folder' + +Plot Results +------------ +Plots tracking results + +Measurements +------------ +Visual display of the measures issued by the measurement module of the ivctrack toolbox + +Player +------ +Gives a preview of the tracking througout the sequence. +This preview can be exported to an MP4 file + +Warning +======= +It is advised to terminate the application after executing a tracking. A bug in the measurements frame may terminate the application unexpectedly and damage the .hdf5 file. +By terminating and relaunching the program, the integrity of the file will be assured, however the bug may still occur. \ No newline at end of file diff --git a/ivctrack/TrackingFrame.py b/ivctrack/TrackingFrame.py new file mode 100644 index 0000000..4a0db0e --- /dev/null +++ b/ivctrack/TrackingFrame.py @@ -0,0 +1,415 @@ +# -*- coding: utf-8 -*- +'''This file implements the frame allowing to select the cells to analyse and to start the tracking +''' +__author__ = ' De Almeida Luis ' + +#------generic imports------ +from Tkinter import * +import tkFileDialog +import tkMessageBox +import os +import csv +import numpy as np + +#------specific imports------ +import matplotlib +matplotlib.use('Tkagg') +from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg +import matplotlib.pyplot as plt +import matplotlib.cm as cm +import matplotlib.animation as anim +import json + +#------ivctrack toolbox imports------ +from hdf5_read import get_hdf5_data +from cellmodel import Cell,test_experiment,import_marks +from measurement import speed_feature_extraction +from reader import ZipSource,Reader + + +class TrackingFrame(Frame): + + """Tracking frame :Allows to :_manually select the cells to track OR/AND import a .csv file containing the coordinates of previous marks. + _set the parameters of the tracking and save them or load previously saved parameters + _set the direction of the tracking""" + + def __init__(self,win,zip_filename): + Frame.__init__(self,win,width=700,height=700) + + #----------------------------------------------------GUI IMPLEMENTATION----------------------------------------- + + #------Parameters(N,radius,.. & tracking direction)------ + self.params_lbl=Label(self,text='Parameters:') + self.params_lbl.grid(row=1,column=0,sticky='W') + + self.p1_lbl=Label(self,text='N') + self.p1_lbl.grid(column=1,row=1, sticky='W') + self.var_N=IntVar() + self.var_N.set(12) + self.p1_entry=Entry(self,textvariable=self.var_N) + self.p1_entry.grid(column=2,row=1) + + self.p2_lbl=Label(self,text='Halo Radius') + self.p2_lbl.grid(column=1,row=2, sticky='W') + self.var_hrad=IntVar() + self.var_hrad.set(20) + self.p1_entry=Entry(self,textvariable=self.var_hrad) + self.p1_entry.grid(column=2,row=2) + + self.p3_lbl=Label(self,text='Soma Radius') + self.p3_lbl.grid(column=1,row=3, sticky='W') + self.var_somrad=IntVar() + self.var_somrad.set(15) + self.p1_entry=Entry(self,textvariable=self.var_somrad) + self.p1_entry.grid(column=2,row=3) + + self.p4_lbl=Label(self,text='Exp Halo') + self.p4_lbl.grid(column=1,row=4, sticky='W') + self.var_hexp=IntVar() + self.var_hexp.set(15) + self.p1_entry=Entry(self,textvariable=self.var_hexp) + self.p1_entry.grid(column=2,row=4) + + self.p5_lbl=Label(self,text='Exp Soma') + self.p5_lbl.grid(column=1,row=5, sticky='W') + self.var_somexp=IntVar() + self.var_somexp.set(2) + self.p1_entry=Entry(self,textvariable=self.var_somexp) + self.p1_entry.grid(column=2,row=5) + + self.p6_lbl=Label(self,text='N_iter') + self.p6_lbl.grid(column=1,row=6, sticky='W') + self.var_niter=IntVar() + self.var_niter.set(5) + self.p1_entry=Entry(self,textvariable=self.var_niter) + self.p1_entry.grid(column=2,row=6) + + self.p7_lbl=Label(self,text='Alpha') + self.p7_lbl.grid(column=1,row=7, sticky='W') + self.var_alpha=DoubleVar() + self.var_alpha.set(.75) + self.p1_entry=Entry(self,textvariable=self.var_alpha) + self.p1_entry.grid(column=2,row=7) + + self.load_param_button=Button(self,text='Load parameters',command=self.load_param) + self.load_param_button.grid(row=8,column=1) + + self.dir_lbl=Label(self,text='Direction:') + self.dir_lbl.grid(row=9,sticky='W') + + self.radValue=StringVar() + self.radValue.set('fwd') + self.radValue.trace('w',self.change_bg) + self.fwd_radbut=Radiobutton(self,text='Forward',variable=self.radValue,value='fwd') + self.fwd_radbut.grid(column=1,row=9,sticky='W') + self.rev_radbut=Radiobutton(self,text='Reverse',variable=self.radValue,value='rev') + self.rev_radbut.grid(column=1,row=10,sticky='W') + + #-----Configuration of the tracking canvas------ + self.f=plt.figure() + self.a=self.f.add_subplot(111) + + self.canvas = FigureCanvasTkAgg(self.f, master=self) + self.canvas.get_tk_widget().grid(column=3,row=1,rowspan=11,columnspan=3) + + cid=self.f.canvas.mpl_connect('button_release_event', self.onclick) + self.marks=[] + + #------Saving parameters------ + self.saveas_lbl=Label(self,text='File (folder) name ') + self.saveas_lbl.grid(column=0,row=11,sticky='W') + + self.hdf5_filename=StringVar() + self.saveas_entry=Entry(self,textvariable=self.hdf5_filename) + self.saveas_entry.grid(column=1,row=11) + + self.save_as_button=Button(self,text='Save as',command=self.browse) + self.save_as_button.grid(row=11,column=2) + + self.create_folder = IntVar() + self.create_folder.set(1) + self.create_folder_checkbutton = Checkbutton(self,text='Create Folder',variable=self.create_folder) + self.create_folder_checkbutton.grid(row=12, column=1) + + + #------tracking related widgets------ + self.reset_button=Button(self,text='Reset',command=self.reset) + self.reset_button.grid(row=12,column=2) + + self.track_button=Button(self,text='Track!',command=self.track) + self.track_button.grid(row=12,column=4,columnspan=1) + + self.import_csv_button=Button(self,text='Import marks (.csv)',command=self.load_csv) + self.import_csv_button.grid(row=12,column=3) + + #------------------------------------------------------END------------------------------------------------------------- + + #------import of the zip file & update of the canvas------ + + self.datazip_filename=zip_filename + self.reader = Reader(ZipSource(self.datazip_filename)) + self.bg=self.reader.getframe() + + self.a.set_xlim(0,len(self.bg[0,:])) + self.a.set_ylim(len(self.bg[:,0]),0) + + self.frame_text=self.a.text(10,20,'') + + self.im = self.a.imshow(self.bg,cmap=cm.gray) + + self.canvas.show() + + #------parameters related to the saving to a MP4 file------ + self.static_halo=[] + self.static_soma=[] + + #------list of plots of tracked cells------ + self.halo=[] + self.soma=[] + self.halo.append(self.a.plot([],[],'o')) + self.soma.append(self.a.plot([],[])) + + def onclick(self,event): + """tracks the cell positioned at the pointer position. + """ + + self.params = {'N':self.var_N.get(),'radius_halo':self.var_hrad.get(),'radius_soma':self.var_somrad.get(),'exp_halo':self.var_hexp.get(),'exp_soma':self.var_somexp.get(),'niter':self.var_niter.get(),'alpha':self.var_alpha.get()} + + #------direct tracking on the displayed frame------ + c=Cell(event.xdata,event.ydata,**self.params) + c.update(self.bg) + + halo=c.rec()[1] + soma=c.rec()[2] + self.static_halo.append(self.a.plot(halo[:,0],halo[:,1],'o')) + self.static_soma.append(self.a.plot(soma[:,0],soma[:,1])) + self.canvas.show() + + if self.radValue.get()=='fwd': + self.marks.append( (c.rec()[0][0],c.rec()[0][1],0) ) + elif self.radValue.get()=='rev': + self.marks.append( (c.rec()[0][0],c.rec()[0][1],self.reader.N()-1) ) + + self.halo.append(self.a.plot([],[],'o')) + self.soma.append(self.a.plot([],[])) + + def reset(self): + """Resets parameters to default values and clears the marks. + """ + #------default parameters------ + self.var_N.set(12) + self.var_alpha.set(.75) + self.var_niter.set(5) + self.var_hrad.set(20) + self.var_somrad.set(15) + self.var_somexp.set(2) + self.var_hexp.set(15) + self.hdf5_filename.set('') + self.radValue.set('fwd') + + + #------refresh the displayed frame------ + self.marks=[] + self.a.cla() + self.a.set_xlim(0,len(self.bg[0,:])) + self.a.set_ylim(len(self.bg[:,0]),0) + self.reader.rewind() + self.bg=self.reader.getframe() + self.im = self.a.imshow(self.bg,cmap=cm.gray) + self.canvas.show() + + + self.static_halo=[] + self.static_soma=[] + + self.halo=[] + self.soma=[] + self.halo.append(self.a.plot([],[],'o')) + self.soma.append(self.a.plot([],[])) + + + def track(self): + """Executes the tracking and saves files into a folder (if desired). The files are : the marks (CSV), the parameters(JSON), the results(hdf5), a video(MP4) + and speed features(CSV). + """ + + if self.hdf5_filename.get() == "" or self.marks==[]: + tkMessageBox.showerror("Track settings incomplete","Either a filename has not been defined or no cells have been selected. Please verify your settings.") + self.saveas_entry.set + else: + + #------if the user wants a folder reuniting all files related to a single tracking or not------ + if (self.create_folder.get()): + foldername = self.hdf5_filename.get() + filename = foldername +'/tracks.hdf5' + self.marks_filename = foldername + '/marks.csv' + param_filename = foldername + '/params.json' + os.mkdir(foldername) + + else : + filename = self.hdf5_filename.get() + '.hdf5' + self.marks_filename = 'selected_marks.csv' + + #------saving of the marks to a CSV file------ + csvmarks=np.asarray(self.marks) + marksfile=open(self.marks_filename, 'wb') + csvwriter=csv.writer(marksfile, delimiter=',') + for c in csvmarks: + csvwriter.writerow([c[0]] + [c[1]] + [int(c[2])]) + marksfile.close() + + #------tracking------ + test_experiment(datazip_filename=self.datazip_filename,marks_filename=self.marks_filename,hdf5_filename=filename,dir=self.radValue.get(),params=self.params) + + if (self.create_folder.get()): + self.feat,self.data=get_hdf5_data(foldername + '/tracks.hdf5',fields=['center','halo','soma']) + self.save_param(param_filename) + self.save_mp4(self.hdf5_filename.get()) + + feat_name, measures=speed_feature_extraction(self.data) + + #------saving of the speed features to a CSV file------ + feat_file = open(foldername + '/features.csv', 'wb') + csvwriter = csv.writer(feat_file, delimiter=',') + csvwriter.writerow(['x'] + ['y'] + [feat_name[0]] + [feat_name[1]] + [feat_name[2]] + [feat_name[3]] + [feat_name[4]] + [feat_name[5]] + [feat_name[6]]) + measures = np.asarray(measures) + i = 0 + for c in csvmarks: + csvwriter.writerow([c[0]] + [c[1]] + [measures[i][0]] + [measures[i][1]] + [measures[i][2]] + [measures[i][3]] + [measures[i][4]] + [measures[i][5]] + [measures[i][6]]) + i+=1 + feat_file.close() + + self.reset() + + + def save_mp4(self,foldername): + """Proceeds to export the tracking results to a video. + """ + #------cleaning of the tracking canvas------ + for k in range(len(self.static_halo)): + self.static_halo[k][0].set_data([],[]) + self.static_soma[k][0].set_data([],[]) + + #------saving to MP4------ + writer = anim.writers['ffmpeg'] + writer = writer(fps=5) + + with writer.saving(self.f,foldername + '/vid.mp4',200): + self.reader.rewind() + print "Saving video..." + for i in range(self.reader.N()): + self.bg=self.reader.getframe() + self.im.set_data(self.bg) + try: + self.reader.next() + except IndexError: + pass + + self.plot_halo(i) + self.plot_soma(i) + + self.frame_text.set_text(i) + + writer.grab_frame() + print "Video correctly saved at " + foldername +'/vid.mp4' + + + def change_bg(self,*args): + """adapts the displayed image to the different modes of tracking : Forward and Reverse + """ + if self.radValue.get()=='fwd': + self.bg=self.reader.rewind() + elif self.radValue.get()=='rev': + self.bg=self.reader.ff() + + self.im=self.a.imshow(self.bg,cmap=cm.gray) + self.canvas.show() + + def load_csv(self): + """loads marks saved in a CSV file and updates the canvas. + """ + #------delete present marks------ + self.marks=[] + self.a.cla() + self.a.set_xlim(0,len(self.bg[0,:])) + self.a.set_ylim(len(self.bg[:,0]),0) + self.a.imshow(self.bg,cmap=cm.gray) + self.canvas.show() + + self.params = {'N':self.var_N.get(),'radius_halo':self.var_hrad.get(),'radius_soma':self.var_somrad.get(),'exp_halo':self.var_hexp.get(),'exp_soma':self.var_somexp.get(),'niter':self.var_niter.get(),'alpha':self.var_alpha.get()} + + self.file_opt={} + self.file_opt['filetypes'] = [('CSV file','.csv')] + self.file_opt['defaultextension'] ='.csv' + self.file_opt['title'] = 'Select a csv file with the marks' + + self.marks=list(import_marks(tkFileDialog.askopenfilename(**self.file_opt))) + + #------tracking on all cells marked by the CSV file on the displayed frame------ + for i in range(len(self.marks)): + c=Cell(self.marks[i][0],self.marks[i][1],**self.params) + c.update(self.bg) + + halo=c.rec()[1] + soma=c.rec()[2] + self.static_halo.append(self.a.plot(halo[:,0],halo[:,1],'o')) + self.static_soma.append(self.a.plot(soma[:,0],soma[:,1])) + self.canvas.show() + + def browse(self): + + self.file_opt={} + self.file_opt['title'] = 'Save as...' + self.hdf5_filename.set(tkFileDialog.asksaveasfilename(**self.file_opt)) + + def save_param(self,filename): + """Exports the parameters to a JSON file + """ + + s = json.dumps(self.params) + fid = open(filename,'w+t') + fid.write(s) + del fid + print 'parameters saved at ',filename + + def load_param(self): + """imports the parameters from a JSON file + """ + file_opt={} + file_opt['filetypes'] = [('JSON files','.json')] + file_opt['defaultextension'] ='.json' + file_opt['title'] = "Choose parameters' file" + + self.param_filename=tkFileDialog.askopenfilename(**file_opt) + + self.params=json.loads(open(self.param_filename).read()) + + self.var_N.set(self.params['N']) + self.var_alpha.set(self.params['alpha']) + self.var_niter.set(self.params['niter']) + self.var_hrad.set(self.params['radius_halo']) + self.var_somrad.set(self.params['radius_soma']) + self.var_somexp.set(self.params['exp_soma']) + self.var_hexp.set(self.params['exp_halo']) + + def plot_halo(self,i): + """Plots the halo once the tracking is complete to allow the video writer to save the results to a MP4 file. + """ + k=0 + for d in self.data: + t=d['halo'] + x=t[i,:,0] + y=t[i,:,1] + self.halo[k][0].set_data(x,y) + k+=1 + + def plot_soma(self,i): + """Plots the soma once the tracking is complete to allow the video writer to save the results to a MP4 file. + """ + k=0 + for d in self.data: + t=d['soma'] + x=t[i,:,0] + y=t[i,:,1] + self.soma[k][0].set_data(x,y) + k+=1 \ No newline at end of file