This commit is contained in:
Djalim Simaila 2023-06-23 10:22:28 +02:00 committed by SIMAILA DJALIM
parent 72f4303b16
commit 190a8f5566
20 changed files with 373931 additions and 69 deletions

View File

@ -25,15 +25,15 @@ L'outil permet également l'affichage de plusieurs graphiques, tels que :
- La coupe d'une couche sélectionnée
- L'évolution de la différence entre le rayon de chaque point et le rayon moyen de la couche en fonction de l'angle theta (de 0 à 2pi).
![images/1.png](images/1.png)
![images/2.png](images/2.png)
![images/3.png](images/3.png)
---
## Installation et utilisation
Veuillez noter que ce programme nécessite une version de Python 3.10 ou supérieure.
Veuillez noter que ce programme nécessite Python 3.10 ou supérieure.
### Installation automatisée
@ -60,17 +60,15 @@ python main.py
Si vous utilisez Anaconda, il est possible que vous rencontriez des problèmes avec la bibliothèque pyQt. Vous pourriez rencontrer une erreur telle que :
```
line 9, in <module>
from PyQt5.QtWidgets import QApplication
ImportError: DLL load failed while importing QtWidgets: La procédure spécifiée est introuvable.
```
Cependant, vous pouvez toujours utiliser le programme en créant un environnement virtuel avec les dépendances nécessaires. Pour cela, vous devez créer un environnement virtuel, l'activer, puis installer les dépendances.
```bash
```bash
pip install venv
python -m venv venv
# Pour les utilisateurs de windows
@ -84,39 +82,40 @@ python main.py
### Instructions pour l'utilisation des graphiques 2D et 3D
Graphiques 2D :
- Pour zoomer, utilisez la molette de la souris.
- Pour vous déplacer, cliquez sur le graphique et déplacez la souris.
- Pour modifier l'échelle, cliquez avec le bouton droit de la souris sur le graphique et déplacez la souris :
- vers le haut pour augmenter l'échelle de l'axe y.
- vers le bas pour diminuer l'échelle de l'axe y.
- vers la gauche pour diminuer l'échelle de l'axe x.
- vers la droite pour augmenter l'échelle de l'axe x.
- Pour modifier l'échelle, cliquez avec le bouton droit de la souris sur le graphique et déplacez la souris :
- vers le haut pour augmenter l'échelle de l'axe y.
- vers le bas pour diminuer l'échelle de l'axe y.
- vers la gauche pour diminuer l'échelle de l'axe x.
- vers la droite pour augmenter l'échelle de l'axe x.
Graphiques 3D :
- Pour zoomer, utilisez la molette de la souris ou maintenez le clic droit et déplacez la souris vers le haut ou le bas.
- Pour vous déplacer, maintenez le bouton 'Majuscule', puis cliquez sur le graphique et déplacez la souris.
- Pour effectuer une rotation, maintenez le clic gauche et déplacez la souris :
- pour une rotation autour de l'axe x, déplacez la souris vers le haut ou le bas dans la zone indiquée en bleu.
- pour une rotation autour de l'axe y, déplacez la souris dans une trajectoire de cercle suivant la zone indiquée en vert.
- pour une rotation autour de l'axe z, déplacez la souris vers la gauche ou la droite dans la zone indiquée en rouge.
- Pour effectuer une rotation, maintenez le clic gauche et déplacez la souris :
- pour une rotation autour de l'axe x, déplacez la souris vers le haut ou le bas dans la zone indiquée en bleu.
- pour une rotation autour de l'axe y, déplacez la souris dans une trajectoire de cercle suivant la zone indiquée en vert.
- pour une rotation autour de l'axe z, déplacez la souris vers la gauche ou la droite dans la zone indiquée en rouge.
![images/4.png](images/4.png)
___
---
## Informations et Documentation pour les Developpeurs
### Informations
- Convention des variables :
- snake_case pour les fonctions et les variables.
- PascalCase pour les classes.
- snake_case pour les fonctions et les variables.
- PascalCase pour les classes.
- Les imports sont relatif au fichier exécuté. (ici main.py)
- L'interface graphique est modifiable avec [Qt Designer](https://build-system.fman.io/qt-designer-download).
- Le fichier .ui est ensuite converti en fichier .py avec la commande suivante : `pyuic5 -x <fichier.ui> -o <fichier.py>`
- L'interface graphique est modifiable avec [Qt Designer](https://build-system.fman.io/qt-designer-download).
- Le fichier .ui est ensuite converti en fichier .py avec la commande suivante : `pyuic5 -x <fichier.ui> -o <fichier.py>`
- Le code est commenté et documenté en anglais.
- Le backend du rendu de graphe "matplotlib" n'est (actuellement) pas compatible avec le programme. Il peut cepandant etre utilisé independamment du programme pour des scripts ou des notesbooks.
- Le backend du rendu de graphe "matplotlib" n'est (actuellement) pas compatible avec le programme. Il peut cepandant etre utilisé independamment du programme pour des scripts ou des notesbooks.
Stack trace de l'analyse d'un fichier .obj :
@ -138,24 +137,28 @@ flowchart TD
La documentation du code est dans le dossier builded doc, il suffit d'ouvrir le fichier index.html avec un navigateur web.
Pour generer la documentation apres modification du code il faut passer par [sphinx](https://www.sphinx-doc.org/en/master/usage/installation.html),
ensuite il faut installer le theme [readthedocs](https://sphinx-rtd-theme.readthedocs.io/en/stable/)
ensuite il faut installer le theme [readthedocs](https://sphinx-rtd-theme.readthedocs.io/en/stable/)
```bash
pip install sphinx_rtd_theme
```
et executer la commande suivante dans le dossier doc :
```bash
make html
```
Cela generera la documentation dans le dossier docs/build.
---
## Contributeurs
- Djalim SIMAILA : [mail](mailto:DjalimS.Pro@outlook.fr), [github](https://github.com/DjalimSimaila)
- Implementation de la specification du projet.
- Implementation de la specification du projet.
- Alexis DOGHMANE : [mail](mailto:alexis.doghmane@inrae.fr)
- Redacteur des specifications du projet.
- Redacteur des specifications du projet.
- Pierre PHILIPPE : [mail](mailto:pierre.philippe@inrae.fr)
- Recherche et documentation sur les calculs d'analyse morphologique.
@ -163,7 +166,7 @@ Cela generera la documentation dans le dossier docs/build.
Avec du code de :
- Jerome Duriez : [mail](mailto:jerome.duriez@inrae.fr)
- Redressement de l'objet 3D en passant par un alignement des axes de l'espace avec les axes d'inertie de l'objet 3D.
- Redressement de l'objet 3D en passant par un alignement des axes de l'espace avec les axes d'inertie de l'objet 3D.
- Rick van Hattem : [mail](mailto:Wolph@Wol.ph)
- Code permettant de calculer le centre de gravité d'un objet, la matrice d'inertie et le volume d'un objet 3D. [source](https://pypi.org/project/numpy-stl/)
- Rick van Hattem : [mail](mailto:Wolph@Wol.ph)
- Code permettant de calculer le centre de gravité d'un objet, la matrice d'inertie et le volume d'un objet 3D. [source](https://pypi.org/project/numpy-stl/)

File diff suppressed because one or more lines are too long

373413
exemple_file/exemple.obj Normal file

File diff suppressed because it is too large Load Diff

0
install_deps.bat Executable file → Normal file
View File

1
install_deps.command Executable file → Normal file
View File

@ -1,2 +1,3 @@
#!/bin/bash
cd "$(dirname "$0")"
python3 -m pip install -r requirements.txt

0
install_deps.sh Executable file → Normal file
View File

0
launch.bat Executable file → Normal file
View File

1
launch.command Executable file → Normal file
View File

@ -1,2 +1,3 @@
#/bin/bash
cd "$(dirname "$0")"
python3 main.py

0
launch.sh Executable file → Normal file
View File

View File

@ -180,7 +180,7 @@ def get_advanced_data(discrete_data:dict, raw_data:dict, V_scan = 0, update_prog
MI_l = R_mean_std/R_mean
MI_p = np.sqrt(mean_sigma_r_squared)/R_mean
R_max = max(raw_data["rayon (en mm)"])
R_max = max(discrete_data["Rayon moyen (en mm)"])
R_in = 40
MI_mR = R_max/R_mean
MI_mH = R_max/H

View File

@ -40,17 +40,24 @@ class ScannedObject:
:ivar y: List of y values of the vertices
:ivar z: List of z values of the vertices
:static method from_xyz_file(): Creates a ScannedObject from a .xyz file
:static method from_file(): Creates a ScannedObject from a file
:static method from_triangles(): Creates a ScannedObject from a list of triangles
:static method from_obj_file(): Creates a ScannedObject from a .obj file
:static method from_xyz_file(): Creates a ScannedObject from a .xyz file
:static method from_ascii_stl_file(): Creates a ScannedObject from a .stl file
:static method from_binary_stl_file(): Creates a ScannedObject from a .stl file
:method get_x(): Returns the x values of the vertices
:method get_y(): Returns the y values of the vertices
:method get_z(): Returns the z values of the vertices
:method get_vertices(): Returns the vertices
:method get_faces(): Returns the faces
:method get_discrete_vertices(): Returns the discrete vertices
:method get_faces(): Returns the faces
:method get_data(): Returns the data
:method export: Exports the data to a file
:method update_from_faces(): Updates the data from the faces
:method normalise(): Normalises the data
:method export_xyz: Exports the data to a file
:method export_obj: Exports the data to a file
:raises FacesNotGiven: If no faces was given
:raises ResultFileNotGiven: If no result file was given
"""

View File

@ -47,6 +47,7 @@ def format_data(data:dict, separator:str, selected_columns:list = None) -> str:
3 ;6 ;9
'
"""
# Get the setting for the output file
is_prettier = SettingManager.get_instance().get_setting('pretiffy_output_file')
output = ''

View File

@ -7,26 +7,61 @@ Created on Fri Apr 21 2023
"""
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg,NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
from PyQt5.QtWidgets import QWidget, QVBoxLayout
def render2D(values:list):
class MplCanvas(FigureCanvasQTAgg):
def __init__(self, parent=None, width=5, height=4, dpi=100):
fig = Figure(figsize=(width, height), dpi=dpi)
self.axes = fig.add_subplot(111)
super(MplCanvas, self).__init__(fig)
def render2D(values:list,title:str,xlabel="",ylabel="",show:bool=True):
"""
Render a 2D model using matplotlib
:param values: A list with the values
"""
fig = plt.figure()
ax = fig.add_subplot()
ax.plot(values)
plt.show()
sc = MplCanvas(None, width=5, height=4, dpi=100)
x = [i[0] for i in values]
y = [i[1] for i in values]
sc.axes.plot(x,y)
sc.axes.set_title(title)
sc.axes.set_xlabel(xlabel)
sc.axes.set_ylabel(ylabel)
toolbar = NavigationToolbar(sc, None)
widget = QWidget()
layout = QVBoxLayout()
layout.addWidget(sc)
layout.addWidget(toolbar)
widget.setLayout(layout)
if show:
plt.show()
else:
return widget
def cross_section(x_values:list, y_values:list):
def cross_section(x_values:list, y_values:list,title:str,xlabel="",ylabel="",show=True):
"""
Render a 2D cross section using matplotlib
:param x: A list with the x values
:param y: A list with the y values
"""
fig = plt.figure()
ax = fig.add_subplot()
ax.scatter(x_values,y_values)
plt.show()
sc = MplCanvas(None, width=5, height=4, dpi=100)
sc.axes.scatter(x_values,y_values)
sc.axes.set_title(title)
sc.axes.set_xlabel(xlabel)
sc.axes.set_ylabel(ylabel)
toolbar = NavigationToolbar(sc, None)
widget = QWidget()
layout = QVBoxLayout()
layout.addWidget(sc)
layout.addWidget(toolbar)
widget.setLayout(layout)
if show:
plt.show()
else:
return widget

View File

@ -30,7 +30,7 @@ def render2D(values:list,title:str,xlabel="",ylabel="",show:bool=True)->vp.Fig|N
if show:
fig.show(run=True)
else:
return fig
return fig.native
def cross_section(x_values:list, y_values:list,title:str,xlabel="",ylabel="",show:bool=True)->vp.Fig|None:
"""
@ -58,4 +58,4 @@ def cross_section(x_values:list, y_values:list,title:str,xlabel="",ylabel="",sho
if show:
fig.show(run=True)
else:
return fig
return fig.native

View File

@ -7,20 +7,44 @@ Created on Fri Apr 21 2023
"""
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg,NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
import numpy as np
from utils.files.input import ScannedObject
from PyQt5.QtWidgets import QWidget, QVBoxLayout
def render3D(obj:ScannedObject):
class MplCanvas(FigureCanvasQTAgg):
def __init__(self, parent=None, width=5, height=4, dpi=100):
self.fig = plt.figure(figsize=(width, height), dpi=dpi)
super(MplCanvas, self).__init__(self.fig)
def render3D(obj:ScannedObject,show:bool=True):
"""
Render a 3D model using matplotlib's Poly3dcollection
:param obj: A ScannedObject to be rendered
"""
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
sc = MplCanvas(None, width=5, height=4, dpi=100)
sc.axes = sc.fig.add_subplot(projection='3d')
faces = np.array(obj.get_faces())
verts = np.array(obj.get_vertices())
mesh = Poly3DCollection(verts[faces], alpha=0.25, edgecolor='none')
ax.add_collection3d(mesh)
plt.show()
sc.axes.add_collection3d(mesh)
sc.axes.set_box_aspect((1, 1, 1), zoom=0.01)
toolbar = NavigationToolbar(sc, None)
widget = QWidget()
layout = QVBoxLayout()
layout.addWidget(sc)
layout.addWidget(toolbar)
widget.setLayout(layout)
if show:
plt.grid(False)
plt.axis('off')
plt.show()
else:
return widget

View File

@ -24,7 +24,7 @@ def render3D(obj:ScannedObject,show:bool=True)->scene.SceneCanvas|None:
vertices = np.asarray(obj.get_vertices())
faces = np.asarray(obj.get_faces())
# Create the canvas
canvas = scene.SceneCanvas(keys='interactive', bgcolor='white')
canvas = scene.SceneCanvas(title="Maillage 3D de l'objet", keys='interactive', bgcolor='white')
# Create a viewbox to display the mesh in
view = canvas.central_widget.add_view()
view.camera = 'arcball'
@ -52,4 +52,4 @@ def render3D(obj:ScannedObject,show:bool=True)->scene.SceneCanvas|None:
canvas.show()
app.run()
else:
return canvas
return canvas.native

View File

@ -16,7 +16,8 @@ from utils.gui.pyqt.settings.Settings import Settings
from utils.gui.pyqt.about.AboutThis import AboutThis
from utils.math.data_extraction import get_mean, get_radius_from_x_y, get_true_theta_from_x_y
from utils.settings.SettingManager import SettingManager
from utils.graph2D.visplot_render import cross_section, render2D
from utils.graph2D.mpl_render import cross_section as mpl_cross_section, render2D as mpl_render2D
from utils.graph2D.visplot_render import cross_section as vispy_cross_section ,render2D as vispy_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
@ -30,15 +31,14 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
# Persist variable to avoid re-computation
self.obj = None
self.raw_data= None
self.discrete_data = None
self.advanced_data = None
# 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)
@ -408,6 +408,8 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
"Sauvegarder le modèle",
"./",
"Fichier OBJ (*.obj)")
if file_path[0] == "":
return
self.obj.export_obj(file_path[0])
###############################################################################
@ -437,40 +439,48 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
"""
if not self.show_graph_checkbox.isChecked():
return
if SettingManager.get_instance().get_setting("2D_renderer") == "vispy":
cross_section = vispy_cross_section
render2D = vispy_render2D
else:
cross_section = mpl_cross_section
render2D = mpl_render2D
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":
if obj.has_faces():
current_slot.addWidget(render3D(obj,False).native)
current_slot.addWidget(render3D(obj,False))
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)
False))
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)
False))
if graph_type == "Evolution du rayon moyen":
current_slot.addWidget(render2D(list(zip(discrete_data['Rayon moyen (en mm)'],discrete_data['Z moy (en mm)'])),
"Evolution du rayon moyen en fonction de Z",
"Rayon moyen (en mm)",
"Z (en mm)",
False).native)
False))
if graph_type == "Difference entre le rayon moyen et la moyenne des rayons":
r_mean= get_mean(discrete_data['Rayon moyen (en mm)'])
current_slot.addWidget(render2D(list(zip(discrete_data['Rayon moyen (en mm)']-r_mean,discrete_data['Z moy (en mm)'])),
"Difference entre le rayon moyen et la moyenne en fonction de Z",
"Rayon moyen (en mm)",
"Z (en mm)",
False).native)
False))
self.set_status("Graphs rendered!")
def renderDiscreteGraphs(self,obj:ScannedObject,raw_data:dict,discrete_data:dict):
@ -490,6 +500,14 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
if not self.show_graph_checkbox.isChecked():
return
if SettingManager.get_instance().get_setting("2D_renderer") == "vispy":
cross_section = vispy_cross_section
render2D = vispy_render2D
else:
cross_section = mpl_cross_section
render2D = mpl_render2D
self.set_status("Rendering discretes graphs... this may take a moment")
for slot in self.slots:
current_slot = slot[0]
@ -502,7 +520,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
"Coupe de la couche",
"X en mm",
"Y en mm",
False).native)
False))
if graph_type == "Difference entre le rayon de chaque points et le rayon moyen de la couche":
self.clear_slot(current_slot)
vertices = obj.get_discrete_vertices(discretisation_value)[layer_nb]
@ -517,7 +535,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
"Difference entre le rayon de chaque points et le rayon moyen de la couche",
"Theta en rad",
"r - <r> en mm",
False).native)
False))
self.set_status("Graphs rendered!")
def clear_slot(self,slot):

View File

@ -39,8 +39,6 @@ def get_standard_deviation(values:list):
sum /= len(values) - 1
return np.sqrt(sum)
def get_x_y_z_mean(discrete_values:list):
"""
Get the mean of the x and y coordinates in the discrete range.
@ -157,7 +155,6 @@ def get_true_theta_from_x_y(xi:float, yi:float, x_mean:float, y_mean:float):
"""
return math.atan2((xi-x_mean),(yi-y_mean))
def get_difference_from_mean_value(values:list, mean_value:float):
"""
Get the difference from the mean value.
@ -186,7 +183,6 @@ def get_distance_between_two_vertices(vertex_1,vertex_2):
"""
return np.sqrt(np.power((vertex_1[0] - vertex_2[0]), 2) + np.power((vertex_1[1] - vertex_2[1]), 2) + np.power((vertex_1[2] - vertex_2[2]), 2))
#todo fix examples
if __name__ == "__main__":
import doctest

View File

@ -75,6 +75,7 @@ def get_mass_properties(obj:ScannedObject)->tuple:
inertia[1, 2] = inertia[2, 1] = -(intg[8] - volume * cog[1] * cog[2])
inertia[0, 2] = inertia[2, 0] = -(intg[9] - volume * cog[2] * cog[0])
return volume, cog, inertia
def verticalise(obj:ScannedObject):
"""
Rotate the object so that the principal axis of inertia is vertical

View File

@ -19,7 +19,7 @@ class SettingManager:
:ivar instance: The instance of the class
"""
instance = None
version = "1.2.2"
version = "1.2.3"
default_settings = {
'name':'Analyse Morphologique',
'authors':['Djalim Simaila <djalim.simaila@inrae.fr>','Alexis Doghmane <alexis@doghmane.fr>'],
@ -34,7 +34,9 @@ class SettingManager:
'output_file_separator':'\t',
'pretiffy_output_file':True,
"should_verticalise":True,
"add_headers": True
"add_headers": True,
"2D_renderer" : "matplotlib",
"2D_renderers" : ["vispy","matplotlib"]
}
def __init__(self):