We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Faster map_array function
### Description:

In another project ( BiAPoL/napari-clusters-plotter#283 ) we faced the problem that the map_array function is quite slow when it comes to large arrays. We came up with a faster method and I thought the new method might be worth considering as a replacement for map_array. Here is a script comparing the old and new methods:

```python
import numpy as np
from skimage.util import map_array
from time import perf_counter

shape = (1,1024,1024)
total = shape[0]*shape[1]*shape[2]
NUM_LABELS=100000
input_data = np.random.randint(NUM_LABELS,size=total).reshape(shape).astype("int64")
from_values = np.arange(NUM_LABELS)
to_values = np.copy(from_values)
np.random.shuffle(to_values)

def generate_cluster_image(label_image, label_list, predictionlist):
    """
    Generates a clusters image from a label image and a list of cluster predictions,
    where each label value corresponds to the cluster identity.
    It is assumed that len(predictionlist) == max(label_image)

    Parameters
    ----------
    label_image: ndarray or dask array
        Label image used for cluster predictions
    predictionlist: Array-like
        An array containing cluster identities for each label

    Returns
    ----------
    ndarray: The clusters image as a numpy array.
    """

    predictionlist_new = np.array(predictionlist) + 1
    plist = np.zeros(np.max(label_image) + 1, dtype=np.uint32)
    plist[label_list] = predictionlist_new

    predictionlist_new = plist

    return predictionlist_new[label_image]

def generate_cluster_image_old(label_image, label_list, predictionlist):
    """
    Generates a clusters image from a label image and a list of cluster predictions,
    where each label value corresponds to the cluster identity.
    It is assumed that len(predictionlist) == max(label_image)

    Parameters
    ----------
    label_image: ndarray or dask array
        Label image used for cluster predictions
    predictionlist: Array-like
        An array containing cluster identities for each label

    Returns
    ----------
    ndarray: The clusters image as a numpy array.
    """
    from skimage.util import map_array

    # reforming the prediction list, this is done to account
    # for cluster labels that start at 0, conveniently hdbscan
    # labelling starts at -1 for noise, removing these from the labels
    predictionlist_new = np.array(predictionlist) + 1
    label_list = np.array(label_list)

    return map_array(np.asarray(label_image), label_list, predictionlist_new).astype(
        "uint32"
    )

t1 = perf_counter()
res_new = generate_cluster_image(input_data, from_values, to_values)
t_new = perf_counter() - t1
print(t_new)

t1 = perf_counter()
res_old = generate_cluster_image_old(input_data, from_values, to_values)
t_old = perf_counter() - t1
print(t_old)

print(f"Speedup {t_old/t_new}")

print(np.array_equal(res_new,res_old))
```

The new method is 15x-30x faster than the old one. 

If you guys think that this is worth it, I can come up with a PR.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `skimage/util/_map_array.py`
Content:
```
1 import numpy as np
2 
3 
4 def map_array(input_arr, input_vals, output_vals, out=None):
5     """Map values from input array from input_vals to output_vals.
6 
7     Parameters
8     ----------
9     input_arr : array of int, shape (M[, ...])
10         The input label image.
11     input_vals : array of int, shape (K,)
12         The values to map from.
13     output_vals : array, shape (K,)
14         The values to map to.
15     out: array, same shape as `input_arr`
16         The output array. Will be created if not provided. It should
17         have the same dtype as `output_vals`.
18 
19     Returns
20     -------
21     out : array, same shape as `input_arr`
22         The array of mapped values.
23     """
24     from ._remap import _map_array
25 
26     if not np.issubdtype(input_arr.dtype, np.integer):
27         raise TypeError('The dtype of an array to be remapped should be integer.')
28     # We ravel the input array for simplicity of iteration in Cython:
29     orig_shape = input_arr.shape
30     # NumPy docs for `np.ravel()` says:
31     # "When a view is desired in as many cases as possible,
32     # arr.reshape(-1) may be preferable."
33     input_arr = input_arr.reshape(-1)
34     if out is None:
35         out = np.empty(orig_shape, dtype=output_vals.dtype)
36     elif out.shape != orig_shape:
37         raise ValueError(
38             'If out array is provided, it should have the same shape as '
39             f'the input array. Input array has shape {orig_shape}, provided '
40             f'output array has shape {out.shape}.'
41         )
42     try:
43         out_view = out.view()
44         out_view.shape = (-1,)  # no-copy reshape/ravel
45     except AttributeError:  # if out strides are not compatible with 0-copy
46         raise ValueError(
47             'If out array is provided, it should be either contiguous '
48             f'or 1-dimensional. Got array with shape {out.shape} and '
49             f'strides {out.strides}.'
50         )
51 
52     # ensure all arrays have matching types before sending to Cython
53     input_vals = input_vals.astype(input_arr.dtype, copy=False)
54     output_vals = output_vals.astype(out.dtype, copy=False)
55     _map_array(input_arr, out_view, input_vals, output_vals)
56     return out
57 
58 
59 class ArrayMap:
60     """Class designed to mimic mapping by NumPy array indexing.
61 
62     This class is designed to replicate the use of NumPy arrays for mapping
63     values with indexing:
64 
65     >>> values = np.array([0.25, 0.5, 1.0])
66     >>> indices = np.array([[0, 0, 1], [2, 2, 1]])
67     >>> values[indices]
68     array([[0.25, 0.25, 0.5 ],
69            [1.  , 1.  , 0.5 ]])
70 
71     The issue with this indexing is that you need a very large ``values``
72     array if the values in the ``indices`` array are large.
73 
74     >>> values = np.array([0.25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.0])
75     >>> indices = np.array([[0, 0, 10], [0, 10, 10]])
76     >>> values[indices]
77     array([[0.25, 0.25, 1.  ],
78            [0.25, 1.  , 1.  ]])
79 
80     Using this class, the approach is similar, but there is no need to
81     create a large values array:
82 
83     >>> in_indices = np.array([0, 10])
84     >>> out_values = np.array([0.25, 1.0])
85     >>> values = ArrayMap(in_indices, out_values)
86     >>> values
87     ArrayMap(array([ 0, 10]), array([0.25, 1.  ]))
88     >>> print(values)
89     ArrayMap:
90       0 → 0.25
91       10 → 1.0
92     >>> indices = np.array([[0, 0, 10], [0, 10, 10]])
93     >>> values[indices]
94     array([[0.25, 0.25, 1.  ],
95            [0.25, 1.  , 1.  ]])
96 
97     Parameters
98     ----------
99     in_values : array of int, shape (K,)
100         The source values from which to map.
101     out_values : array, shape (K,)
102         The destination values from which to map.
103     """
104 
105     def __init__(self, in_values, out_values):
106         self.in_values = in_values
107         self.out_values = out_values
108         self._max_str_lines = 4
109         self._array = None
110 
111     def __len__(self):
112         """Return one more than the maximum label value being remapped."""
113         return np.max(self.in_values) + 1
114 
115     def __array__(self, dtype=None):
116         """Return an array that behaves like the arraymap when indexed.
117 
118         This array can be very large: it is the size of the largest value
119         in the ``in_vals`` array, plus one.
120         """
121         if dtype is None:
122             dtype = self.out_values.dtype
123         output = np.zeros(np.max(self.in_values) + 1, dtype=dtype)
124         output[self.in_values] = self.out_values
125         return output
126 
127     @property
128     def dtype(self):
129         return self.out_values.dtype
130 
131     def __repr__(self):
132         return f'ArrayMap({repr(self.in_values)}, {repr(self.out_values)})'
133 
134     def __str__(self):
135         if len(self.in_values) <= self._max_str_lines + 1:
136             rows = range(len(self.in_values))
137             string = '\n'.join(
138                 ['ArrayMap:']
139                 + [f'  {self.in_values[i]} → {self.out_values[i]}' for i in rows]
140             )
141         else:
142             rows0 = list(range(0, self._max_str_lines // 2))
143             rows1 = list(range(-self._max_str_lines // 2, 0))
144             string = '\n'.join(
145                 ['ArrayMap:']
146                 + [f'  {self.in_values[i]} → {self.out_values[i]}' for i in rows0]
147                 + ['  ...']
148                 + [f'  {self.in_values[i]} → {self.out_values[i]}' for i in rows1]
149             )
150         return string
151 
152     def __call__(self, arr):
153         return self.__getitem__(arr)
154 
155     def __getitem__(self, index):
156         scalar = np.isscalar(index)
157         if scalar:
158             index = np.array([index])
159         elif isinstance(index, slice):
160             start = index.start or 0  # treat None or 0 the same way
161             stop = index.stop if index.stop is not None else len(self)
162             step = index.step
163             index = np.arange(start, stop, step)
164         if index.dtype == bool:
165             index = np.flatnonzero(index)
166 
167         out = map_array(
168             index,
169             self.in_values.astype(index.dtype, copy=False),
170             self.out_values,
171         )
172 
173         if scalar:
174             out = out[0]
175         return out
176 
177     def __setitem__(self, indices, values):
178         if self._array is None:
179             self._array = self.__array__()
180         self._array[indices] = values
181         self.in_values = np.flatnonzero(self._array)
182         self.out_values = self._array[self.in_values]
183
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/skimage/util/_map_array.py b/skimage/util/_map_array.py
--- a/skimage/util/_map_array.py
+++ b/skimage/util/_map_array.py
@@ -20,6 +20,23 @@
     -------
     out : array, same shape as `input_arr`
         The array of mapped values.
+
+    Notes
+    -----
+    If `input_arr` contains values that aren't covered by `input_vals`, they
+    are set to 0.
+
+    Examples
+    --------
+    >>> import numpy as np
+    >>> import skimage as ski
+    >>> ski.util.map_array(
+    ...    input_arr=np.array([[0, 2, 2, 0], [3, 4, 5, 0]]),
+    ...    input_vals=np.array([1, 2, 3, 4, 6]),
+    ...    output_vals=np.array([6, 7, 8, 9, 10]),
+    ... )
+    array([[0, 7, 7, 0],
+           [8, 9, 0, 0]])
     """
     from ._remap import _map_array

{"golden_diff": "diff --git a/skimage/util/_map_array.py b/skimage/util/_map_array.py\n--- a/skimage/util/_map_array.py\n+++ b/skimage/util/_map_array.py\n@@ -20,6 +20,23 @@\n     -------\n     out : array, same shape as `input_arr`\n         The array of mapped values.\n+\n+    Notes\n+    -----\n+    If `input_arr` contains values that aren't covered by `input_vals`, they\n+    are set to 0.\n+\n+    Examples\n+    --------\n+    >>> import numpy as np\n+    >>> import skimage as ski\n+    >>> ski.util.map_array(\n+    ...    input_arr=np.array([[0, 2, 2, 0], [3, 4, 5, 0]]),\n+    ...    input_vals=np.array([1, 2, 3, 4, 6]),\n+    ...    output_vals=np.array([6, 7, 8, 9, 10]),\n+    ... )\n+    array([[0, 7, 7, 0],\n+           [8, 9, 0, 0]])\n     \"\"\"\n     from ._remap import _map_array\n", "issue": "Faster map_array function\n### Description:\r\n\r\nIn another project ( BiAPoL/napari-clusters-plotter#283 ) we faced the problem that the map_array function is quite slow when it comes to large arrays. We came up with a faster method and I thought the new method might be worth considering as a replacement for map_array. Here is a script comparing the old and new methods:\r\n\r\n```python\r\nimport numpy as np\r\nfrom skimage.util import map_array\r\nfrom time import perf_counter\r\n\r\nshape = (1,1024,1024)\r\ntotal = shape[0]*shape[1]*shape[2]\r\nNUM_LABELS=100000\r\ninput_data = np.random.randint(NUM_LABELS,size=total).reshape(shape).astype(\"int64\")\r\nfrom_values = np.arange(NUM_LABELS)\r\nto_values = np.copy(from_values)\r\nnp.random.shuffle(to_values)\r\n\r\ndef generate_cluster_image(label_image, label_list, predictionlist):\r\n    \"\"\"\r\n    Generates a clusters image from a label image and a list of cluster predictions,\r\n    where each label value corresponds to the cluster identity.\r\n    It is assumed that len(predictionlist) == max(label_image)\r\n\r\n    Parameters\r\n    ----------\r\n    label_image: ndarray or dask array\r\n        Label image used for cluster predictions\r\n    predictionlist: Array-like\r\n        An array containing cluster identities for each label\r\n\r\n    Returns\r\n    ----------\r\n    ndarray: The clusters image as a numpy array.\r\n    \"\"\"\r\n\r\n    predictionlist_new = np.array(predictionlist) + 1\r\n    plist = np.zeros(np.max(label_image) + 1, dtype=np.uint32)\r\n    plist[label_list] = predictionlist_new\r\n\r\n    predictionlist_new = plist\r\n\r\n    return predictionlist_new[label_image]\r\n\r\ndef generate_cluster_image_old(label_image, label_list, predictionlist):\r\n    \"\"\"\r\n    Generates a clusters image from a label image and a list of cluster predictions,\r\n    where each label value corresponds to the cluster identity.\r\n    It is assumed that len(predictionlist) == max(label_image)\r\n\r\n    Parameters\r\n    ----------\r\n    label_image: ndarray or dask array\r\n        Label image used for cluster predictions\r\n    predictionlist: Array-like\r\n        An array containing cluster identities for each label\r\n\r\n    Returns\r\n    ----------\r\n    ndarray: The clusters image as a numpy array.\r\n    \"\"\"\r\n    from skimage.util import map_array\r\n\r\n    # reforming the prediction list, this is done to account\r\n    # for cluster labels that start at 0, conveniently hdbscan\r\n    # labelling starts at -1 for noise, removing these from the labels\r\n    predictionlist_new = np.array(predictionlist) + 1\r\n    label_list = np.array(label_list)\r\n\r\n    return map_array(np.asarray(label_image), label_list, predictionlist_new).astype(\r\n        \"uint32\"\r\n    )\r\n\r\nt1 = perf_counter()\r\nres_new = generate_cluster_image(input_data, from_values, to_values)\r\nt_new = perf_counter() - t1\r\nprint(t_new)\r\n\r\nt1 = perf_counter()\r\nres_old = generate_cluster_image_old(input_data, from_values, to_values)\r\nt_old = perf_counter() - t1\r\nprint(t_old)\r\n\r\nprint(f\"Speedup {t_old/t_new}\")\r\n\r\nprint(np.array_equal(res_new,res_old))\r\n```\r\n\r\nThe new method is 15x-30x faster than the old one. \r\n\r\nIf you guys think that this is worth it, I can come up with a PR.\n", "before_files": [{"content": "import numpy as np\n\n\ndef map_array(input_arr, input_vals, output_vals, out=None):\n    \"\"\"Map values from input array from input_vals to output_vals.\n\n    Parameters\n    ----------\n    input_arr : array of int, shape (M[, ...])\n        The input label image.\n    input_vals : array of int, shape (K,)\n        The values to map from.\n    output_vals : array, shape (K,)\n        The values to map to.\n    out: array, same shape as `input_arr`\n        The output array. Will be created if not provided. It should\n        have the same dtype as `output_vals`.\n\n    Returns\n    -------\n    out : array, same shape as `input_arr`\n        The array of mapped values.\n    \"\"\"\n    from ._remap import _map_array\n\n    if not np.issubdtype(input_arr.dtype, np.integer):\n        raise TypeError('The dtype of an array to be remapped should be integer.')\n    # We ravel the input array for simplicity of iteration in Cython:\n    orig_shape = input_arr.shape\n    # NumPy docs for `np.ravel()` says:\n    # \"When a view is desired in as many cases as possible,\n    # arr.reshape(-1) may be preferable.\"\n    input_arr = input_arr.reshape(-1)\n    if out is None:\n        out = np.empty(orig_shape, dtype=output_vals.dtype)\n    elif out.shape != orig_shape:\n        raise ValueError(\n            'If out array is provided, it should have the same shape as '\n            f'the input array. Input array has shape {orig_shape}, provided '\n            f'output array has shape {out.shape}.'\n        )\n    try:\n        out_view = out.view()\n        out_view.shape = (-1,)  # no-copy reshape/ravel\n    except AttributeError:  # if out strides are not compatible with 0-copy\n        raise ValueError(\n            'If out array is provided, it should be either contiguous '\n            f'or 1-dimensional. Got array with shape {out.shape} and '\n            f'strides {out.strides}.'\n        )\n\n    # ensure all arrays have matching types before sending to Cython\n    input_vals = input_vals.astype(input_arr.dtype, copy=False)\n    output_vals = output_vals.astype(out.dtype, copy=False)\n    _map_array(input_arr, out_view, input_vals, output_vals)\n    return out\n\n\nclass ArrayMap:\n    \"\"\"Class designed to mimic mapping by NumPy array indexing.\n\n    This class is designed to replicate the use of NumPy arrays for mapping\n    values with indexing:\n\n    >>> values = np.array([0.25, 0.5, 1.0])\n    >>> indices = np.array([[0, 0, 1], [2, 2, 1]])\n    >>> values[indices]\n    array([[0.25, 0.25, 0.5 ],\n           [1.  , 1.  , 0.5 ]])\n\n    The issue with this indexing is that you need a very large ``values``\n    array if the values in the ``indices`` array are large.\n\n    >>> values = np.array([0.25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.0])\n    >>> indices = np.array([[0, 0, 10], [0, 10, 10]])\n    >>> values[indices]\n    array([[0.25, 0.25, 1.  ],\n           [0.25, 1.  , 1.  ]])\n\n    Using this class, the approach is similar, but there is no need to\n    create a large values array:\n\n    >>> in_indices = np.array([0, 10])\n    >>> out_values = np.array([0.25, 1.0])\n    >>> values = ArrayMap(in_indices, out_values)\n    >>> values\n    ArrayMap(array([ 0, 10]), array([0.25, 1.  ]))\n    >>> print(values)\n    ArrayMap:\n      0 \u2192 0.25\n      10 \u2192 1.0\n    >>> indices = np.array([[0, 0, 10], [0, 10, 10]])\n    >>> values[indices]\n    array([[0.25, 0.25, 1.  ],\n           [0.25, 1.  , 1.  ]])\n\n    Parameters\n    ----------\n    in_values : array of int, shape (K,)\n        The source values from which to map.\n    out_values : array, shape (K,)\n        The destination values from which to map.\n    \"\"\"\n\n    def __init__(self, in_values, out_values):\n        self.in_values = in_values\n        self.out_values = out_values\n        self._max_str_lines = 4\n        self._array = None\n\n    def __len__(self):\n        \"\"\"Return one more than the maximum label value being remapped.\"\"\"\n        return np.max(self.in_values) + 1\n\n    def __array__(self, dtype=None):\n        \"\"\"Return an array that behaves like the arraymap when indexed.\n\n        This array can be very large: it is the size of the largest value\n        in the ``in_vals`` array, plus one.\n        \"\"\"\n        if dtype is None:\n            dtype = self.out_values.dtype\n        output = np.zeros(np.max(self.in_values) + 1, dtype=dtype)\n        output[self.in_values] = self.out_values\n        return output\n\n    @property\n    def dtype(self):\n        return self.out_values.dtype\n\n    def __repr__(self):\n        return f'ArrayMap({repr(self.in_values)}, {repr(self.out_values)})'\n\n    def __str__(self):\n        if len(self.in_values) <= self._max_str_lines + 1:\n            rows = range(len(self.in_values))\n            string = '\\n'.join(\n                ['ArrayMap:']\n                + [f'  {self.in_values[i]} \u2192 {self.out_values[i]}' for i in rows]\n            )\n        else:\n            rows0 = list(range(0, self._max_str_lines // 2))\n            rows1 = list(range(-self._max_str_lines // 2, 0))\n            string = '\\n'.join(\n                ['ArrayMap:']\n                + [f'  {self.in_values[i]} \u2192 {self.out_values[i]}' for i in rows0]\n                + ['  ...']\n                + [f'  {self.in_values[i]} \u2192 {self.out_values[i]}' for i in rows1]\n            )\n        return string\n\n    def __call__(self, arr):\n        return self.__getitem__(arr)\n\n    def __getitem__(self, index):\n        scalar = np.isscalar(index)\n        if scalar:\n            index = np.array([index])\n        elif isinstance(index, slice):\n            start = index.start or 0  # treat None or 0 the same way\n            stop = index.stop if index.stop is not None else len(self)\n            step = index.step\n            index = np.arange(start, stop, step)\n        if index.dtype == bool:\n            index = np.flatnonzero(index)\n\n        out = map_array(\n            index,\n            self.in_values.astype(index.dtype, copy=False),\n            self.out_values,\n        )\n\n        if scalar:\n            out = out[0]\n        return out\n\n    def __setitem__(self, indices, values):\n        if self._array is None:\n            self._array = self.__array__()\n        self._array[indices] = values\n        self.in_values = np.flatnonzero(self._array)\n        self.out_values = self._array[self.in_values]\n", "path": "skimage/util/_map_array.py"}], "after_files": [{"content": "import numpy as np\n\n\ndef map_array(input_arr, input_vals, output_vals, out=None):\n    \"\"\"Map values from input array from input_vals to output_vals.\n\n    Parameters\n    ----------\n    input_arr : array of int, shape (M[, ...])\n        The input label image.\n    input_vals : array of int, shape (K,)\n        The values to map from.\n    output_vals : array, shape (K,)\n        The values to map to.\n    out: array, same shape as `input_arr`\n        The output array. Will be created if not provided. It should\n        have the same dtype as `output_vals`.\n\n    Returns\n    -------\n    out : array, same shape as `input_arr`\n        The array of mapped values.\n\n    Notes\n    -----\n    If `input_arr` contains values that aren't covered by `input_vals`, they\n    are set to 0.\n\n    Examples\n    --------\n    >>> import numpy as np\n    >>> import skimage as ski\n    >>> ski.util.map_array(\n    ...    input_arr=np.array([[0, 2, 2, 0], [3, 4, 5, 0]]),\n    ...    input_vals=np.array([1, 2, 3, 4, 6]),\n    ...    output_vals=np.array([6, 7, 8, 9, 10]),\n    ... )\n    array([[0, 7, 7, 0],\n           [8, 9, 0, 0]])\n    \"\"\"\n    from ._remap import _map_array\n\n    if not np.issubdtype(input_arr.dtype, np.integer):\n        raise TypeError('The dtype of an array to be remapped should be integer.')\n    # We ravel the input array for simplicity of iteration in Cython:\n    orig_shape = input_arr.shape\n    # NumPy docs for `np.ravel()` says:\n    # \"When a view is desired in as many cases as possible,\n    # arr.reshape(-1) may be preferable.\"\n    input_arr = input_arr.reshape(-1)\n    if out is None:\n        out = np.empty(orig_shape, dtype=output_vals.dtype)\n    elif out.shape != orig_shape:\n        raise ValueError(\n            'If out array is provided, it should have the same shape as '\n            f'the input array. Input array has shape {orig_shape}, provided '\n            f'output array has shape {out.shape}.'\n        )\n    try:\n        out_view = out.view()\n        out_view.shape = (-1,)  # no-copy reshape/ravel\n    except AttributeError:  # if out strides are not compatible with 0-copy\n        raise ValueError(\n            'If out array is provided, it should be either contiguous '\n            f'or 1-dimensional. Got array with shape {out.shape} and '\n            f'strides {out.strides}.'\n        )\n\n    # ensure all arrays have matching types before sending to Cython\n    input_vals = input_vals.astype(input_arr.dtype, copy=False)\n    output_vals = output_vals.astype(out.dtype, copy=False)\n    _map_array(input_arr, out_view, input_vals, output_vals)\n    return out\n\n\nclass ArrayMap:\n    \"\"\"Class designed to mimic mapping by NumPy array indexing.\n\n    This class is designed to replicate the use of NumPy arrays for mapping\n    values with indexing:\n\n    >>> values = np.array([0.25, 0.5, 1.0])\n    >>> indices = np.array([[0, 0, 1], [2, 2, 1]])\n    >>> values[indices]\n    array([[0.25, 0.25, 0.5 ],\n           [1.  , 1.  , 0.5 ]])\n\n    The issue with this indexing is that you need a very large ``values``\n    array if the values in the ``indices`` array are large.\n\n    >>> values = np.array([0.25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.0])\n    >>> indices = np.array([[0, 0, 10], [0, 10, 10]])\n    >>> values[indices]\n    array([[0.25, 0.25, 1.  ],\n           [0.25, 1.  , 1.  ]])\n\n    Using this class, the approach is similar, but there is no need to\n    create a large values array:\n\n    >>> in_indices = np.array([0, 10])\n    >>> out_values = np.array([0.25, 1.0])\n    >>> values = ArrayMap(in_indices, out_values)\n    >>> values\n    ArrayMap(array([ 0, 10]), array([0.25, 1.  ]))\n    >>> print(values)\n    ArrayMap:\n      0 \u2192 0.25\n      10 \u2192 1.0\n    >>> indices = np.array([[0, 0, 10], [0, 10, 10]])\n    >>> values[indices]\n    array([[0.25, 0.25, 1.  ],\n           [0.25, 1.  , 1.  ]])\n\n    Parameters\n    ----------\n    in_values : array of int, shape (K,)\n        The source values from which to map.\n    out_values : array, shape (K,)\n        The destination values from which to map.\n    \"\"\"\n\n    def __init__(self, in_values, out_values):\n        self.in_values = in_values\n        self.out_values = out_values\n        self._max_str_lines = 4\n        self._array = None\n\n    def __len__(self):\n        \"\"\"Return one more than the maximum label value being remapped.\"\"\"\n        return np.max(self.in_values) + 1\n\n    def __array__(self, dtype=None):\n        \"\"\"Return an array that behaves like the arraymap when indexed.\n\n        This array can be very large: it is the size of the largest value\n        in the ``in_vals`` array, plus one.\n        \"\"\"\n        if dtype is None:\n            dtype = self.out_values.dtype\n        output = np.zeros(np.max(self.in_values) + 1, dtype=dtype)\n        output[self.in_values] = self.out_values\n        return output\n\n    @property\n    def dtype(self):\n        return self.out_values.dtype\n\n    def __repr__(self):\n        return f'ArrayMap({repr(self.in_values)}, {repr(self.out_values)})'\n\n    def __str__(self):\n        if len(self.in_values) <= self._max_str_lines + 1:\n            rows = range(len(self.in_values))\n            string = '\\n'.join(\n                ['ArrayMap:']\n                + [f'  {self.in_values[i]} \u2192 {self.out_values[i]}' for i in rows]\n            )\n        else:\n            rows0 = list(range(0, self._max_str_lines // 2))\n            rows1 = list(range(-self._max_str_lines // 2, 0))\n            string = '\\n'.join(\n                ['ArrayMap:']\n                + [f'  {self.in_values[i]} \u2192 {self.out_values[i]}' for i in rows0]\n                + ['  ...']\n                + [f'  {self.in_values[i]} \u2192 {self.out_values[i]}' for i in rows1]\n            )\n        return string\n\n    def __call__(self, arr):\n        return self.__getitem__(arr)\n\n    def __getitem__(self, index):\n        scalar = np.isscalar(index)\n        if scalar:\n            index = np.array([index])\n        elif isinstance(index, slice):\n            start = index.start or 0  # treat None or 0 the same way\n            stop = index.stop if index.stop is not None else len(self)\n            step = index.step\n            index = np.arange(start, stop, step)\n        if index.dtype == bool:\n            index = np.flatnonzero(index)\n\n        out = map_array(\n            index,\n            self.in_values.astype(index.dtype, copy=False),\n            self.out_values,\n        )\n\n        if scalar:\n            out = out[0]\n        return out\n\n    def __setitem__(self, indices, values):\n        if self._array is None:\n            self._array = self.__array__()\n        self._array[indices] = values\n        self.in_values = np.flatnonzero(self._array)\n        self.out_values = self._array[self.in_values]\n", "path": "skimage/util/_map_array.py"}]}