Repository: scikit-image/scikit-image

Issue:
<Watershed not using local minima as markers by default, contradicting documentation.>
### Description:

When running the watershed function without markers (markers = None), the function does not use local minima as markers contrary to what the documentation says.

The result with markers = None is clearly different than with markers = local_minima, while it is supposed to be the same.

With markers=None, the number of returned regions is way larger than the number of local minima, at least on several examples.


### Way to reproduce:

``` python

import skimage
from skimage.data import camera
from skimage.morphology import local_minima, label
from skimage.segmentation import watershed

print('\nSkimage version:', skimage.__version__,'\n')

im = camera()
locMin = label(local_minima(im)) # local minima

# Markers = None, supposedly local minima taken as markers
imWs1 = watershed(im, watershed_line = True)
print('Watershed with markers=None (supposedly local minima used as markers):')
print('There are '+str(imWs1.max())+ ' basins, and '+str(locMin.max())+' local minima.\n')


# Markers explicitely chosen as local minima
imWs2 = watershed(im, markers = locMin, watershed_line = True)
print('Watershed with local minima as markers:')
print('There are '+str(imWs2.max())+ ' basins, and '+str(locMin.max())+' local minima.')

```

### Version information:

```Shell
3.9.16 (main, May 15 2023, 23:46:34) 
[GCC 11.2.0]
Linux-5.4.0-150-generic-x86_64-with-glibc2.27
scikit-image version: 0.21.0
numpy version: 1.24.3
```



Discussion:
I just figured out that setting connectivity = 2 

`imWs1 = watershed(im, watershed_line = True, connectivity=2)`

produces the expected result, ie the number of regions in the segmentation is equal to the number of local minima.
Therefore it seems the algorithm does use the local minima as markers when connectivity= 2  and markers=None.

But I have no idea of what is used as markers when connectivity=1 and markers=None, which is a serious problem.
What is clear is that

`imWs1 = watershed(im, watershed_line = True)`

does not yield the same result as 

`imWs2 = watershed(im, markers = locMin, watershed_line = True, connectivity  = 1)`

where locMin are the local minima. Again, this contradicts the documentation and leaves many doubts on what the function does when markers=None.
Hi @samyblusseau,

Thank you for reporting. From looking at the source code, it turns out that the default markers are somehow computed as the local minima, but *not* just through `ski.morphology.label(ski.morphology.local_minima(image))`. Instead:

https://github.com/scikit-image/scikit-image/blob/2ac3e141e8d2e31aa0ec10afc3a935396b0618fc/skimage/segmentation/_watershed.py#L65-L68

Running this code with your example (`image = ski.data.camera()`, `connectivity = 1`, `mask = np.ones(image.shape, bool)`) yields 22963 markers (and, hence, basins, as you reported).

Now, if you just change `connectivity = 2` in the above, you end up with 13563 markers / basins (which, as you reported, is also the result obtained from `ski.morphology.label(ski.morphology.local_minima(image))`).

We should definitely improve the documentation, indeed...

@ana42742 fyi :eyes: 
Hi @mkcor , thanks for your reply!
Indeed I noticed that setting connectivity = 2 makes the function behave as expected.

Still, I'm a bit worried about what the function does exactly with connectivity = 1 and markers = None, as the returned segmentation contains regions that do not seem to correspond to any local minimum, even computed in connectivity 1 (4 neighbours per pixel).

I cannot find the exact used algorithm in the code as it calls `_watershed_cy.watershed_raveled` which I don't know where to find.
I cannot either reproduce the result of the markers = None and connectivity = 1 with any combination of parameters where markers are given explicitly.

In particular, if it is just a matter of connectivity in the computation of local minima and/or in the watershed algorithm, I don't get why 

`imWs1 = watershed(im, watershed_line = True)`

returns a different result than 

```
locMin = label(local_minima(im, connectivity=1))
imWs2 = watershed(im, markers = locMin, watershed_line = True, connectivity=1)
```
(or any other combination of values in {1,2} for connectivities in local_minima and waterhed).

@ana42742 @lagru  , in case you have insights on this.

Thanks again.

Hi @samyblusseau,

It is a matter of computing the local minima differently, as I wrote in my initial reply https://github.com/scikit-image/scikit-image/issues/7117#issuecomment-1712530226. By running the code I highlighted there, you can break down this computation as carried out in the `watershed` function. For example, just the `label` function from `scipy.ndimage` behaves differently from the `label` function from `skimage.morphology`!

Otherwise, you can find `_watershed_cy.watershed_raveled` in the codebase:
https://github.com/scikit-image/scikit-image/blob/2ac3e141e8d2e31aa0ec10afc3a935396b0618fc/skimage/segmentation/_watershed_cy.pyx#L70

But, again, the discrepancy arises at
https://github.com/scikit-image/scikit-image/blob/2ac3e141e8d2e31aa0ec10afc3a935396b0618fc/skimage/segmentation/_watershed.py#L199

which happens way before hitting
https://github.com/scikit-image/scikit-image/blob/2ac3e141e8d2e31aa0ec10afc3a935396b0618fc/skimage/segmentation/_watershed.py#L215

Best,
Marianne
Thanks again @mkcor , sorry I did not understand earlier.
It seems the discrepancy is precisely in the difference in `label` functions between  `skimage.morphology` and `scipy.ndimage`, as you point out.

I hope I'll have a chance to have a look at the latter some day.

Best,
Samy


Resolving patch:
diff --git a/skimage/segmentation/_watershed.py b/skimage/segmentation/_watershed.py
--- a/skimage/segmentation/_watershed.py
+++ b/skimage/segmentation/_watershed.py
@@ -92,17 +92,21 @@ def watershed(
     compactness=0,
     watershed_line=False,
 ):
-    """Find watershed basins in `image` flooded from given `markers`.
+    """Find watershed basins in an image flooded from given markers.
 
     Parameters
     ----------
     image : (M, N[, ...]) ndarray
         Data array where the lowest value points are labeled first.
     markers : int, or (M, N[, ...]) ndarray of int, optional
-        The desired number of markers, or an array marking the basins with the
+        The desired number of basins, or an array marking the basins with the
         values to be assigned in the label matrix. Zero means not a marker. If
-        ``None`` (no markers given), the local minima of the image are used as
-        markers.
+        None, the (default) markers are determined as the local minima of
+        `image`. Specifically, the computation is equivalent to applying
+        :func:`skimage.morphology.local_minima` onto `image`, followed by
+        :func:`skimage.measure.label` onto the result (with the same given
+        `connectivity`). Generally speaking, users are encouraged to pass
+        markers explicitly.
     connectivity : ndarray, optional
         An array with the same number of dimensions as `image` whose
         non-zero elements indicate neighbors for connection.
@@ -111,12 +115,13 @@ def watershed(
     offset : array_like of shape image.ndim, optional
         offset of the connectivity (one offset per dimension)
     mask : (M, N[, ...]) ndarray of bools or 0's and 1's, optional
-        Only points where mask == True will be labeled.
+        Array of same shape as `image`. Only points at which mask == True
+        will be labeled.
     compactness : float, optional
-        Use compact watershed [3]_ with given compactness parameter.
+        Use compact watershed [1]_ with given compactness parameter.
         Higher values result in more regularly-shaped watershed basins.
     watershed_line : bool, optional
-        If watershed_line is True, a one-pixel wide line separates the regions
+        If True, a one-pixel wide line separates the regions
         obtained by the watershed algorithm. The line has the label 0.
         Note that the method used for adding this line expects that
         marker regions are not adjacent; the watershed line may not catch
@@ -125,27 +130,24 @@ def watershed(
     Returns
     -------
     out : ndarray
-        A labeled matrix of the same type and shape as markers
+        A labeled matrix of the same type and shape as `markers`.
 
     See Also
     --------
-    skimage.segmentation.random_walker : random walker segmentation
+    skimage.segmentation.random_walker
         A segmentation algorithm based on anisotropic diffusion, usually
         slower than the watershed but with good results on noisy data and
         boundaries with holes.
 
     Notes
     -----
-    This function implements a watershed algorithm [1]_ [2]_ that apportions
+    This function implements a watershed algorithm [2]_ [3]_ that apportions
     pixels into marked basins. The algorithm uses a priority queue to hold
     the pixels with the metric for the priority queue being pixel value, then
-    the time of entry into the queue - this settles ties in favor of the
+    the time of entry into the queue -- this settles ties in favor of the
     closest marker.
 
-    Some ideas taken from
-    Soille, "Automated Basin Delineation from Digital Elevation Models Using
-    Mathematical Morphology", Signal Processing 20 (1990) 171-182
-
+    Some ideas are taken from [4]_.
     The most important insight in the paper is that entry time onto the queue
     solves two problems: a pixel should be assigned to the neighbor with the
     largest gradient or, if there is no gradient, pixels on a plateau should
@@ -161,14 +163,19 @@ def watershed(
 
     References
     ----------
-    .. [1] https://en.wikipedia.org/wiki/Watershed_%28image_processing%29
+    .. [1] P. Neubert and P. Protzel, "Compact Watershed and Preemptive SLIC:
+           On Improving Trade-offs of Superpixel Segmentation Algorithms,"
+           2014 22nd International Conference on Pattern Recognition,
+           Stockholm, Sweden, 2014, pp. 996-1001, :DOI:`10.1109/ICPR.2014.181`
+           https://www.tu-chemnitz.de/etit/proaut/publications/cws_pSLIC_ICPR.pdf
 
-    .. [2] http://cmm.ensmp.fr/~beucher/wtshed.html
+    .. [2] https://en.wikipedia.org/wiki/Watershed_%28image_processing%29
 
-    .. [3] Peer Neubert & Peter Protzel (2014). Compact Watershed and
-           Preemptive SLIC: On Improving Trade-offs of Superpixel Segmentation
-           Algorithms. ICPR 2014, pp 996-1001. :DOI:`10.1109/ICPR.2014.181`
-           https://www.tu-chemnitz.de/etit/proaut/publications/cws_pSLIC_ICPR.pdf
+    .. [3] http://cmm.ensmp.fr/~beucher/wtshed.html
+
+    .. [4] P. J. Soille and M. M. Ansoult, "Automated basin delineation from
+           digital elevation models using mathematical morphology," Signal
+           Processing, 20(2):171-182, :DOI:`10.1016/0165-1684(90)90127-K`
 
     Examples
     --------


Test patch: