Repository: scikit-image/scikit-image

Issue:
pixel_graph causes TypeError in map_array if mask is not given but edge_dunction is given
### Description:

If you give `edge_function` to `pixel_graph` but don't give `mask,` `mask` stays to be the default `None,` which would cause `TypeError 'The type of an array to be remapped should be an integer.'` in `map_array` even if we make sure the input image is integer.

I believe this is caused by the if statement at the beginning of the function. The if statement at the beginning does not cover the specific case I mentioned above, and the mask would not be changed from None.

The following is the lines I mentioned:

```python
if image.dtype == bool and mask is None:
        mask = image
if mask is None and edge_function is None:
        mask = np.ones_like(image, dtype=bool)
        edge_function = _weighted_abs_diff
```

As the solution, I think separating if statement for `mask` and the one for `edge_function` works:

```python
if image.dtype == bool and mask is None:
        mask = image
if mask is None:
        mask = np.ones_like(image, dtype=bool)
if edge_function is None:
        edge_function = _weighted_abs_diff
```


### Way to reproduce:

```python
import numpy as np
import skimage

image = np.array(
    [[1, 2, 3],
     [4, 5, 6],
     [7, 8, 9]]
) 

func = lambda x, y, z: np.abs(x-y)
adj1 = skimage.graph.pixel_graph(image) # No error
adj2 = skimage.graph.pixel_graph(image, edge_function = func) # TypeError
```

### Version information:

```Shell
3.10.12 (main, Jun 11 2023, 05:26:28) [GCC 11.4.0]
Linux-5.15.120+-x86_64-with-glibc2.35
scikit-image version: 0.19.3
numpy version: 1.23.5
```



Discussion:
Oops! Nice catch @mao1756. Thanks for reporting. Your fix looks right to me. Would you like to submit a pull request to fix it, adding your reproducible example as a test?
@jni Sounds good; I will work on it today.

Resolving patch:
diff --git a/skimage/graph/_graph.py b/skimage/graph/_graph.py
--- a/skimage/graph/_graph.py
+++ b/skimage/graph/_graph.py
@@ -70,11 +70,20 @@ def pixel_graph(image, *, mask=None, edge_function=None, connectivity=1, spacing
         The nodes of the graph. These correspond to the raveled indices of the
         nonzero pixels in the mask.
     """
-    if image.dtype == bool and mask is None:
-        mask = image
-    if mask is None and edge_function is None:
-        mask = np.ones_like(image, dtype=bool)
-        edge_function = _weighted_abs_diff
+    if mask is None:
+        if image.dtype == bool:
+            mask = image
+        else:
+            mask = np.ones_like(image, dtype=bool)
+
+    if edge_function is None:
+        if image.dtype == bool:
+
+            def edge_function(x, y, distances):
+                return distances
+
+        else:
+            edge_function = _weighted_abs_diff
 
     # Strategy: we are going to build the (i, j, data) arrays of a scipy
     # sparse COO matrix, then convert to CSR (which is fast).
@@ -118,13 +127,12 @@ def pixel_graph(image, *, mask=None, edge_function=None, connectivity=1, spacing
     neighbor_indices = neighbors[neighbors_mask]
     neighbor_distances = neighbor_distances_full[neighbors_mask]
     neighbor_indices_sequential = map_array(neighbor_indices, nodes, nodes_sequential)
-    if edge_function is None:
-        data = neighbor_distances
-    else:
-        image_r = image.reshape(-1)
-        data = edge_function(
-            image_r[indices], image_r[neighbor_indices], neighbor_distances
-        )
+
+    image_r = image.reshape(-1)
+    data = edge_function(
+        image_r[indices], image_r[neighbor_indices], neighbor_distances
+    )
+
     m = nodes_sequential.size
     mat = sparse.coo_matrix(
         (data, (indices_sequential, neighbor_indices_sequential)), shape=(m, m)


Test patch:
diff --git a/skimage/graph/tests/test_pixel_graph.py b/skimage/graph/tests/test_pixel_graph.py
--- a/skimage/graph/tests/test_pixel_graph.py
+++ b/skimage/graph/tests/test_pixel_graph.py
@@ -46,3 +46,32 @@ def test_default_edge_func():
     assert num_edges == 12  # lattice in a (3, 3) grid
     np.testing.assert_almost_equal(g[0, 1], 0.78 * np.abs(image[0, 0] - image[0, 1]))
     np.testing.assert_array_equal(n, np.arange(image.size))
+
+
+def test_no_mask_with_edge_func():
+    """Ensure function `pixel_graph` runs when passing `edge_function` but not `mask`."""
+    image = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
+
+    def func(x, y, z):
+        return np.abs(x - y) * 0.5
+
+    expected_g = (
+        np.array(
+            [
+                [0.0, 1.0, 0.0, 3.0, 0.0, 0.0, 0.0, 0.0, 0.0],
+                [1.0, 0.0, 1.0, 0.0, 3.0, 0.0, 0.0, 0.0, 0.0],
+                [0.0, 1.0, 0.0, 0.0, 0.0, 3.0, 0.0, 0.0, 0.0],
+                [3.0, 0.0, 0.0, 0.0, 1.0, 0.0, 3.0, 0.0, 0.0],
+                [0.0, 3.0, 0.0, 1.0, 0.0, 1.0, 0.0, 3.0, 0.0],
+                [0.0, 0.0, 3.0, 0.0, 1.0, 0.0, 0.0, 0.0, 3.0],
+                [0.0, 0.0, 0.0, 3.0, 0.0, 0.0, 0.0, 1.0, 0.0],
+                [0.0, 0.0, 0.0, 0.0, 3.0, 0.0, 1.0, 0.0, 1.0],
+                [0.0, 0.0, 0.0, 0.0, 0.0, 3.0, 0.0, 1.0, 0.0],
+            ]
+        )
+        * 0.5
+    )
+
+    g, n = pixel_graph(image, edge_function=func)
+    np.testing.assert_array_equal(n, np.arange(image.size))
+    np.testing.assert_array_equal(g.todense(), expected_g)