AnalyseMorphologique/utils/gui/pyqt/main_window/MainWindow.py

365 lines
15 KiB
Python

"""
Created on Mon Apr 24 2023
@name: MainWindow.py
@desc: Main window of the application
@auth: Djalim Simaila
@e-mail: djalim.simaila@inrae.fr
"""
import os
from PyQt5 import QtWidgets
from PyQt5.QtCore import QThread
from PyQt5.QtWidgets import QFileDialog, QWidget
from utils.files.input import ScannedObject
from utils.gui.pyqt.settings.Settings import Settings
from utils.settings.SettingManager import SettingManager
from utils.graph2D.visplot_render import cross_section, render2D
from utils.graph3D.visplot_render import render3D
from utils.gui.pyqt.main_window.UI_MainWindow import Ui_MainWindow
from utils.gui.pyqt.main_window.Workers.DiscreteDataWorker import DiscreteDataProcessWorker
from utils.gui.pyqt.main_window.Workers.PreProcessWorker import PreProcessWorker
from utils.gui.pyqt.main_window.Workers.RawDataWorker import RawDataProcessWorker
from utils.gui.pyqt.error_popup.ErrorPopup import ErrorPopup
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
"""
Main window of the application, it contains all the UI elements
"""
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
# Retrieve the UI
self.setupUi(self)
# Setup buttons listeners
self.start_analyse_button.clicked.connect(self.start_preprocess)
self.input_file_choose_btn.clicked.connect(self.select_file)
self.output_folder_choose_btn.clicked.connect(self.select_folder)
self.show_graph_checkbox.stateChanged.connect(self.toggle_graphs)
self.actionSauvegarder_le_model_redress.triggered.connect(self.save_model)
self.actionPr_f_rennces.triggered.connect(self.show_settings)
self.graphType = [
"Aucun",
"Mesh3D",
"Coupe XZ",
"Coupe YZ",
"Evolution du rayon moyen",
]
self.obj = None
self.raw_data= None
self.discrete_data = None
self.completed = 0
self.total = 2
self.comboBoxes = [
self.slot0ComboBox,
self.slot1ComboBox,
self.slot2ComboBox,
self.slot3ComboBox,
self.slot4ComboBox,
self.slot5ComboBox,
self.slot6ComboBox,
self.slot7ComboBox,
self.slot8ComboBox,
self.slot9ComboBox,
self.slot10ComboBox
]
for cb in self.comboBoxes:
cb.addItems(self.graphType)
self.slots = [
[self.slot0,"Aucun"],
[self.slot1,"Aucun"],
[self.slot2,"Aucun"],
[self.slot3,"Aucun"],
[self.slot4,"Aucun"],
[self.slot5,"Aucun"],
[self.slot6,"Aucun"],
[self.slot7,"Aucun"],
[self.slot8,"Aucun"],
[self.slot9,"Aucun"],
[self.slot10,"Aucun"]
]
for slot_nb,slot in enumerate(self.slots):
slot[1] = SettingManager.get_instance().get_last_graph(slot_nb)
self.comboBoxes[slot_nb].setCurrentText(slot[1])
self.settings_window = Settings()
self.threads = []
###############################################################################
# #
# #
# Input/Setting Management #
# #
# #
###############################################################################
def select_file(self):
"""
Open a file dialog to select the input file
"""
file = QFileDialog.getOpenFileName()[0]
self.input_file_path.setPlainText(file)
self.output_file_prefix.setText(os.path.splitext(os.path.basename(file))[0])
def select_folder(self):
"""
Open a file dialog to select the output folder
"""
self.output_folder_path.setPlainText(QFileDialog.getExistingDirectory())
def check_input_file(self):
"""
Check if the input file is valid
"""
if not os.path.isfile(self.input_file_path.toPlainText()):
ErrorPopup("Fichier d'entrée invalide",button_label="Choisir un fichier d'entrée",button_callback=self.select_file).show_popup()
return False
return True
def check_output_folder(self):
"""
Check if the output folder is valid
"""
if not os.path.isdir(self.output_folder_path.toPlainText()):
ErrorPopup("Dossier de sortie invalide",button_label="Choisir un dossier de sortie",button_callback=self.select_folder).show_popup()
return False
return True
###############################################################################
# #
# #
# Data Processing #
# #
# #
###############################################################################
def start_preprocess(self):
"""
Start the analyse, create the thread and connect the signals.
"""
if not self.check_input_file():
return
if not self.check_output_folder():
return
settings = SettingManager.get_instance()
for count,_ in enumerate(self.slots):
self.slots[count][1] = self.comboBoxes[count].currentText()
settings.set_last_graph(count,self.slots[count][1])
self.clear_graphs()
self.completed = 0
# Create the thread to run the analyse
self.preprocess_thread = QThread()
self.preprocess_worker = PreProcessWorker("PreProcessWorker",self.input_file_path.toPlainText())
self.preprocess_worker.moveToThread(self.preprocess_thread)
# Connect the signals
# Start
self.preprocess_thread.started.connect(self.preprocess_worker.run)
# Progress
self.preprocess_worker.status.connect(self.set_status)
self.preprocess_worker.progress.connect(self.update_progress_bar)
self.preprocess_worker.processed_obj.connect(self.set_obj)
self.preprocess_worker.processed_obj.connect(self.process_raw_data)
self.preprocess_worker.processed_obj.connect(self.process_discrete_data)
# Finished
self.preprocess_worker.finished.connect(self.preprocess_thread.quit)
self.preprocess_worker.finished.connect(self.preprocess_worker.deleteLater)
self.preprocess_thread.finished.connect(self.preprocess_thread.deleteLater)
# Start the thread
self.preprocess_thread.start()
self.start_analyse_button.setEnabled(False)
def process_raw_data(self, obj:ScannedObject):
"""
Start the analyse, create the thread and connect the signals.
"""
self.processrawdata_thread = QThread()
self.processraw_worker = RawDataProcessWorker("RawDataProcessWorker",
obj,
self.output_folder_path.toPlainText(),
self.output_file_prefix.text(),
self.discretisation_value_selector.value())
self.processraw_worker.moveToThread(self.processrawdata_thread)
# Connect the signals
# Start
self.processrawdata_thread.started.connect(self.processraw_worker.run)
# Progress
self.processraw_worker.status.connect(self.set_status)
self.processraw_worker.progress.connect(self.update_progress_bar)
self.processraw_worker.processedData.connect(self.set_raw_data)
# Finished
self.processraw_worker.finished.connect(self.finish_analyse)
self.processraw_worker.finished.connect(self.processrawdata_thread.quit)
self.processraw_worker.finished.connect(self.processraw_worker.deleteLater)
self.processrawdata_thread.finished.connect(self.processrawdata_thread.deleteLater)
# Start the thread
self.processrawdata_thread.start()
def process_discrete_data(self, obj:ScannedObject):
"""
Start the analyse, create the thread and connect the signals.
"""
self.processdiscrete_thread = QThread()
self.processdiscrete_worker = DiscreteDataProcessWorker("DiscreteDataProcessWorker",
obj,
self.output_folder_path.toPlainText(),
self.output_file_prefix.text(),
self.discretisation_value_selector.value())
self.processdiscrete_worker.moveToThread(self.processdiscrete_thread)
# Connect the signals
# Start
self.processdiscrete_thread.started.connect(self.processdiscrete_worker.run)
# Progress
self.processdiscrete_worker.status.connect(self.set_status)
self.processdiscrete_worker.progress.connect(self.update_progress_bar)
self.processdiscrete_worker.processedData.connect(self.set_discrete_data)
# Finished
self.processdiscrete_worker.finished.connect(self.finish_analyse)
self.processdiscrete_worker.finished.connect(self.processdiscrete_thread.quit)
self.processdiscrete_worker.finished.connect(self.processdiscrete_worker.deleteLater)
self.processdiscrete_thread.finished.connect(self.processdiscrete_thread.deleteLater)
# Start the thread
self.processdiscrete_thread.start()
def set_obj(self,obj:ScannedObject):
"""
Set the obj to the main window
"""
self.obj = obj
def set_discrete_data(self,discrete_data:dict):
"""
Set the discrete data to the main window
"""
self.discrete_data = discrete_data
def set_raw_data(self,raw_data:dict):
"""
Set the raw data to the main window
"""
self.raw_data = raw_data
def save_model(self):
"""
Save the model to a file
"""
if self.obj is None:
ErrorPopup("Aucune analyse effectuée. Aucun modèle à sauvegarder").show_popup()
return
file_path = QFileDialog.getSaveFileName(self,
"Sauvegarder le modèle",
"./",
"Fichier OBJ (*.obj)")
self.obj.export_obj(file_path[0])
###############################################################################
# #
# #
# Graphs management #
# #
# #
###############################################################################
def toggle_graphs(self):
"""
Show or hide the graphs
"""
if self.show_graph_checkbox.isChecked():
self.Graphs.show()
else:
self.Graphs.hide()
def renderGraphs(self,obj:ScannedObject,raw_data:dict,discrete_data:dict):
"""
Render the graphs
:param obj: The scanned object
:param raw_data: The raw data
:param discrete_data: The discrete data
"""
if not self.show_graph_checkbox.isChecked():
return
for slot in self.slots:
current_slot = slot[0]
graph_type = slot[1]
if graph_type == "Mesh3D":
current_slot.addWidget(render3D(obj,False).native)
if graph_type == "Coupe XZ":
current_slot.addWidget(cross_section(obj.get_x(),
obj.get_z(),
"Coupe XZ",
"X (en mm)",
"Z (en mm)",
False).native)
if graph_type == "Coupe YZ":
current_slot.addWidget(cross_section(obj.get_y(),
obj.get_z(),
"Coupe YZ",
"Y (en mm)",
"Z (en mm)",
False).native)
if graph_type == "Evolution du rayon moyen":
current_slot.addWidget(render2D(list(zip(discrete_data['Z moy (en mm)'],discrete_data['Rayon moyen (en mm)'])),
"Evolution du rayon moyen en fonction de Z",
"Z (en mm)",
"Rayon moyen (en mm)\n",
False).native)
def clear_graphs(self):
"""
Clear the graphs
"""
if not self.show_graph_checkbox.isChecked():
return
for slot,_ in self.slots:
for i in reversed(range(slot.count())):
slot.itemAt(i).widget().setParent(None)
###############################################################################
# #
# #
# User interface updates #
# #
# #
###############################################################################
def finish_analyse(self):
"""
Finish the analyse
"""
self.completed += 1
if self.completed == self.total:
self.status_text.setText("Done")
self.analyse_progress_bar.setValue(100)
self.renderGraphs(self.obj,self.raw_data,self.discrete_data)
self.start_analyse_button.setEnabled(True)
def update_progress_bar(self, value):
"""
Update the progress bar
"""
self.analyse_progress_bar.setValue(value)
def set_status(self, status:str):
"""
Set the status of the analyse
"""
self.status_text.setText(status)
def show_settings(self):
"""
Show the settings window
"""
self.settings_window.show()