Repository: scikit-image/scikit-image

Issue:
Consider returning segments starting from 1 from SLIC
We are getting close to being fully consistent in considering 0 to be the background in a label image. However, SLIC returns segments starting from 0, resulting in problems like this SO question:

https://stackoverflow.com/questions/54506079/slic-segmentation-in-skimage/54526084

where SLIC returns a full segmentation of the image, but segment 0 is ignored by regionprops. (Note: maybe the background segment should be measured by regionprops anyway!)

## Way to reproduce

Input image:

![example-image](https://user-images.githubusercontent.com/492549/52245588-5c98c780-2936-11e9-88be-20bc091adde0.jpg)

```python
import imageio
import numpy as np
import matplotlib.image as mpimg
import matplotlib.pyplot as plt
from skimage.measure import regionprops
from skimage.segmentation import slic
from skimage.segmentation import mark_boundaries

def rgb2gray(rgb):
    return np.dot(rgb[..., :3], [0.2126, 0.7152, 0.0722])

img_file_path = 'example-image.jpg'
image = imageio.imread(img_file_path)
segments_slic = slic(image, n_segments=250, compactness=100)
regions = regionprops(segments_slic, intensity_image=rgb2gray(image))
for props in regions:
    cy, cx = props.centroid
    plt.plot(cx, cy, 'ro')

plt.imshow(mark_boundaries(image, segments_slic))
plt.show()
```

Output:

![figure_1](https://user-images.githubusercontent.com/492549/52245643-9964be80-2936-11e9-95ac-7fe77aa0c592.png)

Note the missing segment annotation on the top left. Adding `1 + ` to the SLIC call fixes this.

```python
>>> import sys; print(sys.version)
3.6.3 |Anaconda custom (x86_64)| (default, Nov  3 2017, 12:34:11)
[GCC 4.2.1 Compatible Clang 4.0.1 (tags/RELEASE_401/final)]
>>> import platform; print(platform.platform())
Darwin-18.2.0-x86_64-i386-64bit
```




Discussion:
Hi @jni. I am a beginner and need some help undertanding the fix.
Like you suggested [here](https://stackoverflow.com/questions/54506079/slic-segmentation-in-skimage/54526084), since we need to start from 1 instead of a 0, how about returning `labels +1` as a temporary fix? 
https://github.com/scikit-image/scikit-image/blob/a3d2557209fb5913609749f06e70b367cef00617/skimage/segmentation/slic_superpixels.py#L185

Or else, any guidelines to go about?
@manasbedmutha98 thanks for chiming in!

Firstly, there's probably a more efficient way of doing this within SLIC so that we don't have to duplicate a potentially huge labels array.

But, secondly, we have to worry about backwards compatibility, so the path to take is a bit tricky. We can't just change the output, but will have to instead add a flag to get the new behaviour, and a warning that the behaviour will change someday.
Can't we consider that the problem comes from `regionprops` and not from `slic`?
I mean that making `regionpropos` not consider 0 as background by default solves the problem.
So, adding an argument `background ` to `regionprops` (like in the function `skimage.measure.label`) may do the trick.
@rfezzani well, that probably should happen anyway, yes (see #2755 and #3180), but I still think SLIC should return labels starting from 1, because it is a convention seen in many places, within the library (e.g. watershed returns labels from 1) but also in SciPy, which considers 0 as background for `label` and for `find_objects` (which is actually the source of this in regionprops).

Resolving patch:
diff --git a/benchmarks/benchmark_segmentation.py b/benchmarks/benchmark_segmentation.py
--- a/benchmarks/benchmark_segmentation.py
+++ b/benchmarks/benchmark_segmentation.py
@@ -9,10 +9,16 @@ def setup(self):
         self.image = np.random.random((400, 400, 100))
         self.image[:200, :200, :] += 1
         self.image[300:, 300:, :] += 0.5
+        self.msk = np.zeros((400, 400, 100))
+        self.msk[10:-10, 10:-10, 10:-10] = 1
 
     def time_slic_basic(self):
         segmentation.slic(self.image, enforce_connectivity=False)
 
+    def time_mask_slic(self):
+        segmentation.slic(self.image, enforce_connectivity=False,
+                          mask=self.msk)
+
     def peakmem_setup(self):
         """peakmem includes the memory used by setup.
 
diff --git a/doc/examples/segmentation/plot_mask_slic.py b/doc/examples/segmentation/plot_mask_slic.py
new file mode 100644
--- /dev/null
+++ b/doc/examples/segmentation/plot_mask_slic.py
@@ -0,0 +1,72 @@
+"""
+======================
+maskSLIC Demonstration
+======================
+
+This example is about comparing the segmentations obtained using the
+plain SLIC method [1]_ and its masked version maskSLIC [2]_.
+
+The maskSLIC method is an extension of the SLIC method for the
+generation of superpixels in a region of interest. maskSLIC is able to
+overcome border problems that affects SLIC method, particularely in
+case of irregular mask.
+
+.. [1] Radhakrishna Achanta, Appu Shaji, Kevin Smith, Aurelien Lucchi,
+    Pascal Fua, and Sabine Suesstrunk, SLIC Superpixels Compared to
+    State-of-the-art Superpixel Methods, TPAMI, May 2012.
+    :DOI:`10.1109/TPAMI.2012.120`
+
+.. [2] Irving, Benjamin. "maskSLIC: regional superpixel generation
+    with application to local pathology characterisation in medical
+    images.", 2016, , :arXiv:`1606.09518`
+
+"""
+
+import matplotlib.pyplot as plt
+
+from skimage import data
+from skimage import color
+from skimage import morphology
+from skimage import segmentation
+
+# Input data
+img = data.immunohistochemistry()
+
+# Compute a mask
+lum = color.rgb2gray(img)
+mask = morphology.remove_small_holes(
+    morphology.remove_small_objects(
+        lum < 0.7, 500),
+    500)
+
+mask = morphology.opening(mask, morphology.disk(3))
+
+# SLIC result
+slic = segmentation.slic(img, n_segments=200)
+
+# maskSLIC result
+m_slic = segmentation.slic(img, n_segments=100, mask=mask)
+
+# Display result
+fig, ax_arr = plt.subplots(2, 2, sharex=True, sharey=True, figsize=(10, 10))
+ax1, ax2, ax3, ax4 = ax_arr.ravel()
+
+ax1.imshow(img)
+ax1.set_title("Origin image")
+
+ax2.imshow(mask, cmap="gray")
+ax2.set_title("Mask")
+
+ax3.imshow(segmentation.mark_boundaries(img, slic))
+ax3.contour(mask, colors='red', linewidths=1)
+ax3.set_title("SLIC")
+
+ax4.imshow(segmentation.mark_boundaries(img, m_slic))
+ax4.contour(mask, colors='red', linewidths=1)
+ax4.set_title("maskSLIC")
+
+for ax in ax_arr.ravel():
+    ax.set_axis_off()
+
+plt.tight_layout()
+plt.show()
diff --git a/skimage/segmentation/slic_superpixels.py b/skimage/segmentation/slic_superpixels.py
--- a/skimage/segmentation/slic_superpixels.py
+++ b/skimage/segmentation/slic_superpixels.py
@@ -1,17 +1,94 @@
-from collections.abc import Iterable
+import warnings
+import functools
+import collections as coll
 import numpy as np
 from scipy import ndimage as ndi
+from scipy.spatial.distance import pdist, squareform
+from scipy.cluster.vq import kmeans2
+from numpy import random
 
+from ._slic import (_slic_cython, _enforce_label_connectivity_cython)
 from ..util import img_as_float, regular_grid
-from ..segmentation._slic import (_slic_cython,
-                                  _enforce_label_connectivity_cython)
 from ..color import rgb2lab
 
 
+def _get_mask_centroids(mask, n_centroids):
+    """Find regularly spaced centroids on a mask.
+
+    Parameters
+    ----------
+    mask : 3D ndarray
+        The mask within which the centroids must be positioned.
+    n_centroids : int
+        The number of centroids to be returned.
+
+    Returns
+    -------
+    centroids : 2D ndarray
+        The coordinates of the centroids with shape (n_centroids, 3).
+    steps : 1D ndarray
+        The approximate distance between two seeds in all dimensions.
+
+    """
+
+    # Get tight ROI around the mask to optimize
+    coord = np.array(np.nonzero(mask), dtype=float).T
+    # Fix random seed to ensure repeatability
+    rnd = random.RandomState(123)
+    idx = np.sort(rnd.choice(np.arange(len(coord), dtype=int),
+                             min(n_centroids, len(coord)),
+                             replace=False))
+    centroids, _ = kmeans2(coord, coord[idx])
+
+    # Compute the minimum distance of each centroid to the others
+    dist = squareform(pdist(centroids))
+    np.fill_diagonal(dist, np.inf)
+    closest_pts = dist.argmin(-1)
+    steps = abs(centroids - centroids[closest_pts, :]).mean(0)
+
+    return centroids, steps
+
+
+def _get_grid_centroids(image, n_centroids):
+    """Find regularly spaced centroids on the image.
+
+    Parameters
+    ----------
+    image : 2D, 3D or 4D ndarray
+        Input image, which can be 2D or 3D, and grayscale or
+        multichannel.
+    n_centroids : int
+        The (approximate) number of centroids to be returned.
+
+    Returns
+    -------
+    centroids : 2D ndarray
+        The coordinates of the centroids with shape (~n_centroids, 3).
+    steps : 1D ndarray
+        The approximate distance between two seeds in all dimensions.
+
+    """
+    d, h, w = image.shape[:3]
+
+    grid_z, grid_y, grid_x = np.mgrid[:d, :h, :w]
+    slices = regular_grid(image.shape[:3], n_centroids)
+
+    centroids_z = grid_z[slices].ravel()[..., np.newaxis]
+    centroids_y = grid_y[slices].ravel()[..., np.newaxis]
+    centroids_x = grid_x[slices].ravel()[..., np.newaxis]
+
+    centroids = np.concatenate([centroids_z, centroids_y, centroids_x],
+                               axis=-1)
+
+    steps = np.asarray([float(s.step) if s.step is not None else 1.0
+                        for s in slices])
+    return centroids, steps
+
+
 def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=0,
          spacing=None, multichannel=True, convert2lab=None,
          enforce_connectivity=True, min_size_factor=0.5, max_size_factor=3,
-         slic_zero=False):
+         slic_zero=False, start_label=None, mask=None):
     """Segments image using k-means clustering in Color-(x,y,z) space.
 
     Parameters
@@ -60,6 +137,13 @@ def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=0,
         in most of the cases.
     slic_zero : bool, optional
         Run SLIC-zero, the zero-parameter mode of SLIC. [2]_
+    start_label: int, optional
+        The labels' index start. Should be 0 or 1.
+    mask : 2D ndarray, optional
+        If provided, superpixels are computed only where mask is True,
+        and seed points are iteratively placed over the masked area to
+        ensure their homogeneous spacial distribution over the mask
+        (see [3]_ for more details).
 
     Returns
     -------
@@ -71,6 +155,8 @@ def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=0,
     ValueError
         If ``convert2lab`` is set to ``True`` but the last array
         dimension is not of length 3.
+    ValueError
+        If ``start_label`` is not 0 or 1.
 
     Notes
     -----
@@ -88,12 +174,26 @@ def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=0,
       interpret them as 3D with the last dimension having length 3, use
       `multichannel=False`.
 
+    * If spacing is None and mask is provided, chamfer distance
+      transform is used to homogeneously place seed points over the
+      mask, otherwise, the exact euclidian distance transform is used.
+
+    * `start_label` is introduced to handle the issue [4]_. The labels
+      indexing starting at 0 will be deprecated in future versions. If
+      `mask` is not `None` labels indexing starts at 1 and masked area
+      is set to 0.
+
     References
     ----------
     .. [1] Radhakrishna Achanta, Appu Shaji, Kevin Smith, Aurelien Lucchi,
         Pascal Fua, and Sabine Süsstrunk, SLIC Superpixels Compared to
         State-of-the-art Superpixel Methods, TPAMI, May 2012.
-    .. [2] http://ivrg.epfl.ch/research/superpixels#SLICO
+        :DOI:`10.1109/TPAMI.2012.120`
+    .. [2] https://www.epfl.ch/labs/ivrl/research/slic-superpixels/#SLICO
+    .. [3] Irving, Benjamin. "maskSLIC: regional superpixel generation with
+           application to local pathology characterisation in medical images.",
+           2016, :arXiv:`1606.09518`
+    .. [4] https://github.com/scikit-image/scikit-image/issues/3722
 
     Examples
     --------
@@ -109,9 +209,11 @@ def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=0,
     """
 
     image = img_as_float(image)
+    use_mask = mask is not None
     dtype = image.dtype
 
     is_2d = False
+
     if image.ndim == 2:
         # 2D grayscale image
         image = image[np.newaxis, ..., np.newaxis]
@@ -124,12 +226,46 @@ def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=0,
         # Add channel as single last dimension
         image = image[..., np.newaxis]
 
+    if multichannel and (convert2lab or convert2lab is None):
+        if image.shape[-1] != 3 and convert2lab:
+            raise ValueError("Lab colorspace conversion requires a RGB image.")
+        elif image.shape[-1] == 3:
+            image = rgb2lab(image)
+
+    if start_label is None:
+        if use_mask:
+            start_label = 1
+        else:
+            warnings.warn("skimage.measure.label's indexing starts from 0. " +
+                          "In future version it will start from 1. " +
+                          "To disable this warning, explicitely " +
+                          "set the `start_label` parameter to 1.",
+                          FutureWarning, stacklevel=2)
+            start_label = 0
+
+    if start_label not in [0, 1]:
+        raise ValueError("start_label should be 0 or 1.")
+
+    # initialize cluster centroids for desired number of segments
+    update_centroids = False
+    if use_mask:
+        mask = np.ascontiguousarray(mask, dtype=np.bool).view('uint8')
+        if mask.ndim == 2:
+            mask = np.ascontiguousarray(mask[np.newaxis, ...])
+        if mask.shape != image.shape[:3]:
+            raise ValueError("image and mask should have the same shape.")
+        # Step 1 of the algorithm [3]_
+        centroids, steps = _get_mask_centroids(mask, n_segments)
+        update_centroids = True
+    else:
+        centroids, steps = _get_grid_centroids(image, n_segments)
+
     if spacing is None:
         spacing = np.ones(3, dtype=dtype)
     elif isinstance(spacing, (list, tuple)):
-        spacing = np.array(spacing, dtype=dtype)
+        spacing = np.ascontiguousarray(spacing, dtype=dtype)
 
-    if not isinstance(sigma, Iterable):
+    if not isinstance(sigma, coll.Iterable):
         sigma = np.array([sigma, sigma, sigma], dtype=dtype)
         sigma /= spacing.astype(dtype)
     elif isinstance(sigma, (list, tuple)):
@@ -139,51 +275,37 @@ def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=0,
         sigma = list(sigma) + [0]
         image = ndi.gaussian_filter(image, sigma)
 
-    if multichannel and (convert2lab or convert2lab is None):
-        if image.shape[-1] != 3 and convert2lab:
-            raise ValueError("Lab colorspace conversion requires a RGB image.")
-        elif image.shape[-1] == 3:
-            image = rgb2lab(image)
+    n_centroids = centroids.shape[0]
+    segments = np.ascontiguousarray(np.concatenate(
+        [centroids, np.zeros((n_centroids, image.shape[3]))],
+        axis=-1))
 
-    depth, height, width = image.shape[:3]
+    # Scaling of ratio in the same way as in the SLIC paper so the
+    # values have the same meaning
+    step = max(steps)
+    ratio = 1.0 / compactness
 
-    # initialize cluster centroids for desired number of segments
-    grid_z, grid_y, grid_x = np.meshgrid(np.arange(depth, dtype=dtype),
-                                         np.arange(height, dtype=dtype),
-                                         np.arange(width, dtype=dtype),
-                                         indexing='ij')
-    slices = regular_grid(image.shape[:3], n_segments)
-    step_z, step_y, step_x = [int(s.step if s.step is not None else 1)
-                              for s in slices]
-    segments_z = grid_z[slices]
-    segments_y = grid_y[slices]
-    segments_x = grid_x[slices]
-
-    segments_color = np.zeros(segments_z.shape + (image.shape[3],),
-                              dtype=dtype)
-    segments = np.concatenate([segments_z[..., np.newaxis],
-                               segments_y[..., np.newaxis],
-                               segments_x[..., np.newaxis],
-                               segments_color],
-                              axis=-1).reshape(-1, 3 + image.shape[3])
-    segments = np.ascontiguousarray(segments)
-
-    # we do the scaling of ratio in the same way as in the SLIC paper
-    # so the values have the same meaning
-    step = dtype.type(max((step_z, step_y, step_x)))
-    ratio = dtype.type(1.0 / compactness)
-
-    image = np.ascontiguousarray(image * ratio)
-
-    labels = _slic_cython(image, segments, step, max_iter, spacing, slic_zero)
+    image = np.ascontiguousarray(image * ratio, dtype=np.double)
+
+    if update_centroids:
+        # Step 2 of the algorithm [3]_
+        _slic_cython(image, mask, segments, step, max_iter, spacing,
+                     slic_zero, ignore_color=True,
+                     start_label=start_label)
+
+    labels = _slic_cython(image, mask, segments, step, max_iter,
+                          spacing, slic_zero, ignore_color=False,
+                          start_label=start_label)
 
     if enforce_connectivity:
-        segment_size = depth * height * width / n_segments
+        if use_mask:
+            segment_size = mask.sum() / n_centroids
+        else:
+            segment_size = np.prod(image.shape[:3]) / n_centroids
         min_size = int(min_size_factor * segment_size)
         max_size = int(max_size_factor * segment_size)
-        labels = _enforce_label_connectivity_cython(labels,
-                                                    min_size,
-                                                    max_size)
+        labels = _enforce_label_connectivity_cython(
+            labels, min_size, max_size, start_label=start_label)
 
     if is_2d:
         labels = labels[0]


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
@@ -75,7 +75,7 @@ def test_2d_bf():
     assert data.shape == labels.shape
     with expected_warnings([NUMPY_MATRIX_WARNING]):
         full_prob_bf = random_walker(data, labels, beta=90, mode='bf',
-                                 return_full_prob=True)
+                                     return_full_prob=True)
     assert (full_prob_bf[1, 25:45, 40:60] >=
             full_prob_bf[0, 25:45, 40:60]).all()
     assert data.shape == labels.shape
@@ -83,7 +83,7 @@ def test_2d_bf():
     labels[55, 80] = 3
     with expected_warnings([NUMPY_MATRIX_WARNING]):
         full_prob_bf = random_walker(data, labels, beta=90, mode='bf',
-                                 return_full_prob=True)
+                                     return_full_prob=True)
     assert (full_prob_bf[1, 25:45, 40:60] >=
             full_prob_bf[0, 25:45, 40:60]).all()
     assert len(full_prob_bf) == 3
diff --git a/skimage/segmentation/tests/test_slic.py b/skimage/segmentation/tests/test_slic.py
--- a/skimage/segmentation/tests/test_slic.py
+++ b/skimage/segmentation/tests/test_slic.py
@@ -17,7 +17,8 @@ def test_color_2d():
     img += 0.01 * rnd.normal(size=img.shape)
     img[img > 1] = 1
     img[img < 0] = 0
-    seg = slic(img, n_segments=4, sigma=0, enforce_connectivity=False)
+    seg = slic(img, n_segments=4, sigma=0, enforce_connectivity=False,
+               start_label=0)
 
     # we expect 4 segments
     assert_equal(len(np.unique(seg)), 4)
@@ -37,7 +38,7 @@ def test_multichannel_2d():
     img[10:, 10:, 6:8] = 1
     img += 0.01 * rnd.normal(size=img.shape)
     img = np.clip(img, 0, 1, out=img)
-    seg = slic(img, n_segments=4, enforce_connectivity=False)
+    seg = slic(img, n_segments=4, enforce_connectivity=False, start_label=0)
 
     # we expect 4 segments
     assert_equal(len(np.unique(seg)), 4)
@@ -58,7 +59,7 @@ def test_gray_2d():
     img[img > 1] = 1
     img[img < 0] = 0
     seg = slic(img, sigma=0, n_segments=4, compactness=1,
-               multichannel=False, convert2lab=False)
+               multichannel=False, convert2lab=False, start_label=0)
 
     assert_equal(len(np.unique(seg)), 4)
     assert_equal(seg.shape, img.shape)
@@ -82,7 +83,7 @@ def test_color_3d():
     img += 0.01 * rnd.normal(size=img.shape)
     img[img > 1] = 1
     img[img < 0] = 0
-    seg = slic(img, sigma=0, n_segments=8)
+    seg = slic(img, sigma=0, n_segments=8, start_label=0)
 
     assert_equal(len(np.unique(seg)), 8)
     for s, c in zip(slices, range(8)):
@@ -104,7 +105,7 @@ def test_gray_3d():
     img[img > 1] = 1
     img[img < 0] = 0
     seg = slic(img, sigma=0, n_segments=8, compactness=1,
-               multichannel=False, convert2lab=False)
+               multichannel=False, convert2lab=False, start_label=0)
 
     assert_equal(len(np.unique(seg)), 8)
     for s, c in zip(slices, range(8)):
@@ -118,7 +119,8 @@ def test_list_sigma():
     img += 0.1 * rnd.normal(size=img.shape)
     result_sigma = np.array([[0, 0, 0, 1, 1, 1],
                              [0, 0, 0, 1, 1, 1]], np.int)
-    seg_sigma = slic(img, n_segments=2, sigma=[1, 50, 1], multichannel=False)
+    seg_sigma = slic(img, n_segments=2, sigma=[1, 50, 1],
+                     multichannel=False, start_label=0)
     assert_equal(seg_sigma, result_sigma)
 
 
@@ -132,9 +134,9 @@ def test_spacing():
                               [1, 1, 1, 1, 1]], np.int)
     img += 0.1 * rnd.normal(size=img.shape)
     seg_non_spaced = slic(img, n_segments=2, sigma=0, multichannel=False,
-                          compactness=1.0)
+                          compactness=1.0, start_label=0)
     seg_spaced = slic(img, n_segments=2, sigma=0, spacing=[1, 500, 1],
-                      compactness=1.0, multichannel=False)
+                      compactness=1.0, multichannel=False, start_label=0)
     assert_equal(seg_non_spaced, result_non_spaced)
     assert_equal(seg_spaced, result_spaced)
 
@@ -143,7 +145,7 @@ def test_invalid_lab_conversion():
     img = np.array([[1, 1, 1, 0, 0],
                     [1, 1, 0, 0, 0]], np.float) + 1
     with testing.raises(ValueError):
-        slic(img, multichannel=True, convert2lab=True)
+        slic(img, multichannel=True, convert2lab=True, start_label=0)
 
 
 def test_enforce_connectivity():
@@ -153,16 +155,18 @@ def test_enforce_connectivity():
 
     segments_connected = slic(img, 2, compactness=0.0001,
                               enforce_connectivity=True,
-                              convert2lab=False)
+                              convert2lab=False, start_label=0)
     segments_disconnected = slic(img, 2, compactness=0.0001,
                                  enforce_connectivity=False,
-                                 convert2lab=False)
+                                 convert2lab=False, start_label=0)
 
     # Make sure nothing fatal occurs (e.g. buffer overflow) at low values of
     # max_size_factor
     segments_connected_low_max = slic(img, 2, compactness=0.0001,
                                       enforce_connectivity=True,
-                                      convert2lab=False, max_size_factor=0.8)
+                                      convert2lab=False,
+                                      max_size_factor=0.8,
+                                      start_label=0)
 
     result_connected = np.array([[0, 0, 0, 1, 1, 1],
                                  [0, 0, 0, 1, 1, 1],
@@ -187,7 +191,7 @@ def test_slic_zero():
     img += 0.01 * rnd.normal(size=img.shape)
     img[img > 1] = 1
     img[img < 0] = 0
-    seg = slic(img, n_segments=4, sigma=0, slic_zero=True)
+    seg = slic(img, n_segments=4, sigma=0, slic_zero=True, start_label=0)
 
     # we expect 4 segments
     assert_equal(len(np.unique(seg)), 4)
@@ -208,5 +212,254 @@ def test_more_segments_than_pixels():
     img[img > 1] = 1
     img[img < 0] = 0
     seg = slic(img, sigma=0, n_segments=500, compactness=1,
-               multichannel=False, convert2lab=False)
+               multichannel=False, convert2lab=False, start_label=0)
     assert np.all(seg.ravel() == np.arange(seg.size))
+
+
+def test_color_2d_mask():
+    rnd = np.random.RandomState(0)
+    msk = np.zeros((20, 21))
+    msk[2:-2, 2:-2] = 1
+    img = np.zeros((20, 21, 3))
+    img[:10, :10, 0] = 1
+    img[10:, :10, 1] = 1
+    img[10:, 10:, 2] = 1
+    img += 0.01 * rnd.normal(size=img.shape)
+    np.clip(img, 0, 1, out=img)
+    seg = slic(img, n_segments=4, sigma=0, enforce_connectivity=False,
+               mask=msk)
+
+    # we expect 4 segments + masked area
+    assert_equal(len(np.unique(seg)), 5)
+    assert_equal(seg.shape, img.shape[:-1])
+    # segments
+    assert_equal(seg[2:10, 2:10], 1)
+    assert_equal(seg[10:-2, 2:10], 4)
+    assert_equal(seg[2:10, 10:-2], 2)
+    assert_equal(seg[10:-2, 10:-2], 3)
+    # non masked area
+    assert_equal(seg[:2, :], 0)
+    assert_equal(seg[-2:, :], 0)
+    assert_equal(seg[:, :2], 0)
+    assert_equal(seg[:, -2:], 0)
+
+
+def test_multichannel_2d_mask():
+    rnd = np.random.RandomState(0)
+    msk = np.zeros((20, 20))
+    msk[2:-2, 2:-2] = 1
+    img = np.zeros((20, 20, 8))
+    img[:10, :10, 0:2] = 1
+    img[:10, 10:, 2:4] = 1
+    img[10:, :10, 4:6] = 1
+    img[10:, 10:, 6:8] = 1
+    img += 0.01 * rnd.normal(size=img.shape)
+    np.clip(img, 0, 1, out=img)
+    seg = slic(img, n_segments=4, enforce_connectivity=False,
+               mask=msk)
+
+    # we expect 4 segments + masked area
+    assert_equal(len(np.unique(seg)), 5)
+    assert_equal(seg.shape, img.shape[:-1])
+    # segments
+    assert_equal(seg[2:10, 2:10], 2)
+    assert_equal(seg[2:10, 10:-2], 1)
+    assert_equal(seg[10:-2, 2:10], 4)
+    assert_equal(seg[10:-2, 10:-2], 3)
+    # non masked area
+    assert_equal(seg[:2, :], 0)
+    assert_equal(seg[-2:, :], 0)
+    assert_equal(seg[:, :2], 0)
+    assert_equal(seg[:, -2:], 0)
+
+
+def test_gray_2d_mask():
+    rnd = np.random.RandomState(0)
+    msk = np.zeros((20, 21))
+    msk[2:-2, 2:-2] = 1
+    img = np.zeros((20, 21))
+    img[:10, :10] = 0.33
+    img[10:, :10] = 0.67
+    img[10:, 10:] = 1.00
+    img += 0.0033 * rnd.normal(size=img.shape)
+    np.clip(img, 0, 1, out=img)
+    seg = slic(img, sigma=0, n_segments=4, compactness=1,
+               multichannel=False, convert2lab=False, mask=msk)
+
+    assert_equal(len(np.unique(seg)), 5)
+    assert_equal(seg.shape, img.shape)
+    # segments
+    assert_equal(seg[2:10, 2:10], 1)
+    assert_equal(seg[2:10, 10:-2], 2)
+    assert_equal(seg[10:-2, 2:10], 3)
+    assert_equal(seg[10:-2, 10:-2], 4)
+    # non masked area
+    assert_equal(seg[:2, :], 0)
+    assert_equal(seg[-2:, :], 0)
+    assert_equal(seg[:, :2], 0)
+    assert_equal(seg[:, -2:], 0)
+
+
+def test_list_sigma_mask():
+    rnd = np.random.RandomState(0)
+    msk = np.zeros((2, 6))
+    msk[:, 1:-1] = 1
+    img = np.array([[1, 1, 1, 0, 0, 0],
+                    [0, 0, 0, 1, 1, 1]], np.float)
+    img += 0.1 * rnd.normal(size=img.shape)
+    result_sigma = np.array([[0, 1, 1, 2, 2, 0],
+                             [0, 1, 1, 2, 2, 0]], np.int)
+    seg_sigma = slic(img, n_segments=2, sigma=[1, 50, 1],
+                     multichannel=False, mask=msk)
+    assert_equal(seg_sigma, result_sigma)
+
+
+def test_spacing_mask():
+    rnd = np.random.RandomState(0)
+    msk = np.zeros((2, 5))
+    msk[:, 1:-1] = 1
+    img = np.array([[1, 1, 1, 0, 0],
+                    [1, 1, 0, 0, 0]], np.float)
+    result_non_spaced = np.array([[0, 1, 1, 2, 0],
+                                  [0, 1, 2, 2, 0]], np.int)
+    result_spaced = np.array([[0, 1, 1, 1, 0],
+                              [0, 2, 2, 2, 0]], np.int)
+    img += 0.1 * rnd.normal(size=img.shape)
+    seg_non_spaced = slic(img, n_segments=2, sigma=0, multichannel=False,
+                          compactness=1.0, mask=msk)
+    seg_spaced = slic(img, n_segments=2, sigma=0, spacing=[1, 50, 1],
+                      compactness=1.0, multichannel=False, mask=msk)
+    assert_equal(seg_non_spaced, result_non_spaced)
+    assert_equal(seg_spaced, result_spaced)
+
+
+def test_enforce_connectivity_mask():
+    msk = np.zeros((3, 6))
+    msk[:, 1:-1] = 1
+    img = np.array([[0, 0, 0, 1, 1, 1],
+                    [1, 0, 0, 1, 1, 0],
+                    [0, 0, 0, 1, 1, 0]], np.float)
+
+    segments_connected = slic(img, 2, compactness=0.0001,
+                              enforce_connectivity=True,
+                              convert2lab=False, mask=msk)
+    segments_disconnected = slic(img, 2, compactness=0.0001,
+                                 enforce_connectivity=False,
+                                 convert2lab=False, mask=msk)
+
+    # Make sure nothing fatal occurs (e.g. buffer overflow) at low values of
+    # max_size_factor
+    segments_connected_low_max = slic(img, 2, compactness=0.0001,
+                                      enforce_connectivity=True,
+                                      convert2lab=False,
+                                      max_size_factor=0.8, mask=msk)
+
+    result_connected = np.array([[0, 1, 1, 2, 2, 0],
+                                 [0, 1, 1, 2, 2, 0],
+                                 [0, 1, 1, 2, 2, 0]], np.float)
+
+    result_disconnected = np.array([[0, 1, 1, 2, 2, 0],
+                                    [0, 1, 1, 2, 2, 0],
+                                    [0, 1, 1, 2, 2, 0]], np.float)
+
+    assert_equal(segments_connected, result_connected)
+    assert_equal(segments_disconnected, result_disconnected)
+    assert_equal(segments_connected_low_max, result_connected)
+
+
+def test_slic_zero_mask():
+
+    rnd = np.random.RandomState(0)
+    msk = np.zeros((20, 21))
+    msk[2:-2, 2:-2] = 1
+    img = np.zeros((20, 21, 3))
+    img[:10, :10, 0] = 1
+    img[10:, :10, 1] = 1
+    img[10:, 10:, 2] = 1
+    img += 0.01 * rnd.normal(size=img.shape)
+    np.clip(img, 0, 1, out=img)
+    seg = slic(img, n_segments=4, sigma=0, slic_zero=True,
+               mask=msk)
+
+    # we expect 4 segments + masked area
+    assert_equal(len(np.unique(seg)), 5)
+    assert_equal(seg.shape, img.shape[:-1])
+    # segments
+    assert_equal(seg[2:10, 2:10], 1)
+    assert_equal(seg[2:10, 10:-2], 2)
+    assert_equal(seg[10:-2, 2:10], 3)
+    assert_equal(seg[10:-2, 10:-2], 4)
+    # non masked area
+    assert_equal(seg[:2, :], 0)
+    assert_equal(seg[-2:, :], 0)
+    assert_equal(seg[:, :2], 0)
+    assert_equal(seg[:, -2:], 0)
+
+
+def test_more_segments_than_pixels_mask():
+    rnd = np.random.RandomState(0)
+    msk = np.zeros((20, 21))
+    msk[2:-2, 2:-2] = 1
+    img = np.zeros((20, 21))
+    img[:10, :10] = 0.33
+    img[10:, :10] = 0.67
+    img[10:, 10:] = 1.00
+    img += 0.0033 * rnd.normal(size=img.shape)
+    np.clip(img, 0, 1, out=img)
+    seg = slic(img, sigma=0, n_segments=500, compactness=1,
+               multichannel=False, convert2lab=False, mask=msk)
+
+    expected = np.arange(seg[2:-2, 2:-2].size) + 1
+    assert np.all(seg[2:-2, 2:-2].ravel() == expected)
+
+
+def test_color_3d_mask():
+
+    msk = np.zeros((20, 21, 22))
+    msk[2:-2, 2:-2, 2:-2] = 1
+
+    rnd = np.random.RandomState(0)
+    img = np.zeros((20, 21, 22, 3))
+    slices = []
+    for dim_size in msk.shape:
+        midpoint = dim_size // 2
+        slices.append((slice(None, midpoint), slice(midpoint, None)))
+    slices = list(product(*slices))
+    colors = list(product(*(([0, 1],) * 3)))
+    for s, c in zip(slices, colors):
+        img[s] = c
+    img += 0.01 * rnd.normal(size=img.shape)
+    np.clip(img, 0, 1, out=img)
+
+    seg = slic(img, sigma=0, n_segments=8, mask=msk)
+
+    # we expect 8 segments + masked area
+    assert_equal(len(np.unique(seg)), 9)
+    for s, c in zip(slices, range(1, 9)):
+        assert_equal(seg[s][2:-2, 2:-2, 2:-2], c)
+
+
+def test_gray_3d_mask():
+
+    msk = np.zeros((20, 21, 22))
+    msk[2:-2, 2:-2, 2:-2] = 1
+
+    rnd = np.random.RandomState(0)
+    img = np.zeros((20, 21, 22))
+    slices = []
+    for dim_size in img.shape:
+        midpoint = dim_size // 2
+        slices.append((slice(None, midpoint), slice(midpoint, None)))
+    slices = list(product(*slices))
+    shades = np.linspace(0, 1, 8)
+    for s, sh in zip(slices, shades):
+        img[s] = sh
+    img += 0.001 * rnd.normal(size=img.shape)
+    np.clip(img, 0, 1, out=img)
+    seg = slic(img, sigma=0, n_segments=8, multichannel=False,
+               convert2lab=False, mask=msk)
+
+    # we expect 8 segments + masked area
+    assert_equal(len(np.unique(seg)), 9)
+    for s, c in zip(slices, range(1, 9)):
+        assert_equal(seg[s][2:-2, 2:-2, 2:-2], c)