Module ReMake.3D_Reconstruction.pointcloud

Expand source code
import math
import numpy as np
import paramiko
from os import path
import json
from datetime import datetime
import cv2
import open3d
import sys
import trimesh
from pathlib import Path

class pointCloud:
    """This class represents the 3D reconstruction bundle for proccesing the data of the scanner.
    """

    def __init__(self):
        """An object of the pointCloud class.
        """

        self.points = []
        self.color = []
        self.flenght = 6 #mm focal lenght of the camera
        self.camera_center_distance = 300 #mm distance camera to center of the bed
        self.pixelsize = 0.00155 #mm horizontal dimension for one pixel 
        self.camera_laser_distance = 100 #mm distance between camaera and laser (the laser ray)
        self.ppm = 0
        self.date = None
        self.res = (0,0)
        self.angle=[]
        self.points = None
        self.toppoints = []
        self.topcolor = []
        self.topangle = []
        self.pointcloud = None
        self.magich = 0.8
        
    def appendLine(self, line):
        """Add a new line of data (points with color information) to the internal buffer.

        Args:
            line (nparry): array of points
        """

        self.points.append(line)

    def debug(self):
        """Show the primary parameters of the scan and draws the points in a 3D viewer.
        """

        print(self.points)
        print(self.ppm)
        print(self.res)
        open3d.visualization.draw_geometries([self.pointcloud])

    
    def download_file(self, name, host = "192.168.1.14", port = 22, password = "raspberry", username = "pi", path = '/home/pi/TestCamera/'):
        """Download the data from the Raspberry Pi via SSh.

        Args:
            name (str): name of the file
            host (str, optional): adress of the Raspberry Pi. Defaults to "192.168.1.14".
            port (int, optional): port of the Raspberry Pi. Defaults to 22.
            password (str, optional): password of the Raspberry Pi. Defaults to "raspberry".
            username (str, optional): username of the Raspberry Pi. Defaults to "pi".
            path (str, optional): local path of the file. Defaults to '/home/pi/TestCamera/'.
        """

        print("Downloading data from {}".format(host))
        ssh = paramiko.SSHClient()
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh.connect(host, port, username, password)
        sftp = ssh.open_sftp()
        #remote_file = sftp.open(path + name)
        try:
            sftp.get(path+name, name)
        finally:
            sftp.close()
            ssh.close()

    def load_data(self, name):
        """Load the data in memory or downloads it from the Raspberry Pi if unavailable.

        Args:
            name (str): name of the data file
        """

        file = Path(name)
        if not file.exists():
            self.download_file(name)
        
        with open(file, 'r') as js:
            data = json.loads(js.read())
            self.date= datetime.strptime(data["date"], "%Y-%m-%d %H:%M:%S")
            self.res = tuple(data["res"])
            points = []
            colors = []
            for s in data["samples"]:
                self.angle.append(s["angle"])
                p_array = np.array(s["points"])

                xy_array = p_array[:,0].tolist()
                col_array = p_array[:,1:]
                points.append(xy_array)
                colors.append(col_array)

            #print(points)
            #self.points = points_arr[0]
            self.color = np.array(colors, dtype=object)
            #self.color.reshape(( len(data["samples"]), self.res[0]))
            self.points = np.array(points)
            #print(points[1])
            #print(self.points.shape)
            self.points.reshape(( len(data["samples"]), self.res[0]))

            toppoints = []
            topcolors = []
            for s in data["top"]:
                self.topangle.append(s["angle"])
                p_array = np.array(s["points"])

                xy_array = p_array[:,0].tolist()
                col_array = p_array[:,1:]
                toppoints.append(xy_array)
                topcolors.append(col_array)
            
            self.topcolor = np.array(colors, dtype=object)

            self.toppoints = np.array(toppoints)
            #print(points)

            self.toppoints.reshape(( len(data["top"]), self.res[0]))

            print("Data loaded successfully")
            #print(self.date)
            js.close()

    def show_laser(self, index):
        """Generate an image from the data available.

        Args:
            index (int): index of the picture taken by the scanner

        Returns:
            nparray: image generated
        """

        img = np.zeros((self.res[0], self.res[1], 3), dtype=np.uint8)
        points = self.points[index]
        for x, y in enumerate(points):
            img[x,y] = tuple(self.color[index, x])
            #print(tuple(self.color[index, x]))
        return img
        
    def compute_ppm(self):
        """Compute the ppm (pixels per meter) variable.
        """

        self.ppm = (self.camera_center_distance - self.camera_laser_distance) / self.flenght
        self.ppm = self.ppm * self.pixelsize

    def set_ppm(self, new_ppm):
        """Manually set the ppm (pixels per meter) variable.

        Args:
            new_ppm (float): new ppm value
        """

        self.ppm = new_ppm
                                
    def create_pointcloud(self, viewPC=False):
        """Generate in memory a new pointcloud from the data loaded and calculate its parameters.

        Args:
            viewPC (bool, optional): flag to show a 3D representation of the points. Defaults to False.
        """

        #self.pointcloud
        if self.ppm == 0:
            self.compute_ppm()

        center = self.res[1]/2
        hcenter = self.res[0]/2
        vertex = []
        colors = []
        multiplier = self.camera_center_distance / self.camera_laser_distance
        
        for index, view in enumerate(self.points):
            alpha = math.radians(self.angle[index])
            for height, pixel in enumerate(view):
                if pixel>=0 :
                    d = self.ppm * (pixel-center) * multiplier
                    x = d * math.cos(alpha)
                    y = d * math.sin(alpha)
                    z = height * self.ppm * self.magich
                    cond =  False or z < 200#(z>0 and z<294) or (abs(pixel-center<40)
                    if cond:
                        vertex.append((x, y, z))
                        rc = self.color[index, height, 0]
                        gc = self.color[index, height, 1]
                        bc = self.color[index, height, 2]

                        colors.append((rc, gc, bc))
        
        #Apply the same alghorithm for the top points but with the scanning plane rotated by 90 degrees 
        for index, view in enumerate(self.toppoints):
            alpha = math.radians(self.topangle[index])
            for height, pixel in enumerate(view):
                if pixel>=0 :
                    d = self.ppm * (pixel-center) * multiplier * .1 + 150
                    h = self.ppm * (height-hcenter) * self.magich * .7
                    x = h * math.cos(alpha)
                    y = h * math.sin(alpha)
                    z = d
                    cond =  abs(d-152) < 10 #???
                    if cond:
                        vertex.append((x, y, z))
                        rc = self.topcolor[index, height, 0]
                        gc = self.topcolor[index, height, 1]
                        bc = self.topcolor[index, height, 2]

                        colors.append((rc, gc, bc))
        #print(colors)
        self.pointcloud = open3d.geometry.PointCloud()
        self.pointcloud.points = open3d.utility.Vector3dVector(np.array(vertex))
        self.pointcloud.colors = open3d.utility.Vector3dVector(np.array(colors)/255)
        
        self.pointcloud.estimate_normals(search_param=open3d.geometry.KDTreeSearchParamHybrid(radius=0.001, max_nn=30))
        _, ind = self.pointcloud.remove_statistical_outlier(nb_neighbors=20, std_ratio=0.02)
        #_, ind = self.pointcloud.remove_radius_outlier(nb_points=20, radius=20)#remove_statistical_outlier(nb_neighbors=1000, std_ratio=0.0001)
        self.pointcloud = self.pointcloud.select_by_index(ind)
        _, ind = self.pointcloud.remove_radius_outlier(nb_points=50, radius=20)#remove_statistical_outlier(nb_neighbors=1000, std_ratio=0.0001)
        self.pointcloud = self.pointcloud.select_by_index(ind)
        self.pointcloud.rotate(self.pointcloud.get_rotation_matrix_from_xyz((np.pi / 2, 0, np.pi / 4)),center=(0, 0, 0))
        if viewPC:
            open3d.visualization.draw_geometries([self.pointcloud])

    def poisson_reconstruction(self, depth):
        """Reconstruct a mesh of the pointcloud using Poisson's algorithm.

        Args:
            depth (int): parameter used to reconstruct the mesh

        Returns:
            mesh: generated mesh
        """

        mesh = open3d.geometry.TriangleMesh.create_from_point_cloud_poisson(self.pointcloud, depth=depth, width=0, scale=1.1, linear_fit=True)[0]
        open3d.geometry.TriangleMesh.compute_triangle_normals(mesh)
        bbox = self.pointcloud.get_axis_aligned_bounding_box()
        mesh = mesh.crop(bbox)
        return mesh
    
    def alpha_reconstruction(self, alpha):
        """Reconstruct a mesh of the pointcloud using Alpha reconstruction algorithm.

        Args:
            alpha (float): alpha parameter used in reconstruction

        Returns:
            mesh: generated mesh
        """

        mesh = open3d.geometry.TriangleMesh.create_from_point_cloud_alpha_shape(self.pointcloud, alpha)
        mesh.compute_vertex_normals()
        return mesh

    def ball_reconstruction(self, mult):
        """Reconstruct a mesh of the pointcloud using the pivoting ball algorithm.

        Args:
            mult (int): multiplier used to determine the ball size

        Returns:
            mesh: generated mesh
        """

        radii = [0.005, 0.01, 0.02, 0.04]*mult
        mesh = open3d.geometry.TriangleMesh.create_from_point_cloud_ball_pivoting(self.pointcloud, open3d.utility.DoubleVector(radii))
        return mesh
    
    def save_mesh(self, mesh, name):
        """Save the mesh using STL format.

        Args:
            mesh (mesh): mesh to be saved
            name (str): name for the file
        """

        open3d.io.write_triangle_mesh("{}.stl".format(name), mesh)

    def fromImage(self, im):
        """Primitive algorithm used to extract data out of an boolean image containing the laser position.

        Args:
            im (nparray): image from which the data should be extracted
        """

        for k, line in enumerate(im):
            if len(self.points) == 0:
                seed = -1
            else:
                seed = self.points[k-1]

            point = self.__linePoint(line, seed)

            self.points.append(point)

    def __linePoint(self, line, seed=-1): #to be made private
        """Determine the laser position in a line of an image using Divide et Impera algorithm and the seed of the last position.

        Args:
            line (nparray): line from an image 
            seed (int, optional): seed with the last position found. Defaults to -1.

        Returns:
            int: position of the laser
        """
        
        cs=0
        cd=len(line)
        mid = (int)((cs+cd)/2)

        if seed < 0:
            seed=mid
         
        if seed >= mid:
            dr=seed
            for st in range(seed,cs,-1):
                if line[st] > 0:
                    return st

                if line[dr] > 0:
                    return dr

                dr = min(dr + 1, cd - 1) #use cd-1 because line is indexed from 0

        else:
            st = seed
            for dr in range(seed, cd, 1):
                if line[st] > 0:
                    return st

                if line[dr] > 0:
                    return dr
                    
                st = max(st - 1, cs)

        return -1

        
if __name__ == "__main__":

    view = True
    p = pointCloud()
    p.load_data('data.json')
    p.set_ppm(1)
    #p.debug()
    img = p.show_laser(1)

    p.create_pointcloud(viewPC=True)

    mesh = p.alpha_reconstruction(50)
    #mesh = p.poisson_reconstruction(3)
    #mesh = p.ball_reconstruction(1)

    p.save_mesh(mesh, "demo")
    #p.debug()
    if view:
        vis = open3d.visualization.Visualizer()
        vis.create_window()
        vis.add_geometry(mesh)#5
        #vis.add_geometry(p.poisson_reconstruction(12))
        #vis.add_geometry(p.ball_reconstruction(1))
        vis.run()
    
    meshh = trimesh.load_mesh('demo.stl')
    #trimesh.repair.fill_holes(meshh)
    #meshh = as_mesh(meshh)
    trimesh.repair.fix_inversion(meshh)
    trimesh.repair.fill_holes(meshh)
    #trimesh.repair.fix_normals(meshh)
    trimesh.repair.fix_winding(meshh)
    trimesh.smoothing.filter_laplacian(meshh)
    #trimesh.repair.fill_holes(meshh)

    if view:
        meshh.show()

    meshh.show()

    meshh.export("demo.stl")

    if view:
        cv2.imshow("any", img)
        k = cv2.waitKey(0)

Classes

class pointCloud

This class represents the 3D reconstruction bundle for proccesing the data of the scanner.

An object of the pointCloud class.

Expand source code
class pointCloud:
    """This class represents the 3D reconstruction bundle for proccesing the data of the scanner.
    """

    def __init__(self):
        """An object of the pointCloud class.
        """

        self.points = []
        self.color = []
        self.flenght = 6 #mm focal lenght of the camera
        self.camera_center_distance = 300 #mm distance camera to center of the bed
        self.pixelsize = 0.00155 #mm horizontal dimension for one pixel 
        self.camera_laser_distance = 100 #mm distance between camaera and laser (the laser ray)
        self.ppm = 0
        self.date = None
        self.res = (0,0)
        self.angle=[]
        self.points = None
        self.toppoints = []
        self.topcolor = []
        self.topangle = []
        self.pointcloud = None
        self.magich = 0.8
        
    def appendLine(self, line):
        """Add a new line of data (points with color information) to the internal buffer.

        Args:
            line (nparry): array of points
        """

        self.points.append(line)

    def debug(self):
        """Show the primary parameters of the scan and draws the points in a 3D viewer.
        """

        print(self.points)
        print(self.ppm)
        print(self.res)
        open3d.visualization.draw_geometries([self.pointcloud])

    
    def download_file(self, name, host = "192.168.1.14", port = 22, password = "raspberry", username = "pi", path = '/home/pi/TestCamera/'):
        """Download the data from the Raspberry Pi via SSh.

        Args:
            name (str): name of the file
            host (str, optional): adress of the Raspberry Pi. Defaults to "192.168.1.14".
            port (int, optional): port of the Raspberry Pi. Defaults to 22.
            password (str, optional): password of the Raspberry Pi. Defaults to "raspberry".
            username (str, optional): username of the Raspberry Pi. Defaults to "pi".
            path (str, optional): local path of the file. Defaults to '/home/pi/TestCamera/'.
        """

        print("Downloading data from {}".format(host))
        ssh = paramiko.SSHClient()
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh.connect(host, port, username, password)
        sftp = ssh.open_sftp()
        #remote_file = sftp.open(path + name)
        try:
            sftp.get(path+name, name)
        finally:
            sftp.close()
            ssh.close()

    def load_data(self, name):
        """Load the data in memory or downloads it from the Raspberry Pi if unavailable.

        Args:
            name (str): name of the data file
        """

        file = Path(name)
        if not file.exists():
            self.download_file(name)
        
        with open(file, 'r') as js:
            data = json.loads(js.read())
            self.date= datetime.strptime(data["date"], "%Y-%m-%d %H:%M:%S")
            self.res = tuple(data["res"])
            points = []
            colors = []
            for s in data["samples"]:
                self.angle.append(s["angle"])
                p_array = np.array(s["points"])

                xy_array = p_array[:,0].tolist()
                col_array = p_array[:,1:]
                points.append(xy_array)
                colors.append(col_array)

            #print(points)
            #self.points = points_arr[0]
            self.color = np.array(colors, dtype=object)
            #self.color.reshape(( len(data["samples"]), self.res[0]))
            self.points = np.array(points)
            #print(points[1])
            #print(self.points.shape)
            self.points.reshape(( len(data["samples"]), self.res[0]))

            toppoints = []
            topcolors = []
            for s in data["top"]:
                self.topangle.append(s["angle"])
                p_array = np.array(s["points"])

                xy_array = p_array[:,0].tolist()
                col_array = p_array[:,1:]
                toppoints.append(xy_array)
                topcolors.append(col_array)
            
            self.topcolor = np.array(colors, dtype=object)

            self.toppoints = np.array(toppoints)
            #print(points)

            self.toppoints.reshape(( len(data["top"]), self.res[0]))

            print("Data loaded successfully")
            #print(self.date)
            js.close()

    def show_laser(self, index):
        """Generate an image from the data available.

        Args:
            index (int): index of the picture taken by the scanner

        Returns:
            nparray: image generated
        """

        img = np.zeros((self.res[0], self.res[1], 3), dtype=np.uint8)
        points = self.points[index]
        for x, y in enumerate(points):
            img[x,y] = tuple(self.color[index, x])
            #print(tuple(self.color[index, x]))
        return img
        
    def compute_ppm(self):
        """Compute the ppm (pixels per meter) variable.
        """

        self.ppm = (self.camera_center_distance - self.camera_laser_distance) / self.flenght
        self.ppm = self.ppm * self.pixelsize

    def set_ppm(self, new_ppm):
        """Manually set the ppm (pixels per meter) variable.

        Args:
            new_ppm (float): new ppm value
        """

        self.ppm = new_ppm
                                
    def create_pointcloud(self, viewPC=False):
        """Generate in memory a new pointcloud from the data loaded and calculate its parameters.

        Args:
            viewPC (bool, optional): flag to show a 3D representation of the points. Defaults to False.
        """

        #self.pointcloud
        if self.ppm == 0:
            self.compute_ppm()

        center = self.res[1]/2
        hcenter = self.res[0]/2
        vertex = []
        colors = []
        multiplier = self.camera_center_distance / self.camera_laser_distance
        
        for index, view in enumerate(self.points):
            alpha = math.radians(self.angle[index])
            for height, pixel in enumerate(view):
                if pixel>=0 :
                    d = self.ppm * (pixel-center) * multiplier
                    x = d * math.cos(alpha)
                    y = d * math.sin(alpha)
                    z = height * self.ppm * self.magich
                    cond =  False or z < 200#(z>0 and z<294) or (abs(pixel-center<40)
                    if cond:
                        vertex.append((x, y, z))
                        rc = self.color[index, height, 0]
                        gc = self.color[index, height, 1]
                        bc = self.color[index, height, 2]

                        colors.append((rc, gc, bc))
        
        #Apply the same alghorithm for the top points but with the scanning plane rotated by 90 degrees 
        for index, view in enumerate(self.toppoints):
            alpha = math.radians(self.topangle[index])
            for height, pixel in enumerate(view):
                if pixel>=0 :
                    d = self.ppm * (pixel-center) * multiplier * .1 + 150
                    h = self.ppm * (height-hcenter) * self.magich * .7
                    x = h * math.cos(alpha)
                    y = h * math.sin(alpha)
                    z = d
                    cond =  abs(d-152) < 10 #???
                    if cond:
                        vertex.append((x, y, z))
                        rc = self.topcolor[index, height, 0]
                        gc = self.topcolor[index, height, 1]
                        bc = self.topcolor[index, height, 2]

                        colors.append((rc, gc, bc))
        #print(colors)
        self.pointcloud = open3d.geometry.PointCloud()
        self.pointcloud.points = open3d.utility.Vector3dVector(np.array(vertex))
        self.pointcloud.colors = open3d.utility.Vector3dVector(np.array(colors)/255)
        
        self.pointcloud.estimate_normals(search_param=open3d.geometry.KDTreeSearchParamHybrid(radius=0.001, max_nn=30))
        _, ind = self.pointcloud.remove_statistical_outlier(nb_neighbors=20, std_ratio=0.02)
        #_, ind = self.pointcloud.remove_radius_outlier(nb_points=20, radius=20)#remove_statistical_outlier(nb_neighbors=1000, std_ratio=0.0001)
        self.pointcloud = self.pointcloud.select_by_index(ind)
        _, ind = self.pointcloud.remove_radius_outlier(nb_points=50, radius=20)#remove_statistical_outlier(nb_neighbors=1000, std_ratio=0.0001)
        self.pointcloud = self.pointcloud.select_by_index(ind)
        self.pointcloud.rotate(self.pointcloud.get_rotation_matrix_from_xyz((np.pi / 2, 0, np.pi / 4)),center=(0, 0, 0))
        if viewPC:
            open3d.visualization.draw_geometries([self.pointcloud])

    def poisson_reconstruction(self, depth):
        """Reconstruct a mesh of the pointcloud using Poisson's algorithm.

        Args:
            depth (int): parameter used to reconstruct the mesh

        Returns:
            mesh: generated mesh
        """

        mesh = open3d.geometry.TriangleMesh.create_from_point_cloud_poisson(self.pointcloud, depth=depth, width=0, scale=1.1, linear_fit=True)[0]
        open3d.geometry.TriangleMesh.compute_triangle_normals(mesh)
        bbox = self.pointcloud.get_axis_aligned_bounding_box()
        mesh = mesh.crop(bbox)
        return mesh
    
    def alpha_reconstruction(self, alpha):
        """Reconstruct a mesh of the pointcloud using Alpha reconstruction algorithm.

        Args:
            alpha (float): alpha parameter used in reconstruction

        Returns:
            mesh: generated mesh
        """

        mesh = open3d.geometry.TriangleMesh.create_from_point_cloud_alpha_shape(self.pointcloud, alpha)
        mesh.compute_vertex_normals()
        return mesh

    def ball_reconstruction(self, mult):
        """Reconstruct a mesh of the pointcloud using the pivoting ball algorithm.

        Args:
            mult (int): multiplier used to determine the ball size

        Returns:
            mesh: generated mesh
        """

        radii = [0.005, 0.01, 0.02, 0.04]*mult
        mesh = open3d.geometry.TriangleMesh.create_from_point_cloud_ball_pivoting(self.pointcloud, open3d.utility.DoubleVector(radii))
        return mesh
    
    def save_mesh(self, mesh, name):
        """Save the mesh using STL format.

        Args:
            mesh (mesh): mesh to be saved
            name (str): name for the file
        """

        open3d.io.write_triangle_mesh("{}.stl".format(name), mesh)

    def fromImage(self, im):
        """Primitive algorithm used to extract data out of an boolean image containing the laser position.

        Args:
            im (nparray): image from which the data should be extracted
        """

        for k, line in enumerate(im):
            if len(self.points) == 0:
                seed = -1
            else:
                seed = self.points[k-1]

            point = self.__linePoint(line, seed)

            self.points.append(point)

    def __linePoint(self, line, seed=-1): #to be made private
        """Determine the laser position in a line of an image using Divide et Impera algorithm and the seed of the last position.

        Args:
            line (nparray): line from an image 
            seed (int, optional): seed with the last position found. Defaults to -1.

        Returns:
            int: position of the laser
        """
        
        cs=0
        cd=len(line)
        mid = (int)((cs+cd)/2)

        if seed < 0:
            seed=mid
         
        if seed >= mid:
            dr=seed
            for st in range(seed,cs,-1):
                if line[st] > 0:
                    return st

                if line[dr] > 0:
                    return dr

                dr = min(dr + 1, cd - 1) #use cd-1 because line is indexed from 0

        else:
            st = seed
            for dr in range(seed, cd, 1):
                if line[st] > 0:
                    return st

                if line[dr] > 0:
                    return dr
                    
                st = max(st - 1, cs)

        return -1

Methods

def alpha_reconstruction(self, alpha)

Reconstruct a mesh of the pointcloud using Alpha reconstruction algorithm.

Args

alpha : float
alpha parameter used in reconstruction

Returns

mesh
generated mesh
Expand source code
def alpha_reconstruction(self, alpha):
    """Reconstruct a mesh of the pointcloud using Alpha reconstruction algorithm.

    Args:
        alpha (float): alpha parameter used in reconstruction

    Returns:
        mesh: generated mesh
    """

    mesh = open3d.geometry.TriangleMesh.create_from_point_cloud_alpha_shape(self.pointcloud, alpha)
    mesh.compute_vertex_normals()
    return mesh
def appendLine(self, line)

Add a new line of data (points with color information) to the internal buffer.

Args

line : nparry
array of points
Expand source code
def appendLine(self, line):
    """Add a new line of data (points with color information) to the internal buffer.

    Args:
        line (nparry): array of points
    """

    self.points.append(line)
def ball_reconstruction(self, mult)

Reconstruct a mesh of the pointcloud using the pivoting ball algorithm.

Args

mult : int
multiplier used to determine the ball size

Returns

mesh
generated mesh
Expand source code
def ball_reconstruction(self, mult):
    """Reconstruct a mesh of the pointcloud using the pivoting ball algorithm.

    Args:
        mult (int): multiplier used to determine the ball size

    Returns:
        mesh: generated mesh
    """

    radii = [0.005, 0.01, 0.02, 0.04]*mult
    mesh = open3d.geometry.TriangleMesh.create_from_point_cloud_ball_pivoting(self.pointcloud, open3d.utility.DoubleVector(radii))
    return mesh
def compute_ppm(self)

Compute the ppm (pixels per meter) variable.

Expand source code
def compute_ppm(self):
    """Compute the ppm (pixels per meter) variable.
    """

    self.ppm = (self.camera_center_distance - self.camera_laser_distance) / self.flenght
    self.ppm = self.ppm * self.pixelsize
def create_pointcloud(self, viewPC=False)

Generate in memory a new pointcloud from the data loaded and calculate its parameters.

Args

viewPC : bool, optional
flag to show a 3D representation of the points. Defaults to False.
Expand source code
def create_pointcloud(self, viewPC=False):
    """Generate in memory a new pointcloud from the data loaded and calculate its parameters.

    Args:
        viewPC (bool, optional): flag to show a 3D representation of the points. Defaults to False.
    """

    #self.pointcloud
    if self.ppm == 0:
        self.compute_ppm()

    center = self.res[1]/2
    hcenter = self.res[0]/2
    vertex = []
    colors = []
    multiplier = self.camera_center_distance / self.camera_laser_distance
    
    for index, view in enumerate(self.points):
        alpha = math.radians(self.angle[index])
        for height, pixel in enumerate(view):
            if pixel>=0 :
                d = self.ppm * (pixel-center) * multiplier
                x = d * math.cos(alpha)
                y = d * math.sin(alpha)
                z = height * self.ppm * self.magich
                cond =  False or z < 200#(z>0 and z<294) or (abs(pixel-center<40)
                if cond:
                    vertex.append((x, y, z))
                    rc = self.color[index, height, 0]
                    gc = self.color[index, height, 1]
                    bc = self.color[index, height, 2]

                    colors.append((rc, gc, bc))
    
    #Apply the same alghorithm for the top points but with the scanning plane rotated by 90 degrees 
    for index, view in enumerate(self.toppoints):
        alpha = math.radians(self.topangle[index])
        for height, pixel in enumerate(view):
            if pixel>=0 :
                d = self.ppm * (pixel-center) * multiplier * .1 + 150
                h = self.ppm * (height-hcenter) * self.magich * .7
                x = h * math.cos(alpha)
                y = h * math.sin(alpha)
                z = d
                cond =  abs(d-152) < 10 #???
                if cond:
                    vertex.append((x, y, z))
                    rc = self.topcolor[index, height, 0]
                    gc = self.topcolor[index, height, 1]
                    bc = self.topcolor[index, height, 2]

                    colors.append((rc, gc, bc))
    #print(colors)
    self.pointcloud = open3d.geometry.PointCloud()
    self.pointcloud.points = open3d.utility.Vector3dVector(np.array(vertex))
    self.pointcloud.colors = open3d.utility.Vector3dVector(np.array(colors)/255)
    
    self.pointcloud.estimate_normals(search_param=open3d.geometry.KDTreeSearchParamHybrid(radius=0.001, max_nn=30))
    _, ind = self.pointcloud.remove_statistical_outlier(nb_neighbors=20, std_ratio=0.02)
    #_, ind = self.pointcloud.remove_radius_outlier(nb_points=20, radius=20)#remove_statistical_outlier(nb_neighbors=1000, std_ratio=0.0001)
    self.pointcloud = self.pointcloud.select_by_index(ind)
    _, ind = self.pointcloud.remove_radius_outlier(nb_points=50, radius=20)#remove_statistical_outlier(nb_neighbors=1000, std_ratio=0.0001)
    self.pointcloud = self.pointcloud.select_by_index(ind)
    self.pointcloud.rotate(self.pointcloud.get_rotation_matrix_from_xyz((np.pi / 2, 0, np.pi / 4)),center=(0, 0, 0))
    if viewPC:
        open3d.visualization.draw_geometries([self.pointcloud])
def debug(self)

Show the primary parameters of the scan and draws the points in a 3D viewer.

Expand source code
def debug(self):
    """Show the primary parameters of the scan and draws the points in a 3D viewer.
    """

    print(self.points)
    print(self.ppm)
    print(self.res)
    open3d.visualization.draw_geometries([self.pointcloud])
def download_file(self, name, host='192.168.1.14', port=22, password='raspberry', username='pi', path='/home/pi/TestCamera/')

Download the data from the Raspberry Pi via SSh.

Args

name : str
name of the file
host : str, optional
adress of the Raspberry Pi. Defaults to "192.168.1.14".
port : int, optional
port of the Raspberry Pi. Defaults to 22.
password : str, optional
password of the Raspberry Pi. Defaults to "raspberry".
username : str, optional
username of the Raspberry Pi. Defaults to "pi".
path : str, optional
local path of the file. Defaults to '/home/pi/TestCamera/'.
Expand source code
def download_file(self, name, host = "192.168.1.14", port = 22, password = "raspberry", username = "pi", path = '/home/pi/TestCamera/'):
    """Download the data from the Raspberry Pi via SSh.

    Args:
        name (str): name of the file
        host (str, optional): adress of the Raspberry Pi. Defaults to "192.168.1.14".
        port (int, optional): port of the Raspberry Pi. Defaults to 22.
        password (str, optional): password of the Raspberry Pi. Defaults to "raspberry".
        username (str, optional): username of the Raspberry Pi. Defaults to "pi".
        path (str, optional): local path of the file. Defaults to '/home/pi/TestCamera/'.
    """

    print("Downloading data from {}".format(host))
    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    ssh.connect(host, port, username, password)
    sftp = ssh.open_sftp()
    #remote_file = sftp.open(path + name)
    try:
        sftp.get(path+name, name)
    finally:
        sftp.close()
        ssh.close()
def fromImage(self, im)

Primitive algorithm used to extract data out of an boolean image containing the laser position.

Args

im : nparray
image from which the data should be extracted
Expand source code
def fromImage(self, im):
    """Primitive algorithm used to extract data out of an boolean image containing the laser position.

    Args:
        im (nparray): image from which the data should be extracted
    """

    for k, line in enumerate(im):
        if len(self.points) == 0:
            seed = -1
        else:
            seed = self.points[k-1]

        point = self.__linePoint(line, seed)

        self.points.append(point)
def load_data(self, name)

Load the data in memory or downloads it from the Raspberry Pi if unavailable.

Args

name : str
name of the data file
Expand source code
def load_data(self, name):
    """Load the data in memory or downloads it from the Raspberry Pi if unavailable.

    Args:
        name (str): name of the data file
    """

    file = Path(name)
    if not file.exists():
        self.download_file(name)
    
    with open(file, 'r') as js:
        data = json.loads(js.read())
        self.date= datetime.strptime(data["date"], "%Y-%m-%d %H:%M:%S")
        self.res = tuple(data["res"])
        points = []
        colors = []
        for s in data["samples"]:
            self.angle.append(s["angle"])
            p_array = np.array(s["points"])

            xy_array = p_array[:,0].tolist()
            col_array = p_array[:,1:]
            points.append(xy_array)
            colors.append(col_array)

        #print(points)
        #self.points = points_arr[0]
        self.color = np.array(colors, dtype=object)
        #self.color.reshape(( len(data["samples"]), self.res[0]))
        self.points = np.array(points)
        #print(points[1])
        #print(self.points.shape)
        self.points.reshape(( len(data["samples"]), self.res[0]))

        toppoints = []
        topcolors = []
        for s in data["top"]:
            self.topangle.append(s["angle"])
            p_array = np.array(s["points"])

            xy_array = p_array[:,0].tolist()
            col_array = p_array[:,1:]
            toppoints.append(xy_array)
            topcolors.append(col_array)
        
        self.topcolor = np.array(colors, dtype=object)

        self.toppoints = np.array(toppoints)
        #print(points)

        self.toppoints.reshape(( len(data["top"]), self.res[0]))

        print("Data loaded successfully")
        #print(self.date)
        js.close()
def poisson_reconstruction(self, depth)

Reconstruct a mesh of the pointcloud using Poisson's algorithm.

Args

depth : int
parameter used to reconstruct the mesh

Returns

mesh
generated mesh
Expand source code
def poisson_reconstruction(self, depth):
    """Reconstruct a mesh of the pointcloud using Poisson's algorithm.

    Args:
        depth (int): parameter used to reconstruct the mesh

    Returns:
        mesh: generated mesh
    """

    mesh = open3d.geometry.TriangleMesh.create_from_point_cloud_poisson(self.pointcloud, depth=depth, width=0, scale=1.1, linear_fit=True)[0]
    open3d.geometry.TriangleMesh.compute_triangle_normals(mesh)
    bbox = self.pointcloud.get_axis_aligned_bounding_box()
    mesh = mesh.crop(bbox)
    return mesh
def save_mesh(self, mesh, name)

Save the mesh using STL format.

Args

mesh : mesh
mesh to be saved
name : str
name for the file
Expand source code
def save_mesh(self, mesh, name):
    """Save the mesh using STL format.

    Args:
        mesh (mesh): mesh to be saved
        name (str): name for the file
    """

    open3d.io.write_triangle_mesh("{}.stl".format(name), mesh)
def set_ppm(self, new_ppm)

Manually set the ppm (pixels per meter) variable.

Args

new_ppm : float
new ppm value
Expand source code
def set_ppm(self, new_ppm):
    """Manually set the ppm (pixels per meter) variable.

    Args:
        new_ppm (float): new ppm value
    """

    self.ppm = new_ppm
def show_laser(self, index)

Generate an image from the data available.

Args

index : int
index of the picture taken by the scanner

Returns

nparray
image generated
Expand source code
def show_laser(self, index):
    """Generate an image from the data available.

    Args:
        index (int): index of the picture taken by the scanner

    Returns:
        nparray: image generated
    """

    img = np.zeros((self.res[0], self.res[1], 3), dtype=np.uint8)
    points = self.points[index]
    for x, y in enumerate(points):
        img[x,y] = tuple(self.color[index, x])
        #print(tuple(self.color[index, x]))
    return img