def _get_hull_volume(points): r""" Calculate the volume of a set of points by dividing the bounding surface into triangles and working out the volume of all the pyramid elements connected to the volume centroid """ " remove any duplicate points - this messes up the triangulation " points = _sp.asarray(misc.unique_list(points)) try: tri = Delaunay(points) except _sp.spatial.qhull.QhullError: print(points) " We only want points included in the convex hull to calculate the centroid " hull_centroid = _sp.array( [points[:, 0].mean(), points[:, 1].mean(), points[:, 2].mean()]) hull_volume = 0.0 for ia, ib, ic in tri.convex_hull: " Points making each triangular face " " Collection of co-ordinates of each point in this face " face_x = points[[ia, ib, ic]][:, 0] face_y = points[[ia, ib, ic]][:, 1] face_z = points[[ia, ib, ic]][:, 2] " Average of each co-ordinate is the centroid of the face " face_centroid = [face_x.mean(), face_y.mean(), face_z.mean()] face_centroid_vector = face_centroid - hull_centroid " Vectors of the sides of the face used to find normal vector and area " vab = points[ib] - points[ia] vac = points[ic] - points[ia] vbc = points[ic] - points[ib] # used later for area " As vectors are co-planar the cross-product will produce the normal vector of the face " face_normal = _sp.cross(vab, vac) face_unit_normal = face_normal / _sp.linalg.norm(face_normal) " As triangles are orientated randomly in 3D we could either transform co-ordinates to align with a plane and perform 2D operations " " to work out the area or we could work out the lengths of each side and use Heron's formula which is easier" " Using Delaunay traingulation will always produce triangular faces but if dealing with other polygons co-ordinate transfer may be necessary " a = _sp.linalg.norm(vab) b = _sp.linalg.norm(vbc) c = _sp.linalg.norm(vac) " Semiperimeter " s = 0.5 * (a + b + c) face_area = _sp.sqrt(s * (s - a) * (s - b) * (s - c)) " Now the volume of the pyramid section defined by the 3 face points and the hull centroid can be calculated " pyramid_volume = _sp.absolute( _sp.dot(face_centroid_vector, face_unit_normal) * face_area / 3) " Each pyramid is summed together to calculate the total volume " hull_volume += pyramid_volume return hull_volume
def from_fibres(network, geometry, **kwargs): r""" Calculate an indiameter by distance transforming sections of the fibre image. By definition the maximum value will be the largest radius of an inscribed sphere inside the fibrous hull """ import numpy as np from scipy.ndimage import distance_transform_edt from OpenPNM.Utilities import misc inrads = np.zeros(network.Np) try: vox_len = geometry._vox_len except: _logger.error("This method can only be applied to a Voronoi geometry" + " where an image of the fibres exists") return inrads for pore in np.unique(geometry._hull_image): _logger.info("Processing pore: " + str(pore)) # Chunk the domain verts = [i for i in network["pore.vert_index"][pore].values()] verts = np.asarray(verts) verts = np.asarray(misc.unique_list(np.around(verts, 6))) xyz = verts / vox_len # Work out range to span over xmin = xyz[:, 0].min() xr = (np.ceil(xyz[:, 0].max()) - np.floor(xmin)).astype(int) + 1 ymin = xyz[:, 1].min() yr = (np.ceil(xyz[:, 1].max()) - np.floor(ymin)).astype(int) + 1 zmin = xyz[:, 2].min() zr = (np.ceil(xyz[:, 2].max()) - np.floor(zmin)).astype(int) + 1 origin = np.array([xmin, ymin, zmin]) # start index si = np.floor(origin).astype(int) bin_img = geometry._fibre_image[si[0]:si[0] + xr, si[1]:si[1] + yr, si[2]:si[2] + zr] dt = distance_transform_edt(bin_img) inrads[pore] = dt.max() del dt del bin_img return inrads * vox_len
def from_fibres(network, geometry, **kwargs): r""" Calculate an indiameter by distance transforming sections of the fibre image. By definition the maximum value will be the largest radius of an inscribed sphere inside the fibrous hull """ import numpy as np from scipy.ndimage import distance_transform_edt from OpenPNM.Utilities import misc inrads = np.zeros(network.Np) try: vox_len = geometry._vox_len except: logger.error("This method can only be applied to a Voronoi geometry" + " where an image of the fibres exists") return inrads for pore in np.unique(geometry._hull_image): logger.info("Processing pore: "+str(pore)) #Chunk the domain verts = np.asarray([i for i in network["pore.vert_index"][pore].values()]) verts = np.asarray(misc.unique_list(np.around(verts, 6))) xyz = verts/vox_len # Work out range to span over xmin = xyz[:, 0].min() xr = (np.ceil(xyz[:, 0].max())-np.floor(xmin)).astype(int)+1 ymin = xyz[:, 1].min() yr = (np.ceil(xyz[:, 1].max())-np.floor(ymin)).astype(int)+1 zmin = xyz[:, 2].min() zr = (np.ceil(xyz[:, 2].max())-np.floor(zmin)).astype(int)+1 origin = np.array([xmin, ymin, zmin]) # start index si = np.floor(origin).astype(int) bin_img = geometry._fibre_image[si[0]:si[0]+xr, si[1]:si[1]+yr, si[2]:si[2]+zr] dt = distance_transform_edt(bin_img) inrads[pore]=dt.max() del dt del bin_img return inrads * vox_len
def in_hull_volume(network, geometry, fibre_rad, vox_len=1e-6, **kwargs): r""" Work out the voxels inside the convex hull of the voronoi vertices of each pore """ Np = network.num_pores() geom_pores = geometry.map_pores(network, geometry.pores()) volume = _sp.zeros(Np) pore_vox = _sp.zeros(Np, dtype=int) fibre_vox = _sp.zeros(Np, dtype=int) voxel = vox_len**3 try: nbps = network.pores('boundary', mode='not') except KeyError: # Boundaries have not been generated nbps = network.pores() # Voxel length fibre_rad = np.around((fibre_rad-(vox_len/2))/vox_len, 0).astype(int) # Get the fibre image fibre_image = _get_fibre_image(network, geom_pores, vox_len, fibre_rad) # Save as private variables geometry._fibre_image = fibre_image hull_image = np.ones_like(fibre_image, dtype=np.int16)*-1 geometry._hull_image = hull_image for pore in nbps: logger.info("Processing Pore: "+str(pore+1)+" of "+str(len(nbps))) verts = np.asarray([i for i in network["pore.vert_index"][pore].values()]) verts = np.asarray(misc.unique_list(np.around(verts, 6))) verts /= vox_len inhull(geometry, verts, pore) itfrq = itemfreq(geometry._hull_image[fibre_image.astype(bool)]) pore_vox[itfrq[:,0]]=itfrq[:,1] itfrq = itemfreq(geometry._hull_image[~fibre_image.astype(bool)]) fibre_vox[itfrq[:,0]]=itfrq[:,1] geometry["pore.fibre_voxels"] = fibre_vox[geom_pores] geometry["pore.pore_voxels"] = pore_vox[geom_pores] volume = pore_vox*voxel return volume[geom_pores]
def in_hull_volume(network, geometry, fibre_rad, vox_len=1e-6, **kwargs): r""" Work out the voxels inside the convex hull of the voronoi vertices of each pore """ Np = network.num_pores() geom_pores = geometry.map_pores(network, geometry.pores()) volume = _sp.zeros(Np) pore_vox = _sp.zeros(Np, dtype=int) fibre_vox = _sp.zeros(Np, dtype=int) voxel = vox_len**3 try: nbps = network.pores('boundary', mode='not') except KeyError: # Boundaries have not been generated nbps = network.pores() # Voxel length fibre_rad = np.around((fibre_rad-(vox_len/2))/vox_len, 0).astype(int) # Get the fibre image fibre_image = _get_fibre_image(network, geom_pores, vox_len, fibre_rad) # Save as private variables geometry._fibre_image = fibre_image hull_image = np.ones_like(fibre_image, dtype=np.uint16)*-1 geometry._hull_image = hull_image for pore in nbps: logger.info("Processing Pore: "+str(pore+1)+" of "+str(len(nbps))) if network["pore.vert_index"][pore] is not None: vi = [i for i in network["pore.vert_index"][pore].values()] verts = np.asarray(vi) verts = np.asarray(misc.unique_list(np.around(verts, 6))) verts /= vox_len pore_vox[pore], fibre_vox[pore] = inhull(geometry, verts, pore) volume = pore_vox*voxel geometry["pore.fibre_voxels"] = fibre_vox[geom_pores] geometry["pore.pore_voxels"] = pore_vox[geom_pores] return volume[geom_pores]
def _get_hull_volume(points): r""" Calculate the volume of a set of points by dividing the bounding surface into triangles and working out the volume of all the pyramid elements connected to the volume centroid """ " remove any duplicate points - this messes up the triangulation " points = _sp.asarray(misc.unique_list(np.around(points,10))) try: tri = Delaunay(points,qhull_options='QJ Pp') except _sp.spatial.qhull.QhullError: print(points) " We only want points included in the convex hull to calculate the centroid " hull_centroid = _sp.array([points[:,0].mean(),points[:,1].mean(),points[:,2].mean()]) hull_volume = 0.0 pyramid_COMs = [] for ia, ib, ic in tri.convex_hull: " Points making each triangular face " " Collection of co-ordinates of each point in this face " face_x = points[[ia,ib,ic]][:,0] face_y = points[[ia,ib,ic]][:,1] face_z = points[[ia,ib,ic]][:,2] " Average of each co-ordinate is the centroid of the face " face_centroid = [face_x.mean(),face_y.mean(),face_z.mean()] face_centroid_vector = face_centroid - hull_centroid " Vectors of the sides of the face used to find normal vector and area " vab = points[ib] - points[ia] vac = points[ic] - points[ia] vbc = points[ic] - points[ib] # used later for area #face_COM = (vab+vac)/3 " As vectors are co-planar the cross-product will produce the normal vector of the face " face_normal = _sp.cross(vab,vac) try: face_unit_normal = face_normal/_sp.linalg.norm(face_normal) except RuntimeWarning: print("Pore Volume Error:" +str(vab)+" "+str(vac)) " As triangles are orientated randomly in 3D we could either transform co-ordinates to align with a plane and perform 2D operations " " to work out the area or we could work out the lengths of each side and use Heron's formula which is easier" " Using Delaunay traingulation will always produce triangular faces but if dealing with other polygons co-ordinate transfer may be necessary " a = _sp.linalg.norm(vab) b = _sp.linalg.norm(vbc) c = _sp.linalg.norm(vac) " Semiperimeter " s = 0.5*(a+b+c) face_area = _sp.sqrt(s*(s-a)*(s-b)*(s-c)) " Now the volume of the pyramid section defined by the 3 face points and the hull centroid can be calculated " pyramid_volume = _sp.absolute(_sp.dot(face_centroid_vector,face_unit_normal)*face_area/3) " Each pyramid is summed together to calculate the total volume " hull_volume += pyramid_volume " The Centre of Mass will not be the same as the geometrical centroid " " A weighted adjustment can be calculated from the pyramid centroid and volume " vha = points[ia]-hull_centroid vhb = points[ib]-hull_centroid vhc = points[ic]-hull_centroid pCOM = ((vha+vhb+vhc)/4)*pyramid_volume pyramid_COMs.append(pCOM) if _sp.isnan(hull_volume): hull_volume = 0.0 if hull_volume>0: hull_COM = hull_centroid + _sp.mean(_sp.asarray(pyramid_COMs),axis=0)/hull_volume else: hull_COM = hull_centroid return hull_volume, hull_COM
def _get_throat_geom(self, verts, normal, fibre_rad): r""" For one set of vertices defining a throat return the key properties This is the main loop for calling other sub-routines. General Method: For each connection or throat defined by the shared vertices Rotate the vertices to align with the xy-plane and get rid of z-coordinate Compute the convex hull of the 2D points giving a set of simplices which define neighbouring vertices in a clockwise fashion For each triplet calculate the offset position given the fibre radius Check for overlapping vertices and ones that lie outside the original hull - recalculate position to offset from or ignore if all overlapping Calculate Area and Perimeter if successfully generated offset vertices to replicate eroded throat Translate back into 3D Any Errors encountered result in the throat area being zero and no vertices being passed back These Errors are not coding mistakes but failures to obtain an eroded facet with non-zero area: Error 1: Less than 3 vertices in the convex hull - Should never happen (unless 2 points are incredibly close together) Error 2: The largest span of points is less than twice the fibre radius (i.e. throat will definitley be occluded) Error 3: All the offset vertices overlap with at least one other vertex - Throat fully occluded Error 4: Not enough offset vertices to continue - Throat fully occluded Error 5: An offset vertex is outside the original set of points - Throat fully occluded """ z_axis = [0, 0, 1] throat_area = 0.0 throat_perimeter = 0.0 output_offset = [] Error = 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, z_axis) if (angle == 0.0) or (angle == np.pi): "We are already aligned" rotate_input = False facet = verts else: rotate_input = True M = tr.rotation_matrix(tr.angle_between_vectors(normal, z_axis), tr.vector_product(normal, z_axis)) facet = np.dot(verts, M[:3, :3].T) x = facet[:, 0] y = facet[:, 1] z = facet[:, 2] " Work out span of points and set axes scales to cover this and be equal in both dimensions " x_range = x.max() - x.min() y_range = y.max() - y.min() if (x_range > y_range): my_range = x_range else: my_range = y_range if (np.around(z.std(), 3) != 0.000): print("Rotation failed") facet_coords_2D = np.column_stack((x, y)) hull = ConvexHull(facet_coords_2D, qhull_options='QJ') verts_2D = facet_coords_2D[hull.vertices] offset = self._outer_offset(verts_2D, fibre_rad) " At this point we may have overlapping areas for which we need to offset from a new point " overlap_array, sweep_radius, line_points = self._set_overlap( verts_2D, offset) #first_array = overlap_array temp_vert_list = [] #new_vert_list=[] if (len(verts_2D) < 3): "Error: Fused Too Many Verts" Error = 1 elif (my_range < fibre_rad * 2): "Error: Facet Too small to Erode" Error = 2 else: if overlap_array.any() == False: " If no overlaps don't worry" "no overlaps" elif self._all_overlap(overlap_array) == True: " If all overlaps then throat is fully occluded" "Error: Throat fully occluded" Error = 3 else: " If one or two sets of overlaps exist and at least one vertex is not overlapped then we need to do a bit more work " " Do some linalg to find a new point to offset from saving un-overlapped verts and newly created verts in a temporary list " count = 0 temp_verts = verts_2D while True: temp_vert_list = [] for i in range(np.shape(line_points)[0]): if np.sum(overlap_array[i]) == 0.0: temp_vert_list.append(temp_verts[i]) else: my_lines = [] for j in range(np.shape(line_points)[0]): if overlap_array[i][j] == 1 and overlap_array[ j][i] == 1: list_a = line_points[i][j] list_b = line_points[j][i] my_lines = self._symmetric_difference( list_a, list_b) my_lines = np.asarray(my_lines) if len(my_lines) == 2: try: quad_points = temp_verts[my_lines] my_new_point = self._new_point(quad_points) temp_vert_list.append(my_new_point) except IndexError: print("IndexError: " + str(my_lines)) except TypeError: print("TypeError: " + str(my_lines)) #new_vert_list.append(my_new_point) temp_verts = np.asarray(misc.unique_list(temp_vert_list)) #new_vert_list=np.asarray(self._unique_list(new_vert_list)) #if len(verts_2D) >=3: offset = self._outer_offset(temp_verts, fibre_rad) overlap_array, sweep_radius, line_points = self._set_overlap( temp_verts, offset) #else: #Error = 4 if overlap_array.any() == False: break elif self._all_overlap(overlap_array) == True: Error = 3 break elif len(temp_verts) < 3: Error = 4 break else: count += 1 temp_verts = np.asarray( self._fuse_verts(verts=temp_verts, percentage=0.05 * count)) offset = self._outer_offset(temp_verts, fibre_rad) overlap_array, sweep_radius, line_points = self._set_overlap( temp_verts, offset) " Continue Looping until one of the above conditions is true or counter reaches 10" if count >= 10: break if len(offset) >= 3 and Error == 0: " Now also check whether any of the offset points lie outside the original convex hull " original_area = np.around(self._PolyArea2D(verts_2D), 10) all_points = np.concatenate((verts_2D, offset), axis=0) try: total_hull = ConvexHull( all_points, qhull_options='Pp') #ignores very small angles total_area = np.around( self._PolyArea2D(all_points[total_hull.vertices]), 10) except sp.spatial.qhull.QhullError: print(all_points) total_area = 999 Error = 5 #total_area=0 offset_hull = ConvexHull(offset) offset_verts_2D = offset[offset_hull.vertices] if (total_area > original_area): # Throat is fully occluded " Don't do anything " if Error != 5: Error = 6 #print("First Array") #print(first_array) #print("Second Array") #print(overlap_array) else: throat_area = self._PolyArea2D(offset_verts_2D) throat_perimeter = self._PolyPerimeter2D(offset_verts_2D) " Make 3D again in rotated plane " offset_verts_3D = np.column_stack( (offset_verts_2D, z[0:len(offset_verts_2D)])) " Get matrix to un-rotate the co-ordinates back to the original orientation if we rotated in the first place" if (rotate_input): M1 = tr.inverse_matrix(M) " Unrotate the offset coordinates " output_offset = np.dot(offset_verts_3D, M1[:3, :3].T) else: output_offset = offset_verts_3D return throat_area, throat_perimeter, output_offset, Error
def _get_hull_volume(points): r""" Calculate the volume of a set of points by dividing the bounding surface into triangles and working out the volume of all the pyramid elements connected to the volume centroid """ " remove any duplicate points - this messes up the triangulation " points = _sp.asarray(misc.unique_list(np.around(points, 10))) try: tri = Delaunay(points, qhull_options='QJ Pp') except _sp.spatial.qhull.QhullError: print(points) " We only want points included in the convex hull to calculate the centroid " hull_centroid = _sp.array( [points[:, 0].mean(), points[:, 1].mean(), points[:, 2].mean()]) hull_volume = 0.0 pyramid_COMs = [] for ia, ib, ic in tri.convex_hull: " Points making each triangular face " " Collection of co-ordinates of each point in this face " face_x = points[[ia, ib, ic]][:, 0] face_y = points[[ia, ib, ic]][:, 1] face_z = points[[ia, ib, ic]][:, 2] " Average of each co-ordinate is the centroid of the face " face_centroid = [face_x.mean(), face_y.mean(), face_z.mean()] face_centroid_vector = face_centroid - hull_centroid " Vectors of the sides of the face used to find normal vector and area " vab = points[ib] - points[ia] vac = points[ic] - points[ia] vbc = points[ic] - points[ib] # used later for area #face_COM = (vab+vac)/3 " As vectors are co-planar the cross-product will produce the normal vector of the face " face_normal = _sp.cross(vab, vac) try: face_unit_normal = face_normal / _sp.linalg.norm(face_normal) except RuntimeWarning: print("Pore Volume Error:" + str(vab) + " " + str(vac)) " As triangles are orientated randomly in 3D we could either transform co-ordinates to align with a plane and perform 2D operations " " to work out the area or we could work out the lengths of each side and use Heron's formula which is easier" " Using Delaunay traingulation will always produce triangular faces but if dealing with other polygons co-ordinate transfer may be necessary " a = _sp.linalg.norm(vab) b = _sp.linalg.norm(vbc) c = _sp.linalg.norm(vac) " Semiperimeter " s = 0.5 * (a + b + c) face_area = _sp.sqrt(s * (s - a) * (s - b) * (s - c)) " Now the volume of the pyramid section defined by the 3 face points and the hull centroid can be calculated " pyramid_volume = _sp.absolute( _sp.dot(face_centroid_vector, face_unit_normal) * face_area / 3) " Each pyramid is summed together to calculate the total volume " hull_volume += pyramid_volume " The Centre of Mass will not be the same as the geometrical centroid " " A weighted adjustment can be calculated from the pyramid centroid and volume " vha = points[ia] - hull_centroid vhb = points[ib] - hull_centroid vhc = points[ic] - hull_centroid pCOM = ((vha + vhb + vhc) / 4) * pyramid_volume pyramid_COMs.append(pCOM) if _sp.isnan(hull_volume): hull_volume = 0.0 if hull_volume > 0: hull_COM = hull_centroid + _sp.mean(_sp.asarray(pyramid_COMs), axis=0) / hull_volume else: hull_COM = hull_centroid return hull_volume, hull_COM
def get_throat_geom(verts,normal,fibre_rad): r""" For one set of vertices defining a throat return the key properties This is the main loop for calling other sub-routines. General Method: For each connection or throat defined by the shared vertices Rotate the vertices to align with the xy-plane and get rid of z-coordinate Compute the convex hull of the 2D points giving a set of simplices which define neighbouring vertices in a clockwise fashion For each triplet calculate the offset position given the fibre radius Check for overlapping vertices and ones that lie outside the original hull - recalculate position to offset from or ignore if all overlapping Calculate Area and Perimeter if successfully generated offset vertices to replicate eroded throat Translate back into 3D Any Errors encountered result in the throat area being zero and no vertices being passed back These Errors are not coding mistakes but failures to obtain an eroded facet with non-zero area: Error 1: Less than 3 vertices in the convex hull - Should never happen (unless 2 points are incredibly close together) Error 2: The largest span of points is less than twice the fibre radius (i.e. throat will definitley be occluded) Error 3: All the offset vertices overlap with at least one other vertex - Throat fully occluded Error 4: Not enough offset vertices to continue - Throat fully occluded Error 5: An offset vertex is outside the original set of points - Throat fully occluded """ z_axis = [0,0,1] throat_area = 0.0 throat_perimeter = 0.0 throat_COM = np.zeros([1,3]) output_offset = [] Error = 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,z_axis) if (angle==0.0)or(angle==np.pi): "We are already aligned" rotate_input = False facet = verts else: rotate_input = True M = tr.rotation_matrix(tr.angle_between_vectors(normal,z_axis),tr.vector_product(normal,z_axis)) facet = np.dot(verts,M[:3,:3].T) x = facet[:,0] y = facet[:,1] z = facet[:,2] " Work out span of points and set axes scales to cover this and be equal in both dimensions " x_range = x.max() - x.min() y_range = y.max() - y.min() if (x_range > y_range): my_range = x_range else: my_range = y_range if (np.around(z.std(),3)!=0.000): print("Rotation failed") facet_coords_2D = np.column_stack((x,y)) hull = ConvexHull(facet_coords_2D,qhull_options='QJ Pp') verts_2D = facet_coords_2D[hull.vertices] offset = outer_offset(verts_2D,fibre_rad) " At this point we may have overlapping areas for which we need to offset from a new point " overlap_array,sweep_radius,line_points = set_overlap(verts_2D,offset) #first_array = overlap_array temp_vert_list=[] #new_vert_list=[] if (len(verts_2D) <3): "Error: Fused Too Many Verts" Error = 1 elif(my_range < fibre_rad*2): "Error: Facet Too small to Erode" Error = 2 else: if overlap_array.any()==False: " If no overlaps don't worry" "no overlaps" elif all_overlap(overlap_array)==True: " If all overlaps then throat is fully occluded" "Error: Throat fully occluded" Error = 3 else: " If one or two sets of overlaps exist and at least one vertex is not overlapped then we need to do a bit more work " " Do some linalg to find a new point to offset from saving un-overlapped verts and newly created verts in a temporary list " count = 0 temp_verts = verts_2D while True: temp_vert_list=[] for i in range(np.shape(line_points)[0]): if np.sum(overlap_array[i])==0.0: temp_vert_list.append(temp_verts[i]) else: my_lines=[] for j in range(np.shape(line_points)[0]): if overlap_array[i][j] ==1 and overlap_array[j][i]==1: list_a = line_points[i][j] list_b = line_points[j][i] my_lines = symmetric_difference(list_a,list_b) my_lines=np.asarray(my_lines) if len(my_lines)==2: try: quad_points=temp_verts[my_lines] my_new_point = new_point(quad_points) temp_vert_list.append(my_new_point) except IndexError: print("IndexError: "+str(my_lines)) except TypeError: print("TypeError: "+str(my_lines)) #new_vert_list.append(my_new_point) temp_verts=np.asarray(misc.unique_list(temp_vert_list)) #new_vert_list=np.asarray(self._unique_list(new_vert_list)) #if len(verts_2D) >=3: offset = outer_offset(temp_verts,fibre_rad) overlap_array,sweep_radius,line_points = set_overlap(temp_verts,offset) #else: #Error = 4 if overlap_array.any()==False: break elif all_overlap(overlap_array)==True: Error = 3 break elif len(temp_verts) <3: Error = 4 break else: count+=1 temp_verts = np.asarray(fuse_verts(verts=temp_verts,percentage=0.05*count)) offset = outer_offset(temp_verts,fibre_rad) overlap_array,sweep_radius,line_points = set_overlap(temp_verts,offset) " Continue Looping until one of the above conditions is true or counter reaches 10" if count >= 10: break if len(offset) >= 3 and Error == 0: " Now also check whether any of the offset points lie outside the original convex hull " original_area = np.around(PolyArea2D(verts_2D),10) all_points = np.concatenate((verts_2D,offset),axis=0) try: total_hull = ConvexHull(all_points,qhull_options='QJ Pp') #ignores very small angles total_area = np.around(PolyArea2D(all_points[total_hull.vertices]),10) except np.spatial.qhull.QhullError: print(all_points) total_area =999 Error = 5 #total_area=0 offset_hull = ConvexHull(offset,qhull_options='QJ Pp') offset_verts_2D = offset[offset_hull.vertices] if (total_area>original_area): # Throat is fully occluded " Don't do anything " if Error != 5: Error = 6 #print("First Array") #print(first_array) #print("Second Array") #print(overlap_array) else: throat_area = PolyArea2D(offset_verts_2D) throat_perimeter = PolyPerimeter2D(offset_verts_2D) throat_COM_2D = PolyWeightedCentroid2D(offset_verts_2D) throat_COM = np.hstack((throat_COM_2D,z[0])) " Make 3D again in rotated plane " offset_verts_3D = np.column_stack((offset_verts_2D,z[0:len(offset_verts_2D)])) " Get matrix to un-rotate the co-ordinates back to the original orientation if we rotated in the first place" if (rotate_input): M1 = tr.inverse_matrix(M) " Unrotate the offset coordinates " output_offset = np.dot(offset_verts_3D,M1[:3,:3].T) throat_COM = np.dot(throat_COM,M1[:3,:3].T) else: output_offset = offset_verts_3D return throat_area, throat_perimeter, output_offset, throat_COM, Error
def _from_hull_image(geometry, **kwargs): r""" Compute the throat area from the image of the pore hulls with distance transforms. N.B. Only works with Voronoi geometry when voxel image ahs been created """ import numpy as np from scipy.ndimage import distance_transform_edt from scipy.stats import itemfreq from OpenPNM.Utilities import misc net = geometry._net throat_areas = np.zeros(net.Nt) try: vox_len = geometry._vox_len # Filter the fibres into pore -1 pore_space = geometry._hull_image.copy().astype(int) pore_space[~geometry._fibre_image.astype(bool)]=-1 for pore in np.unique(geometry._hull_image): logger.info("Processing pore: "+str(pore)) #Chunk the domain verts = np.asarray([i for i in net["pore.vert_index"][pore].values()]) verts = np.asarray(misc.unique_list(np.around(verts, 6))) xyz = verts/vox_len # Work out range to span over xmin = xyz[:, 0].min() xr = (np.ceil(xyz[:, 0].max())-np.floor(xmin)).astype(int)+1 ymin = xyz[:, 1].min() yr = (np.ceil(xyz[:, 1].max())-np.floor(ymin)).astype(int)+1 zmin = xyz[:, 2].min() zr = (np.ceil(xyz[:, 2].max())-np.floor(zmin)).astype(int)+1 origin = np.array([xmin, ymin, zmin]) # start index si = np.floor(origin).astype(int) temp_img = pore_space[si[0]:si[0]+xr, si[1]:si[1]+yr, si[2]:si[2]+zr] #convert to binary image with pore hull as ones bin_img = temp_img == pore #invert image for distance transform bin_img = ~bin_img dt = distance_transform_edt(bin_img) # find voxels on the perimeter, catch diagonals perim = (dt<2) * (dt>0) # find voxels on the perimeter, ignore diagonals perim = dt == 1 # filter the chunked hull image to get perimeter intersection fltr = temp_img[perim] # Count the intersecting voxels for each neighbor pore itmfrq = itemfreq(fltr) # Assign them to the throat area for neighbor, area in itmfrq: if neighbor >= 0: # don't include the fibres throat = net.find_connecting_throat(pore,neighbor) if throat_areas[throat] == 0.0: throat_areas[throat]=area elif throat_areas[throat] != area: throat_areas[throat] += area throat_areas[throat] *= 0.5 else: pass del pore_space except: logger.error("Method only works for Voronoi geometry when voxel image"+ " has been created") return throat_areas * vox_len**2