diff --git a/integration_tests/data_test.py b/integration_tests/data_test.py index cb833a3..2b70e93 100644 --- a/integration_tests/data_test.py +++ b/integration_tests/data_test.py @@ -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') diff --git a/utils/data_processing/data_processing.py b/utils/data_processing/data_processing.py index 1ae0cd9..bb8a8a7 100644 --- a/utils/data_processing/data_processing.py +++ b/utils/data_processing/data_processing.py @@ -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)"]]) diff --git a/utils/files/input.py b/utils/files/input.py index a932893..80f13c9 100644 --- a/utils/files/input.py +++ b/utils/files/input.py @@ -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,55 +92,54 @@ 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])) y.append(float(line.split(delimiter)[1])) z.append(float(line.split(delimiter)[2])) - if 'x' in normalised: + 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: + 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: + 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 diff --git a/utils/files/output.py b/utils/files/output.py index 9f80b03..0b82477 100644 --- a/utils/files/output.py +++ b/utils/files/output.py @@ -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) diff --git a/utils/graph2D/mpl_render.py b/utils/graph2D/mpl_render.py index e6a4c2c..f460c95 100644 --- a/utils/graph2D/mpl_render.py +++ b/utils/graph2D/mpl_render.py @@ -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 """ diff --git a/utils/graph2D/visplot_render.py b/utils/graph2D/visplot_render.py index 07421a4..3cd05f2 100644 --- a/utils/graph2D/visplot_render.py +++ b/utils/graph2D/visplot_render.py @@ -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) diff --git a/utils/graph3D/mpl_render.py b/utils/graph3D/mpl_render.py index 1dcbf43..14274c5 100644 --- a/utils/graph3D/mpl_render.py +++ b/utils/graph3D/mpl_render.py @@ -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 """ diff --git a/utils/gui/pyqt/about/AboutThis.py b/utils/gui/pyqt/about/AboutThis.py index 6691787..b3b2754 100644 --- a/utils/gui/pyqt/about/AboutThis.py +++ b/utils/gui/pyqt/about/AboutThis.py @@ -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) diff --git a/utils/gui/pyqt/error_popup/ErrorPopup.py b/utils/gui/pyqt/error_popup/ErrorPopup.py index b3872a2..904c9f6 100644 --- a/utils/gui/pyqt/error_popup/ErrorPopup.py +++ b/utils/gui/pyqt/error_popup/ErrorPopup.py @@ -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) diff --git a/utils/gui/pyqt/main_window/MainWindow.py b/utils/gui/pyqt/main_window/MainWindow.py index 46b683f..bb38ea6 100644 --- a/utils/gui/pyqt/main_window/MainWindow.py +++ b/utils/gui/pyqt/main_window/MainWindow.py @@ -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", @@ -55,15 +63,8 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): "Coupe de la couche", "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 - en mm", False).native) - self.set_status("Graphs rendered!") + self.set_status("Graphs rendered!") def clear_slot(self,slot): diff --git a/utils/gui/pyqt/main_window/MainWindow.ui b/utils/gui/pyqt/main_window/MainWindow.ui index fd30d8b..c1bbd3b 100644 --- a/utils/gui/pyqt/main_window/MainWindow.ui +++ b/utils/gui/pyqt/main_window/MainWindow.ui @@ -567,6 +567,13 @@ + + + + Exporter les variables + + + diff --git a/utils/gui/pyqt/main_window/UI_MainWindow.py b/utils/gui/pyqt/main_window/UI_MainWindow.py index 8362cd7..f3d9465 100644 --- a/utils/gui/pyqt/main_window/UI_MainWindow.py +++ b/utils/gui/pyqt/main_window/UI_MainWindow.py @@ -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", "σ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")) diff --git a/utils/gui/pyqt/ressources_rc.py b/utils/gui/pyqt/ressources_rc.py index c1b2fd6..b3a8891 100644 --- a/utils/gui/pyqt/ressources_rc.py +++ b/utils/gui/pyqt/ressources_rc.py @@ -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('.')] diff --git a/utils/math/data_extraction.py b/utils/math/data_extraction.py index 9401082..4e02445 100644 --- a/utils/math/data_extraction.py +++ b/utils/math/data_extraction.py @@ -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))