🐛 fix(data_test.py): change variable name from teta_diffs to theta_diffs to improve semantics
✨ feat(data_processing.py): add function to calculate morphological indicators from discrete data The variable name teta_diffs was changed to theta_diffs to improve semantics. A new function was added to calculate morphological indicators from discrete data. The function calculates Tortuosity, Volume, Surface, Mean radius, Standard deviation of radius, Sigma r tot, MI_l, and MI_p. 🔥 refactor(input.py): remove unused result_file_path parameter from ScannedObject constructor and from_xyz_file method ✨ feat(input.py): add encoding parameter to open method in from_obj_file and from_xyz_file methods The result_file_path parameter was not being used in the ScannedObject constructor and from_xyz_file method, so it was removed to simplify the code. The encoding parameter was added to the open method in the from_obj_file and from_xyz_file methods to ensure that the files are opened with the correct encoding. 🐛 fix(output.py): add utf-8 encoding when writing to output file ✨ feat(output.py): remove unused import and function argument, improve code readability The fix adds the utf-8 encoding when writing to the output file to avoid encoding issues. The feat removes the unused import and function argument to improve code readability. The function format_data now only takes the necessary arguments and the unused import is removed. 🐛 fix(main_window.py): fix typo in function name ✨ feat(main_window.py): add persistence to pre-processed data The fix corrects a typo in the function name get_true_theta_from_x_y. The feat adds persistence to the pre-processed data by storing the raw data, discrete data, and advanced data in the main window. This avoids re-computation of the data when switching between tabs. 🎨 style(MainWindow.ui): add export_advanced_metrics button to the UI 🎨 style(UI_MainWindow.py): add export_advanced_metrics button to the UI 🎨 style(ressources_rc.py): update the resource file 🐛 fix(data_extraction.py): fix typo in function name get_mean_teta to get_mean_theta The changes add a new button to the UI named "export_advanced_metrics" which allows the user to export variables. The resource file is updated to reflect the changes. The typo in the function name get_mean_teta is fixed to get_mean_theta.
This commit is contained in:
parent
4402085620
commit
fb2bb6e9ce
@ -104,7 +104,7 @@ def check_raw_data(expected_file, actual_file, ndigits=7, eps=0.00001, silent: b
|
||||
minimal_output_data = ""
|
||||
output_data = ""
|
||||
expected = []
|
||||
list_x_diffs, list_y_diffs, list_z_diffs, list_teta_diffs, list_radius_diffs, list_xmoy_diffs, list_ymoy_diffs = [], [], [], [], [], [], []
|
||||
list_x_diffs, list_y_diffs, list_z_diffs, list_theta_diffs, list_radius_diffs, list_xmoy_diffs, list_ymoy_diffs = [], [], [], [], [], [], []
|
||||
|
||||
with open(expected_file, "r") as f:
|
||||
expected = f.readlines()[1:]
|
||||
@ -124,7 +124,7 @@ def check_raw_data(expected_file, actual_file, ndigits=7, eps=0.00001, silent: b
|
||||
list_x_diffs.append(x_diff)
|
||||
list_y_diffs.append(y_diff)
|
||||
list_z_diffs.append(z_diff)
|
||||
list_teta_diffs.append(t_diff)
|
||||
list_theta_diffs.append(t_diff)
|
||||
list_radius_diffs.append(r_diff)
|
||||
list_xmoy_diffs.append(xmoy_diff)
|
||||
list_ymoy_diffs.append(ymoy_diff)
|
||||
@ -141,7 +141,7 @@ def check_raw_data(expected_file, actual_file, ndigits=7, eps=0.00001, silent: b
|
||||
if z_diff > eps:
|
||||
minimal_output_data += f"{i} : Z diff {z_diff}\n"
|
||||
if t_diff > eps:
|
||||
minimal_output_data += f"{i} : teta diff{ t_diff}\n"
|
||||
minimal_output_data += f"{i} : theta diff{ t_diff}\n"
|
||||
if r_diff > eps:
|
||||
minimal_output_data += f"{i} : R diff {r_diff}\n"
|
||||
if xmoy_diff > eps:
|
||||
@ -158,7 +158,7 @@ def check_raw_data(expected_file, actual_file, ndigits=7, eps=0.00001, silent: b
|
||||
sum_x_diff = round(sum(list_x_diffs)/len(expected), ndigits)
|
||||
sum_y_diff = round(sum(list_y_diffs)/len(expected), ndigits)
|
||||
sum_z_diff = round(sum(list_z_diffs)/len(expected), ndigits)
|
||||
sum_teta_diff = round(sum(list_teta_diffs)/len(expected), ndigits)
|
||||
sum_theta_diff = round(sum(list_theta_diffs)/len(expected), ndigits)
|
||||
sum_radius_diff = round(sum(list_radius_diffs)/len(expected), ndigits)
|
||||
sum_xmoy_diff = round(sum(list_xmoy_diffs)/len(expected), ndigits)
|
||||
sum_ymoy_diffs = round(sum(list_ymoy_diffs)/len(expected), ndigits)
|
||||
@ -167,12 +167,12 @@ def check_raw_data(expected_file, actual_file, ndigits=7, eps=0.00001, silent: b
|
||||
print(f"diff moyenne de x : {sum_x_diff}")
|
||||
print(f"diff moyenne de y : {sum_y_diff}")
|
||||
print(f"diff moyenne de z : {sum_z_diff}")
|
||||
print(f"diff moyenne de t : {sum_teta_diff}")
|
||||
print(f"diff moyenne de t : {sum_theta_diff}")
|
||||
print(f"diff moyenne de r : {sum_radius_diff}")
|
||||
print(f"diff moyenne de xmoy : {sum_xmoy_diff}")
|
||||
print(f"diff moyenne de ymoy : {sum_ymoy_diffs}")
|
||||
print(
|
||||
f"diff gloabale des fichiers : {(sum_x_diff+sum_y_diff+sum_z_diff+sum_teta_diff+sum_radius_diff+sum_xmoy_diff+sum_ymoy_diffs)/7}")
|
||||
f"diff gloabale des fichiers : {(sum_x_diff+sum_y_diff+sum_z_diff+sum_theta_diff+sum_radius_diff+sum_xmoy_diff+sum_ymoy_diffs)/7}")
|
||||
print(f"Voir {output_file} pour plus de détails")
|
||||
print(f"Voir {minimal_output_file} pour les différences significatives")
|
||||
print("_"*80)
|
||||
@ -180,7 +180,7 @@ def check_raw_data(expected_file, actual_file, ndigits=7, eps=0.00001, silent: b
|
||||
return {"x": list_x_diffs,
|
||||
"y": list_y_diffs,
|
||||
"z": list_z_diffs,
|
||||
"teta": list_teta_diffs,
|
||||
"theta": list_theta_diffs,
|
||||
"radius": list_radius_diffs,
|
||||
"xmoy": list_xmoy_diffs,
|
||||
"ymoy": list_ymoy_diffs}
|
||||
@ -236,7 +236,7 @@ def test_get_raw_data(obj: ScannedObject, expected_file: str, ndigits: int = 6,
|
||||
print()
|
||||
print("Time to calculate raw data: ", time.time() - now)
|
||||
output.save_output_file('tmp_analyse_brute.txt', output.format_data(data, '\t', [
|
||||
"X (en mm)", "Y (en mm)", "Z (en mm)", "teta (en rad)", "rayon (en mm)", "Xi-Xmoy", "Yi-Ymoy"]))
|
||||
"X (en mm)", "Y (en mm)", "Z (en mm)", "theta (en rad)", "rayon (en mm)", "Xi-Xmoy", "Yi-Ymoy"]))
|
||||
check_raw_data(expected_file, 'tmp_analyse_brute.txt', ndigits, eps)
|
||||
os.remove('tmp_analyse_brute.txt')
|
||||
|
||||
|
@ -8,7 +8,6 @@ Created on Mon Apr 24 2023
|
||||
from utils.math import data_extraction as de
|
||||
import numpy as np
|
||||
from utils.files.input import ScannedObject
|
||||
from utils.settings.SettingManager import SettingManager
|
||||
|
||||
def progressbar_placeholder(percent:int):
|
||||
"""
|
||||
@ -27,14 +26,20 @@ def get_raw_data(obj:ScannedObject, ndigits:int,delta_z:float=1,update_progress_
|
||||
- X (en mm) : list of x values
|
||||
- Y (en mm) : list of y values
|
||||
- Z (en mm) : list of z values
|
||||
- teta (en rad) : list of teta values
|
||||
- theta (en rad) : list of theta values
|
||||
- rayon (en mm) : list of radius values
|
||||
- Xi-Xmoy : list of Xi-Xmoy values
|
||||
- Yi-Ymoy : list of Yi-Ymoy values
|
||||
"""
|
||||
|
||||
# Create the data dict
|
||||
colones = ["X (en mm)", "Y (en mm)", "Z (en mm)", "teta (en rad)", "rayon (en mm)","Xi-Xmoy","Yi-Ymoy"]
|
||||
colones = ["X (en mm)",
|
||||
"Y (en mm)",
|
||||
"Z (en mm)",
|
||||
"theta (en rad)",
|
||||
"rayon (en mm)",
|
||||
"Xi-Xmoy",
|
||||
"Yi-Ymoy"]
|
||||
data = {}
|
||||
for colone in colones:
|
||||
data[colone] = []
|
||||
@ -50,7 +55,7 @@ def get_raw_data(obj:ScannedObject, ndigits:int,delta_z:float=1,update_progress_
|
||||
data["X (en mm)"].append(round(x, ndigits))
|
||||
data["Y (en mm)"].append(round(y, ndigits))
|
||||
data["Z (en mm)"].append(round(z, ndigits))
|
||||
data["teta (en rad)"].append(round(de.get_teta_from_x_y(x,y,mean_x,mean_y), ndigits))
|
||||
data["theta (en rad)"].append(round(de.get_theta_from_x_y(x,y,mean_x,mean_y), ndigits))
|
||||
data["rayon (en mm)"].append(round(de.get_radius_from_x_y(x,y,mean_x,mean_y), ndigits))
|
||||
data["Xi-Xmoy"].append(round(x-mean_x, ndigits))
|
||||
data["Yi-Ymoy"].append(round(y-mean_y, ndigits))
|
||||
@ -73,8 +78,14 @@ def get_discrete_data(obj:ScannedObject, ndigits:int, delta_z:float=1, update_pr
|
||||
- Rayon moyen (en mm) : list of mean radius values
|
||||
- Rayon ecart type (en mm) : list of radius standard deviation values
|
||||
"""
|
||||
|
||||
# Create the data dict
|
||||
colones = ["X moy (en mm)", "Y moy (en mm)", "Z moy (en mm)","Discretisation(en mm)","Rayon moyen (en mm)","Rayon ecart type (en mm)"]
|
||||
colones = ["X moy (en mm)",
|
||||
"Y moy (en mm)",
|
||||
"Z moy (en mm)",
|
||||
"Discretisation(en mm)",
|
||||
"Rayon moyen (en mm)",
|
||||
"Rayon ecart type (en mm)"]
|
||||
data = {}
|
||||
for colone in colones:
|
||||
data[colone] = []
|
||||
@ -96,25 +107,49 @@ def get_discrete_data(obj:ScannedObject, ndigits:int, delta_z:float=1, update_pr
|
||||
progress += 1
|
||||
return data
|
||||
|
||||
def get_advanced_data(discrete_data:dict, update_progress_bar= progressbar_placeholder):
|
||||
def get_advanced_data(discrete_data:dict, update_progress_bar= progressbar_placeholder)->dict:
|
||||
"""
|
||||
Calculates morphological indicators from the given discrete data
|
||||
|
||||
:param discrete_data: dict(str:list) with the following keys:
|
||||
- X moy (en mm) : list of x mean values
|
||||
- Y moy (en mm) : list of y mean values
|
||||
- Z moy (en mm) : list of z mean values
|
||||
- Rayon moyen (en mm) : list of mean radius values
|
||||
- Rayon ecart type (en mm) : list of radius standard deviation values
|
||||
:param update_progress_bar: Function to update the progress bar
|
||||
:return: dict with the following keys:
|
||||
- Tortuosite
|
||||
- Volume
|
||||
- Surface
|
||||
- Moyenne des rayons moyens
|
||||
- Ecart-type des rayons moyens
|
||||
- Sigma r tot
|
||||
- MI_l
|
||||
- MI_p
|
||||
"""
|
||||
|
||||
# Tortusity
|
||||
l = 0
|
||||
L = 0
|
||||
vertices = list(zip(discrete_data["X moy (en mm)"], discrete_data["Y moy (en mm)"], discrete_data["Z moy (en mm)"]))
|
||||
for i in range(len(vertices)-1):
|
||||
l += de.get_distance_between_two_vertices(vertices[i], vertices[i+1])
|
||||
vertices = list(zip(discrete_data["X moy (en mm)"],
|
||||
discrete_data["Y moy (en mm)"],
|
||||
discrete_data["Z moy (en mm)"]))
|
||||
|
||||
for index in range(len(vertices)-1):
|
||||
l += de.get_distance_between_two_vertices(vertices[index], vertices[index+1])
|
||||
L = de.get_distance_between_two_vertices(vertices[0], vertices[-1])
|
||||
T = l/L
|
||||
update_progress_bar(10)
|
||||
|
||||
# Volume and surface
|
||||
H = discrete_data["Z moy (en mm)"][-1] - discrete_data["Z moy (en mm)"][0]
|
||||
R = de.get_mean([np.power(r,2) for r in discrete_data["Rayon moyen (en mm)"]])
|
||||
V = np.pi * R * H
|
||||
S = 2 * np.pi * R * H
|
||||
update_progress_bar(30)
|
||||
#
|
||||
|
||||
# Morphological indicators
|
||||
R_mean = de.get_mean(discrete_data["Rayon moyen (en mm)"])
|
||||
R_mean_std = de.get_standard_deviation(discrete_data["Rayon moyen (en mm)"])
|
||||
mean_sigma_r_squared = de.get_mean([np.power(r,2) for r in discrete_data["Rayon ecart type (en mm)"]])
|
||||
|
@ -26,11 +26,9 @@ class ScannedObject:
|
||||
|
||||
:param vertices: List of verticesm Ndarray of shape (n,2)
|
||||
:param faces: List of faces, Ndarray of shape (n,2)
|
||||
:param result_file_path: Path to the result file (deprecated, used for the bruteforce discretization)
|
||||
|
||||
:ivar vertices: List of vertices, Ndarray of shape (n,2)
|
||||
:ivar faces: List of faces, Ndarray of shape (n,2)
|
||||
:ivar result_file_path: Path to the result file (deprecated, used for the bruteforce discretization)
|
||||
:ivar x: List of x values of the vertices
|
||||
:ivar y: List of y values of the vertices
|
||||
:ivar z: List of z values of the vertices
|
||||
@ -46,31 +44,10 @@ class ScannedObject:
|
||||
:method get_data(): Returns the data
|
||||
:method export: Exports the data to a file
|
||||
|
||||
|
||||
:raises FacesNotGiven: If no faces was given
|
||||
:raises ResultFileNotGiven: If no result file was given
|
||||
|
||||
|
||||
:Example:
|
||||
|
||||
>>> from utils.files.input import ScannedObject
|
||||
>>> vertices = [(0,0,0), (1,0,0), (1,1,0), (0,1,0), (0,0,1), (1,0,1), (1,1,1), (0,1,1)]
|
||||
>>> faces = [(0,1,2), (0,2,3), (4,5,6), (4,6,7), (0,1,5), (0,4,5), (1,2,6), (1,5,6), (2,3,7), (2,6,7), (3,0,4), (3,7,4)]
|
||||
>>> obj = ScannedObject(vertices, faces)
|
||||
>>> obj.get_x()
|
||||
[0, 1, 1, 0, 0, 1, 1, 0]
|
||||
>>> obj.get_y()
|
||||
[0, 0, 1, 1, 0, 0, 1, 1]
|
||||
>>> obj.get_z()
|
||||
[0, 0, 0, 0, 1, 1, 1, 1]
|
||||
>>> obj.get_vertices()
|
||||
[(0, 0, 0), (1, 0, 0), (1, 1, 0), (0, 1, 0), (0, 0, 1), (1, 0, 1), (1, 1, 1), (0, 1, 1)]
|
||||
>>> obj.get_faces()
|
||||
[(0, 1, 2), (0, 2, 3), (4, 5, 6), (4, 6, 7), (0, 1, 5), (0, 4, 5), (1, 2, 6), (1, 5, 6), (2, 3, 7), (2, 6, 7), (3, 0, 4), (3, 7, 4)]
|
||||
>>> obj.get_discrete_vertices()
|
||||
[[(0, 0, 0), (1, 0, 0), (1, 1, 0), (0, 1, 0)]],[ (0, 0, 1), (1, 0, 1), (1, 1, 1), (0, 1, 1)]]
|
||||
"""
|
||||
def __init__(self, vertices, faces=None, result_file_path=None):
|
||||
def __init__(self, vertices, faces=None):
|
||||
self.vertices = np.asarray(vertices)
|
||||
self.faces = np.asarray(faces)
|
||||
self.old_delta = None
|
||||
@ -82,17 +59,16 @@ class ScannedObject:
|
||||
|
||||
|
||||
@staticmethod
|
||||
def from_obj_file(file_path:str, result_file_path:str = None, ratio:float = 1,normalised:str = '')->'ScannedObject':
|
||||
def from_obj_file(file_path:str, ratio:float = 1,normalised:str = '')->'ScannedObject':
|
||||
"""
|
||||
Create an Object from an OBJ file.
|
||||
|
||||
:param file_path: Path to the OBJ file
|
||||
:param result_file_path: Path to the result file
|
||||
:param ratio: Ratio to apply to the vertices
|
||||
:param normalised: the axis to normalise
|
||||
:return: A ScannedObject
|
||||
"""
|
||||
with open(file_path, 'r') as f:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
x, y, z = [], [], []
|
||||
triangles = []
|
||||
data = f.readlines()
|
||||
@ -105,6 +81,10 @@ class ScannedObject:
|
||||
triangles.append([int(line.split()[1].split("/")[0])-1, int(line.split()[2].split("/")[0])-1, int(line.split()[3].split("/")[0])-1])
|
||||
else:
|
||||
triangles.append([int(line.split()[1])-1, int(line.split()[2])-1, int(line.split()[3])-1])
|
||||
|
||||
# if it is a vertex, the line starts with a 'v ',
|
||||
# taking only 'v' would cause to take the textures coordinates('vt'),
|
||||
# vertex normals ('vn') and space vertices ('vp')
|
||||
elif line.startswith('v '):
|
||||
x.append(float(line.split()[1]) * ratio)
|
||||
y.append(float(line.split()[2]) * ratio)
|
||||
@ -112,34 +92,33 @@ class ScannedObject:
|
||||
|
||||
if 'x' in normalised:
|
||||
xmin = min(x)
|
||||
for count, value in enumerate(x):
|
||||
for count,_ in enumerate(x):
|
||||
x[count] -= xmin
|
||||
|
||||
if 'y' in normalised:
|
||||
ymin = min(y)
|
||||
for count, value in enumerate(y):
|
||||
for count,_ in enumerate(y):
|
||||
y[count] -= ymin
|
||||
|
||||
if 'z' in normalised:
|
||||
zmin = min(z)
|
||||
for count, value in enumerate(z):
|
||||
for count,_ in enumerate(z):
|
||||
z[count] -= zmin
|
||||
|
||||
return ScannedObject(list(zip(x,y,z)), triangles, result_file_path)
|
||||
return ScannedObject(list(zip(x,y,z)), triangles, )
|
||||
|
||||
@staticmethod
|
||||
def from_xyz_file(file_path: str, result_file_path:str = None, delimiter: str = ' ',normalised:str = '')->'ScannedObject':
|
||||
def from_xyz_file(file_path:str, delimiter:str = ' ', normalised:str = '')->'ScannedObject':
|
||||
"""
|
||||
Create an Object from an XYZ file.
|
||||
|
||||
:param file_path: Path to the XYZ file
|
||||
:param result_file_path: Path to the result file
|
||||
:param delimiter: The delimiter used in the xyz file.
|
||||
:param normalised: the axis to normalise
|
||||
:return: A ScannedObject
|
||||
"""
|
||||
x , y , z = [], [], []
|
||||
with open(file_path, 'r') as f:
|
||||
with open(file_path, 'r',encoding='utf-8') as f:
|
||||
data = f.readlines()
|
||||
for line in data:
|
||||
x.append(float(line.split(delimiter)[0]))
|
||||
@ -148,19 +127,19 @@ class ScannedObject:
|
||||
|
||||
if 'x' in normalised:
|
||||
xmin = min(x)
|
||||
for count, value in enumerate(x):
|
||||
for count,_ in enumerate(x):
|
||||
x[count] -= xmin
|
||||
|
||||
if 'y' in normalised:
|
||||
ymin = min(y)
|
||||
for count, value in enumerate(y):
|
||||
for count,_ in enumerate(y):
|
||||
y[count] -= ymin
|
||||
|
||||
if 'z' in normalised:
|
||||
zmin = min(z)
|
||||
for count, value in enumerate(z):
|
||||
for count,_ in enumerate(z):
|
||||
z[count] -= zmin
|
||||
return ScannedObject(list(zip(x,y,z)), result_file_path=result_file_path)
|
||||
return ScannedObject(list(zip(x,y,z)))
|
||||
|
||||
def get_x(self)->list:
|
||||
"""
|
||||
@ -194,6 +173,12 @@ class ScannedObject:
|
||||
return vertices
|
||||
|
||||
def get_discrete_vertices(self, step:float = 1)->list:
|
||||
"""
|
||||
Discretize the vertices using the method specified in the settings.
|
||||
|
||||
:param step: Step of the discretization
|
||||
:return: Discretized vertices
|
||||
"""
|
||||
if SettingManager.get_instance().get_setting("discretisation_method") == "Z0-Zi < DeltaZ":
|
||||
return self.get_discrete_vertices_1(step)
|
||||
return self.get_discrete_vertices_2(step)
|
||||
@ -206,10 +191,14 @@ class ScannedObject:
|
||||
:param step: Step of the discretization
|
||||
:return: Discretized vertices
|
||||
"""
|
||||
|
||||
# if it has already been calculated with the same method and parametters
|
||||
# dont do it again
|
||||
if self.old_delta == step and self.old_discrete_type == 0:
|
||||
return self.old_discrete
|
||||
self.old_delta = step
|
||||
self.old_discrete_type = 0
|
||||
|
||||
current_interval = int(min(self.get_z()))
|
||||
splitted_data = [[]]
|
||||
for line in self.get_vertices(sort=True):
|
||||
@ -223,43 +212,53 @@ class ScannedObject:
|
||||
|
||||
def get_discrete_vertices_2(self, step:float = 1)->list:
|
||||
"""
|
||||
Discretize the vertices of the object using a lenght method.
|
||||
Discretize the vertices of the object using a length method.
|
||||
This implementation will split the object when difference between the
|
||||
first and last point of a slice is greater or equal then the step interval.
|
||||
|
||||
:param step: Step of the discretization
|
||||
:return: Discretized vertices
|
||||
"""
|
||||
|
||||
# if it has already been calculated with the same method and parametters
|
||||
# dont do it again
|
||||
if self.old_delta == step and self.old_discrete_type == 1:
|
||||
return self.old_discrete
|
||||
self.old_delta = step
|
||||
self.old_discrete_type = 1
|
||||
L = [[]]
|
||||
|
||||
splitted_data = [[]]
|
||||
z = min(self.get_z())
|
||||
sorted_vertices = self.get_vertices(sort=True)
|
||||
for index,_ in enumerate(sorted_vertices):
|
||||
L[-1].append(sorted_vertices[index])
|
||||
splitted_data[-1].append(sorted_vertices[index])
|
||||
if sorted_vertices[index][2] - z > step:
|
||||
z = sorted_vertices[index+1][2]
|
||||
L.append([])
|
||||
self.old_discrete = L
|
||||
return L
|
||||
splitted_data.append([])
|
||||
self.old_discrete = splitted_data
|
||||
return splitted_data
|
||||
|
||||
def get_faces(self,resolved:bool = False)->list:
|
||||
"""
|
||||
Get the faces of the object.
|
||||
If the faces are not resolved, the faces will be returned as a list of
|
||||
indices of the vertices. else, the faces will be returned as a list of
|
||||
vertices.
|
||||
|
||||
:param resolved: If the faces should be resolved
|
||||
:return: faces
|
||||
"""
|
||||
if self.faces is None:
|
||||
raise FacesNotGiven('No faces was given')
|
||||
raise FacesNotGiven('No faces were given')
|
||||
if resolved:
|
||||
return self.vertices[self.faces]
|
||||
return self.faces
|
||||
|
||||
def update_from_faces(self,faces:list):
|
||||
"""
|
||||
Update the object from the faces.
|
||||
Update the object from the faces. This will reconstruct the vertices
|
||||
from the faces, it is assumed that the faces are given as a list of
|
||||
vertices.
|
||||
|
||||
:param faces: Faces to update the object from
|
||||
"""
|
||||
@ -304,7 +303,12 @@ class ScannedObject:
|
||||
|
||||
:return: Data of the object
|
||||
"""
|
||||
return {'verticies': self.vertices, 'faces': self.faces, 'x': self.x, 'y': self.y, 'z': self.z}
|
||||
return {'verticies': self.vertices,
|
||||
'faces': self.faces,
|
||||
'x': self.x,
|
||||
'y': self.y,
|
||||
'z': self.z
|
||||
}
|
||||
|
||||
def export_xyz(self, file_path:str,separator:str="\t"):
|
||||
"""
|
||||
@ -314,12 +318,11 @@ class ScannedObject:
|
||||
:param separator: chars used to separate the values
|
||||
"""
|
||||
string = ''
|
||||
with open(file_path, "w") as f:
|
||||
for vertex in self.get_vertices(sort=True):
|
||||
x = round(vertex[0], 6)
|
||||
y = round(vertex[1], 6)
|
||||
z = round(vertex[2], 6)
|
||||
string+=f"{x}{separator}{y}{separator}{z}\n"
|
||||
for vertex in self.get_vertices(sort=True):
|
||||
x = round(vertex[0], 6)
|
||||
y = round(vertex[1], 6)
|
||||
z = round(vertex[2], 6)
|
||||
string+=f"{x}{separator}{y}{separator}{z}\n"
|
||||
save_output_file(file_path,string)
|
||||
|
||||
def export_obj(self,file_path):
|
||||
@ -329,17 +332,16 @@ class ScannedObject:
|
||||
:param file_path: Path of the file
|
||||
"""
|
||||
string = ''
|
||||
with open(file_path, "w") as f:
|
||||
for vertex in self.get_vertices():
|
||||
x = round(vertex[0], 6)
|
||||
y = round(vertex[1], 6)
|
||||
z = round(vertex[2], 6)
|
||||
string+=f"v {x} {y} {z}\n"
|
||||
for face in self.get_faces():
|
||||
string+="f "
|
||||
for vertex in face:
|
||||
string+=f"{vertex+1} "
|
||||
string+="\n"
|
||||
for vertex in self.get_vertices():
|
||||
x = round(vertex[0], 6)
|
||||
y = round(vertex[1], 6)
|
||||
z = round(vertex[2], 6)
|
||||
string+=f"v {x} {y} {z}\n"
|
||||
for face in self.get_faces():
|
||||
string+="f "
|
||||
for vertex in face:
|
||||
string+=f"{vertex+1} "
|
||||
string+="\n"
|
||||
save_output_file(file_path,string)
|
||||
|
||||
|
||||
@ -347,7 +349,7 @@ def parse_result_file(file_path: str, separator: str = "\t")-> tuple:
|
||||
"""
|
||||
This functions parses the discretised output file to retreive the first
|
||||
three colunms. It is used to extract the means of x y z for the consistency
|
||||
check and the bruteforce_discretisation
|
||||
check.
|
||||
|
||||
:param file_path: Path of the file
|
||||
:param separator: chars used to separate the values
|
||||
|
@ -5,10 +5,8 @@ Created on Mon Apr 17 2023
|
||||
@auth: Djalim Simaila
|
||||
@e-mail: djalim.simaila@inrae.fr
|
||||
"""
|
||||
|
||||
from utils.settings.SettingManager import SettingManager
|
||||
|
||||
|
||||
def format_data(data:dict, separator:str, selected_columns:list = None) -> str:
|
||||
"""
|
||||
Format the data to be saved in the output file.
|
||||
@ -41,7 +39,7 @@ def format_data(data:dict, separator:str, selected_columns:list = None) -> str:
|
||||
# Write the columns headers
|
||||
for column_name in selected_columns:
|
||||
if is_prettier:
|
||||
output += column_name.ljust(len(column_name) if len(column_name) > 8 else 9 ) + separator
|
||||
output += column_name.ljust(len(column_name) if len(column_name) > 8 else 9 ) + separator
|
||||
else:
|
||||
output += column_name + separator
|
||||
output += '\n'
|
||||
@ -62,5 +60,5 @@ def save_output_file(output_file:str, content:str):
|
||||
:param output_file: Path to the output file
|
||||
:param content: Content of the output file
|
||||
"""
|
||||
with open(output_file, 'w') as f:
|
||||
f.write(content)
|
||||
with open(output_file, 'w',encoding='utf-8') as file:
|
||||
file.write(content)
|
||||
|
@ -1,7 +1,8 @@
|
||||
"""
|
||||
Created on Fri Apr 21 2023
|
||||
@name: mpl_render.py
|
||||
@desc: A module to render a 2D data using matplotlib
|
||||
@desc: A module to render a 2D data using matplotlib,
|
||||
thoses functions cant be integrated into pyqt gui yet
|
||||
@auth: Djalim Simaila
|
||||
@e-mail: djalim.simaila@inrae.fr
|
||||
"""
|
||||
|
@ -16,7 +16,7 @@ def render2D(values:list,title:str,xlabel="",ylabel="",show:bool=True)->vp.Fig|N
|
||||
:param title: Title of the plot
|
||||
:param xlabel: Label of the x axis
|
||||
:param ylabel: Label of the y axis
|
||||
:param show: If True, show the plot, else return the canvas
|
||||
:param show: If True, show the plot in its own window, else return the canvas
|
||||
"""
|
||||
fig = vp.Fig(size=(600, 500), show=False)
|
||||
plotwidget = fig[0, 0]
|
||||
@ -41,7 +41,7 @@ def cross_section(x_values:list, y_values:list,title:str,xlabel="",ylabel="",sho
|
||||
:param title: Title of the plot
|
||||
:param xlabel: Label of the x axis
|
||||
:param ylabel: Label of the y axis
|
||||
:param show: If True, show the plot, else return the canvas
|
||||
:param show: If True, show the plot in its own window, else return the canvas
|
||||
"""
|
||||
color = (0.3, 0.5, 0.8,.8)
|
||||
fig = vp.Fig(show=False)
|
||||
|
@ -1,7 +1,8 @@
|
||||
"""
|
||||
Created on Fri Apr 21 2023
|
||||
@name: mpl_render.py
|
||||
@desc: A module to render a 3D data using matplotlib
|
||||
@desc: A module to render a 3D data using matplotlib,
|
||||
thoses functions cant be integrated into pyqt gui yet
|
||||
@auth: Djalim Simaila
|
||||
@e-mail: djalim.simaila@inrae.fr
|
||||
"""
|
||||
|
@ -10,6 +10,8 @@ from utils.gui.pyqt.about.UI_AboutThis import Ui_AboutThis
|
||||
from utils.settings.SettingManager import SettingManager
|
||||
|
||||
class AboutThis(QtWidgets.QMainWindow,Ui_AboutThis):
|
||||
"""
|
||||
"""
|
||||
def __init__(self, parent=None) -> None:
|
||||
super().__init__(parent)
|
||||
self.setupUi(self)
|
||||
|
@ -30,8 +30,12 @@ class ErrorPopup(object):
|
||||
self.button_callback = button_callback
|
||||
self.details = details
|
||||
|
||||
|
||||
def show_popup(self):
|
||||
"""
|
||||
Show the error popup with the specified error message.
|
||||
If a button label and a button callback are specified, the popup will
|
||||
have a button with the specified label and the specified callback.
|
||||
"""
|
||||
msg = QMessageBox()
|
||||
msg.setWindowTitle("Erreur")
|
||||
msg.setText("Erreur: " + self.error_text)
|
||||
|
@ -13,7 +13,7 @@ from utils.files.input import ScannedObject
|
||||
from utils.gui.pyqt.main_window.Workers.AdvancedDataWorker import AdvancedDataWorker
|
||||
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_teta_from_x_y
|
||||
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.graph3D.visplot_render import render3D
|
||||
@ -25,11 +25,17 @@ 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)
|
||||
|
||||
# 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
|
||||
@ -42,10 +48,12 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self.actionQuitter.triggered.connect(self.close)
|
||||
self.actionQ_propos_de_ce_logiciel.triggered.connect(self.show_about)
|
||||
|
||||
# add default layer combobox value and setup the listenerr when index change
|
||||
self.layer_ComboBox.addItems(['Aucune couche'])
|
||||
self.layer_ComboBox.currentIndexChanged.connect(self.layer_changed)
|
||||
|
||||
self.graphType = [
|
||||
# Prepare available graph type list for the slots combobox
|
||||
self.graph_type = [
|
||||
"Aucun",
|
||||
"Mesh3D",
|
||||
"Coupe XZ",
|
||||
@ -56,14 +64,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
"Difference entre le rayon de chaque points et le rayon moyen de la couche"
|
||||
]
|
||||
|
||||
self.obj = None
|
||||
self.raw_data= None
|
||||
self.discrete_data = None
|
||||
self.advanced_data = None
|
||||
|
||||
self.completed_tasks = 0
|
||||
self.total_tasks = 3
|
||||
|
||||
# put all slots combo boxes in a list for conveniance
|
||||
self.combo_boxes = [
|
||||
self.slot0ComboBox,
|
||||
self.slot1ComboBox,
|
||||
@ -78,10 +79,12 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self.slot10ComboBox
|
||||
]
|
||||
|
||||
# Setup all combo boxes with values and listener
|
||||
for combo_box in self.combo_boxes:
|
||||
combo_box.addItems(self.graphType)
|
||||
combo_box.addItems(self.graph_type)
|
||||
combo_box.currentIndexChanged.connect(self.graph_type_changed)
|
||||
|
||||
# put all slots in a list for conveniance and store their state in a string
|
||||
self.slots = [
|
||||
[self.slot0,"Aucun"],
|
||||
[self.slot1,"Aucun"],
|
||||
@ -96,19 +99,32 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
[self.slot10,"Aucun"]
|
||||
]
|
||||
|
||||
# Retrieve the slot previous value from the settings
|
||||
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])
|
||||
|
||||
# Graph number indicator
|
||||
self.graph_nb =0
|
||||
self.graph_type_changed()
|
||||
|
||||
# Construct sub windows
|
||||
self.settings_window = Settings()
|
||||
self.about_window = AboutThis()
|
||||
|
||||
# Variable to check if parametters has changed to avoid re-computation
|
||||
self.has_changed = True
|
||||
self.old_discretisation_value = None
|
||||
|
||||
# Task counter and task number, used to know when the analysis
|
||||
# is considered finished
|
||||
# Current tasks are
|
||||
# - process raw data
|
||||
# - process discrete data
|
||||
# - process morphological indicators (advanced data)
|
||||
self.completed_tasks = 0
|
||||
self.total_tasks = 3
|
||||
|
||||
###############################################################################
|
||||
# #
|
||||
# #
|
||||
@ -118,7 +134,8 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
###############################################################################
|
||||
def select_file(self):
|
||||
"""
|
||||
Open a file dialog to select the input file
|
||||
Open a file dialog to select the input file,
|
||||
if the folder_path is empty, it fills it with the files folder.
|
||||
"""
|
||||
file = QFileDialog.getOpenFileName()[0]
|
||||
self.input_file_path.setPlainText(file)
|
||||
@ -136,19 +153,30 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
|
||||
def check_input_file(self):
|
||||
"""
|
||||
Check if the input file is valid
|
||||
Check if the input file is valid by checking:
|
||||
- if it exists
|
||||
- if its extension is .obj
|
||||
"""
|
||||
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()
|
||||
if not os.path.isfile(self.input_file_path.toPlainText()) :
|
||||
ErrorPopup("Fichier d'entrée invalide: Aucun fichier selectionné, ou le fichier n'existe pas",
|
||||
button_label="Choisir un fichier d'entrée",
|
||||
button_callback=self.select_file).show_popup()
|
||||
return False
|
||||
if os.path.splitext(self.input_file_path.toPlainText())[1].lower() != ".obj":
|
||||
ErrorPopup("Fichier d'entrée invalide: l'extension du fichier est incorrecte ",
|
||||
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
|
||||
Check if the output folder is valid by cheking if it exists.
|
||||
"""
|
||||
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()
|
||||
ErrorPopup("Dossier de sortie invalide",
|
||||
button_label="Choisir un dossier de sortie",
|
||||
button_callback=self.select_folder).show_popup()
|
||||
return False
|
||||
return True
|
||||
|
||||
@ -284,13 +312,13 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
|
||||
def set_obj(self,obj:ScannedObject):
|
||||
"""
|
||||
Set the obj to the main window
|
||||
Persists the pre-processed obj
|
||||
"""
|
||||
self.obj = obj
|
||||
|
||||
def set_discrete_data(self,discrete_data:dict):
|
||||
"""
|
||||
Set the discrete data to the main window
|
||||
Persists the calculated discrete data
|
||||
"""
|
||||
self.discrete_data = discrete_data
|
||||
layer = [str(i) for i in range(len(discrete_data["X moy (en mm)"]))]
|
||||
@ -302,13 +330,13 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
|
||||
def set_raw_data(self,raw_data:dict):
|
||||
"""
|
||||
Set the raw data to the main window
|
||||
Persists the calculated raw data
|
||||
"""
|
||||
self.raw_data = raw_data
|
||||
|
||||
def set_advanced_data(self,advanced_data:dict):
|
||||
"""
|
||||
Set the advanced data to the main window
|
||||
Persists the calculated raw data and show the values
|
||||
"""
|
||||
self.advanced_data = advanced_data
|
||||
self.tortuosity.setValue(advanced_data["Tortuosité"])
|
||||
@ -433,15 +461,15 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
y_mean = discrete_data["Y moy (en mm)"][layer_nb]
|
||||
r_mean = discrete_data["Rayon moyen (en mm)"][layer_nb]
|
||||
rs = [get_radius_from_x_y(x,y,x_mean,y_mean) for x,y,z in vertices]
|
||||
θs = [get_true_teta_from_x_y(x,y,x_mean,y_mean) for x,y,z in vertices]
|
||||
θs = [get_true_theta_from_x_y(x,y,x_mean,y_mean) for x,y,z in vertices]
|
||||
min_θ = min(θs)
|
||||
current_slot.addWidget(cross_section([θ-min_θ for θ in θs],
|
||||
[r-r_mean for r in rs],
|
||||
"Difference entre le rayon de chaque points et le rayon moyen de la couche",
|
||||
"Theta en rad",
|
||||
"R - <R>",
|
||||
"r - <r> en mm",
|
||||
False).native)
|
||||
self.set_status("Graphs rendered!")
|
||||
self.set_status("Graphs rendered!")
|
||||
|
||||
|
||||
def clear_slot(self,slot):
|
||||
|
@ -567,6 +567,13 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="16" column="0">
|
||||
<widget class="QPushButton" name="export_advanced_metrics">
|
||||
<property name="text">
|
||||
<string>Exporter les variables</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
|
@ -277,6 +277,9 @@ class Ui_MainWindow(object):
|
||||
self.line.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||
self.line.setObjectName("line")
|
||||
self.formLayout.setWidget(7, QtWidgets.QFormLayout.FieldRole, self.line)
|
||||
self.export_advanced_metrics = QtWidgets.QPushButton(self.tab_6)
|
||||
self.export_advanced_metrics.setObjectName("export_advanced_metrics")
|
||||
self.formLayout.setWidget(16, QtWidgets.QFormLayout.LabelRole, self.export_advanced_metrics)
|
||||
self.gridLayout_15.addLayout(self.formLayout, 0, 0, 1, 1)
|
||||
self.SettingsTab.addTab(self.tab_6, "")
|
||||
self.gridLayout_2.addWidget(self.SettingsTab, 1, 0, 1, 1)
|
||||
@ -519,6 +522,7 @@ class Ui_MainWindow(object):
|
||||
self.sigma_r_tot_label.setText(_translate("MainWindow", "σ<R>tot"))
|
||||
self.MI_p_label.setText(_translate("MainWindow", "MI_p"))
|
||||
self.MI_l_label.setText(_translate("MainWindow", "MI_l"))
|
||||
self.export_advanced_metrics.setText(_translate("MainWindow", "Exporter les variables"))
|
||||
self.SettingsTab.setTabText(self.SettingsTab.indexOf(self.tab_6), _translate("MainWindow", "Valeurs"))
|
||||
self.GraphTabs.setTabText(self.GraphTabs.indexOf(self.tab), _translate("MainWindow", "1"))
|
||||
self.GraphTabs.setTabText(self.GraphTabs.indexOf(self.tab_2), _translate("MainWindow", "2"))
|
||||
|
@ -751,7 +751,7 @@ qt_resource_struct_v2 = b"\
|
||||
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
|
||||
\x00\x00\x01\x87\xdb\xa2\x85\x9e\
|
||||
\x00\x00\x01\x87\xdc\x71\xcf\xb8\
|
||||
"
|
||||
|
||||
qt_version = [int(v) for v in QtCore.qVersion().split('.')]
|
||||
|
@ -101,51 +101,51 @@ def get_radius_std(discrete_values:list):
|
||||
radius.append(get_radius_from_x_y(x,y,x_mean,y_mean))
|
||||
return get_standard_deviation(radius)
|
||||
|
||||
def get_mean_teta(discrete_values:list):
|
||||
def get_mean_theta(discrete_values:list):
|
||||
"""
|
||||
Get the mean of the teta in the discrete range.
|
||||
Get the mean of the theta in the discrete range.
|
||||
|
||||
:param discrete_values: discrete values
|
||||
:return: mean of the teta in the discrete range
|
||||
:return: mean of the theta in the discrete range
|
||||
|
||||
:Example:
|
||||
>>> get_mean_teta([(1,2,3),(4,5,6),(7,8,9)])
|
||||
>>> get_mean_theta([(1,2,3),(4,5,6),(7,8,9)])
|
||||
0.7853981633974483
|
||||
"""
|
||||
x_mean, y_mean, z_mean = get_x_y_z_mean(discrete_values)
|
||||
teta = []
|
||||
theta = []
|
||||
for x,y,z in discrete_values:
|
||||
teta.append(get_teta_from_x_y(x,y,x_mean,y_mean))
|
||||
return get_mean(teta)
|
||||
theta.append(get_theta_from_x_y(x,y,x_mean,y_mean))
|
||||
return get_mean(theta)
|
||||
|
||||
def get_teta_from_x_y(xi:float, yi:float, x_mean:float, y_mean:float):
|
||||
def get_theta_from_x_y(xi:float, yi:float, x_mean:float, y_mean:float):
|
||||
"""
|
||||
Get the teta from the x and y coordinates.
|
||||
Get the theta from the x and y coordinates.
|
||||
|
||||
:param xi: x coordinate
|
||||
:param yi: y coordinate
|
||||
:param x_mean: mean of x coordinates in the discrete range
|
||||
:param y_mean: mean of y coordinates in the discrete range
|
||||
:return: teta for this point
|
||||
:return: theta for this point
|
||||
|
||||
:Example:
|
||||
>>> get_teta_from_x_y(1,2,3,4)
|
||||
>>> get_theta_from_x_y(1,2,3,4)
|
||||
0.7853981633974483
|
||||
"""
|
||||
return math.atan((yi - y_mean)/(xi-x_mean))
|
||||
|
||||
def get_true_teta_from_x_y(xi:float, yi:float, x_mean:float, y_mean:float):
|
||||
def get_true_theta_from_x_y(xi:float, yi:float, x_mean:float, y_mean:float):
|
||||
"""
|
||||
Get the teta from the x and y coordinates.
|
||||
Get the theta from the x and y coordinates.
|
||||
|
||||
:param xi: x coordinate
|
||||
:param yi: y coordinate
|
||||
:param x_mean: mean of x coordinates in the discrete range
|
||||
:param y_mean: mean of y coordinates in the discrete range
|
||||
:return: teta for this point
|
||||
:return: theta for this point
|
||||
|
||||
:Example:
|
||||
>>> get_true_teta_from_x_y(1,2,3,4)
|
||||
>>> get_true_theta_from_x_y(1,2,3,4)
|
||||
0.7853981633974483
|
||||
"""
|
||||
return math.atan2((xi-x_mean),(yi-y_mean))
|
||||
|
Loading…
Reference in New Issue
Block a user