✨ feat(data_processing.py): remove deprecated get_discrete_vertices2 method and rename get_discrete_vertices3 to get_discrete_vertices. Simplify get_discrete_vertices method by removing the selection of the discretisation method from the settings and always using the Z0-Zi < DeltaZ method. This improves code readability and maintainability. ✨ feat(input.py): remove deprecated result_file_path and bruteforce_discretization_result attributes from ScannedObject class. Add old_delta, old_discrete, and old_discrete_type attributes to cache the results of the get_discrete_vertices method. This improves performance by avoiding unnecessary recomputations of the discretized vertices. ✨ feat(MainWindow.py): add support for selecting a layer to display discrete graphs for. Add two new graph types: "Coupe de la couche" and "Difference entre le rayon de chaque points 🔧 fix(ui): change tab index to display the correct tab on startup ✨ feat(ui): add label and combobox to select layer to display 🔧 fix(worker): add discretisation_value parameter to PreProcessWorker constructor ✨ feat(math): add get_true_teta_from_x_y, get_difference_from_mean_value, and get_distance_between_two_vertices functions The UI fix changes the tab index to display the correct tab on startup. The new label and combobox allow the user to select the layer to display. The worker fix adds a discretisation_value parameter to the PreProcessWorker constructor. The new math functions are get_true_teta_from_x_y, get_difference_from_mean_value, and get_distance_between_two_vertices. These functions are useful for calculating teta, differences from mean values, and distances between vertices.
371 lines
13 KiB
Python
371 lines
13 KiB
Python
"""
|
|
Created on Thu Apr 20 2023
|
|
@name: input.py
|
|
@desc: This module contains the functions to parse the input files, and create a ScannedObject.
|
|
@auth: Djalim Simaila
|
|
@e-mail: djalim.simaila@inrae.fr
|
|
"""
|
|
import numpy as np
|
|
from utils.files.output import save_output_file
|
|
from utils.settings.SettingManager import SettingManager
|
|
|
|
|
|
class FacesNotGiven(Exception):
|
|
"""
|
|
Exception raised when no faces was given.
|
|
"""
|
|
|
|
class ResultFileNotGiven(Exception):
|
|
"""
|
|
Exception raised when no faces was given.
|
|
"""
|
|
|
|
class ScannedObject:
|
|
"""
|
|
This class is used to manage the data of the 3D object.
|
|
|
|
: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
|
|
|
|
:static method from_xyz_file(): Creates a ScannedObject from a .xyz file
|
|
:static method from_obj_file(): Creates a ScannedObject from a .obj file
|
|
:method get_x(): Returns the x values of the vertices
|
|
:method get_y(): Returns the y values of the vertices
|
|
:method get_z(): Returns the z values of the vertices
|
|
:method get_vertices(): Returns the vertices
|
|
:method get_faces(): Returns the faces
|
|
:method get_discrete_vertices(): Returns the discrete vertices
|
|
:method get_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):
|
|
self.vertices = np.asarray(vertices)
|
|
self.faces = np.asarray(faces)
|
|
self.old_delta = None
|
|
self.old_discrete = None
|
|
self.old_discrete_type = None
|
|
self.x = np.asarray([vertex[0] for vertex in vertices])
|
|
self.y = np.asarray([vertex[1] for vertex in vertices])
|
|
self.z = np.asarray([vertex[2] for vertex in vertices])
|
|
|
|
|
|
@staticmethod
|
|
def from_obj_file(file_path:str, result_file_path:str = None, 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:
|
|
x, y, z = [], [], []
|
|
triangles = []
|
|
data = f.readlines()
|
|
for line in data :
|
|
if line.startswith('f'):
|
|
# Ignore the normals and textures
|
|
if "//" in line:
|
|
triangles.append([int(line.split()[1].split("//")[0])-1, int(line.split()[2].split("//")[0])-1, int(line.split()[3].split("//")[0])-1])
|
|
elif "/" in line:
|
|
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])
|
|
elif line.startswith('v '):
|
|
x.append(float(line.split()[1]) * ratio)
|
|
y.append(float(line.split()[2]) * ratio)
|
|
z.append(float(line.split()[3]) * ratio)
|
|
|
|
if 'x' in normalised:
|
|
xmin = min(x)
|
|
for count, value in enumerate(x):
|
|
x[count] -= xmin
|
|
|
|
if 'y' in normalised:
|
|
ymin = min(y)
|
|
for count, value in enumerate(y):
|
|
y[count] -= ymin
|
|
|
|
if 'z' in normalised:
|
|
zmin = min(z)
|
|
for count, value in enumerate(z):
|
|
z[count] -= zmin
|
|
|
|
return ScannedObject(list(zip(x,y,z)), triangles, result_file_path)
|
|
|
|
@staticmethod
|
|
def from_xyz_file(file_path: str, result_file_path:str = None, 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:
|
|
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:
|
|
xmin = min(x)
|
|
for count, value in enumerate(x):
|
|
x[count] -= xmin
|
|
|
|
if 'y' in normalised:
|
|
ymin = min(y)
|
|
for count, value in enumerate(y):
|
|
y[count] -= ymin
|
|
|
|
if 'z' in normalised:
|
|
zmin = min(z)
|
|
for count, value in enumerate(z):
|
|
z[count] -= zmin
|
|
return ScannedObject(list(zip(x,y,z)), result_file_path=result_file_path)
|
|
|
|
def get_x(self)->list:
|
|
"""
|
|
Get the x coordinates of the object.
|
|
return: x coordinates
|
|
"""
|
|
return self.x
|
|
|
|
def get_y(self)->list:
|
|
"""
|
|
Get the y coordinates of the object.
|
|
return: y coordinates
|
|
"""
|
|
return self.y
|
|
|
|
def get_z(self)->list:
|
|
"""
|
|
Get the z coordinates of the object.
|
|
return: z coordinates
|
|
"""
|
|
return self.z
|
|
|
|
def get_vertices(self, sort:bool = False)->list:
|
|
"""
|
|
Get the vertices of the object.
|
|
:param sort: Sort the vertices by z coordinate
|
|
|
|
:return: vertices
|
|
"""
|
|
vertices = self.vertices if not sort else sorted(self.vertices, key=lambda vertex: vertex[2])
|
|
return vertices
|
|
|
|
def get_discrete_vertices(self, step:float = 1)->list:
|
|
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)
|
|
|
|
def get_discrete_vertices_1(self, step:float = 1)->list:
|
|
"""
|
|
Discretize the vertices of the object using a split method.
|
|
This implementation will split the object at every step interval.
|
|
|
|
:param step: Step of the discretization
|
|
:return: Discretized vertices
|
|
"""
|
|
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):
|
|
# TODO check distance instead of equality
|
|
if line[2] >= current_interval + step:
|
|
splitted_data.append([])
|
|
current_interval += step
|
|
splitted_data[-1].append(line)
|
|
self.old_discrete = splitted_data
|
|
return splitted_data
|
|
|
|
def get_discrete_vertices_2(self, step:float = 1)->list:
|
|
"""
|
|
Discretize the vertices of the object using a lenght 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 self.old_delta == step and self.old_discrete_type == 1:
|
|
return self.old_discrete
|
|
self.old_delta = step
|
|
self.old_discrete_type = 1
|
|
L = [[]]
|
|
z = min(self.get_z())
|
|
sorted_vertices = self.get_vertices(sort=True)
|
|
for index,_ in enumerate(sorted_vertices):
|
|
L[-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
|
|
|
|
def get_faces(self,resolved:bool = False)->list:
|
|
"""
|
|
Get the faces of the object.
|
|
|
|
:return: faces
|
|
"""
|
|
if self.faces is None:
|
|
raise FacesNotGiven('No faces was given')
|
|
if resolved:
|
|
return self.vertices[self.faces]
|
|
return self.faces
|
|
|
|
def update_from_faces(self,faces:list):
|
|
"""
|
|
Update the object from the faces.
|
|
|
|
:param faces: Faces to update the object from
|
|
"""
|
|
cpt = 0
|
|
vertex_dict = {}
|
|
new_vertices = []
|
|
new_faces = []
|
|
for face in faces:
|
|
new_faces.append([])
|
|
for vertex in face:
|
|
vertex = tuple(vertex)
|
|
if vertex not in vertex_dict:
|
|
vertex_dict[vertex] = cpt
|
|
cpt += 1
|
|
new_vertices.append(vertex)
|
|
new_faces[-1].append(vertex_dict[vertex])
|
|
|
|
self.vertices = np.asarray(new_vertices)
|
|
self.faces = np.asarray(new_faces)
|
|
self.x = self.vertices[:,0]
|
|
self.y = self.vertices[:,1]
|
|
self.z = self.vertices[:,2]
|
|
self.normalise()
|
|
|
|
def normalise(self, axis:str = 'z'):
|
|
"""
|
|
Normalise the object.
|
|
|
|
:param axis: Axis to normalise
|
|
"""
|
|
if 'x' in axis:
|
|
self.x -= min(self.x)
|
|
if 'y' in axis:
|
|
self.y -= min(self.y)
|
|
if 'z' in axis:
|
|
self.z -= min(self.z)
|
|
self.vertices = np.asarray(list(zip(self.x,self.y,self.z)))
|
|
|
|
def get_data(self)->dict:
|
|
"""
|
|
Get the 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}
|
|
|
|
def export_xyz(self, file_path:str,separator:str="\t"):
|
|
"""
|
|
Export the object in a file.
|
|
|
|
:param file_path: Path of the file
|
|
: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"
|
|
save_output_file(file_path,string)
|
|
|
|
def export_obj(self,file_path):
|
|
"""
|
|
Export the object in a file.
|
|
|
|
: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"
|
|
save_output_file(file_path,string)
|
|
|
|
|
|
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
|
|
|
|
:param file_path: Path of the file
|
|
:param separator: chars used to separate the values
|
|
:return: x, y, z
|
|
|
|
:Example:
|
|
>>> parse_result_file("test.txt")
|
|
([1.0, 2.0, 3.0], [1.0, 2.0, 3.0], [1.0, 2.0, 3.0])
|
|
"""
|
|
lines = []
|
|
x, y, z = [], [], []
|
|
with open(file_path, "r") as f:
|
|
lines = f.readlines()[1:]
|
|
for line in lines:
|
|
line = line.replace(",", ".")
|
|
values = line.split(separator)
|
|
x.append(float(values[0]))
|
|
y.append(float(values[1]))
|
|
z.append(float(values[2]))
|
|
return x, y, z
|