🐛 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:
Djalim Simaila 2023-05-09 10:56:43 +02:00
parent 4402085620
commit fb2bb6e9ce
14 changed files with 219 additions and 137 deletions

View File

@ -104,7 +104,7 @@ def check_raw_data(expected_file, actual_file, ndigits=7, eps=0.00001, silent: b
minimal_output_data = "" minimal_output_data = ""
output_data = "" output_data = ""
expected = [] 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: with open(expected_file, "r") as f:
expected = f.readlines()[1:] 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_x_diffs.append(x_diff)
list_y_diffs.append(y_diff) list_y_diffs.append(y_diff)
list_z_diffs.append(z_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_radius_diffs.append(r_diff)
list_xmoy_diffs.append(xmoy_diff) list_xmoy_diffs.append(xmoy_diff)
list_ymoy_diffs.append(ymoy_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: if z_diff > eps:
minimal_output_data += f"{i} : Z diff {z_diff}\n" minimal_output_data += f"{i} : Z diff {z_diff}\n"
if t_diff > eps: 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: if r_diff > eps:
minimal_output_data += f"{i} : R diff {r_diff}\n" minimal_output_data += f"{i} : R diff {r_diff}\n"
if xmoy_diff > eps: 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_x_diff = round(sum(list_x_diffs)/len(expected), ndigits)
sum_y_diff = round(sum(list_y_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_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_radius_diff = round(sum(list_radius_diffs)/len(expected), ndigits)
sum_xmoy_diff = round(sum(list_xmoy_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) 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 x : {sum_x_diff}")
print(f"diff moyenne de y : {sum_y_diff}") print(f"diff moyenne de y : {sum_y_diff}")
print(f"diff moyenne de z : {sum_z_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 r : {sum_radius_diff}")
print(f"diff moyenne de xmoy : {sum_xmoy_diff}") print(f"diff moyenne de xmoy : {sum_xmoy_diff}")
print(f"diff moyenne de ymoy : {sum_ymoy_diffs}") print(f"diff moyenne de ymoy : {sum_ymoy_diffs}")
print( 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 {output_file} pour plus de détails")
print(f"Voir {minimal_output_file} pour les différences significatives") print(f"Voir {minimal_output_file} pour les différences significatives")
print("_"*80) 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, return {"x": list_x_diffs,
"y": list_y_diffs, "y": list_y_diffs,
"z": list_z_diffs, "z": list_z_diffs,
"teta": list_teta_diffs, "theta": list_theta_diffs,
"radius": list_radius_diffs, "radius": list_radius_diffs,
"xmoy": list_xmoy_diffs, "xmoy": list_xmoy_diffs,
"ymoy": list_ymoy_diffs} "ymoy": list_ymoy_diffs}
@ -236,7 +236,7 @@ def test_get_raw_data(obj: ScannedObject, expected_file: str, ndigits: int = 6,
print() print()
print("Time to calculate raw data: ", time.time() - now) print("Time to calculate raw data: ", time.time() - now)
output.save_output_file('tmp_analyse_brute.txt', output.format_data(data, '\t', [ 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) check_raw_data(expected_file, 'tmp_analyse_brute.txt', ndigits, eps)
os.remove('tmp_analyse_brute.txt') os.remove('tmp_analyse_brute.txt')

View File

@ -8,7 +8,6 @@ Created on Mon Apr 24 2023
from utils.math import data_extraction as de from utils.math import data_extraction as de
import numpy as np import numpy as np
from utils.files.input import ScannedObject from utils.files.input import ScannedObject
from utils.settings.SettingManager import SettingManager
def progressbar_placeholder(percent:int): 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 - X (en mm) : list of x values
- Y (en mm) : list of y values - Y (en mm) : list of y values
- Z (en mm) : list of z 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 - rayon (en mm) : list of radius values
- Xi-Xmoy : list of Xi-Xmoy values - Xi-Xmoy : list of Xi-Xmoy values
- Yi-Ymoy : list of Yi-Ymoy values - Yi-Ymoy : list of Yi-Ymoy values
""" """
# Create the data dict # 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 = {} data = {}
for colone in colones: for colone in colones:
data[colone] = [] 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["X (en mm)"].append(round(x, ndigits))
data["Y (en mm)"].append(round(y, ndigits)) data["Y (en mm)"].append(round(y, ndigits))
data["Z (en mm)"].append(round(z, 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["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["Xi-Xmoy"].append(round(x-mean_x, ndigits))
data["Yi-Ymoy"].append(round(y-mean_y, 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 moyen (en mm) : list of mean radius values
- Rayon ecart type (en mm) : list of radius standard deviation values - Rayon ecart type (en mm) : list of radius standard deviation values
""" """
# Create the data dict # 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 = {} data = {}
for colone in colones: for colone in colones:
data[colone] = [] data[colone] = []
@ -96,25 +107,49 @@ def get_discrete_data(obj:ScannedObject, ndigits:int, delta_z:float=1, update_pr
progress += 1 progress += 1
return data 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 # Tortusity
l = 0 l = 0
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)"])) vertices = list(zip(discrete_data["X moy (en mm)"],
for i in range(len(vertices)-1): discrete_data["Y moy (en mm)"],
l += de.get_distance_between_two_vertices(vertices[i], vertices[i+1]) 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]) L = de.get_distance_between_two_vertices(vertices[0], vertices[-1])
T = l/L T = l/L
update_progress_bar(10) update_progress_bar(10)
# Volume and surface # Volume and surface
H = discrete_data["Z moy (en mm)"][-1] - discrete_data["Z moy (en mm)"][0] 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)"]]) R = de.get_mean([np.power(r,2) for r in discrete_data["Rayon moyen (en mm)"]])
V = np.pi * R * H V = np.pi * R * H
S = 2 * np.pi * R * H S = 2 * np.pi * R * H
update_progress_bar(30) update_progress_bar(30)
#
# Morphological indicators
R_mean = de.get_mean(discrete_data["Rayon moyen (en mm)"]) R_mean = de.get_mean(discrete_data["Rayon moyen (en mm)"])
R_mean_std = de.get_standard_deviation(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)"]]) mean_sigma_r_squared = de.get_mean([np.power(r,2) for r in discrete_data["Rayon ecart type (en mm)"]])

View File

@ -26,11 +26,9 @@ class ScannedObject:
:param vertices: List of verticesm Ndarray of shape (n,2) :param vertices: List of verticesm Ndarray of shape (n,2)
:param faces: List of faces, 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 vertices: List of vertices, Ndarray of shape (n,2)
:ivar faces: List of faces, 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 x: List of x values of the vertices
:ivar y: List of y values of the vertices :ivar y: List of y values of the vertices
:ivar z: List of z 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 get_data(): Returns the data
:method export: Exports the data to a file :method export: Exports the data to a file
:raises FacesNotGiven: If no faces was given :raises FacesNotGiven: If no faces was given
:raises ResultFileNotGiven: If no result file 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.vertices = np.asarray(vertices)
self.faces = np.asarray(faces) self.faces = np.asarray(faces)
self.old_delta = None self.old_delta = None
@ -82,17 +59,16 @@ class ScannedObject:
@staticmethod @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. Create an Object from an OBJ file.
:param file_path: Path to the 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 ratio: Ratio to apply to the vertices
:param normalised: the axis to normalise :param normalised: the axis to normalise
:return: A ScannedObject :return: A ScannedObject
""" """
with open(file_path, 'r') as f: with open(file_path, 'r', encoding='utf-8') as f:
x, y, z = [], [], [] x, y, z = [], [], []
triangles = [] triangles = []
data = f.readlines() 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]) triangles.append([int(line.split()[1].split("/")[0])-1, int(line.split()[2].split("/")[0])-1, int(line.split()[3].split("/")[0])-1])
else: else:
triangles.append([int(line.split()[1])-1, int(line.split()[2])-1, int(line.split()[3])-1]) 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 '): elif line.startswith('v '):
x.append(float(line.split()[1]) * ratio) x.append(float(line.split()[1]) * ratio)
y.append(float(line.split()[2]) * ratio) y.append(float(line.split()[2]) * ratio)
@ -112,55 +92,54 @@ class ScannedObject:
if 'x' in normalised: if 'x' in normalised:
xmin = min(x) xmin = min(x)
for count, value in enumerate(x): for count,_ in enumerate(x):
x[count] -= xmin x[count] -= xmin
if 'y' in normalised: if 'y' in normalised:
ymin = min(y) ymin = min(y)
for count, value in enumerate(y): for count,_ in enumerate(y):
y[count] -= ymin y[count] -= ymin
if 'z' in normalised: if 'z' in normalised:
zmin = min(z) zmin = min(z)
for count, value in enumerate(z): for count,_ in enumerate(z):
z[count] -= zmin z[count] -= zmin
return ScannedObject(list(zip(x,y,z)), triangles, result_file_path) return ScannedObject(list(zip(x,y,z)), triangles, )
@staticmethod @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. Create an Object from an XYZ file.
:param file_path: Path to the 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 delimiter: The delimiter used in the xyz file.
:param normalised: the axis to normalise :param normalised: the axis to normalise
:return: A ScannedObject :return: A ScannedObject
""" """
x , y , z = [], [], [] x , y , z = [], [], []
with open(file_path, 'r') as f: with open(file_path, 'r',encoding='utf-8') as f:
data = f.readlines() data = f.readlines()
for line in data: for line in data:
x.append(float(line.split(delimiter)[0])) x.append(float(line.split(delimiter)[0]))
y.append(float(line.split(delimiter)[1])) y.append(float(line.split(delimiter)[1]))
z.append(float(line.split(delimiter)[2])) z.append(float(line.split(delimiter)[2]))
if 'x' in normalised: if 'x' in normalised:
xmin = min(x) xmin = min(x)
for count, value in enumerate(x): for count,_ in enumerate(x):
x[count] -= xmin x[count] -= xmin
if 'y' in normalised: if 'y' in normalised:
ymin = min(y) ymin = min(y)
for count, value in enumerate(y): for count,_ in enumerate(y):
y[count] -= ymin y[count] -= ymin
if 'z' in normalised: if 'z' in normalised:
zmin = min(z) zmin = min(z)
for count, value in enumerate(z): for count,_ in enumerate(z):
z[count] -= zmin 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: def get_x(self)->list:
""" """
@ -194,6 +173,12 @@ class ScannedObject:
return vertices return vertices
def get_discrete_vertices(self, step:float = 1)->list: 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": if SettingManager.get_instance().get_setting("discretisation_method") == "Z0-Zi < DeltaZ":
return self.get_discrete_vertices_1(step) return self.get_discrete_vertices_1(step)
return self.get_discrete_vertices_2(step) return self.get_discrete_vertices_2(step)
@ -206,10 +191,14 @@ class ScannedObject:
:param step: Step of the discretization :param step: Step of the discretization
:return: Discretized vertices :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: if self.old_delta == step and self.old_discrete_type == 0:
return self.old_discrete return self.old_discrete
self.old_delta = step self.old_delta = step
self.old_discrete_type = 0 self.old_discrete_type = 0
current_interval = int(min(self.get_z())) current_interval = int(min(self.get_z()))
splitted_data = [[]] splitted_data = [[]]
for line in self.get_vertices(sort=True): for line in self.get_vertices(sort=True):
@ -223,43 +212,53 @@ class ScannedObject:
def get_discrete_vertices_2(self, step:float = 1)->list: 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 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. first and last point of a slice is greater or equal then the step interval.
:param step: Step of the discretization :param step: Step of the discretization
:return: Discretized vertices :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: if self.old_delta == step and self.old_discrete_type == 1:
return self.old_discrete return self.old_discrete
self.old_delta = step self.old_delta = step
self.old_discrete_type = 1 self.old_discrete_type = 1
L = [[]]
splitted_data = [[]]
z = min(self.get_z()) z = min(self.get_z())
sorted_vertices = self.get_vertices(sort=True) sorted_vertices = self.get_vertices(sort=True)
for index,_ in enumerate(sorted_vertices): 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: if sorted_vertices[index][2] - z > step:
z = sorted_vertices[index+1][2] z = sorted_vertices[index+1][2]
L.append([]) splitted_data.append([])
self.old_discrete = L self.old_discrete = splitted_data
return L return splitted_data
def get_faces(self,resolved:bool = False)->list: def get_faces(self,resolved:bool = False)->list:
""" """
Get the faces of the object. 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 :return: faces
""" """
if self.faces is None: if self.faces is None:
raise FacesNotGiven('No faces was given') raise FacesNotGiven('No faces were given')
if resolved: if resolved:
return self.vertices[self.faces] return self.vertices[self.faces]
return self.faces return self.faces
def update_from_faces(self,faces:list): 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 :param faces: Faces to update the object from
""" """
@ -304,7 +303,12 @@ class ScannedObject:
:return: Data of the object :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"): def export_xyz(self, file_path:str,separator:str="\t"):
""" """
@ -314,12 +318,11 @@ class ScannedObject:
:param separator: chars used to separate the values :param separator: chars used to separate the values
""" """
string = '' string = ''
with open(file_path, "w") as f: for vertex in self.get_vertices(sort=True):
for vertex in self.get_vertices(sort=True): x = round(vertex[0], 6)
x = round(vertex[0], 6) y = round(vertex[1], 6)
y = round(vertex[1], 6) z = round(vertex[2], 6)
z = round(vertex[2], 6) string+=f"{x}{separator}{y}{separator}{z}\n"
string+=f"{x}{separator}{y}{separator}{z}\n"
save_output_file(file_path,string) save_output_file(file_path,string)
def export_obj(self,file_path): def export_obj(self,file_path):
@ -329,17 +332,16 @@ class ScannedObject:
:param file_path: Path of the file :param file_path: Path of the file
""" """
string = '' string = ''
with open(file_path, "w") as f: for vertex in self.get_vertices():
for vertex in self.get_vertices(): x = round(vertex[0], 6)
x = round(vertex[0], 6) y = round(vertex[1], 6)
y = round(vertex[1], 6) z = round(vertex[2], 6)
z = round(vertex[2], 6) string+=f"v {x} {y} {z}\n"
string+=f"v {x} {y} {z}\n" for face in self.get_faces():
for face in self.get_faces(): string+="f "
string+="f " for vertex in face:
for vertex in face: string+=f"{vertex+1} "
string+=f"{vertex+1} " string+="\n"
string+="\n"
save_output_file(file_path,string) 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 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 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 file_path: Path of the file
:param separator: chars used to separate the values :param separator: chars used to separate the values

View File

@ -5,10 +5,8 @@ Created on Mon Apr 17 2023
@auth: Djalim Simaila @auth: Djalim Simaila
@e-mail: djalim.simaila@inrae.fr @e-mail: djalim.simaila@inrae.fr
""" """
from utils.settings.SettingManager import SettingManager from utils.settings.SettingManager import SettingManager
def format_data(data:dict, separator:str, selected_columns:list = None) -> str: def format_data(data:dict, separator:str, selected_columns:list = None) -> str:
""" """
Format the data to be saved in the output file. 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 # Write the columns headers
for column_name in selected_columns: for column_name in selected_columns:
if is_prettier: 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: else:
output += column_name + separator output += column_name + separator
output += '\n' output += '\n'
@ -62,5 +60,5 @@ def save_output_file(output_file:str, content:str):
:param output_file: Path to the output file :param output_file: Path to the output file
:param content: Content of the output file :param content: Content of the output file
""" """
with open(output_file, 'w') as f: with open(output_file, 'w',encoding='utf-8') as file:
f.write(content) file.write(content)

View File

@ -1,7 +1,8 @@
""" """
Created on Fri Apr 21 2023 Created on Fri Apr 21 2023
@name: mpl_render.py @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 @auth: Djalim Simaila
@e-mail: djalim.simaila@inrae.fr @e-mail: djalim.simaila@inrae.fr
""" """

View File

@ -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 title: Title of the plot
:param xlabel: Label of the x axis :param xlabel: Label of the x axis
:param ylabel: Label of the y 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) fig = vp.Fig(size=(600, 500), show=False)
plotwidget = fig[0, 0] 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 title: Title of the plot
:param xlabel: Label of the x axis :param xlabel: Label of the x axis
:param ylabel: Label of the y 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) color = (0.3, 0.5, 0.8,.8)
fig = vp.Fig(show=False) fig = vp.Fig(show=False)

View File

@ -1,7 +1,8 @@
""" """
Created on Fri Apr 21 2023 Created on Fri Apr 21 2023
@name: mpl_render.py @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 @auth: Djalim Simaila
@e-mail: djalim.simaila@inrae.fr @e-mail: djalim.simaila@inrae.fr
""" """

View File

@ -10,6 +10,8 @@ from utils.gui.pyqt.about.UI_AboutThis import Ui_AboutThis
from utils.settings.SettingManager import SettingManager from utils.settings.SettingManager import SettingManager
class AboutThis(QtWidgets.QMainWindow,Ui_AboutThis): class AboutThis(QtWidgets.QMainWindow,Ui_AboutThis):
"""
"""
def __init__(self, parent=None) -> None: def __init__(self, parent=None) -> None:
super().__init__(parent) super().__init__(parent)
self.setupUi(self) self.setupUi(self)

View File

@ -30,8 +30,12 @@ class ErrorPopup(object):
self.button_callback = button_callback self.button_callback = button_callback
self.details = details self.details = details
def show_popup(self): 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 = QMessageBox()
msg.setWindowTitle("Erreur") msg.setWindowTitle("Erreur")
msg.setText("Erreur: " + self.error_text) msg.setText("Erreur: " + self.error_text)

View File

@ -13,7 +13,7 @@ from utils.files.input import ScannedObject
from utils.gui.pyqt.main_window.Workers.AdvancedDataWorker import AdvancedDataWorker from utils.gui.pyqt.main_window.Workers.AdvancedDataWorker import AdvancedDataWorker
from utils.gui.pyqt.settings.Settings import Settings from utils.gui.pyqt.settings.Settings import Settings
from utils.gui.pyqt.about.AboutThis import AboutThis 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.settings.SettingManager import SettingManager
from utils.graph2D.visplot_render import cross_section, render2D from utils.graph2D.visplot_render import cross_section, render2D
from utils.graph3D.visplot_render import render3D 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): class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
""" """
Main window of the application, it contains all the UI elements Main window of the application, it contains all the UI elements
""" """
def __init__(self, parent=None): def __init__(self, parent=None):
super(MainWindow, self).__init__(parent) 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 # Retrieve the UI
self.setupUi(self) self.setupUi(self)
# Setup buttons listeners # Setup buttons listeners
@ -42,10 +48,12 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.actionQuitter.triggered.connect(self.close) self.actionQuitter.triggered.connect(self.close)
self.actionQ_propos_de_ce_logiciel.triggered.connect(self.show_about) 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.addItems(['Aucune couche'])
self.layer_ComboBox.currentIndexChanged.connect(self.layer_changed) self.layer_ComboBox.currentIndexChanged.connect(self.layer_changed)
self.graphType = [ # Prepare available graph type list for the slots combobox
self.graph_type = [
"Aucun", "Aucun",
"Mesh3D", "Mesh3D",
"Coupe XZ", "Coupe XZ",
@ -55,15 +63,8 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
"Coupe de la couche", "Coupe de la couche",
"Difference entre le rayon de chaque points et le rayon moyen 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.combo_boxes = [
self.slot0ComboBox, self.slot0ComboBox,
self.slot1ComboBox, self.slot1ComboBox,
@ -78,10 +79,12 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.slot10ComboBox self.slot10ComboBox
] ]
# Setup all combo boxes with values and listener
for combo_box in self.combo_boxes: 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) 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.slots = [
[self.slot0,"Aucun"], [self.slot0,"Aucun"],
[self.slot1,"Aucun"], [self.slot1,"Aucun"],
@ -96,19 +99,32 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
[self.slot10,"Aucun"] [self.slot10,"Aucun"]
] ]
# Retrieve the slot previous value from the settings
for slot_nb,slot in enumerate(self.slots): for slot_nb,slot in enumerate(self.slots):
slot[1] = SettingManager.get_instance().get_last_graph(slot_nb) slot[1] = SettingManager.get_instance().get_last_graph(slot_nb)
self.combo_boxes[slot_nb].setCurrentText(slot[1]) self.combo_boxes[slot_nb].setCurrentText(slot[1])
# Graph number indicator
self.graph_nb =0 self.graph_nb =0
self.graph_type_changed() self.graph_type_changed()
# Construct sub windows
self.settings_window = Settings() self.settings_window = Settings()
self.about_window = AboutThis() self.about_window = AboutThis()
# Variable to check if parametters has changed to avoid re-computation
self.has_changed = True self.has_changed = True
self.old_discretisation_value = None 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): 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] file = QFileDialog.getOpenFileName()[0]
self.input_file_path.setPlainText(file) self.input_file_path.setPlainText(file)
@ -136,19 +153,30 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def check_input_file(self): 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()): 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() 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 False
return True return True
def check_output_folder(self): 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()): 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 False
return True return True
@ -284,13 +312,13 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def set_obj(self,obj:ScannedObject): def set_obj(self,obj:ScannedObject):
""" """
Set the obj to the main window Persists the pre-processed obj
""" """
self.obj = obj self.obj = obj
def set_discrete_data(self,discrete_data:dict): 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 self.discrete_data = discrete_data
layer = [str(i) for i in range(len(discrete_data["X moy (en mm)"]))] 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): 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 self.raw_data = raw_data
def set_advanced_data(self,advanced_data:dict): 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.advanced_data = advanced_data
self.tortuosity.setValue(advanced_data["Tortuosité"]) 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] y_mean = discrete_data["Y moy (en mm)"][layer_nb]
r_mean = discrete_data["Rayon moyen (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] 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) min_θ = min(θs)
current_slot.addWidget(cross_section([θ-min_θ for θ in θs], current_slot.addWidget(cross_section([θ-min_θ for θ in θs],
[r-r_mean for r in rs], [r-r_mean for r in rs],
"Difference entre le rayon de chaque points et le rayon moyen de la couche", "Difference entre le rayon de chaque points et le rayon moyen de la couche",
"Theta en rad", "Theta en rad",
"R - <R>", "r - <r> en mm",
False).native) False).native)
self.set_status("Graphs rendered!") self.set_status("Graphs rendered!")
def clear_slot(self,slot): def clear_slot(self,slot):

View File

@ -567,6 +567,13 @@
</property> </property>
</widget> </widget>
</item> </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> </layout>
</item> </item>
</layout> </layout>

View File

@ -277,6 +277,9 @@ class Ui_MainWindow(object):
self.line.setFrameShadow(QtWidgets.QFrame.Sunken) self.line.setFrameShadow(QtWidgets.QFrame.Sunken)
self.line.setObjectName("line") self.line.setObjectName("line")
self.formLayout.setWidget(7, QtWidgets.QFormLayout.FieldRole, self.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.gridLayout_15.addLayout(self.formLayout, 0, 0, 1, 1)
self.SettingsTab.addTab(self.tab_6, "") self.SettingsTab.addTab(self.tab_6, "")
self.gridLayout_2.addWidget(self.SettingsTab, 1, 0, 1, 1) 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.sigma_r_tot_label.setText(_translate("MainWindow", "σ<R>tot"))
self.MI_p_label.setText(_translate("MainWindow", "MI_p")) self.MI_p_label.setText(_translate("MainWindow", "MI_p"))
self.MI_l_label.setText(_translate("MainWindow", "MI_l")) 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.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), _translate("MainWindow", "1"))
self.GraphTabs.setTabText(self.GraphTabs.indexOf(self.tab_2), _translate("MainWindow", "2")) self.GraphTabs.setTabText(self.GraphTabs.indexOf(self.tab_2), _translate("MainWindow", "2"))

View File

@ -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\x02\x00\x00\x00\x01\x00\x00\x00\x02\
\x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x01\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('.')] qt_version = [int(v) for v in QtCore.qVersion().split('.')]

View File

@ -101,51 +101,51 @@ def get_radius_std(discrete_values:list):
radius.append(get_radius_from_x_y(x,y,x_mean,y_mean)) radius.append(get_radius_from_x_y(x,y,x_mean,y_mean))
return get_standard_deviation(radius) 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 :param discrete_values: discrete values
:return: mean of the teta in the discrete range :return: mean of the theta in the discrete range
:Example: :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 0.7853981633974483
""" """
x_mean, y_mean, z_mean = get_x_y_z_mean(discrete_values) x_mean, y_mean, z_mean = get_x_y_z_mean(discrete_values)
teta = [] theta = []
for x,y,z in discrete_values: for x,y,z in discrete_values:
teta.append(get_teta_from_x_y(x,y,x_mean,y_mean)) theta.append(get_theta_from_x_y(x,y,x_mean,y_mean))
return get_mean(teta) 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 xi: x coordinate
:param yi: y coordinate :param yi: y coordinate
:param x_mean: mean of x coordinates in the discrete range :param x_mean: mean of x coordinates in the discrete range
:param y_mean: mean of y 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: :Example:
>>> get_teta_from_x_y(1,2,3,4) >>> get_theta_from_x_y(1,2,3,4)
0.7853981633974483 0.7853981633974483
""" """
return math.atan((yi - y_mean)/(xi-x_mean)) 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 xi: x coordinate
:param yi: y coordinate :param yi: y coordinate
:param x_mean: mean of x coordinates in the discrete range :param x_mean: mean of x coordinates in the discrete range
:param y_mean: mean of y 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: :Example:
>>> get_true_teta_from_x_y(1,2,3,4) >>> get_true_theta_from_x_y(1,2,3,4)
0.7853981633974483 0.7853981633974483
""" """
return math.atan2((xi-x_mean),(yi-y_mean)) return math.atan2((xi-x_mean),(yi-y_mean))