[docs]defmap_to_regions(regions,values):r""" Maps pore values from a network onto the image from which it was extracted Parameters ---------- regions : ndarray An image of the pore space partitioned into regions and labeled values : array_like An array containing the numerical values to insert into each region. The value at location *n* will be inserted into the image where ``regions`` is *n+1*. This mismatch is caused by the fact that 0's in the ``regions`` image is assumed to be the backgroung phase, while pore index 0 is valid. Notes ----- This function assumes that the array of pore values are indexed starting at location 0, while in the region image 0's indicate background phase and the region indexing starts at 1. That is, region 1 corresponds to pore 0. Examples -------- `Click here <https://porespy.org/examples/networks/reference/map_to_regions.html>`_ to view online example. """values=np.array(values).flatten()ifnp.size(values)!=regions.max():raiseException('Number of values does not match number of regions')im=np.zeros_like(regions)im=values[regions-1]im=im*(regions>0)returnim
[docs]defadd_boundary_regions(regions,pad_width=3):r""" Add boundary regions on specified faces of an image Parameters ---------- regions : ndarray An image containing labelled regions, such as a watershed segmentation pad_width : array_like Number of layers to add to the beginning and end of each axis. This argument is handled similarly to the ``pad_width`` in the ``np.pad`` function. A scalar adds the same amount to the beginning and end of each axis. [A, B] adds A to the beginning of each axis and B to the ends. [[A, B], ..., [C, D]] adds A to the beginning and B to the end of the first axis, and so on. The default is to add 3 voxels on both ends of each axis. One exception is is [A, B, C] which A to the beginning and end of the first axis, and so on. This extra option is useful for putting 0 on some axes (i.e. [3, 0, 0]). Returns ------- padded_regions : ndarray An image with new regions padded on each side of the specified width. Examples -------- `Click here <https://porespy.org/examples/networks/reference/add_boundary_regions.html>`_ to view online example. """# Parse user specified paddingfaces=np.array(pad_width)iffaces.size==1:faces=np.array([[faces,faces]]*regions.ndim)eliffaces.size==regions.ndim:faces=np.vstack([faces]*2)else:passt=faces.max()mx=regions.max()# Put a border around each region so padded regions are isolatedbd=find_boundaries(regions,connectivity=regions.ndim,mode='inner')# Pad by t in all directions, this will be trimmed down laterface_regions=np.pad(regions*(~bd),pad_width=t,mode='edge')# Set corners to 0 so regions don't connect across facesm='corners'iflen(regions.shape)==2else'edges'edges=borders(shape=face_regions.shape,mode=m,thickness=t)face_regions[edges]=0# Extract a mask of just the facesmask=borders(shape=face_regions.shape,mode='faces',thickness=t)# Relabel regions on facesnew_regions=spim.label(face_regions*mask)[0]+mx*(face_regions>0)new_regions[~mask]=regions.flatten()# Trim image down to user specified sizes=tuple([slice(t-ax[0],-(t-ax[1])orNone)foraxinfaces])new_regions=new_regions[s]new_regions=make_contiguous(new_regions)returnnew_regions
def_generate_voxel_image(network,pore_shape,throat_shape,max_dim=200):r""" Generates a 3d numpy array from an OpenPNM network Parameters ---------- network : OpenPNM GenericNetwork Network from which voxel image is to be generated pore_shape : str Shape of pores in the network, valid choices are "sphere", "cube" throat_shape : str Shape of throats in the network, valid choices are "cylinder", "cuboid" max_dim : int Number of voxels in the largest dimension of the network Returns ------- im : ndarray Voxelated image corresponding to the given pore network model Notes ----- (1) The generated voxel image is labeled with 0s, 1s and 2s signifying solid phase, pores, and throats respectively. """xyz=network["pore.coords"]cn=network["throat.conns"]# Distance bounding box from the network by a fixed amountdelta=network["pore.diameter"].mean()/2ifisinstance(network,op.network.Cubic):delta=op.topotools.get_spacing(network).mean()/2# Shift everything to avoid out-of-boundsextra_clearance=int(max_dim*0.05)# Transform points to satisfy origin at (0, 0, 0)xyz0=xyz.min(axis=0)-deltaxyz+=-xyz0res=(xyz.ptp(axis=0).max()+2*delta)/max_dimshape=np.rint((xyz.max(axis=0)+delta)/res).astype(int)+2*extra_clearance# Transforming from real coords to matrix coordsxyz=np.rint(xyz/res).astype(int)+extra_clearancepore_radi=np.rint(network["pore.diameter"]*0.5/res).astype(int)throat_radi=np.rint(network["throat.diameter"]*0.5/res).astype(int)im_pores=np.zeros(shape,dtype=np.uint8)im_throats=np.zeros_like(im_pores)ifpore_shape=="cube":pore_elem=cuberp=pore_radi*2+1# +1 since num_voxel must be oddrp_max=int(2*round(delta/res))+1ifpore_shape=="sphere":pore_elem=ballrp=pore_radirp_max=int(round(delta/res))ifthroat_shape=="cuboid":raiseException("Not yet implemented, try 'cylinder'.")# Generating voxels for poresfori,poreinenumerate(tqdm(network.Ps,**settings.tqdm)):elem=pore_elem(rp[i])try:im_pores=overlay(im1=im_pores,im2=elem,c=xyz[i])exceptValueError:elem=pore_elem(rp_max)im_pores=overlay(im1=im_pores,im2=elem,c=xyz[i])# Get rid of pore overlapsim_pores[im_pores>0]=1# Generating voxels for throatsfori,throatinenumerate(tqdm(network.Ts,**settings.tqdm)):try:im_throats=insert_cylinder(im_throats,r=throat_radi[i],xyz0=xyz[cn[i,0]],xyz1=xyz[cn[i,1]])exceptValueError:im_throats=insert_cylinder(im_throats,r=rp_max,xyz0=xyz[cn[i,0]],xyz1=xyz[cn[i,1]])# Get rid of throat overlapsim_throats[im_throats>0]=1# Subtract pore-throat overlap from throatsim_throats=(im_throats.astype(bool)*~im_pores.astype(bool)).astype(np.uint8)im=im_pores*1+im_throats*2returnim[extra_clearance:-extra_clearance,extra_clearance:-extra_clearance,extra_clearance:-extra_clearance]
[docs]defgenerate_voxel_image(network,pore_shape="sphere",throat_shape="cylinder",max_dim=None,rtol=0.1):r""" Generate a voxel image from an OpenPNM network object Parameters ---------- network : OpenPNM GenericNetwork Network from which voxel image is to be generated pore_shape : str Shape of pores in the network, valid choices are "sphere", "cube" throat_shape : str Shape of throats in the network, valid choices are "cylinder", "cuboid" max_dim : int Number of voxels in the largest dimension of the network rtol : float Stopping criteria for finding the smallest voxel image such that further increasing the number of voxels in each dimension by 25% would improve the predicted porosity of the image by less that ``rtol`` Returns ------- im : ndarray Voxelated image corresponding to the given pore network model Notes ----- (1) The generated voxelated image is labeled with 0s, 1s and 2s signifying solid phase, pores, and throats respectively. (2) If max_dim is not provided, the method calculates it such that the further increasing it doesn't change porosity by much. Examples -------- `Click here <https://porespy.org/examples/networks/reference/generator_voxel_image.html>`_ to view online example. """logger.info("Generating voxel image from pore network")# If max_dim is provided, generate voxel image using max_dimifmax_dimisnotNone:return_generate_voxel_image(network,pore_shape,throat_shape,max_dim=max_dim)max_dim=200# If max_dim is not provided, find best max_dim that predicts porosityerr=100eps_old=200whileerr>rtol:logger.debug(f"Maximum dimension: {max_dim} voxels")im=_generate_voxel_image(network,pore_shape,throat_shape,max_dim=max_dim)eps=im.astype(bool).sum(dtype=np.int64)/np.prod(im.shape)err=abs(1-eps/eps_old)eps_old=epsmax_dim=int(max_dim*1.25)logger.debug(f"Converged at max_dim = {max_dim} voxels")returnim
[docs]deflabel_phases(network,alias={1:'void',2:'solid'}):r""" Create pore and throat labels based on 'pore.phase' values Parameters ---------- network : dict The network stored as a dictionary as returned from the ``regions_to_network`` function alias : dict A mapping between integer values in 'pore.phase' and string labels. The default is ``{1: 'void', 2: 'solid'}`` which will result in the labels ``'pore.void'`` and ``'pore.solid'``, as well as ``'throat.solid_void'``, ``'throat.solid_solid'``, and ``'throat.void_void'``. The reverse labels are also added for convenience like ``throat.void_solid``. Returns ------- network : dict The same ``network`` as passed in but with new boolean arrays added for the phase labels. Examples -------- `Click here <https://porespy.org/examples/networks/reference/label_phases.html>`_ to view online example. """conns=network['throat.conns']foriinalias.keys():pore_i_hits=network['pore.phase']==inetwork['pore.'+alias[i]]=pore_i_hitsforjinalias.keys():pore_j_hits=network['pore.phase']==jthroat_hits=pore_i_hits[conns[:,0]]*pore_j_hits[conns[:,1]]throat_hits+=pore_i_hits[conns[:,1]]*pore_j_hits[conns[:,0]]ifnp.any(throat_hits):name='throat.'+'_'.join([alias[i],alias[j]])ifnamenotinnetwork.keys():network[name]=np.zeros_like(conns[:,0],dtype=bool)network[name]+=throat_hitsreturnnetwork
[docs]deflabel_boundaries(network,labels=[['left','right'],['front','back'],['top','bottom']],tol=1e-9):r""" Create boundary pore labels based on proximity to axis extrema Parameters ---------- network : dict The network stored as a dictionary as returned from the ``regions_to_network`` function labels : list of lists A 3-element list, with each element containing a pair of strings indicating the label to apply to the beginning and end of each axis. The default is ``[['left', 'right'], ['front', 'back'], ['top', 'bottom']]`` which will apply the label ``'left'`` to all pores with the minimum x-coordinate, and ``'right'`` to the pores with the maximum x-coordinate, and so on. Returns ------- network : dict The same ``network`` as passed in but with new boolean arrays added for the boundary labels. Examples -------- `Click here <https://porespy.org/examples/networks/reference/label_boundaries.html>`_ to view online example. """crds=network['pore.coords']extents=[[crds[:,i].min(),crds[:,i].max()]foriinrange(len(crds[0,:]))]network['pore.boundary']=np.zeros_like(crds[:,0],dtype=bool)fori,axisinenumerate(labels):forj,faceinenumerate(axis):ifface:hits=crds[:,i]==extents[i][j]network['pore.boundary']+=hitsnetwork['pore.'+labels[i][j]]=hitsreturnnetwork