Exemple #1
0
def snow(im,
         voxel_size=1,
         boundary_faces=['top', 'bottom', 'left', 'right', 'front', 'back'],
         marching_cubes_area=False):
    r"""
    Analyzes an image that has been partitioned into void and solid regions
    and extracts the void and solid phase geometry as well as network
    connectivity.

    Parameters
    ----------
    im : ND-array
        Binary image in the Boolean form with True’s as void phase and False’s
        as solid phase.
    voxel_size : scalar
        The resolution of the image, expressed as the length of one side of a
        voxel, so the volume of a voxel would be **voxel_size**-cubed.  The
        default is 1, which is useful when overlaying the PNM on the original
        image since the scale of the image is alway 1 unit lenth per voxel.
    boundary_faces : list of strings
        Boundary faces labels are provided to assign hypothetical boundary
        nodes having zero resistance to transport process. For cubical
        geometry, the user can choose ‘left’, ‘right’, ‘top’, ‘bottom’,
        ‘front’ and ‘back’ face labels to assign boundary nodes. If no label is
        assigned then all six faces will be selected as boundary nodes
        automatically which can be trimmed later on based on user requirements.
    marching_cubes_area : bool
        If ``True`` then the surface area and interfacial area between regions
        will be using the marching cube algorithm. This is a more accurate
        representation of area in extracted network, but is quite slow, so
        it is ``False`` by default.  The default method simply counts voxels
        so does not correctly account for the voxelated nature of the images.

    Returns
    -------
    A dictionary containing the void phase size data, as well as the network
    topological information.  The dictionary names use the OpenPNM
    convention (i.e. 'pore.coords', 'throat.conns') so it may be converted
    directly to an OpenPNM network object using the ``update`` command.
    * ``net``: A dictionary containing all the void and solid phase size data,
        as well as the network topological information.  The dictionary names
        use the OpenPNM convention (i.e. 'pore.coords', 'throat.conns') so it
        may be converted directly to an OpenPNM network object using the
        ``update`` command.
    * ``im``: The binary image of the void space
    * ``dt``: The combined distance transform of the image
    * ``regions``: The void and solid space partitioned into pores and solids
        phases using a marker based watershed with the peaks found by the
        SNOW Algorithm.
    """

    # -------------------------------------------------------------------------
    # SNOW void phase
    regions = snow_partitioning(im=im, return_all=True)
    im = regions.im
    dt = regions.dt
    regions = regions.regions
    b_num = np.amax(regions)
    # -------------------------------------------------------------------------
    # Boundary Conditions
    regions = add_boundary_regions(regions=regions, faces=boundary_faces)
    # -------------------------------------------------------------------------
    # Padding distance transform and image to extract geometrical properties
    dt = pad_faces(im=dt, faces=boundary_faces)
    im = pad_faces(im=im, faces=boundary_faces)
    regions = regions * im
    regions = make_contiguous(regions)
    # -------------------------------------------------------------------------
    # Extract void and throat information from image
    net = regions_to_network(im=regions, dt=dt, voxel_size=voxel_size)
    # -------------------------------------------------------------------------
    # Extract marching cube surface area and interfacial area of regions
    if marching_cubes_area:
        areas = region_surface_areas(regions=regions)
        interface_area = region_interface_areas(regions=regions,
                                                areas=areas,
                                                voxel_size=voxel_size)
        net['pore.surface_area'] = areas * voxel_size**2
        net['throat.area'] = interface_area.area
    # -------------------------------------------------------------------------
    # Find void to void connections of boundary and internal voids
    boundary_labels = net['pore.label'] > b_num
    loc1 = net['throat.conns'][:, 0] < b_num
    loc2 = net['throat.conns'][:, 1] >= b_num
    pore_labels = net['pore.label'] <= b_num
    loc3 = net['throat.conns'][:, 0] < b_num
    loc4 = net['throat.conns'][:, 1] < b_num
    net['pore.boundary'] = boundary_labels
    net['throat.boundary'] = loc1 * loc2
    net['pore.internal'] = pore_labels
    net['throat.internal'] = loc3 * loc4
    # -------------------------------------------------------------------------
    # label boundary cells
    net = label_boundary_cells(network=net, boundary_faces=boundary_faces)
    # -------------------------------------------------------------------------
    # assign out values to dummy dict

    temp = _net_dict(net)
    temp.im = im.copy()
    temp.dt = dt
    temp.regions = regions
    return temp
Exemple #2
0
def snow(im,
         voxel_size=1,
         boundary_faces=['top', 'bottom', 'left', 'right', 'front', 'back'],
         marching_cubes_area=False):
    r"""
    Analyzes an image that has been partitioned into void and solid regions
    and extracts the void and solid phase geometry as well as network
    connectivity.

    Parameters
    ----------
    im : ND-array
        Binary image in the Boolean form with True’s as void phase and False’s
        as solid phase.
    voxel_size : scalar
        The resolution of the image, expressed as the length of one side of a
        voxel, so the volume of a voxel would be **voxel_size**-cubed.  The
        default is 1, which is useful when overlaying the PNM on the original
        image since the scale of the image is alway 1 unit lenth per voxel.
    boundary_faces : list of strings
        Boundary faces labels are provided to assign hypothetical boundary
        nodes having zero resistance to transport process. For cubical
        geometry, the user can choose ‘left’, ‘right’, ‘top’, ‘bottom’,
        ‘front’ and ‘back’ face labels to assign boundary nodes. If no label is
        assigned then all six faces will be selected as boundary nodes
        automatically which can be trimmed later on based on user requirements.
    marching_cubes_area : bool
        If ``True`` then the surface area and interfacial area between regions
        will be using the marching cube algorithm. This is a more accurate
        representation of area in extracted network, but is quite slow, so
        it is ``False`` by default.  The default method simply counts voxels
        so does not correctly account for the voxelated nature of the images.

    Returns
    -------
    A dictionary containing the void phase size data, as well as the network
    topological information.  The dictionary names use the OpenPNM
    convention (i.e. 'pore.coords', 'throat.conns') so it may be converted
    directly to an OpenPNM network object using the ``update`` command.
    """

    # -------------------------------------------------------------------------
    # SNOW void phase
    tup = snow_partitioning(im=im, return_all=True)
    im = tup.im
    dt = tup.dt
    regions = tup.regions
    peaks = tup.peaks
    b_num = sp.amax(regions)
    # -------------------------------------------------------------------------
    # Boundary Conditions
    regions = add_boundary_regions(regions=regions, faces=boundary_faces)
    # -------------------------------------------------------------------------
    # Padding distance transform to extract geometrical properties
    f = boundary_faces
    if f is not None:
        if im.ndim == 2:
            faces = [(int('left' in f) * 3, int('right' in f) * 3),
                     (int(('front') in f) * 3 or int(('bottom') in f) * 3,
                      int(('back') in f) * 3 or int(('top') in f) * 3)]

        if im.ndim == 3:
            faces = [(int('left' in f) * 3, int('right' in f) * 3),
                     (int('front' in f) * 3, int('back' in f) * 3),
                     (int('top' in f) * 3, int('bottom' in f) * 3)]
        dt = sp.pad(dt, pad_width=faces, mode='edge')
        im = sp.pad(im, pad_width=faces, mode='edge')
    else:
        dt = dt
    regions = regions * im
    regions = make_contiguous(regions)
    # -------------------------------------------------------------------------
    # Extract void and throat information from image
    net = regions_to_network(im=regions, dt=dt, voxel_size=voxel_size)
    # -------------------------------------------------------------------------
    # Extract marching cube surface area and interfacial area of regions
    if marching_cubes_area:
        areas = region_surface_areas(regions=regions)
        interface_area = region_interface_areas(regions=regions,
                                                areas=areas,
                                                voxel_size=voxel_size)
        net['pore.surface_area'] = areas * voxel_size**2
        net['throat.area'] = interface_area.area
    # -------------------------------------------------------------------------
    # Find void to void connections of boundary and internal voids
    boundary_labels = net['pore.label'] > b_num
    loc1 = net['throat.conns'][:, 0] < b_num
    loc2 = net['throat.conns'][:, 1] >= b_num
    pore_labels = net['pore.label'] <= b_num
    loc3 = net['throat.conns'][:, 0] < b_num
    loc4 = net['throat.conns'][:, 1] < b_num
    net['pore.boundary'] = boundary_labels
    net['throat.boundary'] = loc1 * loc2
    net['pore.internal'] = pore_labels
    net['throat.internal'] = loc3 * loc4
    # -------------------------------------------------------------------------
    # label boundary pore faces
    if f is not None:
        coords = net['pore.coords']
        condition = coords[net['pore.internal']]
        dic = {
            'left': 0,
            'right': 0,
            'front': 1,
            'back': 1,
            'top': 2,
            'bottom': 2
        }
        if all(coords[:, 2] == 0):
            dic['top'] = 1
            dic['bottom'] = 1
        for i in f:
            if i in ['left', 'front', 'bottom']:
                net['pore.{}'.format(i)] = (coords[:, dic[i]] < min(
                    condition[:, dic[i]]))
            elif i in ['right', 'back', 'top']:
                net['pore.{}'.format(i)] = (coords[:, dic[i]] > max(
                    condition[:, dic[i]]))

    class network_dict(dict):
        pass

    net = network_dict(net)
    net.im = im
    net.dt = dt
    net.regions = regions
    net.peaks = peaks
    return net
Exemple #3
0
def snow_n(im,
           voxel_size=1,
           boundary_faces=['top', 'bottom', 'left', 'right', 'front', 'back'],
           marching_cubes_area=False,
           alias=None):
    r"""
    Analyzes an image that has been segemented into N phases and extracts all
    a network for each of the N phases, including geometerical information as
    well as network connectivity between each phase.

    Parameters
    ----------
    im : ND-array
        Image of porous material where each phase is represented by unique
        integer. Phase integer should start from 1 (0 is ignored)

    voxel_size : scalar
        The resolution of the image, expressed as the length of one side of a
        voxel, so the volume of a voxel would be **voxel_size**-cubed.  The
        default is 1, which is useful when overlaying the PNM on the original
        image since the scale of the image is always 1 unit lenth per voxel.

    boundary_faces : list of strings
        Boundary faces labels are provided to assign hypothetical boundary
        nodes having zero resistance to transport process. For cubical
        geometry, the user can choose ‘left’, ‘right’, ‘top’, ‘bottom’,
        ‘front’ and ‘back’ face labels to assign boundary nodes. If no label is
        assigned then all six faces will be selected as boundary nodes
        automatically which can be trimmed later on based on user requirements.

    marching_cubes_area : bool
        If ``True`` then the surface area and interfacial area between regions
        will be calculated using the marching cube algorithm. This is a more
        accurate representation of area in extracted network, but is quite
        slow, so it is ``False`` by default.  The default method simply counts
        voxels so does not correctly account for the voxelated nature of the
        images.

    alias : dict (Optional)
        A dictionary that assigns unique image label to specific phases. For
        example {1: 'Solid'} will show all structural properties associated
        with label 1 as Solid phase properties. If ``None`` then default
        labelling will be used i.e {1: 'Phase1',..}.

    Returns
    -------
    A dictionary containing all N phases size data, as well as the
    network topological information.  The dictionary names use the OpenPNM
    convention (i.e. 'pore.coords', 'throat.conns') so it may be converted
    directly to an OpenPNM network object using the ``update`` command.
    """
    # -------------------------------------------------------------------------
    # Get alias if provided by user
    al = _create_alias_map(im, alias=alias)
    # -------------------------------------------------------------------------
    # Perform snow on each phase and merge all segmentation and dt together
    snow = snow_partitioning_n(im,
                               r_max=4,
                               sigma=0.4,
                               return_all=True,
                               mask=True,
                               randomize=False,
                               alias=al)
    # -------------------------------------------------------------------------
    # Add boundary regions
    f = boundary_faces
    regions = add_boundary_regions(regions=snow.regions, faces=f)
    # -------------------------------------------------------------------------
    # Padding distance transform to extract geometrical properties
    dt = pad_faces(im=snow.dt, faces=f)
    # -------------------------------------------------------------------------
    # For only one phase extraction with boundary regions
    phases_num = np.unique(im).astype(int)
    phases_num = np.trim_zeros(phases_num)
    if len(phases_num) == 1:
        if f is not None:
            snow.im = pad_faces(im=snow.im, faces=f)
        regions = regions * (snow.im.astype(bool))
        regions = make_contiguous(regions)
    # -------------------------------------------------------------------------
    # Extract N phases sites and bond information from image
    net = regions_to_network(im=regions, dt=dt, voxel_size=voxel_size)
    # -------------------------------------------------------------------------
    # Extract marching cube surface area and interfacial area of regions
    if marching_cubes_area:
        areas = region_surface_areas(regions=regions)
        interface_area = region_interface_areas(regions=regions,
                                                areas=areas,
                                                voxel_size=voxel_size)
        net['pore.surface_area'] = areas * voxel_size**2
        net['throat.area'] = interface_area.area
    # -------------------------------------------------------------------------
    # Find interconnection and interfacial area between ith and jth phases
    net = add_phase_interconnections(net=net,
                                     snow_partitioning_n=snow,
                                     marching_cubes_area=marching_cubes_area,
                                     alias=al)
    # -------------------------------------------------------------------------
    # label boundary cells
    net = label_boundary_cells(network=net, boundary_faces=f)
    # -------------------------------------------------------------------------

    temp = _net_dict(net)
    temp.im = im.copy()
    temp.dt = dt
    temp.regions = regions
    return temp
Exemple #4
0
def add_boundary_regions(regions=None,
                         faces=[
                             'front', 'back', 'left', 'right', 'top', 'bottom'
                         ]):
    r"""
    Given an image partitioned into regions, pads specified faces with new
    regions

    Parameters
    ----------
    regions : ND-array
        An image of the pore space partitioned into regions and labeled
    faces : list of strings
        The faces of ``regions`` which should have boundaries added.  Options
        are:

        *'right'* - Adds boundaries to the x=0 face (``im[0, :, :]``)

        *'left'* - Adds boundaries to the x=X face (``im[-1, :, :]``)

        *'front'* - Adds boundaries to the y=0 face (``im[:, ), :]``)

        *'back'* - Adds boundaries to the x=0 face (``im[:, -1, :]``)

        *'bottom'* - Adds boundaries to the x=0 face (``im[:, :, 0]``)

        *'top'* - Adds boundaries to the x=0 face (``im[:, :, -1]``)

        The default is all faces.

    Returns
    -------
    image : ND-array
        A copy of ``regions`` with the specified boundaries added, so will be
        slightly larger in each direction where boundaries were added.

    """
    # -------------------------------------------------------------------------
    # Edge pad segmentation and distance transform
    if faces is not None:
        regions = np.pad(regions, 1, 'edge')
        # ---------------------------------------------------------------------
        if regions.ndim == 3:
            # Remove boundary nodes interconnection
            regions[:, :, 0] = regions[:, :, 0] + regions.max()
            regions[:, :, -1] = regions[:, :, -1] + regions.max()
            regions[0, :, :] = regions[0, :, :] + regions.max()
            regions[-1, :, :] = regions[-1, :, :] + regions.max()
            regions[:, 0, :] = regions[:, 0, :] + regions.max()
            regions[:, -1, :] = regions[:, -1, :] + regions.max()
            regions[:, :, 0] = (~find_boundaries(
                regions[:, :, 0], mode='outer')) * regions[:, :, 0]
            regions[:, :, -1] = (~find_boundaries(
                regions[:, :, -1], mode='outer')) * regions[:, :, -1]
            regions[0, :, :] = (~find_boundaries(
                regions[0, :, :], mode='outer')) * regions[0, :, :]
            regions[-1, :, :] = (~find_boundaries(
                regions[-1, :, :], mode='outer')) * regions[-1, :, :]
            regions[:, 0, :] = (~find_boundaries(
                regions[:, 0, :], mode='outer')) * regions[:, 0, :]
            regions[:, -1, :] = (~find_boundaries(
                regions[:, -1, :], mode='outer')) * regions[:, -1, :]
            # -----------------------------------------------------------------
            regions = np.pad(regions, 2, 'edge')

            # Remove unselected faces
            if 'front' not in faces:
                regions = regions[:, 3:, :]  # y
            if 'back' not in faces:
                regions = regions[:, :-3, :]
            if 'left' not in faces:
                regions = regions[3:, :, :]  # x
            if 'right' not in faces:
                regions = regions[:-3, :, :]
            if 'bottom' not in faces:
                regions = regions[:, :, 3:]  # z
            if 'top' not in faces:
                regions = regions[:, :, :-3]

        elif regions.ndim == 2:
            # Remove boundary nodes interconnection
            regions[0, :] = regions[0, :] + regions.max()
            regions[-1, :] = regions[-1, :] + regions.max()
            regions[:, 0] = regions[:, 0] + regions.max()
            regions[:, -1] = regions[:, -1] + regions.max()
            regions[0, :] = (
                ~find_boundaries(regions[0, :], mode='outer')) * regions[0, :]
            regions[-1, :] = (~find_boundaries(regions[-1, :],
                                               mode='outer')) * regions[-1, :]
            regions[:, 0] = (
                ~find_boundaries(regions[:, 0], mode='outer')) * regions[:, 0]
            regions[:, -1] = (~find_boundaries(regions[:, -1],
                                               mode='outer')) * regions[:, -1]
            # -----------------------------------------------------------------
            regions = np.pad(regions, 2, 'edge')

            # Remove unselected faces
            if 'left' not in faces:
                regions = regions[3:, :]  # x
            if 'right' not in faces:
                regions = regions[:-3, :]
            if 'front' not in faces and 'bottom' not in faces:
                regions = regions[:, 3:]  # y
            if 'back' not in faces and 'top' not in faces:
                regions = regions[:, :-3]
        else:
            print('add_boundary_regions works only on 2D and 3D images')
        # ---------------------------------------------------------------------
        # Make labels contiguous
        regions = make_contiguous(regions)
    else:
        regions = regions

    return regions
Exemple #5
0
def add_boundary_regions(regions=None, faces=['front', 'back', 'left',
                                              'right', 'top', 'bottom']):
    # -------------------------------------------------------------------------
    # Edge pad segmentation and distance transform
    if faces is not None:
        regions = sp.pad(regions, 1, 'edge')
        # ---------------------------------------------------------------------
        if regions.ndim == 3:
            # Remove boundary nodes interconnection
            regions[:, :, 0] = regions[:, :, 0] + regions.max()
            regions[:, :, -1] = regions[:, :, -1] + regions.max()
            regions[0, :, :] = regions[0, :, :] + regions.max()
            regions[-1, :, :] = regions[-1, :, :] + regions.max()
            regions[:, 0, :] = regions[:, 0, :] + regions.max()
            regions[:, -1, :] = regions[:, -1, :] + regions.max()
            regions[:, :, 0] = (~find_boundaries(regions[:, :, 0],
                                                 mode='outer'))*regions[:, :, 0]
            regions[:, :, -1] = (~find_boundaries(regions[:, :, -1],
                                                  mode='outer'))*regions[:, :, -1]
            regions[0, :, :] = (~find_boundaries(regions[0, :, :],
                                                 mode='outer'))*regions[0, :, :]
            regions[-1, :, :] = (~find_boundaries(regions[-1, :, :],
                                                  mode='outer'))*regions[-1, :, :]
            regions[:, 0, :] = (~find_boundaries(regions[:, 0, :],
                                                 mode='outer'))*regions[:, 0, :]
            regions[:, -1, :] = (~find_boundaries(regions[:, -1, :],
                                                  mode='outer'))*regions[:, -1, :]
            # ---------------------------------------------------------------------
            regions = sp.pad(regions, 2, 'edge')

            # Remove unselected faces
            if 'top' not in faces:
                regions = regions[:, 3:, :]
            if 'bottom' not in faces:
                regions = regions[:, :-3, :]
            if 'front' not in faces:
                regions = regions[3:, :, :]
            if 'back' not in faces:
                regions = regions[:-3, :, :]
            if 'left' not in faces:
                regions = regions[:, :, 3:]
            if 'right' not in faces:
                regions = regions[:, :, :-3]

        elif regions.ndim == 2:
            # Remove boundary nodes interconnection
            regions[0, :] = regions[0, :] + regions.max()
            regions[-1, :] = regions[-1, :] + regions.max()
            regions[:, 0] = regions[:, 0] + regions.max()
            regions[:, -1] = regions[:, -1] + regions.max()
            regions[0, :] = (~find_boundaries(regions[0, :],
                                              mode='outer'))*regions[0, :]
            regions[-1, :] = (~find_boundaries(regions[-1, :],
                                               mode='outer'))*regions[-1, :]
            regions[:, 0] = (~find_boundaries(regions[:, 0],
                                              mode='outer'))*regions[:, 0]
            regions[:, -1] = (~find_boundaries(regions[:, -1],
                                               mode='outer'))*regions[:, -1]
            # ---------------------------------------------------------------------
            regions = sp.pad(regions, 2, 'edge')

            # Remove unselected faces
            if 'top' not in faces:
                regions = regions[3:, :]
            if 'bottom' not in faces:
                regions = regions[:-3, :]
            if 'left' not in faces:
                regions = regions[:, 3:]
            if 'right' not in faces:
                regions = regions[:, :-3]
        else:
            print('add_boundary_regions works only on 2D and 3D images')
        # ---------------------------------------------------------------------
        # Make labels contiguous
        regions = make_contiguous(regions)
    else:
        regions = regions

    return regions