Ejemplo n.º 1
0
 def setUp(self):
     """Runs before each unit test
     Sets up the AmpObject object using "stl_file.stl"
     """
     from ampscan.core import AmpObject
     stl_path = get_path("stl_file.stl")
     self.amp = AmpObject(stl_path)
     stl_path = get_path("stl_file_4.stl")  # R=1.2
     self.amp2 = AmpObject(stl_path)
Ejemplo n.º 2
0
 def setUp(self):
     """Runs before each unit test.
     Sets up AmpObject object using "stl_file_4.stl" "stl_file_5.stl".
     """
     from ampscan.core import AmpObject
     # Load 2 spheres with radius 1, and 1.2
     stl_path = get_path("stl_file_5.stl")  # R=1
     self.amp1 = AmpObject(stl_path)
     stl_path = get_path("stl_file_4.stl")  # R=1.2
     self.amp2 = AmpObject(stl_path)
Ejemplo n.º 3
0
    def setUp(self):
        """Runs before each unit test.
        Sets up the AmpObject object using "stl_file.stl".
        """
        from ampscan.core import AmpObject

        # Radius = 1
        stl_path = get_path("stl_file_4.stl")
        self.amp1 = AmpObject(stl_path)

        # Radius = 1.2
        stl_path = get_path("stl_file_5.stl")
        self.amp2 = AmpObject(stl_path)

        # Spheroid with major radius 1 and minor 0.5
        stl_path = get_path("stl_file_7.stl")
        self.amp3 = AmpObject(stl_path)
Ejemplo n.º 4
0
def calc_volume_closed(amp_in, return_closed=False):
    r"""
    Calculates the volume of a closed surface. If the surface is not closed the algorithm fills in the holes using a simple
    hole filling algorithm, the surface without holes can be accessed if return_closed is set to True.


    Parameters
    ----------
    amp: AmpObject 
        The AmpObject to analyse
    return_closed: bool, default False
        Indicate whether to return the shape with holes filled in  

    Returns
    -------
    vol: float
        The volume of the AmpObject
    amp: AmpObject
        If return_closed is True, then the closed shape is returned 
    """
    amp = AmpObject({
        'vert': amp_in.vert.copy(),
        'faces': amp_in.faces.copy(),
        'values': amp_in.values.copy(),
    })
    amp.calcStruct()
    # Fill in the holes
    while (amp.faceEdges == -99999).sum() != 0:
        # Find the edges which are only conected to one face
        edges = (amp.faceEdges == -99999).sum(axis=1).astype(bool)
        edges = amp.edges[edges, :]
        # Return the vert indicies for the loop
        vInd = logEuPath(edges)
        # Calculate the mmidpoint
        midpoint = amp.vert[vInd, :].mean(axis=0)
        # Add in the new vertex
        amp.vert = np.r_[amp.vert, midpoint[None, :]]
        f0 = amp.vert.shape[0] - 1
        # Add in each face using adjacent vertices in loop
        for f1, f2 in zip(vInd, np.roll(vInd, 1)):
            amp.faces = np.r_[amp.faces, [[f1, f0, f2]]]
        # Update structure and check if any more holes (algorithm keeps going until all holes filled)
        amp.calcStruct()
    # Calculate the area of each face in the array using vector cross product
    v01 = amp.vert[amp.faces[:, 1], :] - amp.vert[amp.faces[:, 0], :]
    v02 = amp.vert[amp.faces[:, 2], :] - amp.vert[amp.faces[:, 0], :]
    cp = np.square(np.cross(v01, v02))
    area = 0.5 * np.sqrt(cp.sum(axis=1))
    # Get surface volume contributions
    sVC = area * amp.vert[amp.faces, 2].mean(axis=1) * amp.norm[:, 2]
    if return_closed is True:
        return sVC.sum(), amp
    else:
        return sVC.sum()
Ejemplo n.º 5
0
 def setBaseline(self, baseline):
     r"""
     Function to set the baseline mesh used for registration of the 
     pca training data meshes
     
     Parameters
     ----------
     baseline: str
         The file handle of the stl file used as the baseline
         
     """
     self.baseline = AmpObject(baseline, 'limb')
Ejemplo n.º 6
0
 def __init__(self,
              moving,
              static,
              method='linPoint2Plane',
              *args,
              **kwargs):
     mData = dict(
         zip(['vert', 'faces', 'values'],
             [moving.vert, moving.faces, moving.values]))
     alData = copy.deepcopy(mData)
     self.m = AmpObject(alData, stype='reg')
     self.s = static
     self.runICP(method=method, *args, **kwargs)
Ejemplo n.º 7
0
    def test_centre_static(self):

        with self.assertRaises(TypeError):
            self.amp.centreStatic(1)
        with self.assertRaises(TypeError):
            self.amp.centreStatic([])

        # Import second shape
        from ampscan.core import AmpObject
        stl_path = get_path("stl_file_2.stl")
        amp2 = AmpObject(stl_path)

        self.amp.centreStatic(amp2)

        for i in range(3):
            # This method has a large degree of error so, it's only testing to 2 dp
            self.assertAlmostEqual(
                self.amp.vert.mean(axis=0)[i],
                amp2.vert.mean(axis=0)[i], 2)
Ejemplo n.º 8
0
    def importFolder(self, path, unify=True):
        r"""
        Function to import multiple stl files from folder into the pca object 
        
        Parameters
        ----------
        path: str
            The path to the folder containing the stl files to be used as the 
            training data for the PCA model
        unify: bool, default True
            Designmate whether to unify the vertices on stl import 

        """
        self.fnames = [f for f in os.listdir(path) if f.endswith('.stl')]
        self.shapes = [
            AmpObject(os.path.join(path, f), 'limb', unify=unify)
            for f in self.fnames
        ]
        for s in self.shapes:
            s.lp_smooth(3, brim=True)
Ejemplo n.º 9
0
    def point2plane(self, steps = 1, neigh = 10, inside = True, subset = None, 
                    scale=None, smooth=1, fixBrim=False, error='norm'):
        r"""
        Point to Plane method for registration between the two meshes 
        
        Parameters
        ----------
        steps: int, default 1
            Number of iterations
        int, default 10
            Number of nearest neighbours to interrogate for each baseline point
        inside: bool, default True
            If True, a barycentric centre check is made to ensure the registered 
            point lines within the target triangle
        subset: array_like, default None
            Indicies of the baseline nodes to include in the registration, default is none so 
            all are used
        scale: float, default None
            If not None scale the baseline mesh to match the target mesh in the z-direction, 
            the value of scale will be used as a plane from which the nodes are scaled.
            Nodes with a higher z value will not be scaled. 
        smooth: int, default 1
            Indicate number of laplacian smooth steps in between the steps 
        fixBrim: bool, default False
            If True, the nodes on the brim line will not be included in the smooth
        error: bool, default False
            If True, the polarity will be included when calculating the distance 
            between the target and baseline mesh
		
        """
        # Calc FaceCentroids
        fC = self.t.vert[self.t.faces].mean(axis=1)
        # Construct knn tree
        tTree = spatial.cKDTree(fC)
        bData = dict(zip(['vert', 'faces', 'values'], 
                         [self.b.vert, self.b.faces, self.b.values]))
        regData = copy.deepcopy(bData)
        self.reg = AmpObject(regData, stype='reg')
        self.disp = AmpObject({'vert': np.zeros(self.reg.vert.shape),
                               'faces': self.reg.faces,
                               'values':self.reg.values})
        if scale is not None:
            tmin = self.t.vert.min(axis=0)[2]
            rmin = self.reg.vert.min(axis=0)[2]
            SF = ((tmin-scale)/(rmin-scale)) - 1
            logic = self.reg.vert[:, 2] < scale
            d = (self.reg.vert[logic, 2] - scale) * SF
            self.disp.vert[logic, 2] += d
            self.reg.vert = self.b.vert + self.disp.vert
        normals = np.cross(self.t.vert[self.t.faces[:,1]] -
                         self.t.vert[self.t.faces[:,0]],
                         self.t.vert[self.t.faces[:,2]] -
                         self.t.vert[self.t.faces[:,0]])
        mag = (normals**2).sum(axis=1)
        for step in np.arange(steps, 0, -1, dtype=float):
            # Index of 10 centroids nearest to each baseline vertex
            ind = tTree.query(self.reg.vert, neigh)[1]
            # Define normals for faces of nearest faces
            norms = normals[ind]
            # Get a point on each face
            fPoints = self.t.vert[self.t.faces[ind, 0]]
            # Calculate dot product between point on face and normals
            d = np.einsum('ijk, ijk->ij', norms, fPoints)
            t = (d - np.einsum('ijk, ik->ij', norms, self.reg.vert))/mag[ind]
            # Calculate the vector from old point to new point
            G = self.reg.vert[:, None, :] + np.einsum('ijk, ij->ijk', norms, t)
            # Ensure new points lie inside points otherwise set to 99999
            # Find smallest distance from old to new point 
            if inside is False:
                G = G - self.reg.vert[:, None, :]
                GMag = np.sqrt(np.einsum('ijk, ijk->ij', G, G))
                GInd = GMag.argmin(axis=1)
            else:
                G, GInd = self.__calcBarycentric(self.reg.vert, G, ind)
            # Define vector from baseline point to intersect point
            D = G[np.arange(len(G)), GInd, :]
#            rVert += D/step
            self.disp.vert += D/step
            if smooth > 0 and step > 1:
                self.disp.lp_smooth(smooth, brim = fixBrim)
                self.reg.vert = self.b.vert + self.disp.vert
            else:
                self.reg.vert = self.b.vert + self.disp.vert
                self.reg.calcNorm()
        self.reg.calcStruct(vNorm=True)
        self.reg.values[:] = self.calcError(error)