AnalyseMorphologique/utils/gui/pyqt/main_window/MainWindow.py
Djalim Simaila bb6f078ec2 🐛 fix(MainWindow.py): fix bug where graph settings were not being saved when the number of graphs was changed
 feat(MainWindow.py): add functionality to save graph settings when the number of graphs is changed
The bug was caused by the fact that the graph settings were not being saved when the number of graphs was changed. This was fixed by adding code to save the graph settings when the number of graphs is changed. The new functionality allows the user to change the number of graphs and have the graph settings saved.
2023-05-02 10:25:09 +02:00

393 lines
16 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_tasks = 0
self.total_tasks = 2
self.combo_boxes = [
self.slot0ComboBox,
self.slot1ComboBox,
self.slot2ComboBox,
self.slot3ComboBox,
self.slot4ComboBox,
self.slot5ComboBox,
self.slot6ComboBox,
self.slot7ComboBox,
self.slot8ComboBox,
self.slot9ComboBox,
self.slot10ComboBox
]
for combo_box in self.combo_boxes:
combo_box.addItems(self.graphType)
combo_box.currentIndexChanged.connect(self.graph_type_changed)
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.combo_boxes[slot_nb].setCurrentText(slot[1])
self.graph_nb =0
self.graph_type_changed()
self.settings_window = Settings()
self.has_changed = True
self.old_discretisation_value = None
###############################################################################
# #
# #
# 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])
if self.output_folder_path.toPlainText() is None or self.output_folder_path.toPlainText() == "":
self.output_folder_path.setPlainText(os.path.dirname(file))
self.has_changed = True
def select_folder(self):
"""
Open a file dialog to select the output folder
"""
self.output_folder_path.setPlainText(QFileDialog.getExistingDirectory())
self.has_changed = True
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
self.clear_graphs()
self.completed_tasks = 0
if not self.has_changed and self.old_discretisation_value == self.discretisation_value_selector.value():
self.completed_tasks = self.total_tasks -1
self.finish_analyse()
return
self.has_changed = False
self.old_discretisation_value = self.discretisation_value_selector.value()
# 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
"""
# Clear the graphs
if not self.show_graph_checkbox.isChecked():
return
self.set_status("Rendering graphs... this may take a moment")
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)
self.set_status("Graphs rendered!")
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_tasks += 1
if self.completed_tasks == self.total_tasks:
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()
def graph_type_changed(self):
"""
Update the number of graphs
"""
self.graph_nb = 0
for combo_box in self.combo_boxes:
if combo_box.currentIndex() != 0:
self.graph_nb += 1
self.graph_nb_spinbox.setValue(self.graph_nb)
settings = SettingManager.get_instance()
for count,_ in enumerate(self.slots):
self.slots[count][1] = self.combo_boxes[count].currentText()
settings.set_last_graph(count,self.slots[count][1])