def centre_of_mass(geometry, vertices='throat.offset_vertices', **kwargs): r""" Calculate the centre of mass of the throat from the voronoi vertices. """ Nt = geometry.num_throats() outer_verts = geometry['throat.vertices'] offset_verts = geometry[vertices] normal = geometry['throat.normal'] z_axis = [0, 0, 1] value = _sp.ndarray([Nt, 3]) for i in range(Nt): if len(offset_verts[i]) > 2: verts = offset_verts[i] elif len(outer_verts[i]) > 2: verts = outer_verts[i] else: verts = [] if len(verts) > 0: # For boundaries some facets will already be aligned with the axis - # if this is the case a rotation is unnecessary and could also cause # problems angle = tr.angle_between_vectors(normal[i], z_axis) if angle == 0.0 or angle == _sp.pi: "We are already aligned" rotate_input = False facet = verts else: rotate_input = True M = tr.rotation_matrix( tr.angle_between_vectors(normal[i], z_axis), tr.vector_product(normal[i], z_axis)) facet = _sp.dot(verts, M[:3, :3].T) # Now we have a rotated facet aligned with the z axis - make 2D facet_2D = _sp.column_stack((facet[:, 0], facet[:, 1])) z = _sp.unique(_sp.around(facet[:, 2], 10)) if len(z) == 1: # We need the vertices arranged in order so perform a convex hull hull = ConvexHull(facet_2D) ordered_facet_2D = facet_2D[hull.vertices] # Call the routine to calculate an area wighted centroid from the # 2D polygon COM_2D = vo.PolyWeightedCentroid2D(ordered_facet_2D) COM_3D = _sp.hstack((COM_2D, z)) # If we performed a rotation we need to rotate back if (rotate_input): MI = tr.inverse_matrix(M) # Unrotate the offset coordinates using the inverse of the # original rotation matrix value[i] = _sp.dot(COM_3D, MI[:3, :3].T) else: value[i] = COM_3D else: logger.error('Rotation Failed: ' + str(_sp.unique(facet[:, 2]))) return value
def _inverse(self): # type: () -> Transformation """The inverse of a given transformation. Returns ------- Transformation """ return Transformation(inverse_matrix(self.matrix))
def centre_of_mass(geometry, vertices='throat.offset_vertices', **kwargs): r""" Calculate the centre of mass of the throat from the voronoi vertices. """ Nt = geometry.num_throats() outer_verts = geometry['throat.vertices'] offset_verts = geometry[vertices] normal = geometry['throat.normal'] z_axis = [0, 0, 1] value = _sp.ndarray([Nt, 3]) for i in range(Nt): if len(offset_verts[i]) > 2: verts = offset_verts[i] elif len(outer_verts[i]) > 2: verts = outer_verts[i] else: verts = [] if len(verts) > 0: # For boundaries some facets will already be aligned with the axis - # if this is the case a rotation is unnecessary and could also cause # problems angle = tr.angle_between_vectors(normal[i], z_axis) if angle == 0.0 or angle == _sp.pi: "We are already aligned" rotate_input = False facet = verts else: rotate_input = True M = tr.rotation_matrix(tr.angle_between_vectors(normal[i], z_axis), tr.vector_product(normal[i], z_axis)) facet = _sp.dot(verts, M[:3, :3].T) # Now we have a rotated facet aligned with the z axis - make 2D facet_2D = _sp.column_stack((facet[:, 0], facet[:, 1])) z = _sp.unique(_sp.around(facet[:, 2], 10)) if len(z) == 1: # We need the vertices arranged in order so perform a convex hull hull = ConvexHull(facet_2D) ordered_facet_2D = facet_2D[hull.vertices] # Call the routine to calculate an area wighted centroid from the # 2D polygon COM_2D = vo.PolyWeightedCentroid2D(ordered_facet_2D) COM_3D = _sp.hstack((COM_2D, z)) # If we performed a rotation we need to rotate back if (rotate_input): MI = tr.inverse_matrix(M) # Unrotate the offset coordinates using the inverse of the # original rotation matrix value[i] = _sp.dot(COM_3D, MI[:3, :3].T) else: value[i] = COM_3D else: logger.error('Rotation Failed: ' + str(_sp.unique(facet[:, 2]))) return value
def distance_transform(network, geometry, offset, **kwargs): r""" Use the Voronoi vertices and perform image analysis to obtain throat properties """ import math import numpy as np from skimage.morphology import convex_hull_image from skimage.measure import regionprops from scipy import ndimage Nt = geometry.num_throats() area = sp.zeros(Nt) perimeter = sp.zeros(Nt) centroid = sp.zeros([Nt, 3]) incentre = sp.zeros([Nt, 3]) inradius = sp.zeros(Nt) equiv_diameter = sp.zeros(Nt) eroded_verts = sp.ndarray(Nt, dtype=object) res = 200 vertices = geometry['throat.vertices'] normals = geometry['throat.normal'] z_axis = [0, 0, 1] for i in range(Nt): logger.info("Processing throat " + str(i+1)+" of "+str(Nt)) # For boundaries some facets will already be aligned with the axis - if this # is the case a rotation is unnecessary and could also cause problems angle = tr.angle_between_vectors(normals[i], z_axis) if angle == 0.0 or angle == np.pi: # We are already aligned rotate_facet = False facet = vertices[i] else: rotate_facet = True M = tr.rotation_matrix(tr.angle_between_vectors(normals[i], z_axis), tr.vector_product(normals[i], z_axis)) facet = np.dot(vertices[i], M[:3, :3].T) x = facet[:, 0] y = facet[:, 1] z = facet[:, 2] # Get points in 2d for image analysis pts = np.column_stack((x, y)) # Translate points so min sits at the origin translation = [pts[:, 0].min(), pts[:, 1].min()] pts -= translation order = np.int(math.ceil(-np.log10(np.max(pts)))) # Normalise and scale the points so that largest span equals the resolution # to save on memory and create clear image" max_factor = np.max([pts[:, 0].max(), pts[:, 1].max()]) f = res/max_factor #"Temporarily try using the vox_len as a scale factor" #f = 1/geometry._vox_len # Scale the offset and define a circular structuring element with radius r = f*offset # Only proceed if r is less than half the span of the image" if r <= res/2: pts *= f minp1 = pts[:, 0].min() minp2 = pts[:, 1].min() maxp1 = pts[:, 0].max() maxp2 = pts[:, 1].max() img = np.zeros([np.int(math.ceil(maxp1-minp1)+1), np.int(math.ceil(maxp2-minp2)+1)]) int_pts = np.around(pts, 0).astype(int) for pt in int_pts: img[pt[0]][pt[1]] = 1 # Pad with zeros all the way around the edges img_pad = np.zeros([np.shape(img)[0] + 2, np.shape(img)[1] + 2]) img_pad[1:np.shape(img)[0]+1, 1:np.shape(img)[1]+1] = img # All points should lie on this plane but could be some rounding errors # so use the order parameter z_plane = sp.unique(np.around(z, order+2)) if len(z_plane) > 1: logger.error('Rotation for image analysis failed') temp_arr = np.ones(1) temp_arr.fill(np.mean(z_plane)) z_plane = temp_arr "Fill in the convex hull polygon" convhullimg = convex_hull_image(img_pad) # Perform a Distance Transform and black out points less than r to create # binary erosion. This is faster than performing an erosion and dt can # also be used later to find incircle" eroded = ndimage.distance_transform_edt(convhullimg) eroded[eroded <= r] = 0 eroded[eroded > r] = 1 # If we are left with less than 3 non-zero points then the throat is # fully occluded if np.sum(eroded) >= 3: # Do some image analysis to extract the key properties regions = regionprops(eroded[1:np.shape(img)[0]+1, 1:np.shape(img)[1]+1].astype(int)) # Change this to cope with genuine multi-region throats if len(regions) == 1: for props in regions: x0, y0 = props.centroid equiv_diameter[i] = props.equivalent_diameter area[i] = props.area perimeter[i] = props.perimeter coords = props.coords # Undo the translation, scaling and truncation on the centroid centroid2d = np.array([x0, y0])/f centroid2d += (translation) centroid3d = np.concatenate((centroid2d, z_plane)) # Distance transform the eroded facet to find the incentre and # inradius dt = ndimage.distance_transform_edt(eroded) inx0, iny0 = \ np.asarray(np.unravel_index(dt.argmax(), dt.shape)) \ .astype(float) incentre2d = np.array([inx0, iny0]) # Undo the translation, scaling and truncation on the incentre incentre2d /= f incentre2d += (translation) incentre3d = np.concatenate((incentre2d, z_plane)) # The offset vertices will be those in the coords that are # closest to the originals" offset_verts = [] for pt in int_pts: vert = np.argmin(np.sum(np.square(coords-pt), axis=1)) if vert not in offset_verts: offset_verts.append(vert) # If we are left with less than 3 different vertices then the # throat is fully occluded as we can't make a shape with # non-zero area if len(offset_verts) >= 3: offset_coords = coords[offset_verts].astype(float) # Undo the translation, scaling and truncation on the # offset_verts offset_coords /= f offset_coords_3d = \ np.vstack((offset_coords[:, 0]+translation[0], offset_coords[:, 1]+translation[1], np.ones(len(offset_verts))*z_plane)).T # Get matrix to un-rotate the co-ordinates back to the # original orientation if we rotated in the first place if rotate_facet: MI = tr.inverse_matrix(M) # Unrotate the offset coordinates incentre[i] = np.dot(incentre3d, MI[:3, :3].T) centroid[i] = np.dot(centroid3d, MI[:3, :3].T) eroded_verts[i] = np.dot(offset_coords_3d, MI[:3, :3].T) else: incentre[i] = incentre3d centroid[i] = centroid3d eroded_verts[i] = offset_coords_3d inradius[i] = dt.max() # Undo scaling on other parameters area[i] /= f*f perimeter[i] /= f equiv_diameter[i] /= f inradius[i] /= f else: area[i] = 0 perimeter[i] = 0 equiv_diameter[i] = 0 if kwargs['set_dependent'] is True: geometry['throat.area'] = area geometry['throat.perimeter'] = perimeter geometry['throat.centroid'] = centroid geometry['throat.diameter'] = equiv_diameter geometry['throat.indiameter'] = inradius*2 geometry['throat.incentre'] = incentre return eroded_verts
def getTF(self, base, target): tf_base = self.getLink(base).position tf_target = self.getLink(target).position tf_target = np.vstack((tf_target, [0, 0, 0, 1])) tf_base = np.vstack((tf_base, [0, 0, 0, 1])) return np.dot(tf.inverse_matrix(tf_base), tf_target)
def _throat_props(self): r""" Use the Voronoi vertices and perform image analysis to obtain throat properties """ offset = self.network.fiber_rad Nt = self.num_throats() centroid = sp.zeros([Nt, 3]) incenter = sp.zeros([Nt, 3]) area = sp.zeros(Nt) perimeter = sp.zeros(Nt) inradius = sp.zeros(Nt) equiv_diameter = sp.zeros(Nt) eroded_verts = sp.ndarray(Nt, dtype=object) res = 200 vertices = self['throat.vertices'] normals = self['throat.normal'] z_axis = [0, 0, 1] for i in self.throats('delaunay'): logger.info("Processing throat " + str(i + 1) + " of " + str(Nt)) # For boundaries some facets will already be aligned with the axis # if this is the case a rotation is unnecessary angle = tr.angle_between_vectors(normals[i], z_axis) if angle == 0.0 or angle == np.pi: # We are already aligned rotate_facet = False facet = vertices[i] else: rotate_facet = True M = tr.rotation_matrix( tr.angle_between_vectors(normals[i], z_axis), tr.vector_product(normals[i], z_axis)) facet = np.dot(vertices[i], M[:3, :3].T) x = facet[:, 0] y = facet[:, 1] z = facet[:, 2] # Get points in 2d for image analysis pts = np.column_stack((x, y)) # Translate points so min sits at the origin translation = [pts[:, 0].min(), pts[:, 1].min()] pts -= translation order = np.int(math.ceil(-np.log10(np.max(pts)))) # Normalise and scale the points so that largest span equals the # resolution to save on memory and create clear image max_factor = np.max([pts[:, 0].max(), pts[:, 1].max()]) f = res / max_factor # Scale the offset and define a structuring element with radius r = f * offset # Only proceed if r is less than half the span of the image" if r <= res / 2: pts *= f minp1 = pts[:, 0].min() minp2 = pts[:, 1].min() maxp1 = pts[:, 0].max() maxp2 = pts[:, 1].max() img = np.zeros([ np.int(math.ceil(maxp1 - minp1) + 1), np.int(math.ceil(maxp2 - minp2) + 1) ]) int_pts = np.around(pts.astype(float), 0).astype(int) for pt in int_pts: img[pt[0]][pt[1]] = 1 # Pad with zeros all the way around the edges img_pad = np.zeros( [np.shape(img)[0] + 2, np.shape(img)[1] + 2]) img_pad[1:np.shape(img)[0] + 1, 1:np.shape(img)[1] + 1] = img # All points should lie on this plane but could be some # rounding errors so use the order parameter z_plane = sp.unique(np.around(z.astype(float), order + 1)) if len(z_plane) > 1: logger.error('Throat ' + str(i) + ' Rotation Failure') temp_arr = np.ones(1) temp_arr.fill(np.mean(z_plane)) z_plane = temp_arr "Fill in the convex hull polygon" convhullimg = convex_hull_image(img_pad) # Perform a Distance Transform and black out points less than r # to create binary erosion. This is faster than performing an # erosion and dt can also be used later to find incircle eroded = ndimage.distance_transform_edt(convhullimg) eroded[eroded <= r] = 0 eroded[eroded > r] = 1 # If we are left with less than 3 non-zero points then the # throat is fully occluded if np.sum(eroded) >= 3: # Do some image analysis to extract the key properties cropped = eroded[1:np.shape(img)[0] + 1, 1:np.shape(img)[1] + 1].astype(int) regions = regionprops(cropped) # Change this to cope with genuine multi-region throats if len(regions) == 1: for props in regions: x0, y0 = props.centroid equiv_diameter[i] = props.equivalent_diameter area[i] = props.area perimeter[i] = props.perimeter coords = props.coords # Undo the translation, scaling and truncation on the # centroid centroid2d = [x0, y0] / f centroid2d += (translation) centroid3d = np.concatenate((centroid2d, z_plane)) # Distance transform the eroded facet to find the # incenter and inradius dt = ndimage.distance_transform_edt(eroded) temp = np.unravel_index(dt.argmax(), dt.shape) inx0, iny0 = np.asarray(temp).astype(float) incenter2d = [inx0, iny0] # Undo the translation, scaling and truncation on the # incenter incenter2d /= f incenter2d += (translation) incenter3d = np.concatenate((incenter2d, z_plane)) # The offset vertices will be those in the coords that # are closest to the originals offset_verts = [] for pt in int_pts: vert = np.argmin( np.sum(np.square(coords - pt), axis=1)) if vert not in offset_verts: offset_verts.append(vert) # If we are left with less than 3 different vertices # then the throat is fully occluded as we can't make a # shape with non-zero area if len(offset_verts) >= 3: offset_coords = coords[offset_verts].astype(float) # Undo the translation, scaling and truncation on # the offset_verts offset_coords /= f offset_coords_3d = \ np.vstack((offset_coords[:, 0]+translation[0], offset_coords[:, 1]+translation[1], np.ones(len(offset_verts))*z_plane)) oc_3d = offset_coords_3d.T # Get matrix to un-rotate the co-ordinates back to # the original orientation if we rotated in the # first place if rotate_facet: MI = tr.inverse_matrix(M) # Unrotate the offset coordinates incenter[i] = np.dot(incenter3d, MI[:3, :3].T) centroid[i] = np.dot(centroid3d, MI[:3, :3].T) eroded_verts[i] = np.dot(oc_3d, MI[:3, :3].T) else: incenter[i] = incenter3d centroid[i] = centroid3d eroded_verts[i] = oc_3d inradius[i] = dt.max() # Undo scaling on other parameters area[i] /= f * f perimeter[i] /= f equiv_diameter[i] /= f inradius[i] /= f else: area[i] = 0 perimeter[i] = 0 equiv_diameter[i] = 0 self['throat.area'] = area self['throat.perimeter'] = perimeter self['throat.centroid'] = centroid self['throat.diameter'] = equiv_diameter self['throat.indiameter'] = inradius * 2 self['throat.incenter'] = incenter self['throat.offset_vertices'] = eroded_verts