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)
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)
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)
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()
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')
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)
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)
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)
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)