Source code for pyfibre.model.tools.network_extraction

import logging

import networkx as nx
import numpy as np

from scipy.ndimage import distance_transform_edt
from scipy.ndimage.filters import gaussian_filter

from skimage.morphology import remove_small_objects
from skimage.transform import rescale

from pyfibre.model.objects.fibre_network import FibreNetwork
from pyfibre.model.tools.filters import tubeness, hysteresis
from pyfibre.utilities import clear_border

from .fire_algorithm import FIREAlgorithm
from .fibre_utilities import remove_redundant_nodes

logger = logging.getLogger(__name__)


[docs]def build_network(image, scale=1, alpha=0.5, sigma=0.5, nuc_thresh=2, nuc_radius=11, lmp_thresh=0.15, angle_thresh=70, r_thresh=7): """ Uses the FibeR Extraction algorithm to extract a fibre network from provided image Parameters ---------- image: array_like, (float); shape=(nx, ny) Image to perform FIRE upon scale: float Scaling factor to apply to image before performing algorithm alpha: float Alpha metric to use in hysteresis threshold algorithm sigma: float Gaussian standard deviation to filter distance image nuc_thresh: float Minimum distance pixel threshold to be classed as nucleation point nuc_radius: float Minimum pixel radii between nucleation points lmp_thresh: float Minimum distance pixel threshold to be classed as lmp point angle_thresh: float Maximum angular deviation of new lmp from fibre trajectory r_thresh: float Maximum length of edges between nodes Returns ------- network: nx.Graph Networkx graph object representing fibre network """ # Prepare input image to gain distance matrix of foreground # from background" image_scale = rescale(image, scale, multichannel=False, mode='constant', anti_aliasing=None) sigma *= scale # Apply tubeness transform to enhance image fibres" image_TB = tubeness(image_scale) threshold = hysteresis(image_TB, alpha=alpha) cleaned = remove_small_objects(threshold, min_size=int(64*scale**2)) distance = distance_transform_edt(cleaned) smoothed = gaussian_filter(distance, sigma=sigma) cleared = clear_border(smoothed) # Set distance thresholds for fibre iterator based on scale factor" nuc_thresh = np.min( [nuc_thresh * scale**2, 1E-1 * scale**2 * cleared.max()]) lmp_thresh = np.min( [lmp_thresh * scale**2, 1E-1 * scale**2 * cleared[np.nonzero(cleared)].mean()]) r_thresh = int(r_thresh * scale) nuc_radius = int(nuc_radius * scale) logger.debug( "Maximum distance = {}".format(cleared.max())) logger.debug( f"Mean distance = {cleared[np.nonzero(cleared)].mean()}") logger.debug( "Using thresholds:\n" " nuc = {} pix\n " "lmp = {} pix\n " "angle = {} deg\n " "edge = {} pix".format( nuc_thresh, lmp_thresh, angle_thresh, r_thresh)) fibre_network = FIREAlgorithm( nuc_thresh=nuc_thresh, lmp_thresh=lmp_thresh, angle_thresh=angle_thresh, r_thresh=r_thresh, nuc_radius=nuc_radius) network = fibre_network.create_network(cleared) # Rescale all node coordinates and edge radii for node in network.nodes(): network.nodes[node]['xy'] = np.array( network.nodes[node]['xy'] // scale, dtype=int) for edge in network.edges(): network.edges[edge]['r'] *= 1. / scale network = clean_network(network) return network
[docs]def clean_network(network, r_thresh=2): """Cleans network by removing isolated nodes, combining any two nodes that are located too close together into one, and removing any components that are too small to be considered fibres""" logger.debug("Remove all isolated nodes") network.remove_nodes_from(list(nx.isolates(network))) network = nx.convert_node_labels_to_integers(network) logger.debug("Checking for redundant nodes") network = remove_redundant_nodes(network, r_thresh) # Remove graph components containing either only 1 node with 1 edge or # 1 node with more than 1 edge" node_remove_list = [] for i, component in enumerate(nx.connected_components(network)): subgraph = network.subgraph(component) edge_count = np.array( [subgraph.degree[node] for node in subgraph], dtype=int) graph_check = np.sum(edge_count > 1) > 1 graph_check *= np.sum(edge_count == 1) > 1 if not graph_check: node_remove_list += list(subgraph.nodes()) network.remove_nodes_from(node_remove_list) network = nx.convert_node_labels_to_integers(network) return network
[docs]def fibre_network_assignment(network): """Extract sub-networks, simplified sub-networks and Fibre objects from a networkx Graph generated by modified FIRE algorithm""" logger.debug("Extracting and simplifying fibre networks from graph") fibre_networks = [] for i, component in enumerate(nx.connected_components(network)): subgraph = network.subgraph(component) fibre_network = FibreNetwork(graph=subgraph) fibre_network.fibres = fibre_network.generate_fibres() if len(fibre_network.fibres) > 0: fibre_network.red_graph = fibre_network.generate_red_graph() fibre_networks.append(fibre_network) # Sort segments ranked by graph size fibre_networks = sorted( fibre_networks, key=lambda network: network.number_of_nodes) return fibre_networks