mirror of
https://github.com/wassname/scikit-image.git
synced 2026-07-04 18:32:22 +08:00
152 lines
5.1 KiB
Cython
152 lines
5.1 KiB
Cython
# -*- python -*-
|
|
|
|
import numpy as numpy
|
|
cimport numpy as numpy
|
|
cimport cython
|
|
|
|
@cython.boundscheck(False)
|
|
def trace_path(numpy.ndarray[numpy.float32_t, ndim=2] costs not None,
|
|
start, ends, diagonal_steps=True):
|
|
"""Find the lowest-cost path from the start point to each given end point.
|
|
|
|
Costs are given by the input array: a move onto any given position in the
|
|
costs array adds that cost to the path. Paths may be constrained to
|
|
vertical and horizontal moves only by passing False for the diagonal_steps
|
|
parameter. Costs must be non-negative!
|
|
|
|
The array of cumulative costs from the starting point, and a list of paths
|
|
from the start to each end point are returned.
|
|
|
|
Parameters
|
|
----------
|
|
costs : ndarray
|
|
start : tuple of ints
|
|
``(x, y)`` position (i.e., ``(column, row)``) of starting position.
|
|
ends : list of tuple of ints
|
|
``[(x1, y1), (x2, y2), ...]`` List of end points.
|
|
diagonal_steps : bool
|
|
Whether to allow diagonal steps (True, by default).
|
|
|
|
Notes
|
|
-----
|
|
Paths are found by (more or less) breadth-first search outward from the
|
|
starting point: each time a lower-cost route to a given pixel is found, that
|
|
pixel is marked "active"; the neighbors of all active pixels are then
|
|
examined to see if their costs can be lowered as well. This continues until
|
|
no pixels are marked active.
|
|
|
|
"""
|
|
if costs.min() < 0:
|
|
raise ValueError("All costs must be non-negative.")
|
|
|
|
try:
|
|
a, b = start
|
|
except:
|
|
raise ValueError("The start point must be an (x, y) pair")
|
|
|
|
if not (0 <= a < costs.shape[0] and 0 <= b < costs.shape[1]):
|
|
raise ValueError("The start point must fall within the array")
|
|
|
|
for end in ends:
|
|
try:
|
|
a, b = end
|
|
except:
|
|
raise ValueError("All end points must be (x, y) pairs")
|
|
if not (0 <= a < costs.shape[0] and 0 <= b < costs.shape[1]):
|
|
raise ValueError("The end points must fall within the array")
|
|
|
|
cdef numpy.ndarray[numpy.float32_t, ndim=2] cumulative_costs = \
|
|
numpy.empty_like(costs)
|
|
|
|
cumulative_costs.fill(numpy.inf)
|
|
cumulative_costs[start] = 0
|
|
costs_shape = (costs.shape[0], costs.shape[1])
|
|
cdef numpy.ndarray[numpy.uint8_t, ndim=2] active_nodes = \
|
|
numpy.zeros(costs_shape, dtype=numpy.uint8)
|
|
|
|
active_nodes[start] = 1
|
|
cdef numpy.ndarray[numpy.uint8_t, ndim=2] parent_nodes = \
|
|
numpy.empty(costs_shape, dtype=numpy.uint8)
|
|
|
|
parent_nodes.fill(255)
|
|
cdef numpy.ndarray[numpy.int8_t, ndim=2] offsets
|
|
if diagonal_steps:
|
|
offsets = numpy.array([[-1, -1],
|
|
[-1, 0],
|
|
[-1, 1],
|
|
[ 0, -1],
|
|
[ 0, 1],
|
|
[ 1, -1],
|
|
[ 1, 0],
|
|
[ 1, 1]], dtype=numpy.int8)
|
|
else:
|
|
offsets = numpy.array([[-1, 0],
|
|
[0, -1],
|
|
[0, 1],
|
|
[1, 0]], dtype=numpy.int8)
|
|
|
|
cdef Py_ssize_t x, y, ox, oy, xo, yo, i
|
|
cdef Py_ssize_t a_xmax, a_xmin, a_ymax, a_ymin, tmp_xmax, \
|
|
tmp_xmin, tmp_ymax, tmp_ymin
|
|
cdef unsigned int xmax, ymax, active, num_steps
|
|
xmax = costs.shape[0] - 1
|
|
ymax = costs.shape[1] - 1
|
|
num_steps = 0
|
|
tmp_xmax = tmp_xmin = start[0]
|
|
tmp_ymax = tmp_ymin = start[1]
|
|
cdef float current_cost, current_cumulative_cost, cumulative_cost, new_cost
|
|
|
|
while True:
|
|
active = 0
|
|
# iterate over array
|
|
for x in range(0, xmax + 1):
|
|
for y in range(0, ymax + 1):
|
|
if active_nodes[x, y]:
|
|
active_nodes[x, y] = 0
|
|
active = 1
|
|
current_cumulative_cost = cumulative_costs[x, y]
|
|
|
|
# iterate over offsets
|
|
for i in range(8):
|
|
ox = offsets[i, 0]
|
|
oy = offsets[i, 1]
|
|
xo = x + ox
|
|
yo = y + oy
|
|
if xo < 0 or xo > xmax or yo < 0 or yo > ymax:
|
|
continue
|
|
|
|
current_cost = costs[xo, yo]
|
|
new_cost = current_cost + current_cumulative_cost
|
|
|
|
# if a cheaper path to a given point is found,
|
|
# activate that point
|
|
if cumulative_costs[xo, yo] > new_cost:
|
|
cumulative_costs[xo, yo] = new_cost
|
|
parent_nodes[xo, yo] = i
|
|
active_nodes[xo, yo] = 1
|
|
|
|
if not active:
|
|
break
|
|
|
|
cdef unsigned int startx, starty
|
|
startx = start[0]
|
|
starty = start[1]
|
|
return_paths = []
|
|
# Trace the paths from the endpoints to the start
|
|
for end in ends:
|
|
path = None
|
|
x = end[0]
|
|
y = end[1]
|
|
if cumulative_costs[x, y] != numpy.inf:
|
|
path = [(x, y)]
|
|
while not (x == startx and y == starty):
|
|
i = parent_nodes[x, y]
|
|
ox = offsets[i, 0]
|
|
oy = offsets[i, 1]
|
|
x -= ox
|
|
y -= oy
|
|
path.append((x, y))
|
|
path.reverse()
|
|
return_paths.append(path)
|
|
return cumulative_costs, return_paths
|