CofeehousePy/deps/scikit-image/skimage/graph/spath.py

82 lines
3.5 KiB
Python

import numpy as np
from . import _spath
def shortest_path(arr, reach=1, axis=-1, output_indexlist=False):
"""Find the shortest path through an n-d array from one side to another.
Parameters
----------
arr : ndarray of float64
reach : int, optional
By default (``reach = 1``), the shortest path can only move
one row up or down for every step it moves forward (i.e.,
the path gradient is limited to 1). `reach` defines the
number of elements that can be skipped along each non-axis
dimension at each step.
axis : int, optional
The axis along which the path must always move forward (default -1)
output_indexlist : bool, optional
See return value `p` for explanation.
Returns
-------
p : iterable of int
For each step along `axis`, the coordinate of the shortest path.
If `output_indexlist` is True, then the path is returned as a list of
n-d tuples that index into `arr`. If False, then the path is returned
as an array listing the coordinates of the path along the non-axis
dimensions for each step along the axis dimension. That is,
`p.shape == (arr.shape[axis], arr.ndim-1)` except that p is squeezed
before returning so if `arr.ndim == 2`, then
`p.shape == (arr.shape[axis],)`
cost : float
Cost of path. This is the absolute sum of all the
differences along the path.
"""
# First: calculate the valid moves from any given position. Basically,
# always move +1 along the given axis, and then can move anywhere within
# a grid defined by the reach.
if axis < 0:
axis += arr.ndim
offset_ind_shape = (2 * reach + 1,) * (arr.ndim - 1)
offset_indices = np.indices(offset_ind_shape) - reach
offset_indices = np.insert(offset_indices, axis,
np.ones(offset_ind_shape), axis=0)
offset_size = np.multiply.reduce(offset_ind_shape)
offsets = np.reshape(offset_indices, (arr.ndim, offset_size), order='F').T
# Valid starting positions are anywhere on the hyperplane defined by
# position 0 on the given axis. Ending positions are anywhere on the
# hyperplane at position -1 along the same.
non_axis_shape = arr.shape[:axis] + arr.shape[axis + 1:]
non_axis_indices = np.indices(non_axis_shape)
non_axis_size = np.multiply.reduce(non_axis_shape)
start_indices = np.insert(non_axis_indices, axis,
np.zeros(non_axis_shape), axis=0)
starts = np.reshape(start_indices, (arr.ndim, non_axis_size), order='F').T
end_indices = np.insert(non_axis_indices, axis,
np.full(non_axis_shape, -1,
dtype=non_axis_indices.dtype), axis=0)
ends = np.reshape(end_indices, (arr.ndim, non_axis_size), order='F').T
# Find the minimum-cost path to one of the end-points
m = _spath.MCP_Diff(arr, offsets=offsets)
costs, traceback = m.find_costs(starts, ends, find_all_ends=False)
# Figure out which end-point was found
for end in ends:
cost = costs[tuple(end)]
if cost != np.inf:
break
traceback = m.traceback(end)
if not output_indexlist:
traceback = np.array(traceback)
traceback = np.concatenate([traceback[:, :axis],
traceback[:, axis + 1:]], axis=1)
traceback = np.squeeze(traceback)
return traceback, cost