We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Viewer: LabelPainter overlay does not update with new loaded image
Reproduce: open the Watershed demo, then load another image of a different `shape`. The overlay won't cover the entire image, and when watershed is called there will be a shape mismatch.


--- 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/viewer/plugins/labelplugin.py`
Content:
```
1 import numpy as np
2 
3 from .base import Plugin
4 from ..widgets import ComboBox, Slider
5 from ..canvastools import PaintTool
6 
7 
8 __all__ = ['LabelPainter']
9 
10 
11 rad2deg = 180 / np.pi
12 
13 
14 class LabelPainter(Plugin):
15     name = 'LabelPainter'
16 
17     def __init__(self, max_radius=20, **kwargs):
18         super(LabelPainter, self).__init__(**kwargs)
19 
20         # These widgets adjust plugin properties instead of an image filter.
21         self._radius_widget = Slider('radius', low=1, high=max_radius,
22                                      value=5, value_type='int', ptype='plugin')
23         labels = [str(i) for i in range(6)]
24         labels[0] = 'Erase'
25         self._label_widget = ComboBox('label', labels, ptype='plugin')
26         self.add_widget(self._radius_widget)
27         self.add_widget(self._label_widget)
28 
29         print(self.help())
30 
31     def help(self):
32         helpstr = ("Label painter",
33                    "Hold left-mouse button and paint on canvas.")
34         return '\n'.join(helpstr)
35 
36     def attach(self, image_viewer):
37         super(LabelPainter, self).attach(image_viewer)
38 
39         image = image_viewer.original_image
40         self.paint_tool = PaintTool(self.image_viewer.ax, image.shape,
41                                     on_enter=self.on_enter)
42         self.paint_tool.radius = self.radius
43         self.paint_tool.label = self._label_widget.index = 1
44         self.artists.append(self.paint_tool)
45 
46     def on_enter(self, overlay):
47         pass
48 
49     @property
50     def radius(self):
51         return self._radius_widget.val
52 
53     @radius.setter
54     def radius(self, val):
55         self.paint_tool.radius = val
56 
57     @property
58     def label(self):
59         return self._label_widget.val
60 
61     @label.setter
62     def label(self, val):
63         self.paint_tool.label = val
64
```

Path: `skimage/viewer/canvastools/painttool.py`
Content:
```
1 import numpy as np
2 import matplotlib.pyplot as plt
3 import matplotlib.colors as mcolors
4 LABELS_CMAP = mcolors.ListedColormap(['white', 'red', 'dodgerblue', 'gold',
5                                           'greenyellow', 'blueviolet'])
6 
7 from skimage.viewer.canvastools.base import CanvasToolBase
8 
9 
10 __all__ = ['PaintTool']
11 
12 
13 class PaintTool(CanvasToolBase):
14     """Widget for painting on top of a plot.
15 
16     Parameters
17     ----------
18     ax : :class:`matplotlib.axes.Axes`
19         Matplotlib axes where tool is displayed.
20     overlay_shape : shape tuple
21         2D shape tuple used to initialize overlay image.
22     alpha : float (between [0, 1])
23         Opacity of overlay
24     on_move : function
25         Function called whenever a control handle is moved.
26         This function must accept the end points of line as the only argument.
27     on_release : function
28         Function called whenever the control handle is released.
29     on_enter : function
30         Function called whenever the "enter" key is pressed.
31     rect_props : dict
32         Properties for :class:`matplotlib.patches.Rectangle`. This class
33         redefines defaults in :class:`matplotlib.widgets.RectangleSelector`.
34 
35     Attributes
36     ----------
37     overlay : array
38         Overlay of painted labels displayed on top of image.
39     label : int
40         Current paint color.
41     """
42     def __init__(self, ax, overlay_shape, radius=5, alpha=0.3, on_move=None,
43                  on_release=None, on_enter=None, rect_props=None):
44         super(PaintTool, self).__init__(ax, on_move=on_move, on_enter=on_enter,
45                                         on_release=on_release)
46 
47         props = dict(edgecolor='r', facecolor='0.7', alpha=0.5, animated=True)
48         props.update(rect_props if rect_props is not None else {})
49 
50         self.alpha = alpha
51         self.cmap = LABELS_CMAP
52         self._overlay_plot = None
53         self._shape = overlay_shape
54         self.overlay = np.zeros(overlay_shape, dtype='uint8')
55 
56         self._cursor = plt.Rectangle((0, 0), 0, 0, **props)
57         self._cursor.set_visible(False)
58         self.ax.add_patch(self._cursor)
59 
60         # `label` and `radius` can only be set after initializing `_cursor`
61         self.label = 1
62         self.radius = radius
63 
64         # Note that the order is important: Redraw cursor *after* overlay
65         self._artists = [self._overlay_plot, self._cursor]
66 
67         self.connect_event('button_press_event', self.on_mouse_press)
68         self.connect_event('button_release_event', self.on_mouse_release)
69         self.connect_event('motion_notify_event', self.on_move)
70 
71     @property
72     def label(self):
73         return self._label
74 
75     @label.setter
76     def label(self, value):
77         if value >= self.cmap.N:
78             raise ValueError('Maximum label value = %s' % len(self.cmap - 1))
79         self._label = value
80         self._cursor.set_edgecolor(self.cmap(value))
81 
82     @property
83     def radius(self):
84         return self._radius
85 
86     @radius.setter
87     def radius(self, r):
88         self._radius = r
89         self._width = 2 * r + 1
90         self._cursor.set_width(self._width)
91         self._cursor.set_height(self._width)
92         self.window = CenteredWindow(r, self._shape)
93 
94     @property
95     def overlay(self):
96         return self._overlay
97 
98     @overlay.setter
99     def overlay(self, image):
100         self._overlay = image
101         if image is None:
102             self.ax.images.remove(self._overlay_plot)
103             self._overlay_plot = None
104         elif self._overlay_plot is None:
105             props = dict(cmap=self.cmap, alpha=self.alpha,
106                          norm=mcolors.no_norm(), animated=True)
107             self._overlay_plot = self.ax.imshow(image, **props)
108         else:
109             self._overlay_plot.set_data(image)
110         self.redraw()
111 
112     def _on_key_press(self, event):
113         if event.key == 'enter':
114             self.callback_on_enter(self.geometry)
115             self.redraw()
116 
117     def on_mouse_press(self, event):
118         if event.button != 1 or not self.ax.in_axes(event):
119             return
120         self.update_cursor(event.xdata, event.ydata)
121         self.update_overlay(event.xdata, event.ydata)
122 
123     def on_mouse_release(self, event):
124         if event.button != 1:
125             return
126         self.callback_on_release(self.geometry)
127 
128     def on_move(self, event):
129         if not self.ax.in_axes(event):
130             self._cursor.set_visible(False)
131             self.redraw() # make sure cursor is not visible
132             return
133         self._cursor.set_visible(True)
134 
135         self.update_cursor(event.xdata, event.ydata)
136         if event.button != 1:
137             self.redraw() # update cursor position
138             return
139         self.update_overlay(event.xdata, event.ydata)
140         self.callback_on_move(self.geometry)
141 
142     def update_overlay(self, x, y):
143         overlay = self.overlay
144         overlay[self.window.at(y, x)] = self.label
145         # Note that overlay calls `redraw`
146         self.overlay = overlay
147 
148     def update_cursor(self, x, y):
149         x = x - self.radius - 1
150         y = y - self.radius - 1
151         self._cursor.set_xy((x, y))
152 
153     @property
154     def geometry(self):
155         return self.overlay
156 
157 
158 class CenteredWindow(object):
159     """Window that create slices numpy arrays over 2D windows.
160 
161     Examples
162     --------
163     >>> a = np.arange(16).reshape(4, 4)
164     >>> w = CenteredWindow(1, a.shape)
165     >>> a[w.at(1, 1)]
166     array([[ 0,  1,  2],
167            [ 4,  5,  6],
168            [ 8,  9, 10]])
169     >>> a[w.at(0, 0)]
170     array([[0, 1],
171            [4, 5]])
172     >>> a[w.at(4, 3)]
173     array([[14, 15]])
174     """
175     def __init__(self, radius, array_shape):
176         self.radius = radius
177         self.array_shape = array_shape
178 
179     def at(self, row, col):
180         h, w = self.array_shape
181         r = self.radius
182         xmin = max(0, col - r)
183         xmax = min(w, col + r + 1)
184         ymin = max(0, row - r)
185         ymax = min(h, row + r + 1)
186         return [slice(ymin, ymax), slice(xmin, xmax)]
187 
188 
189 if __name__ == '__main__':
190     np.testing.rundocs()
191     from skimage import data
192 
193     image = data.camera()
194 
195     f, ax = plt.subplots()
196     ax.imshow(image, interpolation='nearest')
197     paint_tool = PaintTool(ax, image.shape)
198     plt.show()
199
```
--- 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/viewer/canvastools/painttool.py b/skimage/viewer/canvastools/painttool.py
--- a/skimage/viewer/canvastools/painttool.py
+++ b/skimage/viewer/canvastools/painttool.py
@@ -50,8 +50,7 @@
         self.alpha = alpha
         self.cmap = LABELS_CMAP
         self._overlay_plot = None
-        self._shape = overlay_shape
-        self.overlay = np.zeros(overlay_shape, dtype='uint8')
+        self.shape = overlay_shape
 
         self._cursor = plt.Rectangle((0, 0), 0, 0, **props)
         self._cursor.set_visible(False)
@@ -109,6 +108,19 @@
             self._overlay_plot.set_data(image)
         self.redraw()
 
+    @property
+    def shape(self):
+        return self._shape
+
+    @shape.setter
+    def shape(self, shape):
+        self._shape = shape
+        if not self._overlay_plot is None:
+            self._overlay_plot.set_extent((-0.5, shape[1] + 0.5,
+                                           shape[0] + 0.5, -0.5))
+            self.radius = self._radius
+        self.overlay = np.zeros(shape, dtype='uint8')
+
     def _on_key_press(self, event):
         if event.key == 'enter':
             self.callback_on_enter(self.geometry)
diff --git a/skimage/viewer/plugins/labelplugin.py b/skimage/viewer/plugins/labelplugin.py
--- a/skimage/viewer/plugins/labelplugin.py
+++ b/skimage/viewer/plugins/labelplugin.py
@@ -43,6 +43,10 @@
         self.paint_tool.label = self._label_widget.index = 1
         self.artists.append(self.paint_tool)
 
+    def _on_new_image(self, image):
+        """Update plugin for new images."""
+        self.paint_tool.shape = image.shape
+
     def on_enter(self, overlay):
         pass

{"golden_diff": "diff --git a/skimage/viewer/canvastools/painttool.py b/skimage/viewer/canvastools/painttool.py\n--- a/skimage/viewer/canvastools/painttool.py\n+++ b/skimage/viewer/canvastools/painttool.py\n@@ -50,8 +50,7 @@\n         self.alpha = alpha\n         self.cmap = LABELS_CMAP\n         self._overlay_plot = None\n-        self._shape = overlay_shape\n-        self.overlay = np.zeros(overlay_shape, dtype='uint8')\n+        self.shape = overlay_shape\n \n         self._cursor = plt.Rectangle((0, 0), 0, 0, **props)\n         self._cursor.set_visible(False)\n@@ -109,6 +108,19 @@\n             self._overlay_plot.set_data(image)\n         self.redraw()\n \n+    @property\n+    def shape(self):\n+        return self._shape\n+\n+    @shape.setter\n+    def shape(self, shape):\n+        self._shape = shape\n+        if not self._overlay_plot is None:\n+            self._overlay_plot.set_extent((-0.5, shape[1] + 0.5,\n+                                           shape[0] + 0.5, -0.5))\n+            self.radius = self._radius\n+        self.overlay = np.zeros(shape, dtype='uint8')\n+\n     def _on_key_press(self, event):\n         if event.key == 'enter':\n             self.callback_on_enter(self.geometry)\ndiff --git a/skimage/viewer/plugins/labelplugin.py b/skimage/viewer/plugins/labelplugin.py\n--- a/skimage/viewer/plugins/labelplugin.py\n+++ b/skimage/viewer/plugins/labelplugin.py\n@@ -43,6 +43,10 @@\n         self.paint_tool.label = self._label_widget.index = 1\n         self.artists.append(self.paint_tool)\n \n+    def _on_new_image(self, image):\n+        \"\"\"Update plugin for new images.\"\"\"\n+        self.paint_tool.shape = image.shape\n+\n     def on_enter(self, overlay):\n         pass\n", "issue": "Viewer: LabelPainter overlay does not update with new loaded image\nReproduce: open the Watershed demo, then load another image of a different `shape`. The overlay won't cover the entire image, and when watershed is called there will be a shape mismatch.\n\n", "before_files": [{"content": "import numpy as np\n\nfrom .base import Plugin\nfrom ..widgets import ComboBox, Slider\nfrom ..canvastools import PaintTool\n\n\n__all__ = ['LabelPainter']\n\n\nrad2deg = 180 / np.pi\n\n\nclass LabelPainter(Plugin):\n    name = 'LabelPainter'\n\n    def __init__(self, max_radius=20, **kwargs):\n        super(LabelPainter, self).__init__(**kwargs)\n\n        # These widgets adjust plugin properties instead of an image filter.\n        self._radius_widget = Slider('radius', low=1, high=max_radius,\n                                     value=5, value_type='int', ptype='plugin')\n        labels = [str(i) for i in range(6)]\n        labels[0] = 'Erase'\n        self._label_widget = ComboBox('label', labels, ptype='plugin')\n        self.add_widget(self._radius_widget)\n        self.add_widget(self._label_widget)\n\n        print(self.help())\n\n    def help(self):\n        helpstr = (\"Label painter\",\n                   \"Hold left-mouse button and paint on canvas.\")\n        return '\\n'.join(helpstr)\n\n    def attach(self, image_viewer):\n        super(LabelPainter, self).attach(image_viewer)\n\n        image = image_viewer.original_image\n        self.paint_tool = PaintTool(self.image_viewer.ax, image.shape,\n                                    on_enter=self.on_enter)\n        self.paint_tool.radius = self.radius\n        self.paint_tool.label = self._label_widget.index = 1\n        self.artists.append(self.paint_tool)\n\n    def on_enter(self, overlay):\n        pass\n\n    @property\n    def radius(self):\n        return self._radius_widget.val\n\n    @radius.setter\n    def radius(self, val):\n        self.paint_tool.radius = val\n\n    @property\n    def label(self):\n        return self._label_widget.val\n\n    @label.setter\n    def label(self, val):\n        self.paint_tool.label = val\n", "path": "skimage/viewer/plugins/labelplugin.py"}, {"content": "import numpy as np\nimport matplotlib.pyplot as plt\nimport matplotlib.colors as mcolors\nLABELS_CMAP = mcolors.ListedColormap(['white', 'red', 'dodgerblue', 'gold',\n                                          'greenyellow', 'blueviolet'])\n\nfrom skimage.viewer.canvastools.base import CanvasToolBase\n\n\n__all__ = ['PaintTool']\n\n\nclass PaintTool(CanvasToolBase):\n    \"\"\"Widget for painting on top of a plot.\n\n    Parameters\n    ----------\n    ax : :class:`matplotlib.axes.Axes`\n        Matplotlib axes where tool is displayed.\n    overlay_shape : shape tuple\n        2D shape tuple used to initialize overlay image.\n    alpha : float (between [0, 1])\n        Opacity of overlay\n    on_move : function\n        Function called whenever a control handle is moved.\n        This function must accept the end points of line as the only argument.\n    on_release : function\n        Function called whenever the control handle is released.\n    on_enter : function\n        Function called whenever the \"enter\" key is pressed.\n    rect_props : dict\n        Properties for :class:`matplotlib.patches.Rectangle`. This class\n        redefines defaults in :class:`matplotlib.widgets.RectangleSelector`.\n\n    Attributes\n    ----------\n    overlay : array\n        Overlay of painted labels displayed on top of image.\n    label : int\n        Current paint color.\n    \"\"\"\n    def __init__(self, ax, overlay_shape, radius=5, alpha=0.3, on_move=None,\n                 on_release=None, on_enter=None, rect_props=None):\n        super(PaintTool, self).__init__(ax, on_move=on_move, on_enter=on_enter,\n                                        on_release=on_release)\n\n        props = dict(edgecolor='r', facecolor='0.7', alpha=0.5, animated=True)\n        props.update(rect_props if rect_props is not None else {})\n\n        self.alpha = alpha\n        self.cmap = LABELS_CMAP\n        self._overlay_plot = None\n        self._shape = overlay_shape\n        self.overlay = np.zeros(overlay_shape, dtype='uint8')\n\n        self._cursor = plt.Rectangle((0, 0), 0, 0, **props)\n        self._cursor.set_visible(False)\n        self.ax.add_patch(self._cursor)\n\n        # `label` and `radius` can only be set after initializing `_cursor`\n        self.label = 1\n        self.radius = radius\n\n        # Note that the order is important: Redraw cursor *after* overlay\n        self._artists = [self._overlay_plot, self._cursor]\n\n        self.connect_event('button_press_event', self.on_mouse_press)\n        self.connect_event('button_release_event', self.on_mouse_release)\n        self.connect_event('motion_notify_event', self.on_move)\n\n    @property\n    def label(self):\n        return self._label\n\n    @label.setter\n    def label(self, value):\n        if value >= self.cmap.N:\n            raise ValueError('Maximum label value = %s' % len(self.cmap - 1))\n        self._label = value\n        self._cursor.set_edgecolor(self.cmap(value))\n\n    @property\n    def radius(self):\n        return self._radius\n\n    @radius.setter\n    def radius(self, r):\n        self._radius = r\n        self._width = 2 * r + 1\n        self._cursor.set_width(self._width)\n        self._cursor.set_height(self._width)\n        self.window = CenteredWindow(r, self._shape)\n\n    @property\n    def overlay(self):\n        return self._overlay\n\n    @overlay.setter\n    def overlay(self, image):\n        self._overlay = image\n        if image is None:\n            self.ax.images.remove(self._overlay_plot)\n            self._overlay_plot = None\n        elif self._overlay_plot is None:\n            props = dict(cmap=self.cmap, alpha=self.alpha,\n                         norm=mcolors.no_norm(), animated=True)\n            self._overlay_plot = self.ax.imshow(image, **props)\n        else:\n            self._overlay_plot.set_data(image)\n        self.redraw()\n\n    def _on_key_press(self, event):\n        if event.key == 'enter':\n            self.callback_on_enter(self.geometry)\n            self.redraw()\n\n    def on_mouse_press(self, event):\n        if event.button != 1 or not self.ax.in_axes(event):\n            return\n        self.update_cursor(event.xdata, event.ydata)\n        self.update_overlay(event.xdata, event.ydata)\n\n    def on_mouse_release(self, event):\n        if event.button != 1:\n            return\n        self.callback_on_release(self.geometry)\n\n    def on_move(self, event):\n        if not self.ax.in_axes(event):\n            self._cursor.set_visible(False)\n            self.redraw() # make sure cursor is not visible\n            return\n        self._cursor.set_visible(True)\n\n        self.update_cursor(event.xdata, event.ydata)\n        if event.button != 1:\n            self.redraw() # update cursor position\n            return\n        self.update_overlay(event.xdata, event.ydata)\n        self.callback_on_move(self.geometry)\n\n    def update_overlay(self, x, y):\n        overlay = self.overlay\n        overlay[self.window.at(y, x)] = self.label\n        # Note that overlay calls `redraw`\n        self.overlay = overlay\n\n    def update_cursor(self, x, y):\n        x = x - self.radius - 1\n        y = y - self.radius - 1\n        self._cursor.set_xy((x, y))\n\n    @property\n    def geometry(self):\n        return self.overlay\n\n\nclass CenteredWindow(object):\n    \"\"\"Window that create slices numpy arrays over 2D windows.\n\n    Examples\n    --------\n    >>> a = np.arange(16).reshape(4, 4)\n    >>> w = CenteredWindow(1, a.shape)\n    >>> a[w.at(1, 1)]\n    array([[ 0,  1,  2],\n           [ 4,  5,  6],\n           [ 8,  9, 10]])\n    >>> a[w.at(0, 0)]\n    array([[0, 1],\n           [4, 5]])\n    >>> a[w.at(4, 3)]\n    array([[14, 15]])\n    \"\"\"\n    def __init__(self, radius, array_shape):\n        self.radius = radius\n        self.array_shape = array_shape\n\n    def at(self, row, col):\n        h, w = self.array_shape\n        r = self.radius\n        xmin = max(0, col - r)\n        xmax = min(w, col + r + 1)\n        ymin = max(0, row - r)\n        ymax = min(h, row + r + 1)\n        return [slice(ymin, ymax), slice(xmin, xmax)]\n\n\nif __name__ == '__main__':\n    np.testing.rundocs()\n    from skimage import data\n\n    image = data.camera()\n\n    f, ax = plt.subplots()\n    ax.imshow(image, interpolation='nearest')\n    paint_tool = PaintTool(ax, image.shape)\n    plt.show()\n", "path": "skimage/viewer/canvastools/painttool.py"}], "after_files": [{"content": "import numpy as np\n\nfrom .base import Plugin\nfrom ..widgets import ComboBox, Slider\nfrom ..canvastools import PaintTool\n\n\n__all__ = ['LabelPainter']\n\n\nrad2deg = 180 / np.pi\n\n\nclass LabelPainter(Plugin):\n    name = 'LabelPainter'\n\n    def __init__(self, max_radius=20, **kwargs):\n        super(LabelPainter, self).__init__(**kwargs)\n\n        # These widgets adjust plugin properties instead of an image filter.\n        self._radius_widget = Slider('radius', low=1, high=max_radius,\n                                     value=5, value_type='int', ptype='plugin')\n        labels = [str(i) for i in range(6)]\n        labels[0] = 'Erase'\n        self._label_widget = ComboBox('label', labels, ptype='plugin')\n        self.add_widget(self._radius_widget)\n        self.add_widget(self._label_widget)\n\n        print(self.help())\n\n    def help(self):\n        helpstr = (\"Label painter\",\n                   \"Hold left-mouse button and paint on canvas.\")\n        return '\\n'.join(helpstr)\n\n    def attach(self, image_viewer):\n        super(LabelPainter, self).attach(image_viewer)\n\n        image = image_viewer.original_image\n        self.paint_tool = PaintTool(self.image_viewer.ax, image.shape,\n                                    on_enter=self.on_enter)\n        self.paint_tool.radius = self.radius\n        self.paint_tool.label = self._label_widget.index = 1\n        self.artists.append(self.paint_tool)\n\n    def _on_new_image(self, image):\n        \"\"\"Update plugin for new images.\"\"\"\n        self.paint_tool.shape = image.shape\n\n    def on_enter(self, overlay):\n        pass\n\n    @property\n    def radius(self):\n        return self._radius_widget.val\n\n    @radius.setter\n    def radius(self, val):\n        self.paint_tool.radius = val\n\n    @property\n    def label(self):\n        return self._label_widget.val\n\n    @label.setter\n    def label(self, val):\n        self.paint_tool.label = val\n", "path": "skimage/viewer/plugins/labelplugin.py"}, {"content": "import numpy as np\nimport matplotlib.pyplot as plt\nimport matplotlib.colors as mcolors\nLABELS_CMAP = mcolors.ListedColormap(['white', 'red', 'dodgerblue', 'gold',\n                                          'greenyellow', 'blueviolet'])\n\nfrom skimage.viewer.canvastools.base import CanvasToolBase\n\n\n__all__ = ['PaintTool']\n\n\nclass PaintTool(CanvasToolBase):\n    \"\"\"Widget for painting on top of a plot.\n\n    Parameters\n    ----------\n    ax : :class:`matplotlib.axes.Axes`\n        Matplotlib axes where tool is displayed.\n    overlay_shape : shape tuple\n        2D shape tuple used to initialize overlay image.\n    alpha : float (between [0, 1])\n        Opacity of overlay\n    on_move : function\n        Function called whenever a control handle is moved.\n        This function must accept the end points of line as the only argument.\n    on_release : function\n        Function called whenever the control handle is released.\n    on_enter : function\n        Function called whenever the \"enter\" key is pressed.\n    rect_props : dict\n        Properties for :class:`matplotlib.patches.Rectangle`. This class\n        redefines defaults in :class:`matplotlib.widgets.RectangleSelector`.\n\n    Attributes\n    ----------\n    overlay : array\n        Overlay of painted labels displayed on top of image.\n    label : int\n        Current paint color.\n    \"\"\"\n    def __init__(self, ax, overlay_shape, radius=5, alpha=0.3, on_move=None,\n                 on_release=None, on_enter=None, rect_props=None):\n        super(PaintTool, self).__init__(ax, on_move=on_move, on_enter=on_enter,\n                                        on_release=on_release)\n\n        props = dict(edgecolor='r', facecolor='0.7', alpha=0.5, animated=True)\n        props.update(rect_props if rect_props is not None else {})\n\n        self.alpha = alpha\n        self.cmap = LABELS_CMAP\n        self._overlay_plot = None\n        self.shape = overlay_shape\n\n        self._cursor = plt.Rectangle((0, 0), 0, 0, **props)\n        self._cursor.set_visible(False)\n        self.ax.add_patch(self._cursor)\n\n        # `label` and `radius` can only be set after initializing `_cursor`\n        self.label = 1\n        self.radius = radius\n\n        # Note that the order is important: Redraw cursor *after* overlay\n        self._artists = [self._overlay_plot, self._cursor]\n\n        self.connect_event('button_press_event', self.on_mouse_press)\n        self.connect_event('button_release_event', self.on_mouse_release)\n        self.connect_event('motion_notify_event', self.on_move)\n\n    @property\n    def label(self):\n        return self._label\n\n    @label.setter\n    def label(self, value):\n        if value >= self.cmap.N:\n            raise ValueError('Maximum label value = %s' % len(self.cmap - 1))\n        self._label = value\n        self._cursor.set_edgecolor(self.cmap(value))\n\n    @property\n    def radius(self):\n        return self._radius\n\n    @radius.setter\n    def radius(self, r):\n        self._radius = r\n        self._width = 2 * r + 1\n        self._cursor.set_width(self._width)\n        self._cursor.set_height(self._width)\n        self.window = CenteredWindow(r, self._shape)\n\n    @property\n    def overlay(self):\n        return self._overlay\n\n    @overlay.setter\n    def overlay(self, image):\n        self._overlay = image\n        if image is None:\n            self.ax.images.remove(self._overlay_plot)\n            self._overlay_plot = None\n        elif self._overlay_plot is None:\n            props = dict(cmap=self.cmap, alpha=self.alpha,\n                         norm=mcolors.no_norm(), animated=True)\n            self._overlay_plot = self.ax.imshow(image, **props)\n        else:\n            self._overlay_plot.set_data(image)\n        self.redraw()\n\n    @property\n    def shape(self):\n        return self._shape\n\n    @shape.setter\n    def shape(self, shape):\n        self._shape = shape\n        if not self._overlay_plot is None:\n            self._overlay_plot.set_extent((-0.5, shape[1] + 0.5,\n                                           shape[0] + 0.5, -0.5))\n            self.radius = self._radius\n        self.overlay = np.zeros(shape, dtype='uint8')\n\n    def _on_key_press(self, event):\n        if event.key == 'enter':\n            self.callback_on_enter(self.geometry)\n            self.redraw()\n\n    def on_mouse_press(self, event):\n        if event.button != 1 or not self.ax.in_axes(event):\n            return\n        self.update_cursor(event.xdata, event.ydata)\n        self.update_overlay(event.xdata, event.ydata)\n\n    def on_mouse_release(self, event):\n        if event.button != 1:\n            return\n        self.callback_on_release(self.geometry)\n\n    def on_move(self, event):\n        if not self.ax.in_axes(event):\n            self._cursor.set_visible(False)\n            self.redraw() # make sure cursor is not visible\n            return\n        self._cursor.set_visible(True)\n\n        self.update_cursor(event.xdata, event.ydata)\n        if event.button != 1:\n            self.redraw() # update cursor position\n            return\n        self.update_overlay(event.xdata, event.ydata)\n        self.callback_on_move(self.geometry)\n\n    def update_overlay(self, x, y):\n        overlay = self.overlay\n        overlay[self.window.at(y, x)] = self.label\n        # Note that overlay calls `redraw`\n        self.overlay = overlay\n\n    def update_cursor(self, x, y):\n        x = x - self.radius - 1\n        y = y - self.radius - 1\n        self._cursor.set_xy((x, y))\n\n    @property\n    def geometry(self):\n        return self.overlay\n\n\nclass CenteredWindow(object):\n    \"\"\"Window that create slices numpy arrays over 2D windows.\n\n    Examples\n    --------\n    >>> a = np.arange(16).reshape(4, 4)\n    >>> w = CenteredWindow(1, a.shape)\n    >>> a[w.at(1, 1)]\n    array([[ 0,  1,  2],\n           [ 4,  5,  6],\n           [ 8,  9, 10]])\n    >>> a[w.at(0, 0)]\n    array([[0, 1],\n           [4, 5]])\n    >>> a[w.at(4, 3)]\n    array([[14, 15]])\n    \"\"\"\n    def __init__(self, radius, array_shape):\n        self.radius = radius\n        self.array_shape = array_shape\n\n    def at(self, row, col):\n        h, w = self.array_shape\n        r = self.radius\n        xmin = max(0, col - r)\n        xmax = min(w, col + r + 1)\n        ymin = max(0, row - r)\n        ymax = min(h, row + r + 1)\n        return [slice(ymin, ymax), slice(xmin, xmax)]\n\n\nif __name__ == '__main__':\n    np.testing.rundocs()\n    from skimage import data\n\n    image = data.camera()\n\n    f, ax = plt.subplots()\n    ax.imshow(image, interpolation='nearest')\n    paint_tool = PaintTool(ax, image.shape)\n    plt.show()\n", "path": "skimage/viewer/canvastools/painttool.py"}]}