# Copyright (C) 2022 Tony Fischer
#
# This file is part of Bio Matrix Topology (BiMaTo).
#
# Bio Matrix Topology (BiMaTo) is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Bio Matrix Topology (BiMaTo) is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Bio Matrix Topology (BiMaTo). If not, see <http://www.gnu.org/licenses/>.
"""
docstring of module
"""
__author__ = "Tony Fischer (tku137)"
__copyright__ = "Copyright 2022, Tony Fischer (tku137)"
__license__ = "GPLv3"
__email__ = "tonyfischer@mailbox.org"
__status__ = "Development"
__version__ = "2022.2.1"
__credits__ = ["Tony Fischer (tku137)", "Alexander Hayn"]
import numpy as np
from skimage.morphology import ball, remove_small_objects, skeletonize_3d
from skimage.measure import label
[docs]def get_network_skeleton(binary, min_size=10):
"""
Calculates the network skeletonization of an input network binary.
Parameters
----------
binary : numpy.ndarray
Input binary segmentation of typed `bool`
min_size : int
Minimum size of objects in `px`
Returns
-------
numpy.ndarray
Skeleton output
"""
sk = skeletonize_3d(binary).astype(np.bool)
sk_refined = remove_small_objects(sk, min_size=min_size, connectivity=3)
return sk_refined
[docs]def classify_network(skeleton):
"""
Classifies each binary fibril pixel of input skeleton depending on number of neighbouring pixels.
Class 0 is fluid phase. Class 2 is a fibril with class 1 being a fibril ending. Classes higher than 2 mean that this
is a node, while denoting the number of connections outgoing.
Warning: the center pixel is always 1, so the sum of each cut out fibril environment needs to be reduced by 1
Parameters
----------
skeleton : numpy.ndarray
Input skeleton
Returns
-------
numpy.ndarray
Classification matrix
"""
fibril_coords = np.argwhere(skeleton > 0)
# filter border coordinates
x_size, y_size, z_size = skeleton.shape
if not (x_size == y_size == z_size):
raise ValueError("image dimensions are not equal")
fibril_coords = fibril_coords[np.all(fibril_coords > 0, axis=1), :]
fibril_coords = fibril_coords[np.all(fibril_coords < (x_size-1), axis=1), :]
fibril_class = np.zeros_like(skeleton, dtype=np.uint8)
for fib in fibril_coords:
x, y, z = fib
fib_cut = skeleton[x - 1:x + 2, y - 1:y + 2, z - 1:z + 2]
fibril_class[x, y, z] = np.sum(fib_cut) - 1
return fibril_class
[docs]def get_nodes(fibril_class, se_radius=1):
# node analysis
node_coords = np.argwhere(fibril_class > 2) # coordinates of 'nodes' aka fibril_class > 2
node_stack = np.zeros_like(fibril_class) # empty stack
for row in node_coords: # for each node coordinate, draw ball
x, y, z = row.astype('int')
node_indices = np.argwhere(ball(se_radius))
node_indices[:, 0] += x - se_radius
node_indices[:, 1] += y - se_radius
node_indices[:, 2] += z - se_radius
# filter edge coords
node_indices = node_indices[node_indices[:, 0] < node_stack.shape[0]]
node_indices = node_indices[node_indices[:, 1] < node_stack.shape[1]]
node_indices = node_indices[node_indices[:, 2] < node_stack.shape[2]]
# binarize node stack
node_stack[node_indices[:, 0], node_indices[:, 1], node_indices[:, 2]] = 1
return node_stack
[docs]def get_single_fibrils_labeled(skeleton, node_stack):
"""
Returns a label matrix with single fibrils. Each integer number in the label matrix represents pixels of a single,
individual fibril.
Parameters
----------
fibril_class : numpy.ndarray
Matrix with classified skeleton
Returns
-------
numpy.ndarray
Labelled matrix with single fibrils
"""
# skeleton * inverse of node stack yields single fibril pixels
single_fibrils = label(skeleton * np.logical_not(node_stack))
return single_fibrils
[docs]def get_edge_fibril_ids(fibril_labels):
edge_fibril_ids = np.unique(np.concatenate((
np.unique(fibril_labels[:2, :, :]), # front
np.unique(fibril_labels[-2:, :, :]), # back
np.unique(fibril_labels[:, :2, :]), # left
np.unique(fibril_labels[:, -2:, :]), # right
np.unique(fibril_labels[:, :, :2]), # bottom
np.unique(fibril_labels[:, :, -2:]), # top
)))
return edge_fibril_ids