Skip to content
Snippets Groups Projects
input.py 13.2 KiB
Newer Older
Djalim Simaila's avatar
Djalim Simaila committed
"""
This module contains the functions to parse the input files, and create a ScannedObject.
"""
import numpy as np
from utils.files.output import save_output_file


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)
Djalim Simaila's avatar
Djalim Simaila committed
    :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
Djalim Simaila's avatar
Djalim Simaila committed

    :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)]]
    #TODO Add and test exemples
    """
    def __init__(self, vertices, faces=None, result_file_path=None):
        self.vertices = np.asarray(vertices)
        self.faces = np.asarray(faces)
Djalim Simaila's avatar
Djalim Simaila committed
        # Deprecated
        self.result_file_path = result_file_path
        self.bruteforce_discretization_result = 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])
Djalim Simaila's avatar
Djalim Simaila committed


    @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'):
                    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])
                    else:
                        triangles.append([int(line.split()[1])-1, int(line.split()[2])-1, int(line.split()[3])-1])
Djalim Simaila's avatar
Djalim Simaila committed
                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):
        """
        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):
        """
        Discretize the vertices of the object.
        :param step: Step of the discretization
        :return: Discretized vertices
        """
        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)
        return splitted_data

    def get_discrete_vertices2(self, step:float = 1):
        """
        Deprecated
        """
        cpt = 0
        L = [[]]
        for vertex in self.get_vertices(sort=True):
            step = 1
            L[-1].append(vertex)
            if vertex[2] > cpt + step:
                cpt += step
                L.append([])
        return L
    
    def get_discrete_vertices3(self, step:float = 1):
        """
        Deprecated
        """
        cpt = 0
        L = [[]]
        z = min(self.get_z())
        sorted = self.get_vertices(sort=True)
        for index in range(len(sorted)):
            L[-1].append(sorted[index])
            if sorted[index][2] - z > step:
                z = sorted[index][2]
                L.append([])
        return L
    
    def get_faces(self,resolved:bool = False)->list:
Djalim Simaila's avatar
Djalim Simaila committed
        """
        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]
Djalim Simaila's avatar
Djalim Simaila committed
        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))) 

Djalim Simaila's avatar
Djalim Simaila committed
    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 bruteforce_discretization(self):
        """
        Deprecated
        TODO Remove this when its not needed anymore
        """
        if self.bruteforce_discretization_result:
            return self.bruteforce_discretization_result
        if self.result_file_path is None:
            raise ResultFileNotGiven("No result file was given")
        L = []
        splitted_data = [[]]
        moyx, moyy, moyz = parse_result_file(self.result_file_path)
        moy = moyx
        verticies = self.get_vertices(sort=True)
        position = 0
        while position < len(verticies):
            print('progression :',position/len(verticies)*100,end="\r")
Djalim Simaila's avatar
Djalim Simaila committed
            x = verticies[position][0]
            y = verticies[position][1]
            z = verticies[position][2]
            L.append(x)
            splitted_data[-1].append(verticies[position])
            m = np.mean(L)
            if len(moy) > 0 and abs(m - moy[0]) < 0.000001:
                moy.pop(0)
                L = []
                splitted_data.append([])
                copyposition = position
                while int(verticies[copyposition][2]) == int(verticies[copyposition-1][2]):
                    copyposition -= 1
            position += 1
        print(50*" ")
        self.bruteforce_discretization_result = splitted_data
        return splitted_data

    def export_xyz(self, file_path:str,separator:str="\t"):
Djalim Simaila's avatar
Djalim Simaila committed
        """
        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)

Djalim Simaila's avatar
Djalim Simaila committed

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