Files
scikit-image/scikits/image/graph/trace_path.pyx
T
2009-11-22 13:27:15 +02:00

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