Skip to content
Snippets Groups Projects
GUI_main.py 114 KiB
Newer Older
  • Learn to ignore specific revisions
  • lpbsscientist's avatar
    lpbsscientist committed
    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-
    """
    This script is the main script used to produce a GUI which should help for
    
    cell segmentation. This script can only read .nd2 files containing the 
    
    lpbsscientist's avatar
    lpbsscientist committed
    images of cells, especially it displays for each recorded positions (field
    of view) the pictures in the time axis.
    
    The script opens first a window which allows you to load an nd2 file and
    to load or create an hdf file. The hdf file contains all the masks, so if
    it is the first the user segments an nd2 file, a new one should be created.
    And it can be then loaded for later use.  Along with new hdf file, created
    
    by the name entered by the user (say filename), it creates three other hdf 
    files (filename_predicted.h5, filename_thresholded.h5 and 
    filename_segmented.h5) these contain all the steps of the NN to get to the 
    segmented picture. 
    
    After the first window is finished a second one opens, where at each time 
    index, three pictures 
    
    lpbsscientist's avatar
    lpbsscientist committed
    are displayed the t-1 picture, the t picture (current frame which can be
    edited) and the t+1 picture. Using the arrows one can navigate through time.
    On top of the picture, there is always a mask which is displayed, if no cells
    
    are present in the mask then the mask is blank and the user does not see it. 
    
    lpbsscientist's avatar
    lpbsscientist committed
    If one wants to hand anmotate the pictures, one can just start to draw on the
    picture using the different functions (New Cell, Add Region, Brush, Eraser,
    
    Save Mask,...) and the informations will be saved in the mask overlayed on 
    top of the pictures. 
    
    lpbsscientist's avatar
    lpbsscientist committed
    
    If one wants to segment using a neural network, one can press the
    
    corresponding button (Launch CNN) and select the time range and 
    
    lpbsscientist's avatar
    lpbsscientist committed
    the field of views on which the neural network is applied.
    
    Once the neural network has finished predicting, there are still no visible
    masks, but on the field of views and time indices where the NN has been
    applied, the threshold and segment buttons are enabled. By checking these
    two buttons one can either display the thresholded image of the prediction or
    display the segmentation of the thresholded prediction.
    
    At this stage, one can correct the segmentation of the prediction using
    the functions (New Cell, Add Region, etc..) by selecting the Segment
    checkbox and then save them using the Save Seg button.
    
    If the user is happy with the segmentation, the Cell Correspondance button 
    
    lpbsscientist's avatar
    lpbsscientist committed
    can be clicked. Until then, the cells get random numbers attributed by
    the segmentation algorithm. In order to keep track of the cell through time,
    the same cells should have the same number between two different time pictures.
    This can be (with some errors) achieved by the Cell Correspondance button,
    which tries to attribute the same number to corresponding cells in time.
    After that, the final mask is saved and it is always visible when you go on
    
    the corresponding picture. This mask can also be corrected using the 
    usual buttons (because the Cell Correspondance makes also mistakes). 
    
    lpbsscientist's avatar
    lpbsscientist committed
    
    """
    
    import sys
    #append all the paths where the modules are stored. Such that this script
    #looks into all of these folders when importing modules.
    sys.path.append("./unet")
    sys.path.append("./disk")
    sys.path.append("./icons")
    sys.path.append("./init")
    sys.path.append("./misc")
    import time
    import os
    import numpy as np
    
    
    # Import everything for the Graphical User Interface from the PyQt5 library.
    
    from PyQt5.QtWidgets import QApplication, QMainWindow, QDialog, QMenu, QVBoxLayout, QSizePolicy, QMessageBox, QWidget, QPushButton, QShortcut, QComboBox, QCheckBox, QLineEdit, QMenu, QAction, QStatusBar, QErrorMessage
    
    lpbsscientist's avatar
    lpbsscientist committed
    from PyQt5 import QtGui
    from PyQt5.QtCore import pyqtSignal, QObject, Qt
    
    #Import from matplotlib to use it to display the pictures and masks.
    from matplotlib.backends.qt_compat import QtCore, QtWidgets, is_pyqt5
    from matplotlib.backends.backend_qt5agg import (FigureCanvasQTAgg as FigureCanvas, NavigationToolbar2QT as NavigationToolbar)
    from matplotlib.figure import Figure
    import matplotlib.pyplot as plt
    
    #import the colormaps function to create a customed colormap scale with 10 
    
    lpbsscientist's avatar
    lpbsscientist committed
    #colors only
    from matplotlib import cm
    from matplotlib.colors import ListedColormap, LinearSegmentedColormap
    #import Path functions to handle the regions drawn by the user. ("add region
    #and new cell")
    from matplotlib.path import Path
    
    lpbsscientist's avatar
    lpbsscientist committed
    
    #Import all the other python files
    #this file handles the interaction with the disk, so loading/saving images
    #and masks and it also runs the neural network.
    import InteractionDisk_temp as nd
    
    lpbsscientist's avatar
    lpbsscientist committed
    #this file contains a dialog window that takes two integers as entry to swap
    #two cell values
    import ExchangeCellValues as ecv
    #this file contains a dialog window which is opened before the main program
    #and allows to load the nd2 and hdf files by browsing through the computer.
    import DialogFileBrowser as dfb
    
    #this file contains a window that opens to change the value of one cell. It 
    
    lpbsscientist's avatar
    lpbsscientist committed
    #is opened as soon as the user presses with the left click on a specific cell.
    import ChangeOneCellValue as cocv
    #this file contains a dialog window to browse for the excel file where
    
    #all the extracted information on the fluoerscence is written. Or to create a 
    #new excel file by typing a name in the text box. It is thought to have one 
    
    lpbsscientist's avatar
    lpbsscientist committed
    #excel file per field of view.
    import DialogDataBrowser as ddb
    #this file contains a dialog window where a time range and the field of views
    #can be selected to then launch a prediction of the neural network on
    #a specific range of pictures.
    
    lpbsscientist's avatar
    lpbsscientist committed
    import LaunchBatchPrediction as lbp
    
    #this file initializes all the buttons present in the gui, sets the shortcuts
    
    #to these buttons and also connect the buttons to the function that are 
    
    lpbsscientist's avatar
    lpbsscientist committed
    #triggered when the buttons are pressed.
    import InitButtons
    
    #this file contains the layout of the main window so it justs puts the buttons
    #and the pictures at the desired position in the main window.
    import InitLayout
    
    
    import random
    
    #import everything needed to write and read excel files.
    from openpyxl import load_workbook
    from openpyxl import Workbook
    
    
    
    #def show_error(msg):
    #    error_dialog = QErrorMessage()
    #    error_dialog.showMessage(msg)
    #    error_dialog.exec_
        
    
    lpbsscientist's avatar
    lpbsscientist committed
    
    
    class NavigationToolbar(NavigationToolbar):
        """This is the standard matplotlib toolbar but only the buttons
    
        that are of interest for this gui are loaded. These buttons allow 
        to zoom into the pictures/masks and to navigate in the zoomed picture. 
    
    lpbsscientist's avatar
    lpbsscientist committed
        A Home button can be used to set the view back to the original view.
        """
        toolitems = [t for t in NavigationToolbar.toolitems if
                     t[0] in ('Home', 'Pan', 'Zoom','Back', 'Forward')]
    
    lpbsscientist's avatar
    lpbsscientist committed
    class App(QMainWindow):
        """This class creates the main window.
    
        """
    
        def __init__(self, nd2pathstr, hdfpathstr, newhdfstr):
            super().__init__()
    #        initializes the window
    
    lpbsscientist's avatar
    lpbsscientist committed
    #       set a title to the window
            self.title = 'YeaZ 1.0'
    
    lpbsscientist's avatar
    lpbsscientist committed
    
    #        id is an integer that gives the id of the connection between the mouseclick method
    #        and the activation of the button.
    
    #       all these ids are integers which are used to set a connection between
    #       the button and the function that this button calls.
    
    #       There are three of them because it happens that one can trigger three 
    #       different functions with one button.        
    
    lpbsscientist's avatar
    lpbsscientist committed
            self.id = 0
            self.id2 = 0
    
            self.id3 = 0 
    
    lpbsscientist's avatar
    lpbsscientist committed
    
    
    
    #       it calls an object of the class Load Image from the InteractionDisk
    
    lpbsscientist's avatar
    lpbsscientist committed
    #       file which is used to load images and masks from the nd2 file or tiff files. To
    
    lpbsscientist's avatar
    lpbsscientist committed
    #       initialize this object it needs the path of the nd2 file, of an
    #       existing hdf file and the name of a new hdf file. If the user has no
    #       hdf file yet the hdfpathstr will be empty and vice versa if the user
    #       selects an already existing hdf file.
    
    #       It takes all the strings given by the first window 
    #       (called before the main window opens) from the DialogFileBrowser.py 
    
    lpbsscientist's avatar
    lpbsscientist committed
    #       file. (see at the end of this code)
    
            self.reader = nd.Reader(hdfpathstr, newhdfstr, nd2pathstr)
    
    lpbsscientist's avatar
    lpbsscientist committed
    #       these variables are used to create/read/load the excel file used
    #       to write the fluorescence values extracted. For each field of view,
    
    #       the user will be asked each time to create a new xls file for the 
    
    lpbsscientist's avatar
    lpbsscientist committed
    #       field of view or to load an existing field of view (this is the role
    #       of the boolean variable)
            self.xlsfilename = ''
            self.nd2path = nd2pathstr
            self.FlagFluoExtraction = False
    
    
    lpbsscientist's avatar
    lpbsscientist committed
    #        Set the indices for the time axis and the field of view index. These
    #        indices represent everywhere the current picture (the one that can be
    #        edited, i.e. the time t frame)
            self.Tindex = 0
            self.FOVindex = 0
    
    lpbsscientist's avatar
    lpbsscientist committed
    #        loading the first images of the cells from the nd2 file
            self.currentframe = self.reader.LoadOneImage(self.Tindex,self.FOVindex)
    
    lpbsscientist's avatar
    lpbsscientist committed
            
            #check if the t+1 time frame exists, avoid failure if there is only
            #one picture in the folder/nd2 file
            if self.Tindex+1 < self.reader.sizet:
                self.nextframe = self.reader.LoadOneImage(self.Tindex+1, self.FOVindex)
            else:
                self.nextframe = np.zeros([self.reader.sizey, self.reader.sizex])
            
    
    lpbsscientist's avatar
    lpbsscientist committed
            self.previousframe = np.zeros([self.reader.sizey, self.reader.sizex])
    
    lpbsscientist's avatar
    lpbsscientist committed
    
    #        loading the first masks from the hdf5 file
            self.mask_curr = self.reader.LoadMask(self.Tindex, self.FOVindex)
            self.mask_previous = np.zeros([self.reader.sizey, self.reader.sizex])
    
    lpbsscientist's avatar
    lpbsscientist committed
            
            #check if the t+1 mask exists, avoid failure if there is only
            #one mask in the hdf file
            if self.Tindex+1 < self.reader.sizet:
                self.mask_next = self.reader.LoadMask(self.Tindex+1, self.FOVindex)
            else:
                self.mask_next = np.zeros([self.reader.sizey, self.reader.sizex])
            
    
    lpbsscientist's avatar
    lpbsscientist committed
    #        creates a list of all the buttons, which will then be used in order
    #        to disable all the other buttons at once when one button/function
    #        is pressed/used in the gui.
            self.buttonlist = []
    
    lpbsscientist's avatar
    lpbsscientist committed
    #        setting buttons as attributes
    #        the shortcuts for the buttons, the functions to which they are
    #        connected to,... are all set up in the ButtonInit file which is called
    #        in the self.initUI() method below.
    
    lpbsscientist's avatar
    lpbsscientist committed
            self.button_newcell = QPushButton("New cell")
            self.buttonlist.append(self.button_newcell)
    
    lpbsscientist's avatar
    lpbsscientist committed
            self.button_add_region = QPushButton("Add region")
            self.buttonlist.append(self.button_add_region)
    
    lpbsscientist's avatar
    lpbsscientist committed
            self.button_savemask = QPushButton("Save Mask")
            self.buttonlist.append(self.button_savemask)
    
    lpbsscientist's avatar
    lpbsscientist committed
            self.button_drawmouse = QPushButton('Brush')
            self.buttonlist.append(self.button_drawmouse)
    
    lpbsscientist's avatar
    lpbsscientist committed
            self.button_eraser = QPushButton('Eraser')
            self.buttonlist.append(self.button_eraser)
    
    lpbsscientist's avatar
    lpbsscientist committed
            self.button_exval = QPushButton('Exchange Cell Values')
            self.buttonlist.append(self.button_exval)
    
    lpbsscientist's avatar
    lpbsscientist committed
            self.button_showval = QCheckBox('Show Cell Values')
            self.buttonlist.append(self.button_showval)
    
    lpbsscientist's avatar
    lpbsscientist committed
            self.button_hidemask = QCheckBox('Hide Mask')
            self.buttonlist.append(self.button_hidemask)
    
    lpbsscientist's avatar
    lpbsscientist committed
            self.button_nextframe = QPushButton("Next Time Frame")
            self.buttonlist.append(self.button_nextframe)
    
    lpbsscientist's avatar
    lpbsscientist committed
            self.button_previousframe = QPushButton("Previous Time Frame")
            self.buttonlist.append(self.button_previousframe)
    
    lpbsscientist's avatar
    lpbsscientist committed
            self.button_cnn = QPushButton('Launch CNN')
            self.buttonlist.append(self.button_cnn)
    
    lpbsscientist's avatar
    lpbsscientist committed
            self.button_threshold = QCheckBox('Threshold prediction')
            self.buttonlist.append(self.button_threshold)
    
    lpbsscientist's avatar
    lpbsscientist committed
            self.button_segment = QCheckBox('Segment')
            self.buttonlist.append(self.button_segment)
    
    lpbsscientist's avatar
    lpbsscientist committed
            self.button_cellcorespondance = QPushButton('Tracking')
    
    lpbsscientist's avatar
    lpbsscientist committed
            self.buttonlist.append(self.button_cellcorespondance)
    
    lpbsscientist's avatar
    lpbsscientist committed
            self.button_changecellvalue = QPushButton('Change cell value')
    
            self.buttonlist.append(self.button_changecellvalue)        
            
    
    lpbsscientist's avatar
    lpbsscientist committed
            self.button_extractfluorescence = QPushButton('Extract Fluorescence')
            self.buttonlist.append(self.button_extractfluorescence)
    
    lpbsscientist's avatar
    lpbsscientist committed
            self.button_hide_show = QPushButton('CNN')
            self.buttonlist.append(self.button_hide_show)
            
    
    lpbsscientist's avatar
    lpbsscientist committed
            self.initUI()
    
    
    lpbsscientist's avatar
    lpbsscientist committed
    
    
        def initUI(self):
    
            """Initializing the widgets contained in the window. 
            Especially, it creates the widget to plot the 
            pictures/masks by creating an object of the PlotCanvas class self.m. 
    
    lpbsscientist's avatar
    lpbsscientist committed
            Every interaction with the masks or the pictures (loading new
            frames/editing the frames/masks) occurs through this class.
    
    lpbsscientist's avatar
    lpbsscientist committed
            This method initializes all the buttons with the InitButtons file.
            It connects the buttons to the functions that they should trigger,
    
            it sets the shortcuts to the buttons, a tool tip, 
            eventually a message on the status bar when the user hovers 
    
    lpbsscientist's avatar
    lpbsscientist committed
            over the button, etc..
    
            
            This function also sets all the layout in the InitLayout file. It 
    
    lpbsscientist's avatar
    lpbsscientist committed
            takes and places the widgets (buttons, canvas, toolbar).
    
    
            The function initializes a Menu Bar to have a menu which can be 
    
    lpbsscientist's avatar
    lpbsscientist committed
            improved later on.
            It sets a toolbar of the matplotlib library and hides it. But it allows
    
            to connect to the functions of this toolbar through "homemade" 
    
    lpbsscientist's avatar
    lpbsscientist committed
            QPushButtons instead of the ones provided by matplotlib.
            Finally, it sets a StatusBar which displays some text to describe
    
            the use of some buttons, or to show that the program is working on 
    
    lpbsscientist's avatar
    lpbsscientist committed
            something (running the neural network, loading frames, etc...)
    
    lpbsscientist's avatar
    lpbsscientist committed
            After all this has been initialized, the program is ready to be used.
    
    lpbsscientist's avatar
    lpbsscientist committed
            """
            self._main = QtWidgets.QWidget()
            self.setCentralWidget(self._main)
    
    
    #       Here our canvas is created where using matplotlib, 
    
    lpbsscientist's avatar
    lpbsscientist committed
    #       one can plot data to display the pictures and masks.
            self.m = PlotCanvas(self)
    
            
    #       Initialize all the buttons that are needed and the functions that are 
    
    lpbsscientist's avatar
    lpbsscientist committed
    #       connected when the buttons are triggered.
            InitButtons.Init(self)
            InitLayout.Init(self)
    
    lpbsscientist's avatar
    lpbsscientist committed
    #        MENU, TOOLBAR AND STATUS BAR
    #       creates a menu just in case, some other functions can be added later
    #        in this menu.
            menubar = self.menuBar()
    
            self.fileMenu = menubar.addMenu('File')   
    
    lpbsscientist's avatar
    lpbsscientist committed
            self.saveactionmenu = QAction('Save')
            self.fileMenu.addAction(self.saveactionmenu)
            self.saveactionmenu.triggered.connect(self.ButtonSaveMask)
    
    
    lpbsscientist's avatar
    lpbsscientist committed
    #       hide the toolbar and instead of the original buttons of matplotlib,
    #       QPushbuttons are used and are connected to the functions of the toolbar
    
    #       it is than easier to interact with these buttons (for example to 
    
    lpbsscientist's avatar
    lpbsscientist committed
    #       to disable them and so on..)
            self.Nvgtlbar = NavigationToolbar(self.m, self)
            self.addToolBar(self.Nvgtlbar)
            self.Nvgtlbar.hide()
    
    lpbsscientist's avatar
    lpbsscientist committed
    #        creates a status bar, which displays (or should display) some text
    #        whenever a function is used.
            self.statusBar = QStatusBar()
            self.setStatusBar(self.statusBar)
    
    lpbsscientist's avatar
    lpbsscientist committed
            self.show()
    
    lpbsscientist's avatar
    lpbsscientist committed
        def mousePressEvent(self, QMouseEvent):
    
            """this function is implemented just to have the QLineButtons of the 
    
    lpbsscientist's avatar
    lpbsscientist committed
            change time index button, setthreshold button and the setsegmentation
            button out of focus when the user clicks somewhere
            on the gui. (to unfocus the buttons)
            """
            self.button_timeindex.clearFocus()
            if self.button_SetThreshold.isEnabled():
                self.button_SetThreshold.clearFocus()
    
    lpbsscientist's avatar
    lpbsscientist committed
            if self.button_SetSegmentation.isEnabled():
                self.button_SetSegmentation.clearFocus()
    
    lpbsscientist's avatar
    lpbsscientist committed
    #        CONNECT the functions of the toolbar to our custom QPushbuttons.
        def ZoomTlbar(self):
    
            """The button_zoom is connected to the zoom function of the toolbar 
    
    lpbsscientist's avatar
    lpbsscientist committed
            already present in the matplotlib library.
    
            
            Depending on the buttons that are active or checked, when the zoom 
    
    lpbsscientist's avatar
    lpbsscientist committed
            function is used, it does not disable all the buttons.
    
    lpbsscientist's avatar
    lpbsscientist committed
            If the segment and threshold button are not checked or used
            when the zoom button is clicked, it disables all the button
            using self.Disable which disables everything except the button passed
            in argument (in this case button_zoom).
    
    lpbsscientist's avatar
    lpbsscientist committed
            If the zoom button is used while the segment button is checked,
            it disables all the buttons (1st elif) except the segment button
    
            but once it is finished (so the zoom button becomes unchecked) 
    
    lpbsscientist's avatar
    lpbsscientist committed
            then it enables only the editing buttons (as long as the segment
    
            button is still checked) such as New Cell, Add Region, Eraser, 
    
    lpbsscientist's avatar
    lpbsscientist committed
            Brush,etc.. and the other toolbar buttons (3rd elif)
    
    lpbsscientist's avatar
    lpbsscientist committed
            If the zoom button is clicked while the threshold button is checked,
            it disables all the button except the threshold button (2nd elif).
            Once the zoom button is unchecked, it enables the toolbar buttons
            (4th elif)
            In any other case, it just enables all the buttons again.
    
    
    lpbsscientist's avatar
    lpbsscientist committed
            """
            self.Nvgtlbar.zoom()
    
    lpbsscientist's avatar
    lpbsscientist committed
            if self.button_zoom.isChecked() and not(self.button_segment.isChecked() or self.button_threshold.isChecked()):
                self.Disable(self.button_zoom)
    
    lpbsscientist's avatar
    lpbsscientist committed
            elif self.button_zoom.isChecked() and self.button_segment.isChecked():
                self.Disable(self.button_zoom)
                self.button_segment.setEnabled(True)
    
    lpbsscientist's avatar
    lpbsscientist committed
            elif self.button_zoom.isChecked() and self.button_threshold.isChecked():
                self.Disable(self.button_zoom)
                self.button_threshold.setEnabled(True)
    
    lpbsscientist's avatar
    lpbsscientist committed
            elif self.button_zoom.isChecked() == False and self.button_segment.isChecked():
    
    lpbsscientist's avatar
    lpbsscientist committed
                self.button_pan.setEnabled(True)
                self.button_home.setEnabled(True)
                self.button_back.setEnabled(True)
                self.button_forward.setEnabled(True)
    
    lpbsscientist's avatar
    lpbsscientist committed
                self.EnableCorrectionsButtons()
    
    lpbsscientist's avatar
    lpbsscientist committed
            elif self.button_zoom.isChecked() == False and self.button_threshold.isChecked():
    
    lpbsscientist's avatar
    lpbsscientist committed
                self.button_pan.setEnabled(True)
                self.button_home.setEnabled(True)
                self.button_back.setEnabled(True)
                self.button_forward.setEnabled(True)
    
    lpbsscientist's avatar
    lpbsscientist committed
            else:
                self.Enable(self.button_zoom)
    
    lpbsscientist's avatar
    lpbsscientist committed
        def HomeTlbar(self):
    #        connects the home button to the home function of the matplotlib
    #        toolbar. It sets the view to the original view (no zoom)
            self.Nvgtlbar.home()
    
    
    lpbsscientist's avatar
    lpbsscientist committed
        def BackTlbar(self):
    
    #        It calls the back function of the matplotlib toolbar which sets the 
    #        view to the previous one (if the user does several zooms/pans, 
    
    lpbsscientist's avatar
    lpbsscientist committed
    #        this button allows to go back in the "history of views")
            self.Nvgtlbar.back()
    
    
    lpbsscientist's avatar
    lpbsscientist committed
        def ForwardTlbar(self):
    
    #        It calls the forward function of the matplotlib toolbar which sets the 
    #        view to the next one (if the user does several zooms/pans, 
    
    lpbsscientist's avatar
    lpbsscientist committed
    #        this button allows to go forward in the "history of views"
    
    lpbsscientist's avatar
    lpbsscientist committed
            self.Nvgtlbar.forward()
    
    
    lpbsscientist's avatar
    lpbsscientist committed
        def PanTlbar(self):
    
            """The button_pan is connected to the pan function of the toolbar 
    
    lpbsscientist's avatar
    lpbsscientist committed
            already present in the matplotlib library.
    
            
            Depending on the buttons that are active or checked, when the pan 
    
    lpbsscientist's avatar
    lpbsscientist committed
            function is used, it does not disable all the buttons.
    
    lpbsscientist's avatar
    lpbsscientist committed
            If the segment and threshold button are not checked or used
            when the pan button is clicked, it disables all the button
            using self.Disable which disables everything except the button passed
            in argument (in this case button_pan).
    
    lpbsscientist's avatar
    lpbsscientist committed
            If the pan button is used while the segment button is checked,
            it disables all the buttons (1st elif) except the segment button
    
            but once it is finished (so the zoom button becomes unchecked) 
    
    lpbsscientist's avatar
    lpbsscientist committed
            then it enables only the editing buttons (as long as the segment
    
            button is still checked) such as New Cell, Add Region, Eraser, 
    
    lpbsscientist's avatar
    lpbsscientist committed
            Brush,etc.. and the other toolbar buttons (3rd elif)
    
    lpbsscientist's avatar
    lpbsscientist committed
            If the pan button is clicked while the threshold button is checked,
            it disables all the button except the threshold button (2nd elif).
            Once the pan button is unchecked, it enables the toolbar buttons
            (4th elif)
            In any other case, it just enables all the buttons again.
            """
    
            self.Nvgtlbar.pan()
    
            if self.button_pan.isChecked() and not(self.button_segment.isChecked() or self.button_threshold.isChecked()):
                self.Disable(self.button_pan)
    
    lpbsscientist's avatar
    lpbsscientist committed
            elif self.button_pan.isChecked() and self.button_segment.isChecked():
                self.Disable(self.button_pan)
                self.button_segment.setEnabled(True)
    
    lpbsscientist's avatar
    lpbsscientist committed
            elif self.button_pan.isChecked() and self.button_threshold.isChecked():
                self.Disable(self.button_pan)
                self.button_threshold.setEnabled(True)
    
    lpbsscientist's avatar
    lpbsscientist committed
            elif not(self.button_pan.isChecked()) and self.button_segment.isChecked():
    
    lpbsscientist's avatar
    lpbsscientist committed
                self.button_zoom.setEnabled(True)
                self.button_home.setEnabled(True)
                self.button_back.setEnabled(True)
                self.button_forward.setEnabled(True)
    
    lpbsscientist's avatar
    lpbsscientist committed
                self.EnableCorrectionsButtons()
    
    lpbsscientist's avatar
    lpbsscientist committed
            elif not(self.button_pan.isChecked()) and self.button_threshold.isChecked():
                self.button_zoom.setEnabled(True)
                self.button_home.setEnabled(True)
                self.button_back.setEnabled(True)
                self.button_forward.setEnabled(True)
    
    
    lpbsscientist's avatar
    lpbsscientist committed
            else:
                self.Enable(self.button_pan)
    
    
        def ButtonFluo(self):
    
            """This function is called everytime the Extract Fluorescence button is 
            clicked (self.button_extractfluorescence). 
            
            self.FlagFluoExtraction is boolean which is True when the path to the 
    
    lpbsscientist's avatar
    lpbsscientist committed
            excel file has already been loaded into self.xlsfilename.
            This pathname changes for each field of view as it is thought to have
    
            one xls file per field of view. 
    
    lpbsscientist's avatar
    lpbsscientist committed
            So at the beginning and each time the user changes field of view,
            self.FlagFluoExtraction is set to False.
    
    lpbsscientist's avatar
    lpbsscientist committed
            When it is set to False, this function calls a dialog window where
            the user is asked to load an already existing xls file for the current
            field of view or to give a name to create a new xls file for
            the current field of view. (self.Dialogxls)
    
    lpbsscientist's avatar
    lpbsscientist committed
            If it set to true, it means that self.xlsfilename contains the path
            to the xls file for the current field of view and it is directly given
            to the function that writes the fluorescence into the xls file.
            (self.ExtractFluo)
            """
    
    lpbsscientist's avatar
    lpbsscientist committed
            if self.FlagFluoExtraction:
                self.ExtractFluo(self.xlsfilename)
            else:
                self.DialogXls()
    
    lpbsscientist's avatar
    lpbsscientist committed
        def DialogXls(self):
            """This function creates a dialog window which gives two options to the
            user either to load an existing xls file or to give a new name in order
    
            to create a new xls file. 
    
    lpbsscientist's avatar
    lpbsscientist committed
            """
    #        creates the window
            dwind = ddb.FileBrowser()
    
    
    #        this test is True if the user presses ok in the dialog window, if the 
    
    lpbsscientist's avatar
    lpbsscientist committed
    #        user presses cancels it returns and nothing happens.
    
    lpbsscientist's avatar
    lpbsscientist committed
            if dwind.exec_():
    
    lpbsscientist's avatar
    lpbsscientist committed
    #            read the entry given by the file browser
                xlsname = dwind.xlsname
    
    lpbsscientist's avatar
    lpbsscientist committed
    #            reads the entry given by the new filename text field.
                newxlsname = dwind.newxlsentry.text()
    
    
    #            if the string containing the filepath to an existing xls file 
    #            is not empty then it calls directly the function to write the 
    
    lpbsscientist's avatar
    lpbsscientist committed
    #            data into this existing xls file and sets self.xlsfilename
                if xlsname:
    
    lpbsscientist's avatar
    lpbsscientist committed
                    self.xlsfilename = xlsname
                    self.ExtractFluo(xlsname)
    
    lpbsscientist's avatar
    lpbsscientist committed
    #           if xlsname is empty then it creates a new pathfilename and puts
    #           the new created xls file into the folder where nd2 is located.
    #           the string containing the nd2 namepath is split
                else:
                    xlsname = ''
                    templist = self.nd2path.split('/')
    
    lpbsscientist's avatar
    lpbsscientist committed
                    for k in range(0, len(templist)-1):
    
    lpbsscientist's avatar
    lpbsscientist committed
                        xlsname = xlsname + templist[k] + '/'
    #               this is the new path/filename
                    xlsname = xlsname + newxlsname + '.xlsx'
                    self.xlsfilename = xlsname
    
    lpbsscientist's avatar
    lpbsscientist committed
    #               here as a new name has been given, it means that a new xls file
    #                should be created, this is done with CreateXls
                    self.CreateXls(xlsname)
    #                once there is an existing xls file, it writes in this file
    #                using self.ExtractFluo.
                    self.ExtractFluo(xlsname)
    #           this flag is set to true, for the current field of view each
    #           time extract fluorescence is clicked it writes in the file located
    #           at self.xlsfilename.
                self.FlagFluoExtraction = True
            else:
    
    lpbsscientist's avatar
    lpbsscientist committed
                return
    
    
        def CreateXls(self, xlsfilename):
    
            """In case there is no xls file existing, here a new one is created 
    
    lpbsscientist's avatar
    lpbsscientist committed
            and prepared. For each channel a new sheet is created.
            In the first row for each sheet, the time indices are written t = 0,
            t = 1, etc... but only every third column. Because in the row below,
            three values are extracted 'Total intensity', 'Area' and 'Variance'
            at each time index. So three columns for each time index are needed,
    
            for the three data points. 
    
    lpbsscientist's avatar
    lpbsscientist committed
            The first column is left empty (starting from third row) because
            the cell numbers will be written in there.
            """
    
    lpbsscientist's avatar
    lpbsscientist committed
    #        creates a new xls file using xlwt library.
            book = Workbook()
            nbrchannels = self.reader.sizec
    
    lpbsscientist's avatar
    lpbsscientist committed
            for i in range(0,nbrchannels):
                sheetname = self.reader.channel_names[i]
    #            creates a sheet with the name of the corresponding channel.
                if i == 0:
                    sheet = book.active
                    sheet.title = sheetname
                else:
                    sheet = book.create_sheet(sheetname)
                sheet.cell(1,1, 'Cell Number / Time axis')
                sheet.cell(2,1, 'labels')
                timeaxissize = self.reader.sizet
    #           start writing the time index at column 1, column 0 is reserved for
    #           cell numbers.
                timecolindex = 2
    
    lpbsscientist's avatar
    lpbsscientist committed
                for t in range(1,timeaxissize+1):
    #                in row 0 the time index is written
                    sheet.cell(1,timecolindex).value = 't = {}'.format(t-1)
    #                in row 1, the label of the three data points are written
                    sheet.cell(2,timecolindex).value = 'Total Intensity'
                    sheet.cell(2,timecolindex+1).value = 'Total Area'
                    sheet.cell(2,timecolindex+2).value = 'Mean Intensity'
                    sheet.cell(2,timecolindex+3).value =  'Variance'
    #                updates the index, where the next time index should be written
                    timecolindex = timecolindex + 4
    
    lpbsscientist's avatar
    lpbsscientist committed
    #       saves the xls file.
            book.save(xlsfilename)
    
    
    
    
    lpbsscientist's avatar
    lpbsscientist committed
        def ExtractFluo(self, xlsfilename):
            """This is the function that takes as argument the filepath to the xls
            file and writes in the file.
            It iterates over the different channels (or the sheets of the file,
            each channel has one sheet.), and reads the image corresponding
            to the time, field of view and channel index. It reads the already
            existing file and makes a copy in which the data will be written in it.
    
    lpbsscientist's avatar
    lpbsscientist committed
            The first step of calculating the data is to iterate through each
            cell/segment of the mask (so each cell is a submatrix of one value
            in the matrix of the mask).
            For each of these value /cell, the area is extracted as being
    
            the number of pixels corresponding to this cell/value. 
    
    lpbsscientist's avatar
    lpbsscientist committed
            (it is known from the microscope settings how to convert
            the pixel in area).
            The total intensity is just the value of the pixel and it is added over
            all the pixels corresonding to the cell/value.
            The mean is then calculated as being the total intensity divided by
            the number of pixels (which here is equal to the area also).
    
            With the mean it is then possible to calculate the variance of the 
    
    lpbsscientist's avatar
    lpbsscientist committed
            signal for one cell/value.
    
    lpbsscientist's avatar
    lpbsscientist committed
            Then, it is checked if the value of the cell (cell number) already
            exists in the first column, if it already exists it continues to
            find the column corresponding to the time index where the values
            should be written. It sets the flag to True such that it does not
            write the cell as new one and adds it at the end of the column
    
    lpbsscientist's avatar
    lpbsscientist committed
            If the value is not found in the cell number column (new cell or
    
            first time writing in the file), the flag is False, thus it adds the 
    
    lpbsscientist's avatar
    lpbsscientist committed
            cell number at the end of the column.
            It then saves the xls file.
    
    lpbsscientist's avatar
    lpbsscientist committed
            """
    
    lpbsscientist's avatar
    lpbsscientist committed
    #        disables all the buttons except the one passed in argument.
            self.Disable(self.button_extractfluorescence)
    #        shows a message on the status bar to show that the program is working
            self.statusBar.showMessage('Extracting the fluorescence...')
    
    lpbsscientist's avatar
    lpbsscientist committed
    #        opens the file to read it.
            book = load_workbook(self.xlsfilename)
    
    #       makes a copy of the reading file to write in it the new values.
    #        wb = xlscopy(readbook) # a writable copy (can't read values out of this, only write to it)
    
    #        iterate over all the channels, so over all the sheets in the file
            for channel in range(0, self.reader.sizec):
    #           loads the picture corresponding to the channel, time index and fov
                image = self.reader.LoadImageChannel(self.Tindex, self.FOVindex, channel)
    
    lpbsscientist's avatar
    lpbsscientist committed
    #            loads the sheet to read out corresponding to the current channel
                sheet = book.worksheets[channel]
    #            sheet = readbook.sheet_by_index(channel)
    
    lpbsscientist's avatar
    lpbsscientist committed
    #            this line is here to prevent some errors of streaming into
    #            the file due to read file which is open (I am not sure about this
    #            but it is a more or less working solution found on stackoverflow)
    #            os.remove(xlsfilename)
    
    lpbsscientist's avatar
    lpbsscientist committed
    #           load the sheet corresponding to the current channel to write in it
    #            writingsheet = wb.get_sheet(channel)
    
    lpbsscientist's avatar
    lpbsscientist committed
    #           this index contains the value of the maximum number of rows in the
    #           file, it is used to append at the end the cell number column a new
    #           cell/value, and it is updated each time a new cell is added.
                tempidx = sheet.max_row
    
    lpbsscientist's avatar
    lpbsscientist committed
    #           np.unique(array) returns an array which contains all the value
    #           that appear in self.m.plotmask, so it returns every cell value
    #           including the background (value 0) present in self.m.plotmask
                for val in np.unique(self.m.plotmask):
    
    lpbsscientist's avatar
    lpbsscientist committed
    #                exclude the background, as it is not a cell
                    if val != 0:
    
    lpbsscientist's avatar
    lpbsscientist committed
    #                    this (self.m.plotmask==val).sum() adds one at every pixel
    #                    where self.m.plotmask has the value val
                        area = (self.m.plotmask == val).sum()
    #                    it sums the value of the pixel in image at the coordinates
    #                    where self.m.plotmask equals val.
                        tot_intensity = image[self.m.plotmask == val].sum()
    
    lpbsscientist's avatar
    lpbsscientist committed
    #                    calculate the mean to use it in the calc. of the variance.
                        mean = tot_intensity/area
    
    lpbsscientist's avatar
    lpbsscientist committed
    #                    create a copy of plotmask because I had weird experiences
    #                    with np.where where it modified sometimes the given array
    #                    (not sure)
                        temparr = self.m.plotmask.copy()
    
    
    #                    extract the coordinates of the mask matrix where it equals 
    
    lpbsscientist's avatar
    lpbsscientist committed
    #                    the current cell value val
                        coord = np.where(temparr == val)
    
    lpbsscientist's avatar
    lpbsscientist committed
    #                    variable to save the variance.
                        var = 0
    
    lpbsscientist's avatar
    lpbsscientist committed
    #                    we loop over the coord of the pixel where mask == val
                        for i in range(0,len(coord[0])):
    #                        extract the intensity at the coordinate
                            val_intensity = image[coord[0][i], coord[1][i]]
    #                        substract the value of the intensity with the mean,
    #                        and square it. It is then add to the var.
                            var = var + (val_intensity-mean)*(val_intensity-mean)
    
    lpbsscientist's avatar
    lpbsscientist committed
    #                   var is then divided by the number of the pixel
    #                   corresponding to this value (also equal to the area)
    #                   to get the variance.
                        var = var/area
    
    #                    if flag is false it means that the cell number
    #                    corresponding to val is not present in the xls file, first
    #                    column.
                        flag = False
    
    lpbsscientist's avatar
    lpbsscientist committed
    #                    iterate over all the rows
                        for row in range(sheet.max_row+1):
    
    lpbsscientist's avatar
    lpbsscientist committed
    #                        test if in the first column 0, the number of the cell
    #                        is already present
    #                         if sheet.cell_value(row,0) == str(val):
                             if sheet.cell(row = row+1, column = 1).value == str(val):
    
    lpbsscientist's avatar
    lpbsscientist committed
    #                             if is present, the column corresponding to the
    #                             current time index is by iterating over the cols.
                                 for col in range(sheet.max_column+1):
    #                                 test if it is the right column
    #                                 if sheet.cell_value(0, col) == 't = {}'.format(self.Tindex):
                                     if sheet.cell(row = 1, column = col+1).value == 't = {}'.format(self.Tindex):
    #                                   write in the xls file at the row, col coord
                                         sheet.cell(row+1, col+1, str(tot_intensity))
                                         sheet.cell(row+1, col+2, str(area))
                                         sheet.cell(row+1,col+3, str(mean))
                                         sheet.cell(row+1, col+4, str(var))
                                         book.save(xlsfilename)
    
    
    #                                     the flag is set to True so that it does
    #                                     not execute the code where the cell is
    #                                     added in the xls file in a new row.
                                         flag = True
                        if not flag:
    #                    this lines are executed if a new cell is detected or if
    #                    if it is the first time to write in the file.
                            for col in range(sheet.max_column+1):
                                if sheet.cell(row = 1, column =  col+1).value == 't = {}'.format(self.Tindex):
    #                                it write the cell value/cell number in the
    #                                column
                                    sheet.cell(tempidx+1,1, str(val))
    
    lpbsscientist's avatar
    lpbsscientist committed
    #                                writes the data extracted before
                                    sheet.cell(tempidx+1,col+1,str(tot_intensity))
                                    sheet.cell(tempidx+1, col+2, str(area))
                                    sheet.cell(tempidx+1, col+3, str(mean))
                                    sheet.cell(tempidx+1, col+4, str(var))
    #                                it updates the number of rows as a new cell
    #                                has been added, so there is one more row.
                                    tempidx = tempidx + 1
    #                               save in the file
                                    book.save(xlsfilename)
    
    
                        
    #       Enable again all the buttons          
    
    lpbsscientist's avatar
    lpbsscientist committed
            self.Enable(self.button_extractfluorescence)
    
    lpbsscientist's avatar
    lpbsscientist committed
    #        clear the message shown in the status bar
            self.statusBar.clearMessage()
    
    
    lpbsscientist's avatar
    lpbsscientist committed
        
        def ShowHideCNNbuttons(self):
            
            """hide and show the buttons corresponding to the neural network.
                this function is called by the button CNN which is hidden. But
                if activated in the InitLayout.py then you can have a button
                which hides the CNN buttons (which are now on the normal also
                hidden...).
            """
            
            
            if self.button_hide_show.isChecked():
                
                
                self.button_cnn.setVisible(True)
                self.button_segment.setVisible(True)
                self.button_savesegmask.setVisible(True)
                self.button_threshold.setVisible(True)
                self.button_SetThreshold.setVisible(True)
                self.button_savethresholdmask.setVisible(True)
                self.button_SetSegmentation.setVisible(True)
    
    
    
    lpbsscientist's avatar
    lpbsscientist committed
            else:
                
    
                self.button_cnn.setVisible(False)
                self.button_segment.setVisible(False)
                self.button_savesegmask.setVisible(False)
                self.button_threshold.setVisible(False)
                self.button_SetThreshold.setVisible(False)
                self.button_savethresholdmask.setVisible(False)
                self.button_SetSegmentation.setVisible(False)
                
    
    
    lpbsscientist's avatar
    lpbsscientist committed
        def LaunchBatchPrediction(self):
            """This function is called whenever the button Launch CNN is pressed.
            It allows to run the neural network over a time range and selected
            field of views.
    
    lpbsscientist's avatar
    lpbsscientist committed
            It creates a dialog window with two entries, that define the time range
            and a list where the user can select the desired fields of view.
    
    lpbsscientist's avatar
    lpbsscientist committed
            Once it reads all the value, it calls the neural network function
            inside of self.PredThreshSeg and it does the prediction of the neural
            network, thresholds this prediction and then segments it.
            """
    #        creates a dialog window from the LaunchBatchPrediction.py file
            dlg = lbp.CustomDialog(self)
    
    lpbsscientist's avatar
    lpbsscientist committed
    #        this if tests if the user pressed 'ok' in the dialog window
    
            if dlg.exec_() == QDialog.Accepted:
    
        #       it tests if the user has entered some values
        #       if not it ignores and returns.
                if not (dlg.entry1.text()!= '' and dlg.entry2.text() != ''):
                    QMessageBox.critical(self, "Error", "No Time Specified")
                    return 
        #       reads out the entry given by the user and converts the index
        #       to integers
                time_value1 = int(dlg.entry1.text())
                time_value2 = int(dlg.entry2.text())
        
        
        #                it tests if the first value is smaller or equal such that
        #                time_value1 is the lower range of the time range
        #                and time_value2 the upper boundary of the range.
                if time_value1 > time_value2 :
                    QMessageBox.critical(self, "Error", 'Invalid Time Constraints')
                    return
                
                
                
                # displays that the neural network is running
                self.statusBar.showMessage('Running the neural network...')
        
                #it iterates in the list of the user-selected fields 
                #of view, to return the corresponding index, the function
                #dlg.listfov.row(item) is used which gives an integer
                if len(dlg.listfov.selectedItems())==0:
                    QMessageBox.critical(self, "Error", "No FOV Selected")
                
                for item in dlg.listfov.selectedItems():
                    #iterates over the time indices in the range
                    for t in range(time_value1, time_value2+1):                    
                        #calls the neural network for time t and selected
                        #fov
                        if dlg.entry_threshold.text() !=  '':
                            thr_val = float(dlg.entry_threshold.text())
                        else:
                            thr_val = None
                        if dlg.entry_segmentation.text() != '':
                            seg_val = int(dlg.entry_segmentation.text())
                        else:
                            seg_val = 10
                        
                        self.PredThreshSeg(t, dlg.listfov.row(item), thr_val, seg_val)
    
    lpbsscientist's avatar
    lpbsscientist committed
                        
                        # if tracker has been checked then apply it
                        
                        if dlg.tracking_checkbox.isChecked():
                            
                            if t != 0:
                            
    
                                temp_mask = self.reader.CellCorrespondance(t,dlg.listfov.row(item))
    
    lpbsscientist's avatar
    lpbsscientist committed
                                self.reader.SaveMask(t,dlg.listfov.row(item), temp_mask)
                            
                            else:
                                
                                temp_mask = self.reader.LoadSeg(t, dlg.listfov.row(item))
                                self.reader.SaveMask(t,dlg.listfov.row(item), temp_mask)
                                
                            
                
                self.ReloadThreeMasks()
                   
                #once it has iterated over all the fov, the message in 
                #the status bar is cleared and the buttons are enabled.
    
                self.statusBar.clearMessage()
                self.EnableCNNButtons()
       
    
    lpbsscientist's avatar
    lpbsscientist committed
        def PredThreshSeg(self, timeindex, fovindex, thr_val, seg_val):
              """
              This function is called in the LaunchBatchPrediction function.
              This function calls the neural network function in the
              InteractionDisk.py file and then thresholds the result
              of the prediction, saves this thresholded prediction.
              Then it segments the thresholded prediction and saves the
    
              segmentation. 
    
    lpbsscientist's avatar
    lpbsscientist committed
              """
    #          launches the neural network
              self.reader.LaunchPrediction(timeindex, fovindex)
    #          thresholds the prediction
              self.m.ThresholdMask = self.reader.ThresholdPred(thr_val, timeindex,fovindex)
    #          saves the thresholded pred.
              self.reader.SaveThresholdMask(timeindex, fovindex, self.m.ThresholdMask)
    #          segments the thresholded pred.
              self.m.SegmentedMask = self.reader.Segment(seg_val, timeindex,fovindex)
    #          saves the segmentation
              self.reader.SaveSegMask(timeindex, fovindex, self.m.SegmentedMask)
    
    lpbsscientist's avatar
    lpbsscientist committed
        def LaunchPrediction(self):
            """This function is not used in the gui, but it can be used to launch
            the prediction of one picture, with no thresholding and no segmentation
            """
            if not(self.reader.TestPredExisting(self.Tindex, self.FOVindex)):
                self.statusBar.showMessage('Running the neural network...')
                self.Disable(self.button_cnn)
                self.reader.LaunchPrediction(self.Tindex, self.FOVindex)
    
    lpbsscientist's avatar
    lpbsscientist committed
                self.Enable(self.button_cnn)
    
    lpbsscientist's avatar
    lpbsscientist committed
                self.button_cnn.setEnabled(False)
                self.button_threshold.setEnabled(True)
                self.button_segment.setEnabled(True)
                self.button_cellcorespondance.setEnabled(True)
                self.statusBar.clearMessage()
    
    lpbsscientist's avatar
    lpbsscientist committed
        def ChangeOneValue(self):
            """This function is called when the button Change cell value is
            clicked. It displays the instructions on the status bar.
            And if the user clicks in the graph where the current mask is displayed
            it connects the event of the click (meaning that user has clicked on
    
            one cell) to the function self.DialogBoxChangeOneValue. 
    
    lpbsscientist's avatar
    lpbsscientist committed
            This function will then replaces the cell selected by the user with
            the click with a new value entered by the user.
            """
    
    lpbsscientist's avatar
    lpbsscientist committed
    #        displaying the instructions on the statusbar
            self.statusBar.showMessage('Select one cell using the left click and then enter the desired value in the dialog box')
    
    #       disables all the buttons
            self.Disable(self.button_changecellvalue)