Example #1
0
def centre_of_mass(geometry, vertices='throat.offset_vertices', **kwargs):
    r"""
    Calculate the centre of mass of the throat from the voronoi vertices.
    """
    Nt = geometry.num_throats()
    outer_verts = geometry['throat.vertices']
    offset_verts = geometry[vertices]
    normal = geometry['throat.normal']
    z_axis = [0, 0, 1]
    value = _sp.ndarray([Nt, 3])
    for i in range(Nt):
        if len(offset_verts[i]) > 2:
            verts = offset_verts[i]
        elif len(outer_verts[i]) > 2:
            verts = outer_verts[i]
        else:
            verts = []
        if len(verts) > 0:
            # For boundaries some facets will already be aligned with the axis -
            # if this is the case a rotation is unnecessary and could also cause
            # problems
            angle = tr.angle_between_vectors(normal[i], z_axis)
            if angle == 0.0 or angle == _sp.pi:
                "We are already aligned"
                rotate_input = False
                facet = verts
            else:
                rotate_input = True
                M = tr.rotation_matrix(
                    tr.angle_between_vectors(normal[i], z_axis),
                    tr.vector_product(normal[i], z_axis))
                facet = _sp.dot(verts, M[:3, :3].T)
            # Now we have a rotated facet aligned with the z axis - make 2D
            facet_2D = _sp.column_stack((facet[:, 0], facet[:, 1]))
            z = _sp.unique(_sp.around(facet[:, 2], 10))
            if len(z) == 1:
                # We need the vertices arranged in order so perform a convex hull
                hull = ConvexHull(facet_2D)
                ordered_facet_2D = facet_2D[hull.vertices]
                # Call the routine to calculate an area wighted centroid from the
                # 2D polygon
                COM_2D = vo.PolyWeightedCentroid2D(ordered_facet_2D)
                COM_3D = _sp.hstack((COM_2D, z))
                # If we performed a rotation we need to rotate back
                if (rotate_input):
                    MI = tr.inverse_matrix(M)
                    # Unrotate the offset coordinates using the inverse of the
                    # original rotation matrix
                    value[i] = _sp.dot(COM_3D, MI[:3, :3].T)
                else:
                    value[i] = COM_3D
            else:
                logger.error('Rotation Failed: ' +
                             str(_sp.unique(facet[:, 2])))

    return value
Example #2
0
def centre_of_mass(geometry, vertices='throat.offset_vertices', **kwargs):
    r"""
    Calculate the centre of mass of the throat from the voronoi vertices.
    """
    Nt = geometry.num_throats()
    outer_verts = geometry['throat.vertices']
    offset_verts = geometry[vertices]
    normal = geometry['throat.normal']
    z_axis = [0, 0, 1]
    value = _sp.ndarray([Nt, 3])
    for i in range(Nt):
        if len(offset_verts[i]) > 2:
            verts = offset_verts[i]
        elif len(outer_verts[i]) > 2:
            verts = outer_verts[i]
        else:
            verts = []
        if len(verts) > 0:
            # For boundaries some facets will already be aligned with the axis -
            # if this is the case a rotation is unnecessary and could also cause
            # problems
            angle = tr.angle_between_vectors(normal[i], z_axis)
            if angle == 0.0 or angle == _sp.pi:
                "We are already aligned"
                rotate_input = False
                facet = verts
            else:
                rotate_input = True
                M = tr.rotation_matrix(tr.angle_between_vectors(normal[i], z_axis),
                                       tr.vector_product(normal[i], z_axis))
                facet = _sp.dot(verts, M[:3, :3].T)
            # Now we have a rotated facet aligned with the z axis - make 2D
            facet_2D = _sp.column_stack((facet[:, 0], facet[:, 1]))
            z = _sp.unique(_sp.around(facet[:, 2], 10))
            if len(z) == 1:
                # We need the vertices arranged in order so perform a convex hull
                hull = ConvexHull(facet_2D)
                ordered_facet_2D = facet_2D[hull.vertices]
                # Call the routine to calculate an area wighted centroid from the
                # 2D polygon
                COM_2D = vo.PolyWeightedCentroid2D(ordered_facet_2D)
                COM_3D = _sp.hstack((COM_2D, z))
                # If we performed a rotation we need to rotate back
                if (rotate_input):
                    MI = tr.inverse_matrix(M)
                    # Unrotate the offset coordinates using the inverse of the
                    # original rotation matrix
                    value[i] = _sp.dot(COM_3D, MI[:3, :3].T)
                else:
                    value[i] = COM_3D
            else:
                logger.error('Rotation Failed: ' + str(_sp.unique(facet[:, 2])))

    return value
Example #3
0
def rotate_and_chop(verts, normal, axis=[0, 0, 1]):
    r"""
    Method to rotate a set of vertices (or coords) to align with an axis
    points must be coplanar and normal must be given
    Chops axis coord to give vertices back in 2D
    Used to prepare verts for printing or calculating convex hull in order to arrange
    them in hull order for calculations and printing
    """
    xaxis = [1, 0, 0]
    yaxis = [0, 1, 0]
    zaxis = [0, 0, 1]
    angle = tr.angle_between_vectors(normal, axis)
    if angle == 0.0 or angle == np.pi:
        # We are already aligned
        facet = verts
    else:
        M = tr.rotation_matrix(tr.angle_between_vectors(normal, axis),
                               tr.vector_product(normal, axis))
        try:
            facet = np.dot(verts, M[:3, :3].T)
        except ValueError:
            pass

    try:
        x = facet[:, 0]
        y = facet[:, 1]
        z = facet[:, 2]
    except IndexError:
        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
    if axis == xaxis:
        output = np.column_stack((y, z))
    elif axis == yaxis:
        output = np.column_stack((x, z))
    elif axis == zaxis:
        output = np.column_stack((x, y))
    else:
        output = facet

    return output
Example #4
0
def rotate_and_chop(verts, normal, axis=[0, 0, 1]):
    r"""
    Method to rotate a set of vertices (or coords) to align with an axis
    points must be coplanar and normal must be given
    Chops axis coord to give vertices back in 2D
    Used to prepare verts for printing or calculating convex hull in order to arrange
    them in hull order for calculations and printing
    """
    xaxis = [1, 0, 0]
    yaxis = [0, 1, 0]
    zaxis = [0, 0, 1]
    angle = tr.angle_between_vectors(normal, axis)
    if angle == 0.0 or angle == np.pi:
        # We are already aligned
        facet = verts
    else:
        M = tr.rotation_matrix(tr.angle_between_vectors(normal, axis),
                               tr.vector_product(normal, axis))
        try:
            facet = np.dot(verts, M[:3, :3].T)
        except ValueError:
            pass

    try:
        x = facet[:, 0]
        y = facet[:, 1]
        z = facet[:, 2]
    except IndexError:
        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
    if axis == xaxis:
        output = np.column_stack((y, z))
    elif axis == yaxis:
        output = np.column_stack((x, z))
    elif axis == zaxis:
        output = np.column_stack((x, y))
    else:
        output = facet

    return output
def distance_transform(network, geometry, offset, **kwargs):
    r"""
    Use the Voronoi vertices and perform image analysis to obtain throat properties
    """

    import math
    import numpy as np
    from skimage.morphology import convex_hull_image
    from skimage.measure import regionprops
    from scipy import ndimage

    Nt = geometry.num_throats()
    area = sp.zeros(Nt)
    perimeter = sp.zeros(Nt)
    centroid = sp.zeros([Nt, 3])
    incentre = sp.zeros([Nt, 3])
    inradius = sp.zeros(Nt)
    equiv_diameter = sp.zeros(Nt)
    eroded_verts = sp.ndarray(Nt, dtype=object)

    res = 200
    vertices = geometry['throat.vertices']
    normals = geometry['throat.normal']
    z_axis = [0, 0, 1]

    for i in range(Nt):
        logger.info("Processing throat " + str(i+1)+" of "+str(Nt))
        # For boundaries some facets will already be aligned with the axis - if this
        # is the case a rotation is unnecessary and could also cause problems
        angle = tr.angle_between_vectors(normals[i], z_axis)
        if angle == 0.0 or angle == np.pi:
            # We are already aligned
            rotate_facet = False
            facet = vertices[i]
        else:
            rotate_facet = True
            M = tr.rotation_matrix(tr.angle_between_vectors(normals[i], z_axis),
                                   tr.vector_product(normals[i], z_axis))
            facet = np.dot(vertices[i], M[:3, :3].T)
        x = facet[:, 0]
        y = facet[:, 1]
        z = facet[:, 2]
        # Get points in 2d for image analysis
        pts = np.column_stack((x, y))
        # Translate points so min sits at the origin
        translation = [pts[:, 0].min(), pts[:, 1].min()]
        pts -= translation
        order = np.int(math.ceil(-np.log10(np.max(pts))))
        # Normalise and scale the points so that largest span equals the resolution
        # to save on memory and create clear image"
        max_factor = np.max([pts[:, 0].max(), pts[:, 1].max()])
        f = res/max_factor
        #"Temporarily try using the vox_len as a scale factor"
        #f = 1/geometry._vox_len
        # Scale the offset and define a circular structuring element with radius
        r = f*offset
        # Only proceed if r is less than half the span of the image"
        if r <= res/2:
            pts *= f
            minp1 = pts[:, 0].min()
            minp2 = pts[:, 1].min()
            maxp1 = pts[:, 0].max()
            maxp2 = pts[:, 1].max()
            img = np.zeros([np.int(math.ceil(maxp1-minp1)+1),
                            np.int(math.ceil(maxp2-minp2)+1)])
            int_pts = np.around(pts, 0).astype(int)
            for pt in int_pts:
                img[pt[0]][pt[1]] = 1
            # Pad with zeros all the way around the edges
            img_pad = np.zeros([np.shape(img)[0] + 2, np.shape(img)[1] + 2])
            img_pad[1:np.shape(img)[0]+1, 1:np.shape(img)[1]+1] = img

            # All points should lie on this plane but could be some rounding errors
            # so use the order parameter
            z_plane = sp.unique(np.around(z, order+2))
            if len(z_plane) > 1:
                logger.error('Rotation for image analysis failed')
                temp_arr = np.ones(1)
                temp_arr.fill(np.mean(z_plane))
                z_plane = temp_arr
            "Fill in the convex hull polygon"
            convhullimg = convex_hull_image(img_pad)
            # Perform a Distance Transform and black out points less than r to create
            # binary erosion. This is faster than performing an erosion and dt can
            # also be used later to find incircle"
            eroded = ndimage.distance_transform_edt(convhullimg)
            eroded[eroded <= r] = 0
            eroded[eroded > r] = 1
            # If we are left with less than 3 non-zero points then the throat is
            # fully occluded
            if np.sum(eroded) >= 3:
                # Do some image analysis to extract the key properties
                regions = regionprops(eroded[1:np.shape(img)[0]+1,
                                             1:np.shape(img)[1]+1].astype(int))
                # Change this to cope with genuine multi-region throats
                if len(regions) == 1:
                    for props in regions:
                        x0, y0 = props.centroid
                        equiv_diameter[i] = props.equivalent_diameter
                        area[i] = props.area
                        perimeter[i] = props.perimeter
                        coords = props.coords
                    # Undo the translation, scaling and truncation on the centroid
                    centroid2d = np.array([x0, y0])/f
                    centroid2d += (translation)
                    centroid3d = np.concatenate((centroid2d, z_plane))
                    # Distance transform the eroded facet to find the incentre and
                    # inradius
                    dt = ndimage.distance_transform_edt(eroded)
                    inx0, iny0 = \
                        np.asarray(np.unravel_index(dt.argmax(), dt.shape)) \
                          .astype(float)
                    incentre2d = np.array([inx0, iny0])
                    # Undo the translation, scaling and truncation on the incentre
                    incentre2d /= f
                    incentre2d += (translation)
                    incentre3d = np.concatenate((incentre2d, z_plane))
                    # The offset vertices will be those in the coords that are
                    # closest to the originals"
                    offset_verts = []
                    for pt in int_pts:
                        vert = np.argmin(np.sum(np.square(coords-pt), axis=1))
                        if vert not in offset_verts:
                            offset_verts.append(vert)
                    # If we are left with less than 3 different vertices then the
                    # throat is fully occluded as we can't make a shape with
                    # non-zero area
                    if len(offset_verts) >= 3:
                        offset_coords = coords[offset_verts].astype(float)
                        # Undo the translation, scaling and truncation on the
                        # offset_verts
                        offset_coords /= f
                        offset_coords_3d = \
                            np.vstack((offset_coords[:, 0]+translation[0],
                                       offset_coords[:, 1]+translation[1],
                                       np.ones(len(offset_verts))*z_plane)).T

                        # Get matrix to un-rotate the co-ordinates back to the
                        # original orientation if we rotated in the first place
                        if rotate_facet:
                            MI = tr.inverse_matrix(M)
                            # Unrotate the offset coordinates
                            incentre[i] = np.dot(incentre3d, MI[:3, :3].T)
                            centroid[i] = np.dot(centroid3d, MI[:3, :3].T)
                            eroded_verts[i] = np.dot(offset_coords_3d, MI[:3, :3].T)

                        else:
                            incentre[i] = incentre3d
                            centroid[i] = centroid3d
                            eroded_verts[i] = offset_coords_3d

                        inradius[i] = dt.max()
                        # Undo scaling on other parameters
                        area[i] /= f*f
                        perimeter[i] /= f
                        equiv_diameter[i] /= f
                        inradius[i] /= f
                    else:
                        area[i] = 0
                        perimeter[i] = 0
                        equiv_diameter[i] = 0

    if kwargs['set_dependent'] is True:
        geometry['throat.area'] = area
        geometry['throat.perimeter'] = perimeter
        geometry['throat.centroid'] = centroid
        geometry['throat.diameter'] = equiv_diameter
        geometry['throat.indiameter'] = inradius*2
        geometry['throat.incentre'] = incentre

    return eroded_verts
Example #6
0
    def _throat_props(self):
        r"""
        Use the Voronoi vertices and perform image analysis to obtain throat
        properties
        """
        offset = self.network.fiber_rad
        Nt = self.num_throats()
        centroid = sp.zeros([Nt, 3])
        incenter = sp.zeros([Nt, 3])
        area = sp.zeros(Nt)
        perimeter = sp.zeros(Nt)
        inradius = sp.zeros(Nt)
        equiv_diameter = sp.zeros(Nt)
        eroded_verts = sp.ndarray(Nt, dtype=object)

        res = 200
        vertices = self['throat.vertices']
        normals = self['throat.normal']
        z_axis = [0, 0, 1]

        for i in self.throats('delaunay'):
            logger.info("Processing throat " + str(i + 1) + " of " + str(Nt))
            # For boundaries some facets will already be aligned with the axis
            # if this is the case a rotation is unnecessary
            angle = tr.angle_between_vectors(normals[i], z_axis)
            if angle == 0.0 or angle == np.pi:
                # We are already aligned
                rotate_facet = False
                facet = vertices[i]
            else:
                rotate_facet = True
                M = tr.rotation_matrix(
                    tr.angle_between_vectors(normals[i], z_axis),
                    tr.vector_product(normals[i], z_axis))
                facet = np.dot(vertices[i], M[:3, :3].T)
            x = facet[:, 0]
            y = facet[:, 1]
            z = facet[:, 2]
            # Get points in 2d for image analysis
            pts = np.column_stack((x, y))
            # Translate points so min sits at the origin
            translation = [pts[:, 0].min(), pts[:, 1].min()]
            pts -= translation
            order = np.int(math.ceil(-np.log10(np.max(pts))))
            # Normalise and scale the points so that largest span equals the
            # resolution to save on memory and create clear image
            max_factor = np.max([pts[:, 0].max(), pts[:, 1].max()])
            f = res / max_factor
            # Scale the offset and define a structuring element with radius
            r = f * offset
            # Only proceed if r is less than half the span of the image"
            if r <= res / 2:
                pts *= f
                minp1 = pts[:, 0].min()
                minp2 = pts[:, 1].min()
                maxp1 = pts[:, 0].max()
                maxp2 = pts[:, 1].max()
                img = np.zeros([
                    np.int(math.ceil(maxp1 - minp1) + 1),
                    np.int(math.ceil(maxp2 - minp2) + 1)
                ])
                int_pts = np.around(pts.astype(float), 0).astype(int)
                for pt in int_pts:
                    img[pt[0]][pt[1]] = 1
                # Pad with zeros all the way around the edges
                img_pad = np.zeros(
                    [np.shape(img)[0] + 2,
                     np.shape(img)[1] + 2])
                img_pad[1:np.shape(img)[0] + 1, 1:np.shape(img)[1] + 1] = img
                # All points should lie on this plane but could be some
                # rounding errors so use the order parameter
                z_plane = sp.unique(np.around(z.astype(float), order + 1))
                if len(z_plane) > 1:
                    logger.error('Throat ' + str(i) + ' Rotation Failure')
                    temp_arr = np.ones(1)
                    temp_arr.fill(np.mean(z_plane))
                    z_plane = temp_arr
                "Fill in the convex hull polygon"
                convhullimg = convex_hull_image(img_pad)
                # Perform a Distance Transform and black out points less than r
                # to create binary erosion. This is faster than performing an
                # erosion and dt can also be used later to find incircle
                eroded = ndimage.distance_transform_edt(convhullimg)
                eroded[eroded <= r] = 0
                eroded[eroded > r] = 1
                # If we are left with less than 3 non-zero points then the
                # throat is fully occluded
                if np.sum(eroded) >= 3:
                    # Do some image analysis to extract the key properties
                    cropped = eroded[1:np.shape(img)[0] + 1,
                                     1:np.shape(img)[1] + 1].astype(int)
                    regions = regionprops(cropped)
                    # Change this to cope with genuine multi-region throats
                    if len(regions) == 1:
                        for props in regions:
                            x0, y0 = props.centroid
                            equiv_diameter[i] = props.equivalent_diameter
                            area[i] = props.area
                            perimeter[i] = props.perimeter
                            coords = props.coords
                        # Undo the translation, scaling and truncation on the
                        # centroid
                        centroid2d = [x0, y0] / f
                        centroid2d += (translation)
                        centroid3d = np.concatenate((centroid2d, z_plane))
                        # Distance transform the eroded facet to find the
                        # incenter and inradius
                        dt = ndimage.distance_transform_edt(eroded)
                        temp = np.unravel_index(dt.argmax(), dt.shape)
                        inx0, iny0 = np.asarray(temp).astype(float)
                        incenter2d = [inx0, iny0]
                        # Undo the translation, scaling and truncation on the
                        # incenter
                        incenter2d /= f
                        incenter2d += (translation)
                        incenter3d = np.concatenate((incenter2d, z_plane))
                        # The offset vertices will be those in the coords that
                        # are closest to the originals
                        offset_verts = []
                        for pt in int_pts:
                            vert = np.argmin(
                                np.sum(np.square(coords - pt), axis=1))
                            if vert not in offset_verts:
                                offset_verts.append(vert)
                        # If we are left with less than 3 different vertices
                        # then the throat is fully occluded as we can't make a
                        # shape with non-zero area
                        if len(offset_verts) >= 3:
                            offset_coords = coords[offset_verts].astype(float)
                            # Undo the translation, scaling and truncation on
                            # the offset_verts
                            offset_coords /= f
                            offset_coords_3d = \
                                np.vstack((offset_coords[:, 0]+translation[0],
                                           offset_coords[:, 1]+translation[1],
                                           np.ones(len(offset_verts))*z_plane))
                            oc_3d = offset_coords_3d.T
                            # Get matrix to un-rotate the co-ordinates back to
                            # the original orientation if we rotated in the
                            # first place
                            if rotate_facet:
                                MI = tr.inverse_matrix(M)
                                # Unrotate the offset coordinates
                                incenter[i] = np.dot(incenter3d, MI[:3, :3].T)
                                centroid[i] = np.dot(centroid3d, MI[:3, :3].T)
                                eroded_verts[i] = np.dot(oc_3d, MI[:3, :3].T)
                            else:
                                incenter[i] = incenter3d
                                centroid[i] = centroid3d
                                eroded_verts[i] = oc_3d

                            inradius[i] = dt.max()
                            # Undo scaling on other parameters
                            area[i] /= f * f
                            perimeter[i] /= f
                            equiv_diameter[i] /= f
                            inradius[i] /= f
                        else:
                            area[i] = 0
                            perimeter[i] = 0
                            equiv_diameter[i] = 0

        self['throat.area'] = area
        self['throat.perimeter'] = perimeter
        self['throat.centroid'] = centroid
        self['throat.diameter'] = equiv_diameter
        self['throat.indiameter'] = inradius * 2
        self['throat.incenter'] = incenter
        self['throat.offset_vertices'] = eroded_verts