Exemple #1
0
def main():
    app = QApplication(sys.argv)
    chart = DynamicSpline()
    chart.setTitle("Dynamic spline chart")
    chart.legend().hide()
    chart.setAnimationOptions(QChart.AllAnimations)

    view = QChartView(chart)
    view.setRenderHint(QPainter.Antialiasing) 
    view.resize(1000, 500)
    view.show()
    sys.exit(app.exec_())
Exemple #2
0
        self.timer = QTimer(self)
        self.timer.setInterval(1000)
        self.timer.timeout.connect(self.handleTimeout)
        self.timer.start()

    def handleTimeout(self):
        x = self.plotArea().width() / self.axisX.tickCount()
        y = (self.axisX.max() - self.axisX.min()) / self.axisX.tickCount()
        self.m_x += y
        # 在PyQt5.11.3及以上版本中,QRandomGenerator.global()被重命名为global_()
        self.m_y = QRandomGenerator.global_().bounded(5) - 2.5
        self.series.append(self.m_x, self.m_y)
        self.scroll(x, 0)
        if self.m_x >= 100:
            self.timer.stop()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    chart = DynamicSpline()
    chart.setTitle("Dynamic spline chart")
    chart.legend().hide()
    chart.setAnimationOptions(QChart.AllAnimations)

    view = QChartView(chart)
    view.setRenderHint(QPainter.Antialiasing)  # 抗锯齿
    view.resize(400, 300)
    view.show()
    sys.exit(app.exec_())
Exemple #3
0
@site: http://alyl.vip, http://orzorz.vip, http://coding.net/u/892768447, http://github.com/892768447
@email: [email protected]
@file: LineChart
@description: 
'''
import sys

from PyQt5.QtChart import QChartView, QLineSeries, QChart
from PyQt5.QtGui import QPainter
from PyQt5.QtWidgets import QApplication


__version__ = "0.0.1"


if __name__ == "__main__":
    app = QApplication(sys.argv)
    chart = QChart()
    chart.setTitle("Line Chart 1")
    series = QLineSeries(chart)
    series.append(0, 6)
    series.append(2, 4)
    chart.addSeries(series)
    chart.createDefaultAxes()  # 创建默认轴

    view = QChartView(chart)
    view.setRenderHint(QPainter.Antialiasing)  # 抗锯齿
    view.resize(800, 600)
    view.show()
    sys.exit(app.exec_())
class initial(QDialog):
    def __init__(self):
        super(initial, self).__init__()
        loadUi("main.ui", self)
        # definir elementos de la UI y acciones/funciones asociadas

        # creamos sets para mostrar resultados en el barchart

        self.setRecall = QBarSet("Recalls")
        self.setRecall.append([0, 0, 0])
        self.setAccurracy = QBarSet("Accurracy")
        self.setAccurracy.append([0, 0, 0])

        self.series = QBarSeries()

        # Elementos Tab Entrenamiento:
        # ===========================

        # Btn Insertar primeros datos entrenamiento
        self.trainingAddFilesBtn.clicked.connect(
            self.insertarNoticiasEntrenamientoDespoblacion)

        # Btn Insertar segundos datos entrenamiento
        self.trainingAddFilesBtn2.clicked.connect(
            self.insertarNoticiasEntrenamientoNoDespoblacion)

        # Btn Preprocesamiento de texto
        self.procesarTextoBtn.clicked.connect(self.procesarTextoEntrenamiento)

        # ComboBox selector de Modelo a entrenar
        # self.chooseModelComboBox.activated.connect(self.elegirModeloEntrenamiento)

        # añadir elementos al comboBox y sus valores asociados
        self.chooseModelComboBox.addItem("KNN", 1)
        self.chooseModelComboBox.addItem("Naive Bayes", 2)
        self.chooseModelComboBox.addItem("Decision Tree", 3)

        # Btn para entrenar el modelo seleccionado
        self.trainModelBtn.clicked.connect(self.entrenarModelo)

        # Elementos Tab Testeo:
        # ====================

        # Btn Insertar Datos Testeo
        self.testingFilesBtn.clicked.connect(self.insertarNoticiasTesteo)

        # Btn Seleccionar Modelo
        self.selectTestModelBtn.clicked.connect(self.elegirModeloTesteo)

        # Btn Mostrar Resultados
        self.testBtn.clicked.connect(self.mostrarResultados)

        # Tab Testeo
        #self.tabTest.clicked.connect(self.abrirTabTesteo)

        # nombre excel
        self.nombreresultadoexcel = ':)'

    # funciones
    # =========

    # abrir tab testeo
    def abrirTabTesteo(self):
        self.stepTipsField.setPlainText(
            "En esta pestaña puede realizar el testeo sobre un nuevo set de datos para un modelo ya existente."
        )

    # abrir dialog window para seleccionar los datos de entrenamiento
    def insertarNoticiasEntrenamientoDespoblacion(self):

        del desp[:]
        print(desp)
        # cambiar texto campo descripcion
        self.stepTipsField.setPlainText(
            "Seleccionamos los directorios donde tenemos los archivos de texto que utilizaremos para entrenar nuestro modelo."
        )

        # abrir ventana seleccion archivos
        desp.append(self.openDialogBox())

        print(desp)
        #cambiar self.procesarTextoBtn a habilitado
        self.trainingAddFilesBtn2.setEnabled(True)

    # abrir dialog window para seleccionar los segundos datos de entrenamiento
    def insertarNoticiasEntrenamientoNoDespoblacion(self):

        del no_despoblacion[:]

        # cambiar texto campo descripcion
        self.stepTipsField.setPlainText(
            "Seleccionamos los directorios donde tenemos los archivos de texto que utilizaremos para entrenar nuestro modelo."
        )

        # abrir ventana seleccion archivos
        no_despoblacion.append(self.openDialogBox())

        #cambiar self.procesarTextoBtn a habilitado
        self.procesarTextoBtn.setEnabled(True)

    # aplicar preprocesamiento de texto
    def procesarTextoEntrenamiento(self):
        # cambiar texto campo descripcion
        self.stepTipsField.setPlainText(
            "El preprocesamiento a realizar consta de 4 etapas:\n1. Tokenizar: separar las palabras que componen un texto, obteniendo como resultado una secuencia de tokens.\n2. Normalización: se pasa a minúsculas tdoos los tokens.\n3.Filtrado de stopwords: en esta etapa eliminamos  aquellas palabras con poco valor semántico, denominadas stopwords.\n4.Stemming: extraemos el lexema de los tokens restantes  (un ejemplo sería ‘cas-’ para la palabra ‘casero’)"
        )

        del noticias[:]
        del clases[:]
        # bucle inserción de noticias mediante open
        ingresar_noticias(desp[0], noticias)
        ingresar_noticias(no_despoblacion[0], noticias)
        ingresar_noticias(desp[0], despoblacion)

        # Procesamiento de texto
        texto_procesado(processed_text_entrenamiento, noticias)

        # Creación de arreglo de clases
        crea_clases(clases, processed_text_entrenamiento, despoblacion)

        # cambiar self.trainModelBtn a habilitado
        self.trainModelBtn.setEnabled(True)

        # cambiar texto campo descripcion
        self.stepTipsField.setPlainText(
            "El preprocesamiento a realizar consta de 4 etapas:\n1. Tokenizar: separar las palabras que componen un texto, obteniendo como resultado una secuencia de tokens.\n2. Normalización: se pasa a minúsculas tdoos los tokens.\n3.Filtrado de stopwords: en esta etapa eliminamos  aquellas palabras con poco valor semántico, denominadas stopwords.\n4.Stemming: extraemos el lexema de los tokens restantes  (un ejemplo sería ‘cas-’ para la palabra ‘casero’).\n====================\nEl preprocesamiento ha acabado"
        )

        # cambiar self.procesarTextoBtn a deshabilitado
        self.procesarTextoBtn.setEnabled(False)

    # abrir ventana seleccion archivos
    def openDialogBox(self):
        filenames = QFileDialog.getOpenFileNames()
        return filenames[0]

    # mostrar resultados testeo en nueva tabla
    def mostrarResultados(self):
        # cambiar texto campo descripcion
        self.stepTipsField.setPlainText(
            "A continuación se muestra una tabla con los resultados de la clasificación realizada por el modelo seleccionado."
        )

        # para ocupar toda la tabla
        self.tableWidgetshowTest.horizontalHeader().setSectionResizeMode(
            QtWidgets.QHeaderView.Stretch)

        # resetear tabla
        self.tableWidgetshowTest.setRowCount(0)
        nombre = self.nombreresultadoexcel

        # mostrar contenido xlsx
        documento = xlrd.open_workbook(nombre + '.xlsx')
        df = documento.sheet_by_index(0)
        self.tableWidgetshowTest.setRowCount((df.nrows) - 1)
        self.tableWidgetshowTest.setColumnCount(2)
        for x in range(1, df.nrows):
            for y in range(2):
                print('x: ' + df.cell_value(x, y - 1))
                item = QTableWidgetItem()
                nombreArchivo = df.cell_value(x, y - 1).split("/")
                item.setText(nombreArchivo[len(nombreArchivo) - 1])
                self.tableWidgetshowTest.setItem(x - 1, y - 1, item)

    # insertar archivos fase testeo
    def insertarNoticiasTesteo(self):
        # cambiar texto campo descripcion
        self.stepTipsField.setPlainText(
            "Seleccione los archivos que utilizará durante la fase de testeo.")

        # abrir ventana seleccion archivos
        filepaths = self.openDialogBox()

        #ingresar noticias
        ingresar_noticias(filepaths, nuevas)

        #Procesamiento de texto
        texto_procesado(processed_text_testeo, nuevas)

        # cambiar self.selectTestModelBtn a deshabilitado
        self.selectTestModelBtn.setEnabled(True)

        # cambiar self.testingFilesBtn a habilitado
        # self.testingFilesBtn.setEnabled(False)

    # seleccionar modelo fase testeo
    def elegirModeloTesteo(self):
        # cambiar texto campo descripcion
        self.stepTipsField.setPlainText(
            "Seleccione el diccionario .pk y modelo correspondiente .pk1.")

        # abrir ventana seleccion archivos
        modelopath = self.openDialogBox()

        if (len(modelopath) == 2):
            # cargar diccionario
            cv1 = cargar_modelo(modelopath[0])

            # cargar modelo
            modelo = cargar_modelo(modelopath[1])

            # aplicar tfidf
            X_testcv = tfid_fit(processed_text_testeo, cv1)

            # insertar predicciones
            predicciones = []
            for i in X_testcv:
                predicciones.append(prediccion(modelo, i))

            # crear dataframe
            df = pd.DataFrame(data=predicciones, index=nuevas)

            # nombrar archivo y exportar a excel
            archivo = modelopath[0]
            new_archivo = archivo.replace('modelos', 'resultados')
            nombre = new_archivo[:len(new_archivo) - 3]
            self.nombreresultadoexcel = nombre
            df.to_excel(nombre + ".xlsx", "Sheet1")

            # cambiar self.testBtn a habilitado
            self.testBtn.setEnabled(True)

            # cambiar texto campo descripcion
            self.stepTipsField.setPlainText(
                "Resultados exportados a la carpeta resultados en formato Excel."
            )

    # aplicar modelo NaiveBayes entrenamiento
    def entrenamientoNaiveBayes(self):
        # Proceso TFIDF
        X_traincv = cv.fit_transform(processed_text_entrenamiento)
        # Partición de datos
        X_train, X_test, Y_train, Y_test = train_test_split(X_traincv,
                                                            clases,
                                                            test_size=0.15,
                                                            random_state=324)

        #Modelos
        naive = naive_bayes(X_train, Y_train)
        print(naive)
        print(
            "####################### Test Score ##############################\n"
        )
        test_score(naive, X_train, Y_train)

        # Creamos los datos a testear
        Y_true_naive, Y_pred_naive = datos_test(Y_test, naive, X_test)

        # Datos de los modelos
        print(
            "###################### Accuracy ###############################\n"
        )
        accuracy(Y_true_naive, Y_pred_naive)

        self.setAccurracy.replace(
            1,
            accuracy_score(Y_true_naive, Y_pred_naive) * 100)

        print(
            "####################### Recall ##############################\n")
        print(recall_score(Y_true_naive, Y_pred_naive, average='macro'))
        self.setRecall.replace(
            1,
            recall_score(Y_true_naive, Y_pred_naive, average='macro') * 100)

        a = "Modelo Naive-Bayes\n==================\nRecall:" + str(
            recall_score(Y_true_naive, Y_pred_naive,
                         average='macro')) + "\nAccuracy: " + str(
                             accuracy_score(Y_true_naive, Y_pred_naive))
        self.stepTipsField.setPlainText(a)
        #llamar a funcion para actualizar los valores del Barchart
        self.appendResults()

        print(
            "\n###################### Matriz de confusion ###############################\n"
        )
        matrizconf(Y_true_naive, Y_pred_naive)

        #Guardamos modelo
        now = datetime.now()
        # dd/mm/YY H:M:S
        dt_string = now.strftime("dia_%d-%m-%Y,hora_%H-%M-%S")
        guardar_modelo('modelos/naive_' + dt_string, naive)
        with open('modelos/naive_' + dt_string + '.pk', 'wb') as f:
            pickle.dump(cv, f)

    # aplicar modelo Decision Tree entrenamiento
    def entrenamientoArbolDecision(self):
        # Proceso TFIDF
        X_traincv = cv.fit_transform(processed_text_entrenamiento)
        #Partición de datos
        X_train, X_test, Y_train, Y_test = train_test_split(X_traincv,
                                                            clases,
                                                            test_size=0.15,
                                                            random_state=324)

        #Modelos
        tree = decision_tree(X_train, Y_train)
        print(
            "####################### Test Score ##############################\n"
        )
        test_score(tree, X_train, Y_train)

        #Creamos los datos a testear
        Y_true_tree, Y_pred_tree = datos_test(Y_test, tree, X_test)

        #Datos de los modelos
        print(
            "###################### Accuracy ###############################\n"
        )
        accuracy(Y_true_tree, Y_pred_tree)

        #incluir nueva accurracy al set de resultados de NaiveBayes
        #self.setDTrees.append(accuracy_score(Y_true_tree, Y_pred_tree)*100)
        self.setAccurracy.replace(
            2,
            accuracy_score(Y_true_tree, Y_pred_tree) * 100)
        #llamar a funcion para actualizar los valores del Barchart

        print(
            "####################### Recall ##############################\n")
        print(recall_score(Y_true_tree, Y_pred_tree, average='macro'))
        #self.setDTrees.append(recall_score(Y_true_tree, Y_pred_tree, average='macro')*100)
        self.setRecall.replace(
            2,
            recall_score(Y_true_tree, Y_pred_tree, average='macro') * 100)
        a = "Modelo Arbol Decision\n=====================\nRecall:" + str(
            recall_score(Y_true_tree, Y_pred_tree,
                         average='macro')) + "\nAccuracy: " + str(
                             accuracy_score(Y_true_tree, Y_pred_tree))
        self.stepTipsField.setPlainText(a)
        self.appendResults()
        #Matriz confusion
        print(
            "\n###################### Matriz de confusion ###############################\n"
        )
        matrizconf(Y_true_tree, Y_pred_tree)

        now = datetime.now()
        # dd/mm/YY H:M:S
        dt_string = now.strftime("dia_%d-%m-%Y,hora_%H-%M-%S")
        guardar_modelo('modelos/tree_' + dt_string, tree)
        with open('modelos/tree_' + dt_string + '.pk', 'wb') as f:
            pickle.dump(cv, f)

    # aplicar modelo KNN
    def entrenamientoKnn(self):
        # Proceso TFIDF
        X_traincv = cv.fit_transform(processed_text_entrenamiento)
        #Partición de datos
        X_train, X_test, Y_train, Y_test = train_test_split(X_traincv,
                                                            clases,
                                                            test_size=0.15,
                                                            random_state=324)

        #Modelos
        modknn = knn(X_train, Y_train)

        print(
            "####################### Test Score ##############################\n"
        )
        test_score(modknn, X_train, Y_train)

        #Creamos los datos a testear
        Y_true_knn, Y_pred_knn = datos_test(Y_test, modknn, X_test)

        #Datos de los modelos
        print(
            "###################### Accuracy ###############################\n"
        )

        accuracy(Y_true_knn, Y_pred_knn)

        #llamar a funcion para actualizar los valores del Barchart
        self.setAccurracy.replace(0,
                                  accuracy_score(Y_true_knn, Y_pred_knn) * 100)

        print(
            "####################### Recall ##############################\n")
        print(recall_score(Y_true_knn, Y_pred_knn, average='macro'))

        #self.setDTrees.append(recall_score(Y_true_knn, Y_pred_knn, average='macro')*100)
        self.setRecall.replace(
            0,
            recall_score(Y_true_knn, Y_pred_knn, average='macro') * 100)
        a = "Modelo KNN\n===============\nRecall:" + str(
            recall_score(Y_true_knn, Y_pred_knn,
                         average='macro')) + "\nAccuracy: " + str(
                             accuracy_score(Y_true_knn, Y_pred_knn))
        self.stepTipsField.setPlainText(a)
        self.appendResults()
        #Matriz confusion
        print(
            "\n###################### Matriz de confusion ###############################\n"
        )
        matrizconf(Y_true_knn, Y_pred_knn)

        #Guardamos modelo
        now = datetime.now()
        # dd/mm/YY H:M:S
        dt_string = now.strftime("dia_%d-%m-%Y,hora_%H-%M-%S")
        guardar_modelo('modelos/knn_' + dt_string, modknn)
        with open('modelos/knn_' + dt_string + '.pk', 'wb') as f:
            pickle.dump(cv, f)

    # comprobar modelo seleccionado en comboBox
    def entrenarModelo(self):
        #cambiar texto en self.stepTipsField
        self.stepTipsField.setPlainText("Entrenando el modelo seleccionado...")

        #tomar valor actual del comboBox
        modelSelect = self.chooseModelComboBox.currentData()

        #no existe switch en python (o.o)
        if modelSelect == 1:
            self.entrenamientoKnn()

        if modelSelect == 2:
            self.entrenamientoNaiveBayes()

        if modelSelect == 3:
            self.entrenamientoArbolDecision()

    # add resultados entrenamiento y actualizar barchart
    def appendResults(self):
        #clear de series
        self.series = QBarSeries()

        #add sets de Accurracy y Recall de todos los modelos procesados a series
        self.series.append(self.setAccurracy)
        self.series.append(self.setRecall)

        # crear nuevo Chart
        chart = QChart()

        # add series al nuevo Chart
        chart.addSeries(self.series)
        chart.setTitle("Precisiones de Modelos")
        chart.setAnimationOptions(QChart.SeriesAnimations)

        # parametro QChart
        modelosEjeX = ('KNN', 'Naive Bayes', 'Decision Trees')

        # parametros ejeX
        ejeX = QBarCategoryAxis()
        ejeX.append(modelosEjeX)

        # parametros ejeY
        ejeY = QValueAxis()
        chart.addAxis(ejeX, Qt.AlignBottom)
        chart.addAxis(ejeY, Qt.AlignLeft)

        # leyenda Barchart
        chart.legend().setVisible(True)
        chart.legend().setAlignment(Qt.AlignBottom)

        # Mostrar ventana Barchart
        self.QChartView = QChartView(chart)
        self.QChartView.resize(600, 600)
        self.QChartView.show()
class SeeingMonitor(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super(SeeingMonitor, self).__init__()
        self.setupUi(self)

        self.Camera = None
        self.THRESH = None
        self.threshold_auto = False
        self.frame = None
        self.draw_only_frame = None
        self.video_source = VideoSource.NONE
        self.export_video = False
        self.select_noiseArea = False
        self.coordinates_noiseArea = []
        self.lineedit_path.setText(QDir.currentPath())
        self.lineedit_filename.setText("seeing.csv")
        self.save_filename = None
        self._updateFileSave()
        self.pause_pressed = False

        self.datetimeedit_start.setMinimumDateTime(QDateTime.currentDateTime())
        self.datetimeedit_end.setMinimumDateTime(QDateTime.currentDateTime())

        if platform.system() == 'Linux':
            self.button_start.setEnabled(False)
            self.button_settings.setEnabled(False)

        self.button_start.clicked.connect(self.startLiveCamera)
        self.button_settings.clicked.connect(self.showSettings)
        self.button_simulation.clicked.connect(self.startSimulation)
        self.button_import.clicked.connect(self.importVideo)
        self.button_export.clicked.connect(self.exportVideo)
        self.button_noise.clicked.connect(self.selectNoiseArea)
        self.lineedit_path.textEdited.connect(self._updateFileSave)
        self.lineedit_filename.textEdited.connect(self._updateFileSave)
        self.slider_threshold.valueChanged.connect(self._updateThreshold)
        self.checkbox_thresh.stateChanged.connect(self._updateThresholdState)

        # Update the Tilt value
        self.spinbox_b.valueChanged.connect(self._updateFormulaZTilt)
        self.spinbox_d.valueChanged.connect(self._updateFormulaZTilt)
        # Update the constants in the FWHM seeing formula
        self.spinbox_d.valueChanged.connect(self._updateFormulaConstants)
        self.spinbox_lambda.valueChanged.connect(self._updateFormulaConstants)

        # Timer for acquiring images at regular intervals
        self.acquisition_timer = QTimer(parent=self.centralwidget)
        self.timer_interval = None

        self._updateThreshold()
        self._updateFormulaZTilt()
        self._updateFormulaConstants()

        # Storing the Delta X and Y in an array to calculate the Standard Deviation
        self.arr_delta_x = deque(maxlen=100)
        self.arr_delta_y = deque(maxlen=100)

        self.plot_length = 1000
        self.fwhm_lat = 0
        self.fwhm_tra = 0
        self.max_lat = 1
        self.min_lat = 0
        self.max_tra = 1
        self.min_tra = 0

        self.series_lat = QLineSeries()
        self.series_lat.setName("Lateral")
        self.series_tra = QLineSeries()
        self.series_tra.setName("Transversal")

        self.chart = QChart()
        self.chart.addSeries(self.series_lat)
        self.chart.addSeries(self.series_tra)

        # self.chart.createDefaultAxes()
        self.axis_horizontal = QDateTimeAxis()
        self.axis_horizontal.setMin(QDateTime.currentDateTime().addSecs(-60 *
                                                                        1))
        self.axis_horizontal.setMax(QDateTime.currentDateTime().addSecs(0))
        self.axis_horizontal.setFormat("HH:mm:ss.zzz")
        self.axis_horizontal.setLabelsFont(
            QFont(QFont.defaultFamily(self.font()), pointSize=5))
        self.axis_horizontal.setLabelsAngle(-20)
        self.chart.addAxis(self.axis_horizontal, Qt.AlignBottom)

        self.axis_vertical_lat = QValueAxis()
        self.axis_vertical_lat.setRange(self.max_lat, self.min_lat)
        self.chart.addAxis(self.axis_vertical_lat, Qt.AlignLeft)

        self.axis_vertical_tra = QValueAxis()
        self.axis_vertical_tra.setRange(self.max_tra, self.min_tra)
        self.chart.addAxis(self.axis_vertical_tra, Qt.AlignRight)

        self.series_lat.attachAxis(self.axis_horizontal)
        self.series_lat.attachAxis(self.axis_vertical_lat)

        self.series_tra.attachAxis(self.axis_horizontal)
        self.series_tra.attachAxis(self.axis_vertical_tra)

        self.chart.setTitle("Full Width at Half Maximum")
        self.chart.legend().setVisible(True)
        self.chart.legend().setAlignment(Qt.AlignBottom)
        self.chartView = QChartView(self.chart, parent=self.graphicsView)
        self.chartView.resize(640, 250)
        self.chartView.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
        self.chartView.setRenderHint(QPainter.Antialiasing)

    def closeEvent(self, event):
        try:
            self.Camera.StopLive()
        except AttributeError:
            pass

        try:
            self.cap.release()
        except AttributeError:
            pass

        try:
            self.video_writer.release()
        except AttributeError:
            pass

        event.accept()

    def _callbackFunction(self, hGrabber, pBuffer, framenumber, pData):
        """ This is an example callback function for image processig  with 
            opencv. The image data in pBuffer is converted into a cv Matrix
            and with cv.mean() the average brightness of the image is
            measuered.

        :param: hGrabber: This is the real pointer to the grabber object.
        :param: pBuffer : Pointer to the first pixel's first byte
        :param: framenumber : Number of the frame since the stream started
        :param: pData : Pointer to additional user data structure
        """
        if pData.buffer_size > 0:
            image = C.cast(pBuffer, C.POINTER(C.c_ubyte * pData.buffer_size))

            cvMat = np.ndarray(buffer=image.contents,
                               dtype=np.uint8,
                               shape=(pData.height, pData.width,
                                      pData.iBitsPerPixel))

            frame = np.uint8(cvMat)
            self.frame = cv2.resize(frame, (640, 480))
            self.draw_only_frame = self.frame.copy()
            self._monitor()

    def _startLiveCamera(self):

        # Create a function pointer
        Callbackfunc = IC.TIS_GrabberDLL.FRAMEREADYCALLBACK(
            self._callbackFunction)
        ImageDescription = CallbackUserData()

        # Create the camera object
        self.Camera = IC.TIS_CAM()

        self.Camera.ShowDeviceSelectionDialog()
        if self.Camera.IsDevValid() != 1:
            print("[Error Camera Selection] Couldn't open camera device !")
            # QMessageBox.warning(self, "Error Camera Selection", "Couldn't open camera device !")
            # raise Exception("Unable to open camera device !")
            return

        # Now pass the function pointer and our user data to the library
        self.Camera.SetFrameReadyCallback(Callbackfunc, ImageDescription)

        # Handle each incoming frame automatically
        self.Camera.SetContinuousMode(0)

        print('Starting live stream ...')
        self.Camera.StartLive(
            0
        )  ####### PAUSE LIVE STREAM WHEN PAUSE CLICKED ??? ##############################################
        # self.Camera.StartLive(1)

        Imageformat = self.Camera.GetImageDescription()[:3]
        ImageDescription.width = Imageformat[0]
        ImageDescription.height = Imageformat[1]
        ImageDescription.iBitsPerPixel = Imageformat[2] // 8
        ImageDescription.buffer_size = ImageDescription.width * ImageDescription.height * ImageDescription.iBitsPerPixel

        while self.video_source == VideoSource.CAMERA:
            pass

        # self.timer_interval = 20
        # try:
        #     self.acquisition_timer.disconnect()
        # except TypeError:
        #     pass
        # self.acquisition_timer.timeout.connect(self._updateLiveCamera)
        # self.acquisition_timer.start(self.timer_interval)

    def startLiveCamera(self):
        try:
            self.acquisition_timer.disconnect()
        except TypeError:
            pass

        self.video_source = VideoSource.CAMERA
        self.button_export.setEnabled(True)
        self._setPauseButton()

        # Disable other functionalities
        # self.button_simulation.setEnabled(False)

        t = threading.Thread(target=self._startLiveCamera,
                             args=(),
                             daemon=True)
        t.start()

    def showSettings(self):
        if not self.Camera.IsDevValid():
            QMessageBox.warning(
                self, "Camera Selection Error",
                "Please select a camera first by clicking on the button <strong>Start</strong>"
            )
            return

        try:
            self.Camera.ShowPropertyDialog()
        except Exception as e:
            logging.error(traceback.format_exc())
            QMessageBox.warning(self, "Property Dialog Error",
                                traceback.format_exc())

    # def _updateLiveCamera(self):
    #     # Capturing a frame
    #     self.Camera.SnapImage()
    #     frame = self.Camera.GetImage()
    #     frame = np.uint8(frame)
    #     self.frame = cv2.resize(frame, (640, 480))
    #     self.draw_only_frame = self.frame.copy()
    #     self._monitor()

    # self.displayParameters()

    def displayParameters(self):
        parameters_text = ""

        ExposureTime = [0]
        self.Camera.GetPropertyAbsoluteValue("Exposure", "Value", ExposureTime)
        parameters_text = parameters_text + str(ExposureTime[0]) + "\n"

        GainValue = [0]
        self.Camera.GetPropertyAbsoluteValue("Gain", "Value", GainValue)
        parameters_text = parameters_text + str(GainValue[0]) + "\n"

        self.parameters_label.setText(parameters_text)
        self.parameters_label.adjustSize()

    def startSimulation(self):

        if self.Camera != None and self.Camera.IsDevValid() == 1:
            self.Camera.StopLive()
        self.video_source = VideoSource.SIMULATION
        self.button_export.setEnabled(True)
        self._setPauseButton()

        # Disable other functionalities
        # self.button_start.setEnabled(False)
        self.button_settings.setEnabled(False)

        # Generating fake images of DIMM star (One single star that is split by the DIMM)
        self.starsGenerator = FakeStars()
        self.timer_interval = 100

        try:
            self.acquisition_timer.disconnect()
        except TypeError:
            pass
        self.acquisition_timer.timeout.connect(self._updateSimulation)
        self.acquisition_timer.start(self.timer_interval)

    def _updateSimulation(self):
        frame = self.starsGenerator.generate()
        self.frame = cv2.resize(frame, (640, 480))
        self.draw_only_frame = self.frame.copy()
        self._monitor()


################################################################################################################################################################

    def _writeCSV(self, headerOnly=False):
        if headerOnly:
            with open(self.save_filename, "w") as csvFile:
                fieldnames = ["timestamp", "lateral", "transversal", "star"]
                writer = csv.DictWriter(csvFile, fieldnames=fieldnames)
                writer.writeheader()
                csvFile.close()

        else:
            with open(self.save_filename, "a") as csvFile:
                writer = csv.writer(csvFile)
                writer.writerow([
                    self.current.toTime_t(), self.fwhm_lat, self.fwhm_tra,
                    self.lineedit_star.text()
                ])
                # csvFile.write(",".join([str(self.current) , str(self.fwhm_lat), str(self.fwhm_tra), self.lineedit_star.text()]))
                # csvFile.write("\n")
                # csvFile.close()

    def selectNoiseArea(self):
        self.label_info.setText("Please select on the video a noise area.")
        self.button_noise.setText("Selecting ...")
        self.coordinates_noiseArea = []
        self.select_noiseArea = True

    def _set_noiseArea(self, x1, y1, x2, y2):
        if len(self.coordinates_noiseArea) == 0:
            self.coordinates_noiseArea.append([x1, y1])
            self.coordinates_noiseArea.append([x2, y2])

        elif len(self.coordinates_noiseArea) == 2:
            self.coordinates_noiseArea[0] = [x1, y1]
            self.coordinates_noiseArea[1] = [x2, y2]

    def _draw_noiseArea(self):
        if len(self.coordinates_noiseArea) >= 2:
            cv2.rectangle(self.draw_only_frame,
                          (self.coordinates_noiseArea[0][0],
                           self.coordinates_noiseArea[0][1]),
                          (self.coordinates_noiseArea[1][0],
                           self.coordinates_noiseArea[1][1]), (0, 255, 0), 1)

    def _updateFileSave(self):
        self.save_filename = join(self.lineedit_path.text(),
                                  self.lineedit_filename.text())
        self._writeCSV(headerOnly=True)

    def _updateThreshold(self):
        if self.threshold_auto:
            if self.coordinates_noiseArea.__len__() >= 2:
                noise_area = self.frame[self.coordinates_noiseArea[0][1]:self.
                                        coordinates_noiseArea[1][1],
                                        self.coordinates_noiseArea[0][0]:self.
                                        coordinates_noiseArea[1][0]]
                try:
                    self.THRESH = noise_area.max() + 20
                    # self.THRESH = int(round(noise_area.mean()))
                except ValueError:
                    return

                self.slider_threshold.setValue(self.THRESH)
                self.checkbox_thresh.setText("Threshold ({}, auto)".format(
                    self.THRESH))
        else:
            self.THRESH = self.slider_threshold.value()
            self.checkbox_thresh.setText("Threshold ({})".format(self.THRESH))

    def _updateThresholdState(self, state):
        if state == 0:
            self.threshold_auto = False
            self.slider_threshold.setEnabled(True)
        else:
            if self.coordinates_noiseArea.__len__() < 2:
                QMessageBox.information(self, "Select Noise Area",
                                        "Please select the noise area")
                self.selectNoiseArea()

            self.threshold_auto = True
            self.slider_threshold.setEnabled(False)

    def _updateFormulaZTilt(self):
        self.spinbox_d.setStyleSheet("QSpinBox { background-color: blue; }")
        try:
            b = float(self.spinbox_b.value()) / float(self.spinbox_d.value())
        except ZeroDivisionError:
            QMessageBox.warning(self, "Zero Division Error",
                                "D (Apertures Diameter cannot be Zero")
            return
        self.K_lat = 0.364 * (1 - 0.532 * np.power(b, -1 / 3) -
                              0.024 * np.power(b, -7 / 3))
        self.K_tra = 0.364 * (1 - 0.798 * np.power(b, -1 / 3) -
                              0.018 * np.power(b, -7 / 3))

    def _updateFormulaConstants(self):
        # Calculate value to make process faster
        self.A = 0.98 * np.power(
            float(self.spinbox_d.value()) / float(self.spinbox_lambda.value()),
            0.2)

    def _calcSeeing(self):
        std_x = np.std(self.arr_delta_x)
        std_y = np.std(self.arr_delta_y)

        # Seeing
        self.current = QDateTime.currentDateTime()
        self.fwhm_lat = self.A * np.power(std_x / self.K_lat, 0.6)
        self.fwhm_tra = self.A * np.power(std_y / self.K_tra, 0.6)

        threading.Thread(target=self._plotSeeing, args=(), daemon=True).start()
        threading.Thread(target=self._writeCSV, args=(), daemon=True).start()

        self.label_info.setText("lat: " + str(self.fwhm_lat) + " | lon: " +
                                str(self.fwhm_tra))

    def _calcSeeing_arcsec(self):
        std_x = np.std(self.arr_delta_x)
        std_y = np.std(self.arr_delta_y)

        # Seeing
        self.current = QDateTime.currentDateTime()
        self.fwhm_lat = self.A * np.power(
            std_x / self.K_lat, 0.6) * 205.0 * self.spinbox_pwidth.value(
            ) / self.spinbox_focal.value()
        self.fwhm_tra = self.A * np.power(
            std_y / self.K_tra, 0.6) * 205.0 * self.spinbox_pheight.value(
            ) / self.spinbox_focal.value()

        threading.Thread(target=self._plotSeeing, args=(), daemon=True).start()
        threading.Thread(target=self._writeCSV, args=(), daemon=True).start()

        self.label_info.setText("lat: " + str(self.fwhm_lat) + " | lon: " +
                                str(self.fwhm_tra))

    def _monitor(self):

        tic = time.time()

        gray = cv2.cvtColor(self.frame, cv2.COLOR_BGR2GRAY)

        self._updateThreshold()
        _, thresholded = cv2.threshold(gray, self.THRESH, 255,
                                       cv2.THRESH_TOZERO)

        # _, contours, _ = cv2.findContours(thresholded, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        contours, _ = cv2.findContours(thresholded, cv2.RETR_EXTERNAL,
                                       cv2.CHAIN_APPROX_NONE)

        contours = contours[:2]

        # if contours.__len__() > 2:
        #     QMessageBox.warning(self, "Thresholding error", "More than 2 projections were found. " + \
        #         "Please increase threshold manually or select a better noise area.")

        cv2.drawContours(self.draw_only_frame, contours, -1, (0, 255, 0), 2)
        self._draw_noiseArea()

        try:
            moments_star_1 = cv2.moments(contours[0])
            moments_star_2 = cv2.moments(contours[1])

        except IndexError:
            print("Only {} were found ! (Must be at least 2)".format(
                len(contours)))

        else:

            try:
                cX_star1 = int(moments_star_1["m10"] / moments_star_1["m00"])
                cY_star1 = int(moments_star_1["m01"] / moments_star_1["m00"])

                cX_star2 = int(moments_star_2["m10"] / moments_star_2["m00"])
                cY_star2 = int(moments_star_2["m01"] / moments_star_2["m00"])

            except ZeroDivisionError:
                return

            if self.enable_seeing.isChecked():
                delta_x = abs(cX_star2 - cX_star1)
                delta_y = abs(cY_star2 - cY_star1)

                self.arr_delta_x.append(delta_x)
                self.arr_delta_y.append(delta_y)

                # self._calcSeeing()
                self._calcSeeing_arcsec()

            cv2.drawMarker(self.draw_only_frame, (cX_star1, cY_star1),
                           color=(0, 0, 255),
                           markerSize=30,
                           thickness=1)
            cv2.drawMarker(self.draw_only_frame, (cX_star2, cY_star2),
                           color=(0, 0, 255),
                           markerSize=30,
                           thickness=1)

        finally:

            self._displayImage()

            threading.Thread(target=self._writeVideoFile, args=(),
                             daemon=True).start()

        toc = time.time()
        elapsed = toc - tic
        try:
            print("FPS max = {}".format(int(1.0 / elapsed)))
        except ZeroDivisionError:
            pass

    def _displayImage(self):
        qImage = array2qimage(self.draw_only_frame)
        self.stars_capture.setPixmap(QPixmap(qImage))

    def _plotSeeing(self):

        self.axis_horizontal.setMin(QDateTime.currentDateTime().addSecs(-60 *
                                                                        1))
        self.axis_horizontal.setMax(QDateTime.currentDateTime().addSecs(0))

        if self.series_lat.count() > self.plot_length - 1:
            self.series_lat.removePoints(
                0,
                self.series_lat.count() - self.plot_length - 1)

        if self.series_tra.count() > self.plot_length - 1:
            self.series_tra.removePoints(
                0,
                self.series_tra.count() - self.plot_length - 1)

        if self.fwhm_lat > self.max_lat:
            self.max_lat = self.fwhm_lat
            self.axis_vertical_lat.setMax(self.max_lat + 10)
        if self.fwhm_lat < self.min_lat:
            self.min_lat = self.fwhm_lat
            self.axis_vertical_lat.setMax(self.min_lat - 10)
        if self.fwhm_tra > self.max_tra:
            self.max_tra = self.fwhm_tra
            self.axis_vertical_tra.setMax(self.max_tra + 10)
        if self.fwhm_tra < self.min_tra:
            self.min_tra = self.fwhm_tra
            self.axis_vertical_tra.setMax(self.min_tra - 10)

        # print(self.fwhm_lat, self.fwhm_tra)

        self.series_lat.append(self.current.toMSecsSinceEpoch(), self.fwhm_lat)
        self.series_tra.append(self.current.toMSecsSinceEpoch(), self.fwhm_tra)

    def importVideo(self):

        self.video_source = VideoSource.VIDEO
        self.button_export.setEnabled(True)
        self._setPauseButton()

        options = QFileDialog.Options()
        options |= QFileDialog.DontUseNativeDialog
        filename, _ = QFileDialog.getOpenFileName(
            self,
            "Import from Video File",
            QDir.currentPath(),
            "Video Files (*.avi *.mp4 *.mpeg *.flv *.3gp *.mov);;All Files (*)",
            options=options)

        if filename:
            if self.Camera != None and self.Camera.IsDevValid() == 1:
                self.Camera.StopLive()

            self.cap = cv2.VideoCapture(filename)

            # print("CAP_PROP_POS_MSEC :", self.cap.get(cv2.CAP_PROP_POS_MSEC))
            # print("CAP_PROP_POS_FRAMES :", self.cap.get(cv2.CAP_PROP_POS_FRAMES))
            # print("CAP_PROP_POS_AVI_RATIO :", self.cap.get(cv2.CAP_PROP_POS_AVI_RATIO))
            # print("CAP_PROP_FRAME_WIDTH :", self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
            # print("CAP_PROP_FRAME_HEIGHT :", self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
            # print("CAP_PROP_FPS :", self.cap.get(cv2.CAP_PROP_FPS))
            # print("CAP_PROP_FOURCC :", self.cap.get(cv2.CAP_PROP_FOURCC))
            # print("CAP_PROP_FRAME_COUNT :", self.cap.get(cv2.CAP_PROP_FRAME_COUNT))
            # print("CAP_PROP_FORMAT :", self.cap.get(cv2.CAP_PROP_FORMAT))
            # print("CAP_PROP_MODE :", self.cap.get(cv2.CAP_PROP_MODE))
            # print("CAP_PROP_BRIGHTNESS :", self.cap.get(cv2.CAP_PROP_BRIGHTNESS))
            # print("CAP_PROP_CONTRAST :", self.cap.get(cv2.CAP_PROP_CONTRAST))
            # print("CAP_PROP_SATURATION :", self.cap.get(cv2.CAP_PROP_SATURATION))
            # print("CAP_PROP_HUE :", self.cap.get(cv2.CAP_PROP_HUE))
            # print("CAP_PROP_GAIN :", self.cap.get(cv2.CAP_PROP_GAIN))
            # print("CAP_PROP_EXPOSURE :", self.cap.get(cv2.CAP_PROP_EXPOSURE))
            # print("CAP_PROP_CONVERT_RGB :", self.cap.get(cv2.CAP_PROP_CONVERT_RGB))
            # print("CAP_PROP_WHITE_APERTURE :", self.cap.get(cv2.CAP_PROP_APERTURE))
            # print("CAP_PROP_RECTIFICATION :", self.cap.get(cv2.CAP_PROP_RECTIFICATION))
            # print("CAP_PROP_ISO_SPEED :", self.cap.get(cv2.CAP_PROP_ISO_SPEED))
            # print("CAP_PROP_BUFFERSIZE :", self.cap.get(cv2.CAP_PROP_BUFFERSIZE))

            if self.cap.isOpened() == False:
                QMessageBox.warning(self, "Import from Video",
                                    "Cannot load file '{}'.".format(filename))
                return

            self.timer_interval = round(1000.0 /
                                        self.cap.get(cv2.CAP_PROP_FPS))
            try:
                self.acquisition_timer.disconnect()
            except TypeError:
                pass
            self.acquisition_timer.timeout.connect(self._grabVideoFrame)
            self.acquisition_timer.start(self.timer_interval)

    def _grabVideoFrame(self):
        ret, frame = self.cap.read()
        if ret == True:
            self.frame = cv2.resize(frame, (640, 480))
            self.draw_only_frame = self.frame.copy()
            self._monitor()

        else:
            try:
                self.acquisition_timer.disconnect()
            except TypeError:
                pass

            QMessageBox.information(self, "Import from Video",
                                    "Video complete !")
            self.cap.release()

    def exportVideo(self):
        # if not self.enable_seeing.isChecked():
        #     answer = QMessageBox.question(self,
        #         "Export to Video File",
        #         "Seeing Monitoring is not activated. Continue ?",
        #         QMessageBox.Yes|QMessageBox.No,
        #         QMessageBox.No)

        #     if answer == QMessageBox.No:
        #         return

        options = QFileDialog.Options()
        options |= QFileDialog.DontUseNativeDialog
        filename, _ = QFileDialog.getSaveFileName(
            self,
            "Export to Video File",
            QDir.currentPath(),
            "All Files (*);;Video Files (*.avi *.mp4 *.mpeg *.flv *.3gp *.mov)",
            options=options)

        if filename:
            if splitext(filename)[1] != ".avi":
                filename = splitext(filename)[0] + ".avi"
                QMessageBox.information(
                    self, "Export to Video File",
                    "Only '.avi' extension is supported. Video will be saved as '{}'"
                    .format(filename))

            print(round(1000.0 / float(self.timer_interval)))

            self.video_writer = cv2.VideoWriter(
                filename,
                cv2.VideoWriter_fourcc(*'MJPG'),
                round(1000.0 / float(self.timer_interval)),
                (
                    640, 480
                )  #################################################################################
            )
            self.export_video = True

    def _writeVideoFile(self):
        current = QDateTime.currentDateTime()
        if self.export_video and current >= self.datetimeedit_start.dateTime() and \
            current < self.datetimeedit_end.dateTime():

            # self.video_writer.write(self.frame)
            self.video_writer.write(self.draw_only_frame)

    def _setPauseButton(self):
        self.button_pause.setEnabled(True)
        self.button_pause.setText("⏸ Pause")
        self.button_pause.clicked.connect(self._pause)

    def _pause(self):
        self.pause_pressed = True

        # IC_SuspendLive IC_StopLive ##################################################################################
        self.button_pause.setText("▶ Resume")
        self.button_pause.clicked.connect(self._resume)

        if self.video_source == VideoSource.CAMERA:
            self.Camera.StopLive()
        else:
            self.acquisition_timer.stop()

    def _resume(self):
        self.pause_pressed = False
        self._setPauseButton()

        if self.video_source == VideoSource.CAMERA:
            self.Camera.StartLive(0)
        else:
            try:
                self.acquisition_timer.disconnect()
            except TypeError:
                pass

            self.acquisition_timer.start(self.timer_interval)

            if self.video_source == VideoSource.CAMERA:
                pass
            elif self.video_source == VideoSource.SIMULATION:
                self.acquisition_timer.timeout.connect(self._updateSimulation)
            elif self.video_source == VideoSource.VIDEO:
                self.acquisition_timer.timeout.connect(self._grabVideoFrame)
Exemple #6
0
class SproutUI(QtWidgets.QMainWindow):
    def __init__(self):
        super(SproutUI, self).__init__()
        uic.loadUi('Sprout.ui', self)
        self.setWindowTitle("Sprout")
        self.setWindowIcon(QIcon('./Images/SproutIcon.ico'))

        self.get_default_path()

        self.save_window_ui = SaveWindow(self)

        self.chartView = None

        self.myThread = None
        # columns: wedges, rows: rings (r1(w1,w2,...,w7,wAvg),r2(...),r3(...),rAvg(...))
        self.densities = []
        self.measurement_data = []

        self.error_message = ""

        self.loading_image = QPixmap("./Images/LoadingImage")

    def ui(self):
        """
        Sets Sprout user interface and displays the user interface
        :return: None
        """
        self.tabWidget_1.setCurrentIndex(0)
        self.tabWidget_2.setCurrentIndex(0)
        self.lineEdit_numMeasurements.setFocus(0)

        # Main Screen and save button
        self.browse_button_1.clicked.connect(self.browse_file)
        self.browse_button_2.clicked.connect(self.browse_folder)
        self.lineEdit_intermediateStepPath.setText(in_data['intermediate_path'])

        self.lineEdit_numMeasurements.editingFinished.connect(self.update_num_diameter_measurements)
        self.lineEdit_numWedges.editingFinished.connect(self.update_num_wedges)
        self.lineEdit_numRings.editingFinished.connect(self.update_num_rings)
        self.lineEdit_imageDPI.editingFinished.connect(self.update_image_dpi)

        self.start_button.clicked.connect(self.start_button_func)
        self.stop_button.clicked.connect(self.stop_button_func)
        self.save_button.clicked.connect(self.show_save_files)

        # Graphs View
        self.comboBox_rings.currentIndexChanged.connect(self.filter_rings_graph)
        self.comboBox_wedges.currentIndexChanged.connect(self.filter_wedges_graph)

        self.progressBar.setValue(0)
        self.progressBar.hide()
        self.label_progressBar.hide()

        self.disable_dashboard()

        self.progressBar.valueChanged.connect(self.progress_change)

        self.tabWidget_1.tabBar().setCursor(QtCore.Qt.PointingHandCursor)
        self.tabWidget_2.tabBar().setCursor(QtCore.Qt.PointingHandCursor)

        self.show()

    def get_default_path(self):
        """
        Gets the "Documents" directory of the user of the local computer.
        :return: None
        """
        temp = str(os.path.expanduser("~"))
        split = temp.split(os.path.sep)
        temp = str(split[0] + "/" + split[1] + "/" + split[2] + "/" + "Documents/Sprout/Run")
        in_data['intermediate_path'] = temp

    def browse_file(self):
        """
        Function mapped to the browse button used to select the file path of the image that will be used to
        calculate the fiber density of a bamboo cross-section.
        :return: None
        """
        url = QFileDialog.getOpenFileName(self, "Open a file", "", "*jpg; *jpeg;; *bmp;; *tif")

        if url[0] is not '':
            try:
                self.label_bamboo.setPixmap(self.loading_image)
                self.label_bamboo.repaint()
            except Exception:
                self.warning_message_box("Missing loading image. ")

            try:
                assert (url[0])[-4:] in ('.bmp', '.jpg', 'jpeg', '.tif'), "Image format is not supported."
            except Exception as e:
                self.label_bamboo.clear()
                self.warning_message_box(str(e))
                return

            try:
                cross_section = QPixmap(url[0])
                cross_section = cross_section.scaled(500, 500)
                if cross_section.isNull():
                    self.label_bamboo.clear()
                    self.warning_message_box("Unable to open input file.  \n\n")
                else:
                    self.label_bamboo.clear()
                    self.label_bamboo.setPixmap(cross_section)
                    self.lineEdit_imagePath.setText(url[0])
                    in_data['img_path'] = url[0]
            except Exception as e:
                self.label_bamboo.clear()
                self.warning_message_box("Unable to open input file, verify  \n file path or image file type.\n\n")

    def browse_folder(self):
        """
        Function mapped to the browse button used to select the folder path that will be used to
        save the intermediate step for the fiber density calculation.
        :return: None
        """
        url = QFileDialog.getExistingDirectory(self, "Open a directory", "", QFileDialog.ShowDirsOnly)
        if url is not '':
            self.lineEdit_intermediateStepPath.setText(url)
            in_data['intermediate_path'] = url

    def disable_dashboard(self):
        """
        Disables the dashboard and hides the graphs.
        :return: None
        """
        self.dashboard_tab.setEnabled(False)
        self.tabWidget_2.setEnabled(False)
        self.graphs_tab.setEnabled(False)
        self.region_density_tab.setEnabled(False)
        self.measurement_data_tab.setEnabled(False)

        self.tabWidget_2.setCurrentIndex(0)

        self.widget_rings.hide()
        self.widget_wedges.hide()
        self.comboBox_rings.hide()
        self.comboBox_wedges.hide()

    def update_num_diameter_measurements(self):
        """
        Update the value for the total number of measurements in the user interface main screen that serves as feedback.
        :return: None
        """
        global in_data

        if self.is_int_inbound(self.lineEdit_numMeasurements.text(), 3, 100):
            temp = int(self.lineEdit_numMeasurements.text()) * 4
            self.label_numMeasurementsFeedback.setText("Total Diameter Measurements: " + str(temp))
            in_data['num_measurement'] = temp
        else:
            self.lineEdit_numMeasurements.clear()

    def update_num_wedges(self):
        """
        Update the value for number of wedges in the user interface main screen that serves as feedback.
        :return: None
        """
        global in_data, wedge_degree

        if self.is_int_inbound(self.lineEdit_numWedges.text(), 3, 100):
            temp = int(self.lineEdit_numWedges.text()) * 4
            wedge_degree = 360 / temp
            self.label_numWedgesFeedback.setText("Num. Wedges: " + str(temp) + " @ {:.1f}º".format(wedge_degree))
            self.label_numRegionsFeedback.setText("Num. Regions: " + str(temp * int(in_data['num_rings'])))
            in_data['num_wedges'] = temp
        else:
            self.lineEdit_numWedges.clear()

    def update_num_rings(self):
        """
        Update the value for number of rings in the user interface main screen that serves as feedback.
        :return: None
        """
        global in_data

        temp = self.lineEdit_numRings.text()
        if self.is_int_inbound(temp, 1, 25):
            self.label_numRingsFeedback.setText("Num. Rings: " + str(temp))
            self.label_numRegionsFeedback.setText("Num. Regions: " + str(int(temp) * int(in_data['num_wedges'])))
            in_data['num_rings'] = int(temp)
        else:
            self.lineEdit_numRings.clear()

    def update_image_dpi(self):
        """
        Update the value for the image dpi (dot per inch) in the user interface main screen that serves as feedback.
        :return: None
        """
        global in_data

        if self.is_int_inbound(self.lineEdit_imageDPI.text(), 1200, 4800):
            temp = int(self.lineEdit_imageDPI.text())
            self.label_imageDPIFeedback.setText("Image DPI: " + str(temp))
            in_data['img_dpi'] = temp
        else:
            self.lineEdit_imageDPI.clear()

    def start_button_func(self):
        """
        Sets what the start button will do when it is pressed. It starts the fiber density calculation and
        disables user input. The button is replaced by the stop button.
        :return: None
        """
        global in_data, debounce
        # if program has not started
        if debounce is not 0:
            if (time.time() - debounce) < .30:
                return
            debounce = 0

        # Test input data for being empty
        if(self.lineEdit_imagePath.text() is "" or self.lineEdit_intermediateStepPath.text() is "" or
                self.lineEdit_numWedges.text() is "" or self.lineEdit_numRings.text() is "" or
                self.lineEdit_numMeasurements.text() is "" or self.lineEdit_imageDPI.text() is ""):
            self.warning_message_box("Make sure all inputs are filled in.")
            return

        # Test numeric input
        if not self.is_int_inbound(self.lineEdit_numMeasurements.text(), 3, 100, self.label_numMeasurements.text()):
            return
        if not self.is_int_inbound(self.lineEdit_numWedges.text(), 3, 100, self.label_numWedges.text()):
            return
        if not self.is_int_inbound(self.lineEdit_numRings.text(), 1, 25, self.label_numRings.text()):
            return
        if not self.is_int_inbound(self.lineEdit_imageDPI.text(), 1200, 4800, self.label_imageDPI.text()):
            return

        # After all inputs have been validated
        self.disable_dashboard()

        # Save input data in in_data dictionary
        in_data['units'] = self.comboBox_units.currentText()
        in_data['num_measurement'] = int(self.lineEdit_numMeasurements.text())*4
        in_data['img_dpi'] = int(self.lineEdit_imageDPI.text())
        in_data['enhance'] = bool(self.checkBox_imageEnhancement.isChecked())
        in_data['pixelMap'] = bool(self.checkBox_pixelMap.isChecked())

        self.inputs_set_enabled(False)

        self.progressBar.show()
        self.label_progressBar.show()
        self.progressBar.setValue(1)

        self.stop_button.setEnabled(True)
        self.start_button.hide()

        # Start Sprout Controller for fiber density calculation
        self.myThread = Sprout.SproutController(self, in_data)
        try:
            self.myThread.start()
            self.myThread.progress.connect(self.progressBar.setValue)
        except:
            self.warning_message_box("Error while starting process.")

    def stop_button_func(self):
        """
        Sets what the stop button will do when it is pressed or is called when the fiber density calculation
        is completed. It cancels the running session and enables user input. If running session is completed it
        enables the user input, and proceeds to display the dashboard (graphs, region density table, and measurement
         data). The button is replaced by the start button.
        :return: None
        """
        global debounce
        # if program is currently in progress

        self.stop_button.setEnabled(False)
        self.stop_button.repaint()

        if not self.myThread.isFinished():
            self.myThread.requestInterruption()
            self.myThread.wait()

        if self.progressBar.value() == 100:
            # if finished successfully

            # self.progressBar.setValue(100)
            self.dashboard_tab.setEnabled(True)
            self.tabWidget_2.setEnabled(True)
            self.graphs_tab.setEnabled(True)
            self.region_density_tab.setEnabled(True)
            self.measurement_data_tab.setEnabled(True)

            # create graphs
            self.create_graphs()

            # create table
            self.create_table()

            # set measurement data
            self.display_measurement_data()

            self.tabWidget_1.setCurrentIndex(1)
            self.tabWidget_2.setCurrentIndex(0)

        self.inputs_set_enabled(True)

        self.progressBar.hide()
        self.label_progressBar.hide()
        self.progressBar.setValue(0)

        self.start_button.show()
        debounce = time.time()

    def inputs_set_enabled(self, val: bool):
        """
        Enable or disable the options presented in the home screen depending on input parameter: val.
        :param val: True to anabel all the options in the home screen and False to disable.
        :return: None
        """
        self.browse_button_1.setEnabled(val)
        self.browse_button_2.setEnabled(val)
        self.comboBox_units.setEnabled(val)
        self.lineEdit_numMeasurements.setEnabled(val)
        self.lineEdit_numWedges.setEnabled(val)
        self.lineEdit_numRings.setEnabled(val)
        self.lineEdit_imageDPI.setEnabled(val)
        self.checkBox_imageEnhancement.setEnabled(val)
        self.checkBox_pixelMap.setEnabled(val)

        self.label_imagePath.setEnabled(val)
        self.label_intermediateStepPath.setEnabled(val)
        self.label_numMeasurements.setEnabled(val)
        self.label_numWedges.setEnabled(val)
        self.label_numRings.setEnabled(val)
        self.label_imageDPI.setEnabled(val)
        self.label_units.setEnabled(val)

    def create_graphs(self):
        """
        Creates the graphs that will be displayed int the dashboard's Graphs tab.
        :return: None
        """
        global default_comboBox_graph_item_count

        # Set Graphs ComboBox
        for x in range(self.comboBox_rings.count()):
            self.comboBox_rings.removeItem(default_comboBox_graph_item_count)

        for x in range(self.comboBox_wedges.count()):
            self.comboBox_wedges.removeItem(default_comboBox_graph_item_count)

        self.comboBox_rings.setCurrentIndex(0)
        self.comboBox_wedges.setCurrentIndex(0)

        # Ring Graph
        self.ring_chart = QChart()

        for x in range(len(self.densities)):
            ring_series = QLineSeries()
            for y in range(len(self.densities[x])-1):
                ring_series.append(y+1, self.densities[x][y])
            self.ring_chart.addSeries(ring_series)
            if x < len(self.densities)-1:
                self.comboBox_rings.addItem("Ring " + str(x+1))

        self.ring_chart.setTitle('Fiber Density VS Wedges')
        self.ring_chart.legend().hide()
        self.ring_chart.createDefaultAxes()
        self.ring_chart.axes(Qt.Horizontal)[0].setRange(1, len(self.densities[0])-1)
        self.ring_chart.axes(Qt.Vertical)[0].setRange(0, 1)
        self.ring_chart.axes(Qt.Horizontal)[0].setTitleText("Wedge Number")
        self.ring_chart.axes(Qt.Vertical)[0].setTitleText("Fiber Density")

        self.chartView = QChartView(self.ring_chart, self.widget_rings)
        self.chartView.resize(self.widget_rings.size())

        # Wedges Graph
        self.wedge_chart = QChart()

        for y in range(len(self.densities[0])):
            if in_data['num_rings'] == 1:
                ring_series = QScatterSeries()
            else:
                ring_series = QLineSeries()
            for x in range(len(self.densities)-1):
                ring_series.append(x+1, self.densities[x][y])
            self.wedge_chart.addSeries(ring_series)
            if y < len(self.densities[0])-1:
                self.comboBox_wedges.addItem("Wedge " + str(y+1))

        self.wedge_chart.setTitle('Fiber Density VS Rings')
        self.wedge_chart.legend().hide()
        self.wedge_chart.createDefaultAxes()
        if (len(self.densities)) == 2:
            self.wedge_chart.axes(Qt.Horizontal)[0].setRange(0, 2)
        else:
            self.wedge_chart.axes(Qt.Horizontal)[0].setRange(1, len(self.densities)-1)
        self.wedge_chart.axes(Qt.Vertical)[0].setRange(0, 1)
        self.wedge_chart.axes(Qt.Horizontal)[0].setTitleText("Ring Number")
        self.wedge_chart.axes(Qt.Vertical)[0].setTitleText("Fiber Density")

        self.chartView = QChartView(self.wedge_chart, self.widget_wedges)
        self.chartView.resize(self.widget_wedges.size())

        self.widget_rings.show()
        self.widget_wedges.show()

        if in_data['num_rings'] == 1:
            self.comboBox_rings.hide()
        else:
            self.comboBox_rings.show()
        self.comboBox_wedges.show()

    def filter_rings_graph(self):
        """
        Filters the rings graph by: All(includes average), All Wedges(only rings are shown),
        Average(only average is shown), and individual rings(varies depending on number of rings).
        :return: None
        """
        global default_comboBox_graph_item_count

        for x in range(len(self.ring_chart.series())):
            self.ring_chart.series()[x].show()

        if self.comboBox_rings.currentText() == "All Rings":
            for x in range(len(self.ring_chart.series()) - 1):
                self.ring_chart.series()[x].show()
            self.ring_chart.series()[len(self.ring_chart.series()) - 1].hide()
        elif self.comboBox_rings.currentText() == "Average":
            for x in range(len(self.ring_chart.series()) - 1):
                self.ring_chart.series()[x].hide()
            self.ring_chart.series()[len(self.ring_chart.series()) - 1].show()
        elif "Ring" in self.comboBox_rings.currentText():
            for x in range(len(self.ring_chart.series())):
                self.ring_chart.series()[x].hide()
            self.ring_chart.series()[self.comboBox_rings.currentIndex() - default_comboBox_graph_item_count].show()

    def filter_wedges_graph(self):
        """
        Filters the wedges graph by: All(includes average), All Wedges(only wedges are shown),
        Average(only average is shown), and individual wedges(varies depending on number of wedges).
        :return: None
        """
        global default_comboBox_graph_item_count

        for x in range(len(self.wedge_chart.series())):
            self.wedge_chart.series()[x].show()

        if self.comboBox_wedges.currentText() == "All Wedges":
            for x in range(len(self.wedge_chart.series()) - 1):
                self.wedge_chart.series()[x].show()
            self.wedge_chart.series()[len(self.wedge_chart.series()) - 1].hide()
        elif self.comboBox_wedges.currentText() == "Average":
            for x in range(len(self.wedge_chart.series()) - 1):
                self.wedge_chart.series()[x].hide()
            self.wedge_chart.series()[len(self.wedge_chart.series()) - 1].show()
        elif "Wedge" in self.comboBox_wedges.currentText():
            for x in range(len(self.wedge_chart.series())):
                self.wedge_chart.series()[x].hide()
            self.wedge_chart.series()[self.comboBox_wedges.currentIndex() - default_comboBox_graph_item_count].show()

    def create_table(self):
        """
        Creates the table that will be presented in the dashboard's Region Density tab
        based on fiber density calculations.
        :return: None
        """
        i = 0
        j = 0
        column_name = []
        row_name = []

        self.tableWidget.setRowCount(len(self.densities))
        self.tableWidget.setColumnCount(len(self.densities[0]))

        for ring in self.densities:
            for wedge in ring:
                item = QTableWidgetItem("{:.4f}".format(wedge))
                item.setTextAlignment(Qt.AlignCenter)

                # if average of average
                if j == (len(self.densities[0])-1) and i == (len(self.densities)-1):
                    font = QFont()
                    font.setBold(True)
                    item.setFont(font)

                self.tableWidget.setItem(i, j, item)
                j += 1
            j = 0
            i += 1

        for x in range(len(self.densities[0])):
            if x == len(self.densities[0]) - 1:
                column_name.append("Average")
            else:
                column_name.append("Wedge " + str(x + 1))
        for y in range(len(self.densities)):
            if y == len(self.densities) - 1:
                row_name.append("Average")
            else:
                row_name.append("Ring " + str(y + 1))

        self.tableWidget.setHorizontalHeaderLabels(column_name)
        self.tableWidget.setVerticalHeaderLabels(row_name)
        self.tableWidget.setEditTriggers(QtWidgets.QTableWidget.NoEditTriggers)

    def display_measurement_data(self):
        """
        Manages all output data related to the measurement data that is presented in the dashboard Measurement Data.
        :return: None
        """
        self.lineEdit_area.setText(str("{:.4f}".format(self.measurement_data[0])) + " " + in_data['units'] + "^2")
        self.lineEdit_avgOuterDiameter.setText(str("{:.4f}".format(self.measurement_data[1])) + " " + in_data['units'])
        self.lineEdit_avgInnerDiameter.setText(str("{:.4f}".format(self.measurement_data[2])) + " " + in_data['units'])
        self.lineEdit_AverageT.setText(str("{:.4f}".format(self.measurement_data[3])) + " " + in_data['units'])
        self.lineEdit_centroid_x.setText(str("{:.4f}".format(self.measurement_data[4])) + " " + in_data['units'])
        self.lineEdit_centroid_y.setText(str("{:.4f}".format(self.measurement_data[5])) + " " + in_data['units'])
        self.lineEdit_momentOfInertia_x.setText(str("{:.4f}".format(self.measurement_data[6])) + " " +
                                                in_data['units'] + "^4")
        self.lineEdit_momentOfInertia_y.setText(str("{:.4f}".format(self.measurement_data[7])) + " " +
                                                in_data['units'] + "^4")
        self.lineEdit_productOfInertia.setText(str("{:.4f}".format(self.measurement_data[8])) + " " +
                                               (in_data['units']) + "^4")

    def is_int_inbound(self, ui_in: str, lower: int, upper: int, ui_in_name: str = None):
        """
        Test if user input is in specified upper and lower bound, including upper and lower bound value.
        :param ui_in: user input for value that will be tested
        :param lower: lowest value that ui_in can have to return True
        :param upper: highest value that ui_in can hava to return True
        :param ui_in_name: user input label name that is used in popup messages for users to relate error
        :return: True if ui_in is in upper and lower bound (lower <= ui_in <= upper)
                    otherwise False
        """
        if not (str.isdigit(ui_in)) or int(ui_in) > upper or int(ui_in) < lower:
            if ui_in_name is not None:
                self.warning_message_box(str(ui_in_name) + "\nPlease input a number from "
                                         + str(lower) + " to " + str(upper))
            return False
        else:
            return True

    def warning_message_box(self, message):
        """
        Display a popup message box to inform users of error.
        :param message: Message to be displayed in the popup message
        :return:
        """
        mbox = QMessageBox.critical(self, "Warning!!", message)
        if mbox == QMessageBox.Ok:
            self.lineEdit_numMeasurements.setFocus(0)

    def show_save_files(self):
        """
        Call to displays the popup for the Save Popup Window.
        :return: None
        """
        self.windowModality()
        self.save_window_ui.lineEdit_filePath.setText(os.getcwd())
        self.save_window_ui.show()
        self.save_window_ui.raise_()

    def progress_change(self):
        """
        Signals the user interface when process is completed or wen error occurs.
        :return: None
        """
        if self.progressBar.value() == 2:
            self.stop_button_func()
            self.warning_message_box(str(self.error_message))
        elif self.progressBar.value() == 100:
            self.densities = DM.get_fiber_density_average()
            self.measurement_data = DM.get_dimensional_measurements()
            self.stop_button_func()
class MyWidget(QWidget):
    def __init__(self):
        super().__init__()
        uic.loadUi('ws_ui.ui', self)
        self.con = sqlite3.connect("ws_database.db")
        self.setWindowFlags(QtCore.Qt.FramelessWindowHint)

        self.create_linechart()
        self.tmp, self.hmd, self.prs = 0, 0, 0
        self.make_measure()

        self.measure_timer = QTimer(self)
        self.measure_timer.setInterval(MEASURE_FREQUENCIES * 1000)
        self.measure_timer.timeout.connect(self.make_measure)
        self.measure_timer.start()

        self.update_timer = QTimer(self)
        self.update_timer.setInterval(1000)
        self.update_timer.timeout.connect(self.update_labels)
        self.update_timer.start()

        self.update_labels()

    def quit(self):
        self.destroy()
        quit()

    def make_measure(self):
        sns = sensor_measure()

        if sns[0] != ERROR_CODE:
            self.tmp = sns[0]

        else:
            print('tmp error')

        if sns[1] != ERROR_CODE:
            self.hmd = sns[1]

        else:
            print('hmd error')

        if sns[2] != ERROR_CODE:
            self.prs = sns[2]

        else:
            print('prs error')

        time = int(dt.datetime.now().timestamp())

        req = """
              INSERT INTO short_term_data(tmp, hmd, prs, time_from_epoch)
              VALUES(?,?,?,?)
              """

        self.con.execute(req, (self.tmp, self.hmd, self.prs, time))
        self.con.commit()

        self.update_linechart()

    def update_labels(self):
        deg = u'\N{DEGREE SIGN}'
        hpa = 'ʰᴾᵃ'
        self.time_label.setText(dt.datetime.now().strftime('%H:%M'))
        self.tmp_label.setText('{} {}C'.format(self.tmp, deg))
        self.hmd_label.setText('{} %'.format(self.hmd))
        self.prs_label.setText('{} {}'.format(self.prs, hpa))

    def create_linechart(self):
        self.chart = QChart()
        self.chart.legend().hide()

        self.series = QLineSeries()

        self.axisValue = QValueAxis()
        self.axisCurrentTime = QValueAxis()
        self.axisTime = QDateTimeAxis()
        self.axisTime.setFormat("hh:mm")

        self.chartview = QChartView(self.chart, self.groupBox)
        self.chartview.resize(540, 460)
        self.chartview.move(0, 0)
        self.chartview.setRenderHint(QPainter.Antialiasing)

    def update_linechart(self):
        if self.axisTime in self.chart.axes():
            self.chart.removeAxis(self.axisTime)

        if self.axisCurrentTime in self.chart.axes():
            self.chart.removeAxis(self.axisCurrentTime)

        if self.axisValue in self.chart.axes():
            self.chart.removeAxis(self.axisValue)

        if self.series in self.chart.series():
            self.chart.removeSeries(self.series)

        self.series.clear()

        self.axisValue.setMax(50)
        self.axisValue.setMin(-50)

        req = """
              SELECT tmp, time_from_epoch
              FROM short_term_data
              WHERE (time_from_epoch - ?) < 86400 AND NOT tmp = ?
              """

        cur = self.con.cursor()
        result = list(cur.execute(req, (int(dt.datetime.now().timestamp()), ERROR_CODE)))

        for measure in result:
            self.series.append(measure[1] * 1000, measure[0])

        self.axisTime.setMin(QDateTime.fromMSecsSinceEpoch(int(dt.datetime.now().timestamp()) * 1000 - 86390000))
        self.axisTime.setMax(QDateTime.fromMSecsSinceEpoch(int(dt.datetime.now().timestamp()) * 1000))

        self.chart.addSeries(self.series)
        self.chart.addAxis(self.axisTime, Qt.AlignBottom)
        self.series.attachAxis(self.axisTime)
        self.chart.addAxis(self.axisValue, Qt.AlignLeft)
        self.series.attachAxis(self.axisValue)

        self.chart.setTitle('Температура')