ClementP's picture
Upload 55 files
69591a9 verified
# Functions to generate kernels of curve intersection
import numpy as np
import cv2
import itertools
from numba import njit, int64
from numba.typed import List
from numba.types import Tuple
# Define the element type: a tuple of two int64
tuple_type = Tuple((int64, int64))
def find_neighbours(fibers_map, point):
"""
Find the next point in the fiber starting from the given point.
The function returns None if the point is not in the fiber.
"""
# Get the fiber id
neighbors = []
h, w = fibers_map.shape
for i in range(-1, 2):
for j in range(-1, 2):
# Skip the center point
if i == 0 and j == 0:
continue
# Get the next point
nextpoint = (point[0] + i, point[1] + j)
# Check if the next point is in the image
if (
nextpoint[0] < 0
or nextpoint[0] >= h
or nextpoint[1] < 0
or nextpoint[1] >= w
):
continue
# Check if the next point is in the fiber
if fibers_map[nextpoint]:
neighbors.append(nextpoint)
return neighbors
def compute_points_angle(fibers_map, points, steps=25):
"""
For each endpoint, follow the fiber for a given number of steps and estimate the tangent line by
fitting a line to the visited points. The angle of the line is returned.
"""
points_angle = np.zeros((len(points),), dtype=np.float32)
for i, point in enumerate(points):
# Find the fiber it belongs to
# Lets navigate along the fiber starting from the point during steps pixels.
# We compute the angles at each step and return the mean angle.
visited = trace_from_point(
fibers_map > 0, (point[0], point[1]), max_length=steps
)
visited = np.array(visited)
vx, vy, x, y = cv2.fitLine(visited[:, ::-1], cv2.DIST_L2, 0, 0.01, 0.01)
# Compute the angle of the line
points_angle[i] = np.arctan(vy / vx)
return points_angle
def generate_nonadjacent_combination(input_list, take_n):
"""
It generates combinations of m taken n at a time where there is no adjacent n.
INPUT:
input_list = (iterable) List of elements you want to extract the combination
take_n = (integer) Number of elements that you are going to take at a time in
each combination
OUTPUT:
all_comb = (np.array) with all the combinations
"""
all_comb = []
for comb in itertools.combinations(input_list, take_n):
comb = np.array(comb)
d = np.diff(comb)
if len(d[d == 1]) == 0 and comb[-1] - comb[0] != 7:
all_comb.append(comb)
return all_comb
def populate_intersection_kernel(combinations):
"""
Maps the numbers from 0-7 into the 8 pixels surrounding the center pixel in
a 9 x 9 matrix clockwisely i.e. up_pixel = 0, right_pixel = 2, etc. And
generates a kernel that represents a line intersection, where the center
pixel is occupied and 3 or 4 pixels of the border are ocuppied too.
INPUT:
combinations = (np.array) matrix where every row is a vector of combinations
OUTPUT:
kernels = (List) list of 9 x 9 kernels/masks. each element is a mask.
"""
n = len(combinations[0])
template = np.array(([-1, -1, -1], [-1, 1, -1], [-1, -1, -1]), dtype="int")
match = [(0, 1), (0, 2), (1, 2), (2, 2), (2, 1), (2, 0), (1, 0), (0, 0)]
kernels = []
for n in combinations:
tmp = np.copy(template)
for m in n:
tmp[match[m][0], match[m][1]] = 1
kernels.append(tmp)
return kernels
def give_intersection_kernels():
"""
Generates all the intersection kernels in a 9x9 matrix.
INPUT:
None
OUTPUT:
kernels = (List) list of 9 x 9 kernels/masks. each element is a mask.
"""
input_list = np.arange(8)
taken_n = [4, 3]
kernels = []
for taken in taken_n:
comb = generate_nonadjacent_combination(input_list, taken)
tmp_ker = populate_intersection_kernel(comb)
kernels.extend(tmp_ker)
return kernels
def find_line_intersection(input_image, show=0):
"""
Applies morphologyEx with parameter HitsMiss to look for all the curve
intersection kernels generated with give_intersection_kernels() function.
INPUT:
input_image = (np.array dtype=np.uint8) binarized m x n image matrix
OUTPUT:
output_image = (np.array dtype=np.uint8) image where the nonzero pixels
are the line intersection.
"""
input_image = input_image.astype(np.uint8)
kernel = np.array(give_intersection_kernels())
output_image = np.zeros(input_image.shape)
for i in np.arange(len(kernel)):
out = cv2.morphologyEx(
input_image,
cv2.MORPH_HITMISS,
kernel[i, :, :],
borderValue=0,
borderType=cv2.BORDER_CONSTANT,
)
output_image = output_image + out
return output_image
@njit
def get_neighbors_8(y, x, shape):
neighbors = List.empty_list(tuple_type)
for dy in range(-1, 2):
for dx in range(-1, 2):
if dy == 0 and dx == 0:
continue
ny, nx = y + dy, x + dx
if 0 <= ny < shape[0] and 0 <= nx < shape[1]:
neighbors.append((ny, nx))
return neighbors
@njit
def find_endpoints(skel):
endpoints = List.empty_list(tuple_type)
for y in range(skel.shape[0]):
for x in range(skel.shape[1]):
if skel[y, x] == 1:
count = 0
neighbors = get_neighbors_8(y, x, skel.shape)
for ny, nx in neighbors:
if skel[ny, nx] == 1:
count += 1
if count == 1:
endpoints.append((y, x))
return endpoints
@njit
def trace_skeleton(skel):
endpoints = find_endpoints(skel)
if len(endpoints) < 1:
return List.empty_list(tuple_type) # Return empty list with proper type
return trace_from_point(skel, endpoints[0], max_length=skel.sum())
@njit
def trace_from_point(skel, point, max_length=25):
visited = np.zeros_like(skel, dtype=np.uint8)
path = List.empty_list(tuple_type)
# Check if the starting point is on the skeleton
y, x = point
if y < 0 or y >= skel.shape[0] or x < 0 or x >= skel.shape[1] or skel[y, x] != 1:
return path
stack = List.empty_list(tuple_type)
stack.append(point)
while len(stack) > 0 and len(path) < max_length:
y, x = stack.pop()
if visited[y, x]:
continue
visited[y, x] = 1
path.append((y, x))
neighbors = get_neighbors_8(y, x, skel.shape)
for ny, nx in neighbors:
if skel[ny, nx] == 1 and not visited[ny, nx]:
stack.append((ny, nx))
return path