Exemple #1
0
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
Exemple #2
0
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
Exemple #3
0
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
Exemple #4
0
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]
Exemple #5
0
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]
Exemple #6
0
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
Exemple #7
0
    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
Exemple #8
0
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
Exemple #9
0
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
Exemple #10
0
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