Repository: scikit-image/scikit-image

Issue:
random_walker segmentation can create incorrect laplacian matrix
## Description

Reported [on image.sc](https://forum.image.sc/t/possible-bug-in-random-walker-markers-with-isolated-seeds/44451), confirmed locally.

## Way to reproduce
```python
import numpy as np
from skimage.segmentation import random_walker

data = np.asarray([[12823, 12787, 12710], [12883, 13425, 12067], [11934, 11929, 12309]])
markers = np.asarray([[0, -1, 2], [0, 0, -1], [1, 0, 0]])
labels = random_walker(data, markers, beta=10)
```

results in the following traceback:

```pytb
Traceback (most recent call last):
  File "rw.py", line 6, in <module>
    labels = random_walker(data, markers, beta=10)
  File "/Users/jni/projects/scikit-image/skimage/segmentation/random_walker_segmentation.py", line 483, in random_walker
    lap_sparse, B = _build_linear_system(data, spacing, labels, nlabels, mask,
  File "/Users/jni/projects/scikit-image/skimage/segmentation/random_walker_segmentation.py", line 157, in _build_linear_system
    rows = lap_sparse[unlabeled_indices, :]
  File "/Users/jni/conda/envs/all/lib/python3.8/site-packages/scipy/sparse/_index.py", line 33, in __getitem__
    row, col = self._validate_indices(key)
  File "/Users/jni/conda/envs/all/lib/python3.8/site-packages/scipy/sparse/_index.py", line 137, in _validate_indices
    row = self._asindices(row, M)
  File "/Users/jni/conda/envs/all/lib/python3.8/site-packages/scipy/sparse/_index.py", line 169, in _asindices
    raise IndexError('index (%d) out of range' % max_indx)
IndexError: index (6) out of range
```

## Version information
```
# Paste the output of the following python commands
from __future__ import print_function
import sys; print(sys.version)
import platform; print(platform.platform())
import skimage; print("scikit-image version: {}".format(skimage.__version__))
import numpy; print("numpy version: {}".format(numpy.__version__))
```

```python
3.8.2 (default, Mar 26 2020, 10:43:30)
[Clang 4.0.1 (tags/RELEASE_401/final)]
macOS-10.15.6-x86_64-i386-64bit
scikit-image version: 0.18.0.dev0
numpy version: 1.18.4
```

This is where it all seems to go wrong:

https://github.com/scikit-image/scikit-image/blob/06f6a129697febe510e11fc1bca58b5311d9d861/skimage/segmentation/random_walker_segmentation.py#L127-L133

For this example, `pixel_nb` is 6, even though there are 7 unmasked pixels.

CC @emmanuelle @JDWarner 


Discussion:
This issue has been mentioned on **Image.sc Forum**. There might be relevant details there:

https://forum.image.sc/t/possible-bug-in-random-walker-markers-with-isolated-seeds/44451/2

It seems that `skimage.segmentation.random_walker_segmentation._preprocess` doesn't detect the `marker == 2` as isolated :confused: 
```python
>>> markers = np.asarray([[0, -1, 2], [0, 0, -1], [1, 0, 0]])
>>> from skimage.segmentation.random_walker_segmentation import _preprocess
>>> labels, nlabels, mask, inds_isolated_seeds, isolated_values = _preprocess(markers)
>>> inds_isolated_seeds
(array([], dtype=int64), array([], dtype=int64))
```
@jni I think `pixel_nb = edges.shape[1]` is wrong. Denoting N be the total number of image pixels, the size of laplacian matrix should be NxN. In this code, N should be `N=l_x * l_y * l_z`. Thus `pixel_nb ` should be `pixel_nb = l_x * l_y * l_z`.  Note that the number of edges is `n_x * n_y * (nz - 1) + n_x * (n_y - 1) * nz + (n_x - 1) * n_y * nz`.  So `edge.shape[1] !=N`

Resolving patch:
diff --git a/skimage/segmentation/random_walker_segmentation.py b/skimage/segmentation/random_walker_segmentation.py
--- a/skimage/segmentation/random_walker_segmentation.py
+++ b/skimage/segmentation/random_walker_segmentation.py
@@ -119,7 +119,7 @@ def _build_laplacian(data, spacing, mask, beta, multichannel):
         edges = inv_idx.reshape(edges.shape)
 
     # Build the sparse linear system
-    pixel_nb = edges.shape[1]
+    pixel_nb = l_x * l_y * l_z
     i_indices = edges.ravel()
     j_indices = edges[::-1].ravel()
     data = np.hstack((weights, weights))


Test patch:
diff --git a/skimage/segmentation/tests/test_random_walker.py b/skimage/segmentation/tests/test_random_walker.py
--- a/skimage/segmentation/tests/test_random_walker.py
+++ b/skimage/segmentation/tests/test_random_walker.py
@@ -183,6 +183,24 @@ def test_2d_inactive():
     return data, labels
 
 
+def test_2d_laplacian_size():
+    # test case from: https://github.com/scikit-image/scikit-image/issues/5034
+    # The markers here were modified from the ones in the original issue to
+    # avoid a singular matrix, but still reproduce the issue.
+    data = np.asarray([[12823, 12787, 12710],
+                       [12883, 13425, 12067],
+                       [11934, 11929, 12309]])
+    markers = np.asarray([[0, -1, 2],
+                          [0, -1, 0],
+                          [1, 0, -1]])
+    expected_labels = np.asarray([[1, -1, 2],
+                                  [1, -1, 2],
+                                  [1, 1, -1]])
+    labels = random_walker(data, markers, beta=10)
+    np.testing.assert_array_equal(labels, expected_labels)
+    return data, labels
+
+
 def test_3d():
     n = 30
     lx, ly, lz = n, n, n