class PhaseNetworkElements(QWidget):
    def __init__(self, camera=None, log=None, fps=10.):
        super().__init__()
        self.feedback = False
        # ---  --- #
        if log != None:
            self.log = log
        else:
            self.log = LogDisplay()
        #self.log.showLog()
        # --- default --- #
        self.fps       = fps
        self.cmap      = 'jet'
        self.sepration = ' '
        self.param_peak= [] # a N x 3 array where each line is [x0, a, b] the parameter of the fit, N being the number of peaks.
        self.dicspan   = {}
        self.postprocss_func = None
        self.procssfunc_default = True
        # --- main attriute --- #
        self.camera    = camera
        if not self.camera.isCameraInit:
            self.camera.__init__(cam_id=0, log=self.log)
        self.qlabl_max = QLabel()
        self.dataframe = np.zeros([10,10])
        # ---  --- #
        self.initUI()

    def initUI(self):
        self.splitter      = QSplitter(PyQt5.QtCore.Qt.Horizontal)
        self.fitting_frame = None
        # ---  --- #
        self.layout      = QVBoxLayout(self)
        # --- guassian fit init --- #
        self.gaussianfit     = GaussianFit(log=self.log)
        self.gaussian_plots  = {}
        self.fittingactivate = QCheckBox()
        self.fittingactivate.setTristate(False)
        self.fittingactivate.stateChanged.connect(self.acceptOrNot)
        # --- init frames --- #
        self.initView()
        self.initParameterZone()
        self.initVertHistogram()
        self.initMultiplotPeak()
        self.initLissajousPlot()
        # --- default --- #
        self.updatePtNbrLabel()
        # --- layout --- #
        vsplitter      = QSplitter(PyQt5.QtCore.Qt.Vertical)
        vsplitter.addWidget( self.camera_view )
        vsplitter.addWidget( self.paramFrame )
        self.splitter.addWidget( vsplitter )
        self.splitter.addWidget( self.histogFrame )
        self.splitter.addWidget( self.multiplotFrame )
        vsplitter      = QSplitter(PyQt5.QtCore.Qt.Vertical)
        vsplitter.addWidget( self.lissajousFrame )
        vsplitter.addWidget( QFrame() )
        self.splitter.addWidget( vsplitter )
        self.layout.addWidget( self.splitter )
        self.setLayout(self.layout)
        # ---  --- #

    def initView(self):
        self.camera_view     = CameraDisplay(camera=self.camera, log=self.log)
        # ---  --- #
        self.camera_view.image_view.setMinimumWidth(100)
        self.camera_view.image_view.setMinimumHeight(200)

    def initParameterZone(self):
        self.paramFrame = QFrame()
        self.paramFrame.setToolTip('Frame where we control the main parameter for the data sampling.')
        self.paramFrame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
        self.paramFrame.setLineWidth(3)
        self.paramFrame.setMidLineWidth(1)
        # --- widgets --- #
        self.histogram_data = QComboBox()
        self.histogram_data.addItem('raw')
        self.histogram_data.addItem('remove backgrnd')
        self.histogram_data.addItem('normalise')
        self.histogram_data.setCurrentIndex(0)
        self.histrealtime   = QCheckBox()
        self.histrealtime.setTristate(False)
        self.histrealtime.setCheckState(2)
        self.setLinkToCameraTimer()
        self.button_save   = QPushButton('Save sampling data')
        self.button_save.setToolTip('Save the plots in the milti-plot window in a txt file such that each line corresponds to the y-data. Moreover we save the total intensity.')
        self.savefile      = QFileDialog(self)
        self.savefile_name = QLabel('Select a file')
        self.savefile_name.setWordWrap(True)
        self.choosedirectory  = QPushButton('&Change file')
        self.postprocss    = QComboBox()
        self.postprocss.addItem('Max peak')
        self.postprocss.addItem('Sum area span')
        # --- connections --- #
        self.choosedirectory.clicked.connect(self.setNewSaveFile)
        self.button_save.clicked.connect( self.saveDataFromMultiplot )
        self.histrealtime.stateChanged.connect( self.setLinkToCameraTimer )
        self.histogram_data.currentIndexChanged.connect( self.setHistgrmPlotRange )
        self.postprocss.currentIndexChanged.connect( self.setPostProcessFunction )
        # --- make layout --- #
        label_1 = QLabel('Histogram data: ')
        label_1.setWordWrap(True)
        label_1.setToolTip('Set what transformation we operate from the image data to the histogram data.\n"raw" means we integrate over the Y-axis and divide by the number of line.\n"normalise" like "raw" but we normalise to the maximum afterward.')
        label_2 = QLabel('Histogram in continuous mode: ')
        label_2.setWordWrap(True)
        label_2.setToolTip('When unchecked, stop the updating of the histogram. In other words, allow to stop the histogram without stopping the video image.')
        label_3 = QLabel('Sampling post-processing: ')
        label_3.setWordWrap(True)
        label_3.setToolTip('The post-processing refer to the data processing from the vertivcal histogram of the image.')
        grid    = QGridLayout()
        grid.addWidget(label_1               , 1,0)
        grid.addWidget( self.histogram_data  , 1,1)
        grid.addWidget(label_2               , 0,0)
        grid.addWidget( self.histrealtime    , 0,1)
        grid.addWidget(label_3               , 2,0)
        grid.addWidget(self.postprocss       , 2,1)
        grid.addWidget( QHLine()             , 3,0 , 1,2)
        grid.addWidget( self.button_save     , 4,0 , 1,2)
        grid.addWidget( self.choosedirectory , 5,0)
        grid.addWidget(self.savefile_name    , 5,1)
        self.paramFrame.setLayout( grid )

    def initVertHistogram(self):
        self.plot_hist    = pg.PlotWidget()
        self.plot_hist.setMinimumHeight(600)
        self.plot_hist.setMinimumWidth(100)
        plot_viewbox      = self.plot_hist.getViewBox()
        plot_viewbox.invertX(True)
        self.plot_hist.showAxis('right')
        self.plot_hist.hideAxis('left')
        self.plot_hist.showGrid(x=True)
        plot_viewbox.setAspectLocked(False)
        plot_viewbox.enableAutoRange(pg.ViewBox.YAxis, enable=True)
        # --- measured data --- #
        self.data_hist = pg.PlotDataItem()
        self.plot_hist.addItem(self.data_hist)
        # --- widgets --- #
        self.spanNumber     = QSpinBox()#QPushButton('add new span')
        self.spanNumber.setMaximum(20)
        self.spanNumber.setValue(0)
        self.spanNumber.valueChanged.connect( self.makeSpans )
        # --- make layout --- #
        self.histogFrame = QFrame()
        self.histogFrame.setToolTip('Vertical histogram of the image. We integrated over the Y-axis of the image.')
        layout = QGridLayout()
        layout.addWidget(QLabel('Span Number:') , 0,0)
        layout.addWidget( self.spanNumber       , 0,1)
        layout.addWidget(self.plot_hist   , 1,0 , 1,2)
        self.histogFrame.setLayout( layout )

    def initMultiplotPeak(self):
        self.multi_plot   = pg.GraphicsLayoutWidget()
        self.samplingtime = QSpinBox()
        self.samplingtime.setRange(1, 60)
        self.samplingtime.setValue(5)
        self.dicmultiplot = {}
        self.samplingPtNbr  = QLabel()
        # ---  --- #
        self.plot_max = PeakPlot(name='plot_max', span=None, log=self.log)
        #self.dicmultiplot[self.plot_max.name] = [self.plot_max]
        # ---  --- #
        self.spanNumber.valueChanged.connect( self.updateMultiplots )
        self.samplingtime.valueChanged.connect( self.updatePtNbrLabel )
        self.camera_view.fps_input.valueChanged.connect( self.updatePtNbrLabel )
        # --- make layout --- #
        label_1 = QLabel('Sampling time (s): ')
        label_1.setWordWrap(True)
        label_2 = QLabel('(s) --> pts nbr:')
        label_2.setWordWrap(True)
        self.multiplotFrame = QFrame()
        self.multiplotFrame.setToolTip('Multiplot frame. Each plot correspond to the post-processed data sampling of the corresponding span number.')
        layout = QGridLayout()
        layout.addWidget(label_1 , 0,0)
        layout.addWidget(self.samplingtime                 , 0,1)
        layout.addWidget(label_2     , 0,2)
        layout.addWidget(self.samplingPtNbr                , 0,3)
        layout.addWidget(self.multi_plot                   , 1,0 , 1,4)
        self.multiplotFrame.setLayout( layout )

    def initLissajousPlot(self):
        self.lissajousFrame = QFrame()
        self.lissajousFrame.setToolTip('Lissajous plot. We plot the post processed intensities as expressed below the graph.')
        self.lissajousFrame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
        self.lissajousFrame.setLineWidth(3)
        self.lissajousFrame.setMidLineWidth(1)
        # ---  --- #
        self.plot_lissjs = pg.PlotWidget()
        self.plot_lissjs.setMinimumHeight(300)
        self.plot_lissjs.setMinimumWidth(300)
        self.plot_lissjs.showGrid(x=True, y=True)
        lissjs_viewbox   = self.plot_lissjs.getViewBox()
        self.data_lissjs = pg.ScatterPlotItem() #pg.PlotDataItem()
        #self.data_lissjs.setSymbol('o')
        self.plot_lissjs.addItem(self.data_lissjs)
        # ---  --- #
        lissjs_viewbox.setRange(xRange=(-1,+1), yRange=(-1,+1))
        self.plot_xaxis  = QComboBox()
        self.plot_yaxis  = QComboBox()
        self.makeLissajousAxisSelection()
        self.button_plot_lissajs = QPushButton('Plot')
        self.button_plot_lissajs.setCheckable(True)
        # --- make layout --- #
        label_equation = QLabel('-    i = (I/I_tot)\n- x,y = [i-(i_Max+i_min)/2]/(i_Max-i_min)/2')
        label_equation.setWordWrap(True)
        layout = QGridLayout()
        layout.addWidget( QLabel('X axis') , 0,1)
        layout.addWidget( QLabel('Y axis') , 0,2)
        layout.addWidget( QLabel('Plot : '), 1,0)
        layout.addWidget( self.plot_xaxis  , 1,1)
        layout.addWidget( self.plot_yaxis  , 1,2)
        layout.addWidget( self.button_plot_lissajs, 2,0 , 1,3)
        layout.addWidget( self.plot_lissjs , 3,0 , 1,3)
        layout.addWidget( QHLine()         , 4,0 , 1,3)
        layout.addWidget( label_equation   , 5,0 , 1,3)
        self.lissajousFrame.setLayout( layout )

    def setNewSaveFile(self):
        # --- stop timers to avoid over load --- #
        wasOn = self.camera_view.isOn
        self.camera_view.stop_continuous_view()
        # ---  --- #
        filename = self.savefile.getSaveFileName(None)
        if filename == '':
            # --- restart processes --- #
            if wasOn:
                self.camera_view.start_continuous_view()
            return False
        dir_path = self.savefile.directory().absolutePath()
        os.chdir(dir_path)
        self.savefile_name.setText( filename[0] )
        # --- restart processes --- #
        if wasOn:
            self.camera_view.start_continuous_view()
        # ---  --- #
        return True

    def saveDataFromMultiplot(self):
        # --- stop timers to avoid over load --- #  # we stop the process because it does not work while timer is running
        wasOn = self.camera_view.isOn
        self.camera_view.stop_continuous_view()
        # --- check if there are spans --- #
        if self.spanNumber.value() == 0:
            # --- restart processes --- #
            if wasOn:
                self.camera_view.start_continuous_view()
            return None
        # --- set file name --- #
        hasWorked = self.setNewSaveFile()
        if not hasWorked:
            # --- restart processes --- #
            if wasOn:
                self.camera_view.start_continuous_view()
            return None
        # ---  open file save --- #
        filename = self.savefile_name.text()
        try:
            f = open(filename, 'w+')
        except:
            err_msg  = 'Error: in saveDataFromMultiplot, not able to open file.'
            err_msg += '\nFilename is: {}'.format(filename)
            self.log.addText( err_msg )
        # --- make save --- #
        min_len   = np.inf
        data_list = []
        for key in self.dicmultiplot: # adding data from each plot in multiplot view
            data_list.append( self.dicmultiplot[key][0].plot.yData )
            min_len = np.min( [min_len, len(data_list[-1])] )
        # --- adding the data fot the total intensity --- #
        data_list.append( self.plot_max.plot.yData )
        min_len = np.min( [min_len, len(data_list[-1])] )
        # ---  --- #
        min_len = int(min_len)
        if len(data_list) == 0:
            return None
        # --- equalise the length of the data, from the end since it is the most recent one --- # 
        for i in range(len(data_list)):
            data_list[i] = data_list[i][-min_len:]
        data_list = np.array(data_list)
        # ---  --- #
        np.savetxt(filename, data_list)
        # --- restart processes --- #
        if wasOn:
            self.camera_view.start_continuous_view()

    def makeLissajousAxisSelection(self):
        nx = self.plot_xaxis.count()
        ny = self.plot_yaxis.count()
        if nx != ny:
            self.log.addText('Error in makeLissajousAxisSelection, the two axis combobox do not have the same item number.')
            return None
        for i in reversed(range(nx)):
            self.plot_xaxis.removeItem(i)
            self.plot_yaxis.removeItem(i)
        for i in range(self.spanNumber.value()):
            self.plot_xaxis.addItem(str(i+1))
            self.plot_yaxis.addItem(str(i+1))

    def toggleLissajousPlot(self):
        if   self.button_plot_lissajs.isChecked():
            self.doLissajous = True
        else:
            self.doLissajous = False

    def addSpan(self):
        N = len( list(self.dicspan.keys()) )
        newspan = SpanObject(name='span_{}'.format(N+1), pos_init=N*50 +1, log=self.log)
        self.dicspan[N+1] = newspan
        # ---  --- #
        self.plot_hist.addItem( newspan.span )
        # --- add label --- #
        #newspan.label.setParentItem(self.plot_hist.getViewBox())
        self.plot_hist.addItem( newspan.label )
        # ---  --- #
        self.addPlot(newspan)

    def removeSpan(self):
        N = len( list(self.dicspan.keys()) )
        if N == 0:
            return None
        # ---  --- #
        self.removePlot( self.dicspan[N] )
        # ---  --- #
        self.plot_hist.removeItem( self.dicspan[N].span )
        self.plot_hist.removeItem( self.dicspan[N].label )
        self.dicspan[N].setParent(None)
        del self.dicspan[N]

    def makeSpans(self):
        n = self.spanNumber.value()
        n_current = len(list(self.dicspan.keys()))
        if   n == n_current:
            pass
        elif n > n_current:
            for i in range(n-n_current):
                self.addSpan()
        elif n < n_current:
            for i in range(n_current-n):
                self.removeSpan()
        self.makeLissajousAxisSelection()
        self.setSameYAxisMultiplots()

    def addPlot(self, span):
        N = len( list(self.dicspan.keys()) )
        newplot = PeakPlot(name='plot_{}'.format(span.name), span=span, log=self.log)
        self.dicmultiplot[newplot.name] = [newplot]
        # ---  --- #
        span.span.sigRegionChangeFinished.connect( self.updateMultiplots )
        # ---  --- #
        self.multi_plot.nextRow()
        self.multi_plot.addItem( newplot )
        # ---  --- #
        label_item = self.multi_plot.addLabel(newplot.name[10:], angle = -90)
        self.dicmultiplot[newplot.name].append( label_item )

    def removePlot(self, span):
        '''
        In dictionary of plots, we retreive the plot list [plot_item, label_item], associated
        to the span region.
        '''
        N = len( list(self.dicspan.keys()) )
        plot_to_remove = self.dicmultiplot['plot_{}'.format(span.name)]
        # ---  remove plot --- #
        self.multi_plot.removeItem( plot_to_remove[0] )
        plot_to_remove[0].setParent(None)
        # --- remove label --- #
        self.multi_plot.removeItem( plot_to_remove[1] )
        # ---  --- #
        del self.dicmultiplot['plot_{}'.format(span.name)]
        span.setAssigned(False)

    def setSameYAxisMultiplots(self):
        key_init = list(self.dicmultiplot.keys())[0]
        common_viewBox = self.dicmultiplot[key_init][0].getViewBox()
        common_viewBox.enableAutoRange(pg.ViewBox.YAxis, enable=True)
        for key in self.dicmultiplot:
            plot = self.dicmultiplot[key][0]
            plot.setYLink(common_viewBox)

    def setLinkToCameraTimer(self):
        if   self.histrealtime.checkState() == 0:
            self.camera_view.timer.timeout.disconnect(self.updatePlotHistogram)
        elif self.histrealtime.checkState() == 2:
            self.camera_view.timer.timeout.connect(self.updatePlotHistogram)

    def addDataToFile(self):
        # --- stop timers to avoid over load --- #
        wasOn = self.isOn
        self.stop_continuous_view()
        self.fittingtimer.stop()
        # ---  --- #
        f = open(self.savefile_name.text(), 'a')
        coretxt  = ''
        coretxt += '\n'
        if   self.peakcount == 0:
            pass
        elif self.peakcount == 1:
            coretxt += self.sepration+str(self.param_peak[0][1])
        else:
            for i in range(self.peakcount):
                coretxt += self.sepration+str(self.param_peak[i,1])
        f.write(coretxt)
        f.close()
        # --- restart processes --- #
        if self.fittingactivate.checkState() != 0:
            self.fittingtimer.start()
        if wasOn:
            self.start_continuous_view()

    def acceptOrNot(self, i):
        if type(self.data_hist.xData)==type(None):
            self.fittingactivate.setCheckState(0)
        return None

    def changeMode(self):
        ind = self.modeselect.currentIndex()
        if   ind == 0:
            self.gaussianfit.setMode('all')
        elif ind == 1:
            self.gaussianfit.setMode('pbp')

    def setPostProcessFunction(self):
        ind = self.postprocss.currentIndex()
        if   ind == 0:
            func = eval("lambda x_data: np.max(x_data)")
            self.postprocss_func = func
        elif ind == 1:
            func = eval("lambda x_data: np.sum(x_data)")
            self.postprocss_func = func
        return None

    def setFittingRate(self):
        self.fittingtimer.setInterval(1e3/self.frqcyfitting.value())

    def setHistgrmPlotRange(self):
        ind = self.histogram_data.currentIndex()
        if   ind == 0 or ind == 1:
            self.plot_hist.getViewBox().enableAutoRange(pg.ViewBox.XAxis, enable=True)
        elif ind == 2:
            self.plot_hist.setXRange(0, 1.)

    def postProcessLissajous(self, xy_data):
        Max_ = np.max(xy_data)
        min_ = np.min(xy_data)
        A    = (Max_ + min_)*0.5
        B    = (Max_ - min_)*0.5
        return (xy_data-A)/B

    def updatePlotHistogram(self):
        frame = self.camera_view.frame
        if type(frame) == type(None):
            return None
        # --- mode data --- #
        ind = self.histogram_data.currentIndex()
        if   ind == 0: # raw
            ydata = np.sum(frame,axis=0)/frame.shape[0]
        elif ind == 1: # remove background
            frame = frame-np.mean(frame)
            ydata = np.sum(frame,axis=0)/frame.shape[0]
            ydata = ydata + np.abs(np.min([0, np.min(ydata)]))
        elif ind == 2: # normalise
            ydata = np.sum(frame,axis=0)/frame.shape[0]
            ydata = ydata/np.max(ydata)
        # --- plot data --- #
        self.data_hist.setData( ydata, np.arange(len(ydata)) )
        # ---  --- #
        self.updateMultiplots()

    def updateMultiplots(self):
        if type(self.postprocss_func) == type(None):
            self.setPostProcessFunction()
        # ---  --- #
        sum_max_peak = 0
        data = self.data_hist.xData
        for key in self.dicmultiplot:
            plot = self.dicmultiplot[key][0]
            plot.setLengthMax( int(self.samplingtime.value()*self.camera_view.fps) )
            # ---  --- #
            region = plot.span.span.getRegion()
            m , M  = int(np.min(region)), int(np.max(region))
            err_msg  = ''
            cond_1 = type(data) != type(None) and m != M
            cond_2 = len(data) >= m or len(data) >= M
            cond_3 = len(data[m:M]) != 0
            if cond_1 and cond_2 and cond_3:
                try:
                    new_val = self.postprocss_func(data[m:M])# np.max(data[m:M])
                    plot.addDataElement( new_val )
                except:
                    err_msg += 'Error: in updatePlot for object PeakPlot: '+plot.name
                    err_msg += '\nIssue with: self.addDataElement( np.max(self.data[m:M]) ),'
                    err_msg += '\nsample: {}'.format(self.data[m:M])
            else:
                err_msg += '\nOne of the following condition is unsatisfied:\n \
                type(data) != type(None) and m != M: {0}\n \
                len(data) >= m or len(data) >= M: {1}\n \
                len(data[m:M]) != 0: {2}'.format(cond_1,cond_2,cond_3)
                self.log.addText( err_msg )
            # ---  --- #
            sum_max_peak += plot.peakdata[-1]
        self.plot_max.setLengthMax( int(self.samplingtime.value()*self.camera_view.fps) )
        self.plot_max.addDataElement(sum_max_peak)
        # ---  --- #
        if self.button_plot_lissajs.isChecked():# if self.doLissajous:
            self.updateLissajousPlot()

    def updateLissajousPlot(self):
        xaxis_ind = self.plot_xaxis.currentText()
        yaxis_ind = self.plot_yaxis.currentText()
        try:
            xplot     = self.dicmultiplot['plot_span_{}'.format(xaxis_ind)][0]
            yplot     = self.dicmultiplot['plot_span_{}'.format(yaxis_ind)][0]
        except:
            if self.feedback:
                err_msg  = 'Error: in updateLissajousPlot. Wrong key for dicmultiplot.'
                self.log.addText( err_msg )
            return None
        xdata     = xplot.plot.yData
        ydata     = yplot.plot.yData
        max_data  = self.plot_max.plot.yData
        if type(xdata) != type(None) and type(ydata) != type(None):
            n = np.min( [len(xdata), len(ydata), len(max_data)] )
            xdata = xdata[-n:]/max_data[-n:]
            ydata = ydata[-n:]/max_data[-n:]
            xdata = self.postProcessLissajous(xdata)
            ydata = self.postProcessLissajous(ydata)
            self.data_lissjs.setData( xdata, ydata )

    def updatePtNbrLabel(self):
        self.samplingPtNbr.setText( str(self.samplingtime.value()*self.camera_view.fps) )
class Preview(QWidget):
    def __init__(self, camera=None, log=None, fps=10.):
        super().__init__()
        # ---  --- #
        if log != None:
            self.log = log
        else:
            self.log = LogDisplay()
        #self.log.show()
        # --- default --- #
        self.fps = fps
        self.normalise_hist = True
        self.cmap = 'jet'
        # --- main attriute --- #
        self.camera = camera
        if self.camera == None:
            dir_path = '/home/cgou/ENS/STAGE/M2--stage/Camera_acquisition/Miscellaneous/Camera_views/'
            self.camera = Camera(cam_id=0, log=self.log)
            if not self.camera.isCameraInit:
                self.camera = SimuCamera(0,
                                         directory_path=dir_path,
                                         log=self.log)
                self.camera.__info__()
        elif not self.camera.isCameraInit:
            self.camera.__init__(cam_id=0, log=self.log)
        # ---  --- #
        self.contview = ContinuousView(fps=self.fps)
        self.timer = pg.QtCore.QTimer()  #QTimer()# pg.QtCore.QTimer()
        self.qlabl_max = QLabel()
        self.isOn = False
        # ---  --- #
        self.initUI()
        # ---  --- #
        self.camera.setExposure(self.image_widget.exposure.value())

    def initUI(self):
        # ---  --- #
        self.layout = QVBoxLayout(self)
        self.initView()
        # --- button widget --- #
        self.button_acquire = QPushButton('Acquire frame')
        self.button_acquire.setStyleSheet("background-color: orange")
        self.button_acq_movie = QPushButton('Acquire movie')
        self.button_acq_movie.setStyleSheet("background-color: orange")
        # ---  --- #
        self.movie_frameNbre = QSpinBox()
        self.movie_frameNbre.setMinimum(0)
        self.movie_frameNbre.setMaximum(1000)
        self.movie_frameNbre.setValue(200)
        self.dir_save = QFileDialog()
        self.dir_save_label = QLabel('No file selected')
        self.dir_save_label.setWordWrap(True)
        self.name_save = QLineEdit()
        self.name_save.setText('img_data')
        self.progressbar = QProgressBar()
        self.progressbar.setValue(0)
        self.format_save = QComboBox()
        self.format_save.addItem('tif')
        self.format_save.addItem('tiff')
        self.format_save.addItem('png')
        self.format_save.addItem('jpg')
        self.histogram_mode = QComboBox()
        self.histogram_mode.addItem('Normalise')
        self.histogram_mode.addItem('Raw')
        # --- connections --- #
        self.button_acquire.clicked.connect(self.acquireFrame)
        self.button_acq_movie.clicked.connect(self.acquireMovie)
        self.histogram_mode.currentIndexChanged.connect(self.setHistogramMode)
        # --- layout --- #
        self.layout.addWidget(self.view_layout)
        grid = QGridLayout()
        grid.addWidget(self.histogram_mode, 0, 0)
        grid.addWidget(self.button_acquire, 0, 1, 1, 2)
        grid.addWidget(QLabel('Number of frame'), 1, 0)
        grid.addWidget(self.movie_frameNbre, 1, 1)
        grid.addWidget(self.button_acq_movie, 1, 2)
        grid.addWidget(QLabel('Filemane prefix:'), 2, 0)
        grid.addWidget(self.name_save, 2, 1)
        grid.addWidget(self.format_save, 2, 2)
        grid.addWidget(QLabel('Directory:'), 3, 0)
        grid.addWidget(self.dir_save_label, 3, 1, 1, 2)
        grid.addWidget(self.progressbar, 4, 0, 1, 3)
        self.layout.addLayout(grid)
        self.setLayout(self.layout)
        # ---  --- #
        self.update_timer = QTimer()

    def initView(self):
        self.image_widget = CameraDisplay(camera=self.camera, log=self.log)
        self.image_view = self.image_widget.image_view
        # --- histogram --- #
        self.hist_layWidget = pg.GraphicsLayoutWidget()
        self.plot_hist = self.hist_layWidget.addPlot()
        self.plot_hist.setXLink(self.image_view.getView())
        if self.normalise_hist:
            self.plot_hist.setYRange(0, 1)
        else:
            self.plot_hist.enableAutoRange(y=True)
        # ---  --- #
        self.data_hist = pg.PlotDataItem()
        self.plot_hist.addItem(self.data_hist)
        # --- link histogram to image view --- #
        self.image_widget.frame_updated.connect(self.updatePlotHistogram)
        # ---  --- #
        self.image_view.setLevels(0, 255)
        self.image_view.getHistogramWidget().item.setHistogramRange(
            0, 255)  #not working when update
        self.image_view.ui.roiBtn.hide()
        self.image_view.ui.menuBtn.hide()
        # ---  --- #
        self.image_view.setMinimumWidth(400)
        self.image_view.setMinimumHeight(200)
        self.hist_layWidget.setMinimumWidth(400)
        self.hist_layWidget.setMinimumHeight(100)
        # ---  --- #
        self.view_layout = QSplitter(PyQt5.QtCore.Qt.Vertical)
        self.view_layout.addWidget(self.image_widget)
        self.view_layout.addWidget(self.hist_layWidget)

    def setFPS(self):
        self.fps = self.fps_input.value()
        self.contview.setFPS(self.fps)
        self.timer.setInterval(1e3 / self.fps)

    def setHistogramMode(self, indx):
        val = self.histogram_mode.currentText()
        if val == 'Normalise':
            self.normalise_hist = True
            self.plot_hist.setYRange(0, 1)
        elif val == 'Raw':
            self.normalise_hist = False
            self.plot_hist.enableAutoRange(y=True)

    def nextFrame(self):
        wasOn = self.isOn
        if not self.isOn:
            self.camera.capture_video()
        # ---  --- #
        self.update_image()
        # ---  --- #
        if not wasOn:
            self.camera.capture_video()

    def updatePlotHistogram(self):
        frame = self.image_widget.frame
        if self.normalise_hist:
            bckgrnd = np.mean(frame)
            frame = frame - bckgrnd
        ydata = np.sum(frame, axis=0) / frame.shape[0]
        if self.normalise_hist:
            ydata -= np.min([0, np.min(ydata)])
            ydata = ydata / np.max(ydata)
        self.data_hist.setData(ydata)

    def update_slider(self):
        self.exposure_slider.setValue(self.exposure_spinb.value() * 100)
        self.update_exposure()

    def update_spinbox(self):
        self.exposure_spinb.setValue(float(self.exposure_slider.value()) / 100)
        self.update_exposure()

    def update_exposure(self):
        exp_val = self.exposure_spinb.value()
        self.camera.setExposure(exp_val)

    def acquireFrame(self):
        wasOn = self.isOn
        if self.isOn:
            self.startStop_continuous_view()
        # ---  --- #
        try:
            frame = self.camera.frame
            plt.imshow(frame, cmap=self.cmap)
            plt.show()
        except:
            pass
        # ---  --- #
        if wasOn:
            self.startStop_continuous_view()

    def acquireMovie(self):
        wasOn = self.isOn
        if self.isOn:
            self.startStop_continuous_view()
        # ---  --- #
        dir_save_path = self.dir_save.getExistingDirectory() + '/'
        self.dir_save_label.setText(dir_save_path)
        filename = self.name_save.text()
        format_ = self.format_save.currentText()
        # ---  --- #
        self.progressbar.setValue(0)
        self.progressbar.setMaximum(self.movie_frameNbre.value())
        # ---  --- #
        movie = self.camera.acquire_movie(self.movie_frameNbre.value())
        try:
            img_to_save = Image.fromarray(movie[0])
            img_to_save.save(dir_save_path + filename +
                             '_{0:03d}.{1}'.format(0, format_))
            self.progressbar.setValue(1)
        except:
            err_msg = 'Error: in acquireMovie. The filename is not accepted\nFilename: {0}\nDirectory path: {1}'.format(
                filename, dir_save_path)
            self.log.addText(err_msg)
            return None
        for i in range(1, len(movie)):
            img_to_save = Image.fromarray(movie[i])
            img_to_save.save(dir_save_path + filename +
                             '_{0:03d}.{1}'.format(i, format_))
            self.progressbar.setValue(i + 1)
        # ---  --- #
        if wasOn:
            self.startStop_continuous_view()
class DCMeasurement(QWidget):
    def __init__(self, camera=None, log=None, fps=10.):
        super().__init__()
        # ---  --- #
        if log != None:
            self.log = log
        else:
            self.log = LogDisplay()
        #self.log.showLog()
        # --- default --- #
        self.fps = fps
        self.cmap = 'jet'
        self.normalise = True
        self.sepration = ' '
        self.param_peak = [
        ]  # a N x 3 array where each line is [x0, a, b] the parameter of the fit, N being the number of peaks.
        self.measured_max = []
        self.power_peak = []
        self.fittingtimer = pg.QtCore.QTimer()
        # --- main attriute --- #
        self.camera = camera
        if not self.camera.isCameraInit:
            self.camera.__init__(cam_id=0, log=self.log)
        self.contview = ContinuousView(fps=self.fps)
        self.qlabl_max = QLabel()
        # ---  --- #
        self.initUI()

    def initUI(self):
        self.splitter = QSplitter(PyQt5.QtCore.Qt.Horizontal)
        self.fitting_frame = None
        # ---  --- #
        self.layout = QVBoxLayout(self)
        # --- guassian fit init --- #
        self.dic_spanfitting = {}
        self.gaussianfit = GaussianFit(log=self.log)
        self.gaussian_plots = {}
        self.fittingactivate = QCheckBox()
        self.fittingactivate.setTristate(False)
        self.fittingactivate.stateChanged.connect(self.acceptOrNot)
        self.fittingactivate.stateChanged.connect(
            self.fittingActivationDeactivation)
        # --- init frames --- #
        self.initView()
        self.initDisplayFittingFrame()
        self.initParameterFittingFrame()
        self.relativeHeightsLayout()
        # --- layout --- #
        self.splitter.addWidget(self.viewFrame)
        vsplitter = QSplitter(PyQt5.QtCore.Qt.Vertical)
        vsplitter.addWidget(self.fittingFrame)
        vsplitter.addWidget(self.paramfittingframe)
        vsplitter.addWidget(self.matrelatFrame)
        self.splitter.addWidget(vsplitter)
        self.layout.addWidget(self.splitter)
        self.setLayout(self.layout)
        # ---  --- #
        self.fittingtimer.start()
        self.setXdataPoints()
        self.setFittingMethod()

    def initView(self):
        self.camera_view = CameraDisplay(camera=self.camera, log=self.log)
        # ---  --- #
        self.camera_view.image_view.setMinimumWidth(600)
        self.camera_view.image_view.setMinimumHeight(200)
        # ---  --- #
        self.viewFrame = QFrame()
        self.viewFrame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
        self.viewFrame.setLineWidth(3)
        self.viewFrame.setMidLineWidth(1)
        layout = QVBoxLayout()
        layout.addWidget(self.camera_view)
        self.viewFrame.setLayout(layout)

    def initDisplayFittingFrame(self):
        self.plot_hist = pg.PlotWidget()
        self.plot_hist.setMinimumWidth(800)
        plot_viewbox = self.plot_hist.getViewBox()
        plot_viewbox.setAspectLocked(False)
        plot_viewbox.enableAutoRange(pg.ViewBox.XAxis, enable=True)
        # --- measured data --- #
        self.data_hist = pg.PlotDataItem()
        self.plot_hist.addItem(self.data_hist)
        # --- threshold line object --- #
        self.threshold = pg.InfiniteLine(pos=1., angle=0, movable=True)
        self.plot_hist.addItem(self.threshold)
        # --- widgets --- #
        self.normalise_hist = QComboBox()
        self.normalise_hist.addItem('raw')
        self.normalise_hist.addItem('normalise')
        self.normalise_hist.setCurrentIndex(0)
        self.nbrpeak = QSpinBox()
        self.nbrpeak.setRange(1, 20)
        self.nbrpeak.setValue(2)
        self.histrealtime = QCheckBox()
        self.histrealtime.setTristate(False)
        self.histrealtime.setCheckState(2)
        self.setLinkToCameraTimer()
        # --- connections --- #
        self.threshold.sigPositionChangeFinished.connect(self.updatePlots)
        self.normalise_hist.currentIndexChanged.connect(self.setModeFitting)
        self.nbrpeak.valueChanged.connect(self.updatePlots)
        self.histrealtime.stateChanged.connect(self.setLinkToCameraTimer)
        # --- default --- #
        self.setModeFitting()
        plot_viewbox.enableAutoRange(pg.ViewBox.YAxis, enable=True)
        if self.normalise:
            self.plot_hist.setYRange(0, 1)
        # --- make layout --- #
        self.fittingFrame = QFrame()
        vlayout = QVBoxLayout()
        hlayout = QHBoxLayout()
        hlayout.addWidget(QLabel('mode fitting:'))
        hlayout.addWidget(self.normalise_hist)
        hlayout.addWidget(QLabel('Number maximum of peaks:'))
        hlayout.addWidget(self.nbrpeak)
        hlayout.addWidget(QLabel('Continuous mode:'))
        hlayout.addWidget(self.histrealtime)
        vlayout.addLayout(hlayout)
        vlayout.addWidget(self.plot_hist)
        self.fittingFrame.setLayout(vlayout)

    def initParameterFittingFrame(self):
        # --- widgets --- #
        self.peakcount_lab = QLabel('#')
        self.modeselect = QComboBox()
        self.modeselect.addItem('all')
        self.modeselect.addItem('peak by peak')
        self.choosedirectory = QPushButton('&New file')
        self.savefile_name = QLabel('Select a file')
        self.savefile_name.setWordWrap(True)
        self.savefile = QFileDialog()
        self.button_addData = QPushButton('Add data')
        self.button_addData.setStyleSheet("background-color: green")
        self.button_makefit = QPushButton('Make fit')
        self.frqcyfitting = QSpinBox()
        self.frqcyfitting.setRange(1, 12)
        self.frqcyfitting.setValue(10)
        self.fitting_xdataNbre = QSpinBox()
        self.fitting_xdataNbre.setRange(100, 5e3)
        self.fitting_xdataNbre.setValue(10**3)
        self.fitting_param_dspl = QTableWidget()
        self.fitting_param_dspl.setRowCount(5)
        self.saving_state = QLabel('###')
        self.saving_state.setWordWrap(True)
        self.save_count = QSpinBox()
        self.save_count.setRange(0, 1e4)
        self.save_count.setValue(0)
        self.button_test = QPushButton('TEST')
        # ---  --- #
        self.fittingtimer.setInterval(self.frqcyfitting.value())
        # --- connections --- #
        self.modeselect.currentIndexChanged.connect(self.setFittingMethod)
        self.choosedirectory.clicked.connect(self.setNewSaveFile)
        self.button_addData.clicked.connect(self.addDataToFile)
        self.frqcyfitting.valueChanged.connect(self.setFittingRate)
        self.fittingtimer.timeout.connect(self.updatePlots)
        self.fitting_xdataNbre.valueChanged.connect(self.setXdataPoints)
        self.button_makefit.clicked.connect(self.singleShotFittingPlot)
        self.nbrpeak.valueChanged.connect(self.initPeakByPeakFitting)
        # --- make layout --- #
        self.paramfittingframe = QFrame()
        self.param_grid = QGridLayout()
        self.param_grid.addWidget(QLabel('Activate fitting '), 0, 0)
        self.param_grid.addWidget(self.fittingactivate, 0, 1)
        self.param_grid.addWidget(QLabel('Fitting rate:'), 0, 2)
        self.param_grid.addWidget(self.frqcyfitting, 0, 3)
        self.param_grid.addWidget(QLabel('Fitting Sampling number'), 0, 4)
        self.param_grid.addWidget(self.fitting_xdataNbre, 0, 5)
        self.param_grid.addWidget(QLabel('Peak nbr:'), 1, 0)
        self.param_grid.addWidget(self.peakcount_lab, 1, 1)
        self.param_grid.addWidget(QLabel('Fitting method:'), 1, 2)
        self.param_grid.addWidget(self.modeselect, 1, 3)
        self.param_grid.addWidget(self.button_makefit, 1, 4, 1, 2)
        self.param_grid.addWidget(self.fitting_param_dspl, 2, 0, 4, 6)
        self.param_grid.setRowMinimumHeight(2, 35)
        self.param_grid.setRowMinimumHeight(3, 35)
        self.param_grid.setRowMinimumHeight(4, 35)
        self.param_grid.setRowMinimumHeight(5, 35)
        self.updateParamLayout()
        self.param_grid.addWidget(self.choosedirectory, 6, 0)
        self.param_grid.addWidget(self.savefile_name, 6, 1, 1, 6)
        self.param_grid.addWidget(self.button_addData, 7, 0, 1, 4)
        self.param_grid.addWidget(self.saving_state, 7, 4)
        self.param_grid.addWidget(self.save_count, 7, 5)
        for i in range(self.param_grid.rowCount() + 1):
            self.param_grid.setRowStretch(i, 1)
        # ---  --- #
        self.paramfittingframe.setLayout(self.param_grid)

    def initPeakByPeakFitting(self):
        if self.modeselect.currentIndex() != 1:
            return None
        # --- reset spans --- #
        self.removeAllSpans()
        # ---  --- #
        for i in range(self.nbrpeak.value()):
            newspan = SpanObject(name='span_{}'.format(i),
                                 orientation='vertical',
                                 log=self.log,
                                 pos_init=i * 50 + 1)
            newspan.span.sigRegionChangeFinished.connect(self.updatePowerPeak)
            self.dic_spanfitting[newspan.name] = newspan
            self.plot_hist.addItem(newspan.span)
            self.plot_hist.addItem(newspan.label)
        self.gaussianfit.setSpanDictionary(self.dic_spanfitting)
        # ---  --- #
        self.power_peak = np.zeros(self.nbrpeak.value())

    def initPowerEstimation(self):
        if self.modeselect.currentIndex() != 2:
            return None
        # --- reset spans --- #
        self.removeAllSpans()
        # ---  --- #
        for i in range(self.nbrpeak.value()):
            newspan = SpanObject(name='span_{}'.format(i),
                                 orientation='vertical',
                                 log=self.log,
                                 pos_init=i * 50 + 1)
            newspan.span.sigRegionChangeFinished.connect(self.updatePowerPeak)
            self.dic_spanfitting[newspan.name] = newspan
            self.plot_hist.addItem(newspan.span)
        # ---  --- #
        self.power_peak = np.zeros(self.nbrpeak.value())

    def removeAllSpans(self):
        KEYS = list(self.dic_spanfitting.keys())
        for key in KEYS:
            self.plot_hist.removeItem(self.dic_spanfitting[key].span)
            self.dic_spanfitting[key].setParent(None)
            del self.dic_spanfitting[key]

    def removeAllLabels(self):
        KEYS = list(self.dic_spanfitting.keys())
        for key in KEYS:
            self.plot_hist.removeItem(self.dic_spanfitting[key].label)

    def addDataToFile(self):
        # --- stop timers to avoid over load --- #
        wasOn = self.camera_view.isOn
        self.camera_view.stop_continuous_view()
        self.fittingtimer.stop()
        # ---  --- #
        self.saving_state.setText('Saving ...')
        self.button_addData.setStyleSheet("background-color: red")
        time.sleep(.5)
        self.img_count = self.save_count.value()
        # ---  --- #
        if len(self.param_peak) == 0:
            # --- restart processes --- #
            if self.fittingactivate.checkState() != 0:
                self.fittingtimer.start()
            if wasOn:
                self.camera_view.start_continuous_view()
            # ---  --- #
            self.saving_state.setText('Save unsuccessful: param of length 0.')
            return None
        elif len(self.power_peak) == 0:
            self.power_peak = np.zeros(self.nbrpeak.value())
        # ---  --- #
        N = self.param_peak.shape[0]
        try:
            open(self.savefile_name.text(), 'r')
        except:
            f = open(self.savefile_name.text(), 'a')
            header = ''
            for i in range(N):
                header += 'Peak_measured_{}'.format(i) + self.sepration
                header += 'Peak_power_{}'.format(i) + self.sepration
                header += 'Peak_fitted_{}'.format(i) + self.sepration
            f.write(header)
            f.close()
            self.log.addText('Creating new file')
        # ---  --- #
        f = open(self.savefile_name.text(), 'a')
        coretxt = ''
        coretxt += '\n'
        for i in range(N):
            coretxt += str(self.measured_max[i]) + self.sepration
            coretxt += str(self.power_peak[i]) + self.sepration
            coretxt += str(self.param_peak[i, 1]) + self.sepration
        f.write(coretxt)
        f.close()
        # --- save coresponding frame as tif --- #
        img_to_save = Image.fromarray(self.camera_view.frame)
        if self.savefile_name.text()[:-4] == '.':
            img_to_save.save(self.savefile_name.text()[:-4] +
                             '_{0:03d}.tif'.format(self.img_count))
        else:
            img_to_save.save(self.savefile_name.text() +
                             '_{0:03d}.tif'.format(self.img_count))
        self.img_count += 1
        # ---  --- #
        self.saving_state.setText('Saved No:')
        self.save_count.setValue(self.img_count)
        self.button_addData.setStyleSheet("background-color: green")
        time.sleep(0.5)
        # --- restart processes --- #
        if self.fittingactivate.checkState() != 0:
            self.fittingtimer.start()
        if wasOn:
            self.camera_view.start_continuous_view()

    def acceptOrNot(self, i):
        if type(self.data_hist.xData) == type(None):
            self.fittingactivate.setCheckState(0)
        # ---  --- #
        self.setXdataPoints()

    def clearGaussianFits(self):
        KEYS = list(self.gaussian_plots.keys())
        for i in reversed(range(len(KEYS))):
            key = KEYS[i]
            #self.plot_hist.removeItem(self.gaussian_plots[key])
            self.gaussian_plots[key].clear()
            del self.gaussian_plots[key]
            #self.log.addText('Deleting plot: {}'.format(key))
        self.gaussian_plots = {}

    def clearLayout(self, layout):
        for i in reversed(range(layout.count())):
            item = layout.itemAt(i)
            layout.removeItem(item)

    def fittingActivationDeactivation(self, i):
        if self.fittingactivate.checkState() == 0:
            self.fittingtimer.stop()
        elif self.fittingactivate.checkState() == 2:
            self.fittingtimer.start()

    def getParamFit(self):
        # --- fitting --- #
        self.gaussianfit.setXData(self.data_hist.xData)
        self.gaussianfit.setYData(self.data_hist.yData)
        self.gaussianfit.setPeakNumber(self.peakcount)
        self.gaussianfit.setCenters(self.param_peak[:, 0])
        self.gaussianfit.setAmplitudes(self.param_peak[:, 1])
        self.gaussianfit.setSTD(self.param_peak[:, 2])
        # ---  --- #
        self.gaussianfit.makeGaussianFit()
        # ---  --- #
        self.param_peak = self.gaussianfit.param
        if self.modeselect.currentIndex() == 1:
            self.measured_max = self.gaussianfit.maximums

    def quickPeakCount(self):
        xdata = self.data_hist.xData
        ydata = self.data_hist.yData
        if type(xdata) == type(None) or type(ydata) == type(None):
            self.peakcount = 0
            return None
        # --- threshold value --- #
        threshold = self.threshold.value()
        self.gaussianfit.setThreshold(self.threshold.value())
        truth_list = (np.abs(ydata - threshold) +
                      (ydata - threshold)).astype(bool)
        # ---  --- #
        block_list = [[i for i, value in it] for key, it in itertools.groupby(
            enumerate(truth_list), key=operator.itemgetter(1)) if key != 0]
        self.peakcount = len(block_list)
        self.param_peak = np.ones([self.peakcount, 3])
        self.measured_max = np.ones(self.peakcount)
        for i in range(self.peakcount):
            x0 = (block_list[i][-1] + block_list[i][0]) / 2.
            a = np.max(ydata[block_list[i]])
            b = (block_list[i][-1] - block_list[i][0]) / 2.
            if b != 0: b = b**-1
            self.param_peak[i] = [x0, a, b]
            self.measured_max[i] = a
        # ---  --- #
        self.peakcount_lab.setText(str(self.peakcount))
        self.updateParamLayout()

    def plotGaussianFit(self):
        # ---  remove old gaussian plots --- #
        self.clearGaussianFits()
        # ---  --- #
        for key in self.gaussianfit.dic_gauss:
            x = self.xdataFit  #self.data_hist.xData
            y_fit = self.gaussianfit.gaussian(x,
                                              *self.gaussianfit.dic_gauss[key])
            self.gaussian_plots[key] = pg.PlotCurveItem(x=x, y=y_fit, pen='r')
        # ---  --- #
        for key in self.gaussian_plots:
            self.plot_hist.addItem(self.gaussian_plots[key])

    def relativeHeightsLayout(self):
        self.matrelatFrame = QFrame()
        self.matrelat_layout = QGridLayout()
        self.matrelat_layout.setColumnMinimumWidth(0, 200)
        n = 0
        self.matrelat = np.zeros([n, n])
        numbering = np.arange(n)
        # --- with qtable --- #
        self.table = QTableWidget()
        self.matrelat_layout.addWidget(self.table)
        # ---  --- #
        self.matrelatFrame.setLayout(self.matrelat_layout)

    def removeAllSpans(self):
        KEYS = list(self.dic_spanfitting.keys())
        for key in KEYS:
            span_to_remove = self.dic_spanfitting[key]
            self.plot_hist.removeItem(span_to_remove.span)
            del self.dic_spanfitting[key]

    def setNewSaveFile(self):
        # --- stop timers to avoid over load --- #
        wasOn = self.camera_view.isOn
        self.camera_view.stop_continuous_view()
        self.fittingtimer.stop()
        # ---  --- #
        filename = self.savefile.getSaveFileName()
        dir_path = self.savefile.directory().absolutePath()
        os.chdir(dir_path)
        self.savefile_name.setText(filename[0])
        # --- set image count --- #
        self.img_count = 0
        # --- restart processes --- #
        if self.fittingactivate.checkState() != 0:
            self.fittingtimer.start()
        if wasOn:
            self.camera_view.start_continuous_view()

    def setXdataPoints(self):
        if type(self.data_hist.xData) != type(None):
            self.xdataFit = np.linspace(np.min(self.data_hist.xData),
                                        np.max(self.data_hist.xData),
                                        self.fitting_xdataNbre.value())
        else:
            self.xdataFit = np.linspace(0, 100, self.fitting_xdataNbre.value())

    def setFittingMethod(self):
        ind = self.modeselect.currentIndex()
        if ind == 0:
            self.gaussianfit.setMode('all')
            self.button_makefit.setEnabled(False)
            self.button_makefit.setFlat(True)
            self.fittingactivate.setCheckState(2)
            self.removeAllLabels()
            self.removeAllSpans()
            self.power_peak = []
        elif ind == 1:
            self.gaussianfit.setMode('pbp')
            self.button_makefit.setEnabled(True)
            self.button_makefit.setFlat(False)
            self.fittingactivate.setCheckState(0)
            self.initPeakByPeakFitting()
            self.initPowerEstimation()

    def setFPS(self):
        self.fps = self.fps_input.value()
        self.contview.setFPS(self.fps)
        self.timer.setInterval(1e3 / self.fps)

    def setFittingRate(self):
        self.fittingtimer.setInterval(1e3 / self.frqcyfitting.value())

    def setModeFitting(self):
        ind = self.normalise_hist.currentIndex()
        if ind == 0:
            self.normalise = False
            self.gaussianfit.setMaxAmp(255)
            #self.plot_hist.setYRange(0, 255)
            self.plot_hist.getViewBox().enableAutoRange(pg.ViewBox.YAxis,
                                                        enable=True)
            #self.threshold.setValue(255)
        elif ind == 1:
            self.normalise = True
            self.gaussianfit.setMaxAmp(1.)
            self.plot_hist.setYRange(0, 1.)
            self.threshold.setValue(1.)
        # ---  --- #
        self.updatePlots()

    def setLinkToCameraTimer(self):
        if self.histrealtime.checkState() == 0:
            self.camera_view.frame_updated.disconnect(self.updatePlotHistogram)
        elif self.histrealtime.checkState() == 2:
            self.camera_view.frame_updated.connect(self.updatePlotHistogram)

    def singleShotFittingPlot(self):
        self.quickPeakCount()
        self.getParamFit()
        self.plotGaussianFit()
        self.updatePowerPeak()
        self.updateParamLayout()
        self.updateRelativeHeightMatrix()

    def updatePowerPeak(self):
        if len(self.power_peak) != len(list(self.dic_spanfitting.keys())):
            self.log.addText(
                'Error: in updatePowerPeak, length of power_peak list and number of span do not coincide.'
            )
            return None
        # ---  --- #
        for i in range(len(self.power_peak)):
            region = self.dic_spanfitting['span_{}'.format(i)].span.getRegion()
            m, M = int(np.min(region)), int(np.max(region))
            self.power_peak[i] = np.sum(self.data_hist.yData[m:M])
        # ---  --- #
        self.updateParamLayout()

    def updateParamLayout(self):
        try:
            n = self.param_peak.shape[0]
        except:
            self.log.addText('self.peakcount most likely not defined yet')
            return None
        self.fitting_param_dspl.clear()
        self.fitting_param_dspl.setItem(0, 0,
                                        QTableWidgetItem('Peak position'))
        self.fitting_param_dspl.setItem(1, 0,
                                        QTableWidgetItem('Peak amplitudes'))
        self.fitting_param_dspl.setItem(2, 0, QTableWidgetItem('STD'))
        self.fitting_param_dspl.setItem(3, 0, QTableWidgetItem('Measured max'))
        self.fitting_param_dspl.setItem(4, 0, QTableWidgetItem('Peak power'))
        self.fitting_param_dspl.setColumnCount(n + 1)
        # ---  --- #
        if not n >= 1:
            return None
        for i in range(n):
            try:
                self.fitting_param_dspl.setItem(
                    0, i + 1,
                    QTableWidgetItem('{:.3f}'.format(self.param_peak[i, 0])))
                self.fitting_param_dspl.setItem(
                    1, i + 1,
                    QTableWidgetItem('{:.3f}'.format(self.param_peak[i, 1])))
                self.fitting_param_dspl.setItem(
                    2, i + 1,
                    QTableWidgetItem('{:.3f}'.format(self.param_peak[i, 2])))
                self.fitting_param_dspl.setItem(
                    3, i + 1,
                    QTableWidgetItem('{:.3f}'.format(self.measured_max[i])))
                if len(self.power_peak) != 0:
                    self.fitting_param_dspl.setItem(
                        4, i + 1,
                        QTableWidgetItem('{:.3f}'.format(self.power_peak[i])))
            except:
                pass

    def updateRelativeHeightMatrix(self):
        n = self.param_peak.shape[0]
        self.table.clear()
        self.table.setRowCount(n)
        self.table.setColumnCount(n)
        self.matrelat = np.zeros([n, n])
        # ---  --- #
        if not n >= 1:
            return None
        for i in range(n):
            for j in range(n):
                try:
                    self.table.setItem(
                        i, j,
                        QTableWidgetItem(
                            str(
                                round(
                                    self.param_peak[i, 1] /
                                    self.param_peak[j, 1], 3))))
                except:
                    pass
        #self.matrelat_layout.setRowMinimumHeight(0, 35*n)

    def updatePlots(self):
        self.quickPeakCount()
        if self.peakcount > self.nbrpeak.value():
            return None
        if self.fittingactivate.checkState() == 2:
            self.getParamFit()
            self.plotGaussianFit()
            self.updateParamLayout()
            self.updateRelativeHeightMatrix()

    def updatePlotHistogram(self):
        frame = self.camera_view.frame
        if type(frame) == type(None):
            return None
        # ---  --- #
        # ---  --- #
        ydata = np.sum(frame, axis=0) / frame.shape[0]
        if self.normalise:
            try:
                ydata = ydata / np.max(ydata)
            except:
                err_msg = 'Error: in updatePlotHistogram. ydata/np.max(ydata) does not work.'
                err_msg += '\nydata max: {}'.format(np.max(ydata))
                self.log.addText(err_msg)
        # --- plot data --- #
        self.data_hist.setData(ydata)
class CameraDisplay(QWidget):
    frame_updated = pyqtSignal()
    # ~~~~~~~~~~~~~~~~~~~~~~~~ #
    def __init__(self, camera=None, log=None, fps=10.):
        super().__init__()
        # ---  --- #
        if log != None:
            self.log = log
        else:
            self.log = LogDisplay()
        #self.log.show()
        # --- default --- #
        self.fps       = fps
        self.normalise_hist = True
        self.cmap      = 'jet'
        # --- main attriute --- #
        self.camera    = camera
        self.contview  = ContinuousView(fps=self.fps)
        self.timer     = pg.QtCore.QTimer() #QTimer()# pg.QtCore.QTimer()
        self.qlabl_max = QLabel()
        self.isOn      = False
        self.frame     = None
        # --- color acuisition --- #
        self.initColorDic()
        # ---  --- #
        self.initUI()
        self.initCamera(camera)
        # ---  --- #
        self.camera.setExposure( self.exposure.value() )

    def initUI(self):
        # ---  --- #
        self.layout      = QVBoxLayout(self)
        self.initView()
        # --- button widget --- #
        self.button_startstop = QPushButton('Start/Stop')
        self.button_startstop.setStyleSheet("background-color: red")
        self.button_nextFrame = QPushButton('Next frame')
        # ---  --- #
        self.fps_input     = QSpinBox()
        self.fps_input.setRange(1, 48)
        self.fps_input.setValue(self.fps)
        self.exposure      = QDoubleSpinBox()
        self.exposure.setSingleStep(0.01)
        self.cam_framerate = QDoubleSpinBox()
        self.exposure.setSingleStep(0.01)
        self.pixelclock    = QDoubleSpinBox()
        self.which_camera  = QComboBox()
        self.which_camera.addItem('USB camera')
        self.which_camera.addItem('From Image Dir.')
        # --- set default --- #
        self.exposure.setRange(0.10, 99.0)
        self.exposure.setValue(12.5)
        self.cam_framerate.setRange(1.00, 15.0)
        self.cam_framerate.setValue( 10.0 )
        self.pixelclock.setRange(5, 30)
        self.pixelclock.setValue(20)
        # --- connections --- #
        self.button_startstop.clicked.connect(self.startStop_continuous_view)
        self.fps_input.valueChanged.connect(self.setFPS)
        self.button_nextFrame.clicked.connect( self.nextFrame )
        self.exposure.valueChanged.connect( self.update_exposure )
        self.cam_framerate.valueChanged.connect( self.update_camFPS )
        self.pixelclock.valueChanged.connect( self.update_pixelClock )
        self.which_camera.currentIndexChanged.connect( self.changeCameraStyle )
        # --- layout --- #
        label_1 = QLabel('fps :')
        label_1.setWordWrap(True)
        label_2 = QLabel('value max:') 
        label_2.setWordWrap(True)
        label_3 = QLabel('Which camera')
        label_3.setWordWrap(True)
        label_4 = QLabel('Exposure (ms):')
        label_4.setWordWrap(True)
        label_5 = QLabel('Camera fps:')
        label_5.setWordWrap(True)
        label_6 = QLabel('Pixel clock (MHz):')
        label_6.setWordWrap(True)
        grid = QGridLayout()
        grid.addWidget( self.button_startstop, 0,0)
        grid.addWidget( self.button_nextFrame, 0,1)
        grid.addWidget( label_1              , 0,2)
        grid.addWidget( self.fps_input       , 0,3)
        grid.addWidget( label_2              , 0,4)
        grid.addWidget( self.qlabl_max       , 0,5)
        grid.addWidget(QVLine()              , 0,6 , 2,1)
        grid.addWidget(label_3               , 0,7)
        grid.addWidget( self.which_camera    , 1,7 , 2,1)
        grid.addWidget(label_4               , 1,0)
        grid.addWidget( self.exposure        , 1,1)
        grid.addWidget(label_5               , 1,2)
        grid.addWidget( self.cam_framerate   , 1,3)
        grid.addWidget(label_6               , 1,4)
        grid.addWidget( self.pixelclock      , 1,5)
        self.layout.addLayout(grid)
        self.layout.addWidget(self.image_view)
        self.setLayout(self.layout)

    def initColorDic(self):
        self.colordic = {}
        # --- jet-like cmap --- #
        alpha     = 1.0
        positions = [0.2, 0.5, 0.75, 1.0]
        colors    = [[0,0,1.,alpha],[0,1.,1.,alpha],[1.,1.,0,alpha],[170/255,0,0,alpha]] #colors    = ['#0000ff', '#00ffff', '#ffff00', '#aa0000']
        self.colordic['jet'] = pg.ColorMap(positions, colors)
        # --- jet reversed cmap --- #
        positions_r = [1-p_ for p_ in positions]
        self.colordic['jet_r'] = pg.ColorMap(positions_r, colors)
        # --- plasma cmap --- #
        self.colordic['plasma'] = generatePgColormap('plasma')

    def initView(self):
        self.image_view     = pg.ImageView()
        # ---  --- #
        self.image_view.setColorMap(self.colordic[self.cmap])
        self.image_view.setLevels(0,255)
        self.image_view.getHistogramWidget().item.setHistogramRange(0,255)
        # ---  --- #
        self.image_view.setMinimumWidth(800)
        self.image_view.setMinimumHeight(600)

    def initCamera(self, camera):
        self.default_camera = camera
        self.simu_camera    = SimuCamera(directory_path=os.getcwd(), log=self.log)
        # ---  --- #
        self.camera = self.default_camera
        if not self.camera.isCameraInit:
            self.camera.__init__()

    def hideHistogram(self):
        self.image_view.ui.histogram.hide()

    def hidePlotButtons(self):
        self.image_view.ui.roiBtn.hide()
        self.image_view.ui.menuBtn.hide()

    def setFPS(self):
        self.fps = self.fps_input.value()
        self.contview.setFPS( self.fps )
        self.timer.setInterval(1e3/self.fps)

    def update_frame(self):
        self.frame = self.camera.get_frame()
        self.qlabl_max.setText( str(np.max(self.frame)) )
        self.image_view.setImage(self.frame.T, autoHistogramRange=False, autoLevels=False)
        self.frame_updated.emit()

    def update_exposure(self):
        self.camera.setExposure( self.exposure.value() )
        self.log.addText( 'New exposure: {}ms'.format( self.camera.getExposure() ) )
        # ---  --- #
        new_pixelclock = self.camera.getPixelClock()
        self.pixelclock.setValue( new_pixelclock )
        new_camFPS     = self.camera.getFrameRate()
        self.cam_framerate.setValue( new_camFPS )

    def update_camFPS(self):
        newfps = self.camera.setFrameRate( self.cam_framerate.value() )
        self.log.addText('New camera fps: {:.3f}'.format( self.camera.getFrameRate() ) )
        self.cam_framerate.setValue(newfps)
        # ---  --- #
        new_pixelclock = self.camera.getPixelClock()
        self.pixelclock.setValue( new_pixelclock )
        new_exposure   = self.camera.getExposure()
        self.exposure.setValue( new_exposure )

    def update_pixelClock(self):
        self.camera.setPixelClock( int(self.pixelclock.value()) )
        self.log.addText( 'New pixel clock: {}Mhz'.format( self.camera.getPixelClock() ))
        # ---  --- #
        new_camFPS     = self.camera.getFrameRate()
        self.cam_framerate.setValue( new_camFPS )
        new_exposure   = self.camera.getExposure()
        self.exposure.setValue( new_exposure )

    def nextFrame(self):
        wasOn = self.isOn
        if not self.isOn:
            self.camera.capture_video()
        # ---  --- #
        self.update_frame()
        # ---  --- #
        if not wasOn:
            self.camera.capture_video()

    def startStop_continuous_view(self):
        if  self.isOn:
            self.stop_continuous_view()
            self.button_startstop.setStyleSheet("background-color: red")
            self.button_nextFrame.setFlat(False)
            self.button_nextFrame.setEnabled(True)
        else:
            self.start_continuous_view()
            self.button_startstop.setStyleSheet("background-color: green")
            self.button_nextFrame.setFlat(True)
            self.button_nextFrame.setEnabled(False)

    def start_continuous_view(self):
        self.camera.capture_video()
        if   True:
            self.start_continuous_view_qtimer()
        elif False:
            self.start_continuous_view_qthread()
        # ---  --- #
        self.isOn = True

    def stop_continuous_view(self):
        self.camera.stop_video()
        if   True:
            self.stop_continuous_view_qtimer()
        elif False:
            self.stop_continuous_view_qthread()
        # ---  --- #
        self.isOn = False

    def start_continuous_view_qthread(self):
        # ---  --- #
        self.thread = QThread()
        self.thread.setTerminationEnabled(True)
        # --- connect --- #
        self.contview.moveToThread(self.thread)
        self.thread.started.connect(self.contview.startFeed)
        self.contview.newshot.connect(self.update_image)
        self.contview.finished.connect(self.thread.quit)
        # ---  --- #
        self.thread.start()

    def stop_continuous_view_qthread(self):
        self.contview.stopFeed()

    def start_continuous_view_qtimer(self):
        # ---  --- #
        self.timer.timeout.connect(self.update_frame)
        self.timer.start(1e3/self.fps) #ms

    def stop_continuous_view_qtimer(self):
        self.timer.stop()

    def changeCameraStyle(self):
        self.camera.stop_video()
        # ---  --- #
        indx = self.which_camera.currentIndex()
        if   indx == 0:
            self.camera = self.default_camera
            # ---  --- #
            self.exposure.setEnabled(True)
            self.cam_framerate.setEnabled(True)
            self.pixelclock.setEnabled(True)
        elif indx == 1:
            self.camera.stop_video()
            dir_path    = QFileDialog().getExistingDirectory()
            self.log.addText('NEW DIR PATH: {}'.format(dir_path))
            self.simu_camera.setDirectoryPath( dir_path )
            self.simu_camera.initialize()
            self.camera = self.simu_camera
            # ---  --- #
            self.exposure.setEnabled(False)
            self.cam_framerate.setEnabled(False)
            self.pixelclock.setEnabled(False)
class MainWindow(QMainWindow): # inherits from the QMainWindow class
    def __init__(self):
        super().__init__() # The super() method returns the parent object of the MainWindow class, and we call its constructor. This means that we first initiate the QWidget class constructor.
        #self.app = QApplication([]) # only one per application

        # --- paths --- #
        self.localpath  = os.path.dirname(os.path.realpath(__file__))
        self.importpath = 'Dependencies_import/'
        self.iconpath   = 'IMGdirectory/'
        # --- initialisations --- #
        self.initWindow()
        self.initWindowMenu()

        #self.show()
        #self.app.exec_()

    def initWindow(self):
        '''
        Initialize the MainWindow configuration and display.
        '''
        # --- geometry and position --- #
        x0, y0, w, h = 150, 100, 900, 800
        self.setGeometry(x0, y0, w, h)
        self.setWindToCenter() # use the method defined below to center the window in the screen
        # --- names and titles --- #
        mytitle = "Main window"
        self.setWindowTitle(mytitle)
        mainapp_iconpath = r"./" + self.importpath + self.iconpath + "icon_mainapp.png"
        self.setWindowIcon(QIcon(mainapp_iconpath))
        # --- Parent Widget : central widget --- #
        self.initMainWidget()
        self.initTabLayout()
        # --- make  log display tab --- #
        self.log        = LogDisplay()
        self.insertNewTabLogDisplay(self.log)
        # --- main attributes --- #
        self.cameraManag = CameraManagementWindow()
        self.initCamera()

    def initWindowMenu(self):
        '''
        Initialize the menu of the MainWindow.
        '''
        self.menubar = self.menuBar() # we define an attribute, menubar, which derives from the method menuBar() that comes from the QMainWindow parent object.
        # --- set the main menus --- #
        self.filemenu     = self.menubar.addMenu('&File')
        self.setupmenu    = self.menubar.addMenu('&Setups')
        self.toolsmenu    = self.menubar.addMenu('&Tools')
        self.cameramenu   = self.menubar.addMenu('&Camera')
        self.statusBar()
        # --- set the actions in the different menues --- #
        self.initFileMenu()
        self.initSetupMenu()
        self.initToolsMenu()
        self.initCameraMenu()
        #self.getFile()

    def initMainWidget(self):
        self.centralwidget = QWidget() # QMainWindow needs a QWidget to display Layouts. Thus we define a central widget so that all

    def initTabLayout(self):
        # ---  --- #
        self.centraltab    = QTabWidget()
        self.centraltab.setTabShape(0)
        self.centraltab.setTabsClosable(True)
        self.centraltab.tabCloseRequested.connect( self.closeTabe )
        self.setCentralWidget(self.centraltab)

    def initCamera(self):
        dir_path = '/home/cgou/ENS/STAGE/M2--stage/Camera_acquisition/Miscellaneous/Camera_views/'
        self.camera   = Camera(cam_id=0, log=self.log)
        if not self.camera.isCameraInit:
            self.camera = SimuCamera(0, directory_path=dir_path, log=self.log)
            self.log.addText( self.camera.__str__() )
        else:
            self.log.addText('Camera in use is: USB camera')

    def setWindToCenter(self):
        '''
        Set the MainWindow at the center of the desktop.
        '''
        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())

    def closeTabe(self, index):
        '''
        Remove the Tab of index index (integer).
        '''
        self.centraltab.removeTab(index)
        self.camera.stop_video()
        self.camera.close_camera()
        self.log.addText('Is camera closed ? {}'.format( not self.camera.isCameraInit ) )

    def getFile(self):
        '''
        Set the menue bar such that we can fetch a text file and display
        it on a textEdit widget
        '''
        self.textEdit = QTextEdit()
        grid          = QGridLayout()
        grid.addWidget(self.textEdit,3,1,5,1)
        self.centralwidget.setLayout(grid)

        openFile = QAction('Open', self)
        openFile.setShortcut('Ctrl+O')
        openFile.setStatusTip('Open new File')
        openFile.triggered.connect(self.showDialog)

        self.filemenu.addAction(openFile)

    def initFileMenu(self):
        # --- Exit application --- #
        closeapp = QAction('&Exit', self) # QAction(QIcon('incon.png'), '&Exit', self)
        closeapp.triggered.connect(self.closeMainWindow)
        self.filemenu.addAction( '------' )
        self.filemenu.addAction(closeapp)

    def initSetupMenu(self):
        '''
        Make a new tab window with the display of the chosen structure (SWG, BS, ...).
        '''
        # --- Preview --- #
        openNewtab  = QAction('Preview', self)
        openNewtab.triggered.connect(self.insertNewTabPreview)
        self.setupmenu.addAction(openNewtab)
        # --- DC plot tab --- #
        openNewtab  = QAction('Peak comparison (PkComp)', self)
        openNewtab.triggered.connect(self.insertNewTabPeakComparison)
        self.setupmenu.addAction(openNewtab)
        # --- Lissajous plot tab --- #
        openNewtab  = QAction('Lissajous (Li)', self)
        openNewtab.triggered.connect(self.insertNewTabLissajousPlot)
        self.setupmenu.addAction(openNewtab)

    def initToolsMenu(self):
        '''
        Make a new tab window with the display of the chosen structure (SWG, BS, ...).
        '''
        # --- Application diagram --- #
        openNewtab  = QAction('App diagram', self)
        openNewtab.triggered.connect(self.insertNewTabAppDiagram)
        self.toolsmenu.addAction(openNewtab)

    def initCameraMenu(self):
        '''
        Make a new tab window with the display of the chosen structure (SWG, BS, ...).
        '''
        # --- Camera tab --- #
        openNewtab  = QAction('&Camera setup', self)
        openNewtab.triggered.connect(self.cameraManagementWindow)
        self.cameramenu.addAction(openNewtab)

    def cameraManagementWindow(self):
        self.cameraManag.show()

    def showDialog(self):
        '''
        Generate the window where we can browse for the wanted text file.
        '''
        fname = QFileDialog.getOpenFileName(self, 'Open file', './') #'/home/cgou')
        if fname[0]:
            f = open(fname[0], 'r')
            with f:
                data = f.read()
                self.textEdit.setText(data)

    def insertNewTabLogDisplay(self, log_objct):
        newtabindex = self.centraltab.addTab(log_objct,"Log")
        currentTbaBar = self.centraltab.tabBar()
        currentTbaBar.setTabButton(newtabindex, PyQt5.QtWidgets.QTabBar.RightSide, QLabel('')) # hide the close button
        self.centraltab.setCurrentIndex( newtabindex )

    def insertNewTabMainWidget(self):
        newtabindex = self.centraltab.addTab( self.centralwidget, "Main" ) # also: addTab(QWidget , QIcon , QString )
        self.centraltab.setCurrentIndex( newtabindex )

    def insertNewTabPreview(self):
        newtabindex = self.centraltab.addTab( Preview(camera=self.camera, log=self.log), "Preview" ) # also: addTab(QWidget , QIcon , QString )
        self.centraltab.setCurrentIndex( newtabindex )

    def insertNewTabAppDiagram(self):
        newtabindex = self.centraltab.addTab( self.centralwidget, "Main" ) # also: addTab(QWidget , QIcon , QString )
        self.centraltab.setCurrentIndex( newtabindex )

    def insertNewTabPeakComparison(self):
        newtabindex = self.centraltab.addTab( DCMeasurement(camera=self.camera, log=self.log), "PkComp" ) # also: addTab(QWidget , QIcon , QString )
        self.centraltab.setCurrentIndex( newtabindex )

    def insertNewTabLissajousPlot(self):
        newtabindex = self.centraltab.addTab( PhaseNetworkElements(camera=self.camera, log=self.log), "Lissa" ) # also: addTab(QWidget , QIcon , QString )
        self.centraltab.setCurrentIndex( newtabindex )

    def closeMainWindow(self):
        try:
            self.camera.stop_video()
            self.camera.close_camera()
        except:
            pass
        print('Is camera closed ? {}'.format( not self.camera.isCameraInit ) )
        self.close()