File size: 7,194 Bytes
69591a9 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 |
# 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
|