We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[utils] Add visualization capabilities for independent tasks
Visualization is end-to-end for the moment dynamic, but this means that a static version is not currently available, nor that there is a visualization option for text detection or text recognition only. We should discuss and add visualization for the following blocks:
- [ ] Text detection: display bounding boxes of detected items over the image
- [ ] Text recognition: display the label and confidence in a corner of the crop

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `doctr/utils/visualization.py`
Content:
```
1 # Copyright (C) 2021, Mindee.
2 
3 # This program is licensed under the Apache License version 2.
4 # See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
5 
6 import matplotlib.pyplot as plt
7 from matplotlib.figure import Figure
8 import matplotlib.patches as patches
9 import mplcursors
10 from PIL import ImageFont, ImageDraw, Image
11 import numpy as np
12 import cv2
13 from typing import Tuple, List, Dict, Any, Union, Optional
14 
15 from .common_types import BoundingBox, RotatedBbox
16 
17 __all__ = ['visualize_page', 'synthetize_page']
18 
19 
20 def rect_patch(
21     geometry: BoundingBox,
22     page_dimensions: Tuple[int, int],
23     label: Optional[str] = None,
24     color: Tuple[float, float, float] = (0, 0, 0),
25     alpha: float = 0.3,
26     linewidth: int = 2,
27     fill: bool = True,
28 ) -> patches.Rectangle:
29     """Create a matplotlib rectangular patch for the element
30 
31     Args:
32         geometry: bounding box of the element
33         page_dimensions: dimensions of the Page
34         label: label to display when hovered
35         color: color to draw box
36         alpha: opacity parameter to fill the boxes, 0 = transparent
37         linewidth: line width
38         fill: whether the patch should be filled
39 
40     Returns:
41         a rectangular Patch
42     """
43 
44     if len(geometry) != 2 or any(not isinstance(elt, tuple) or len(elt) != 2 for elt in geometry):
45         raise ValueError("invalid geometry format")
46 
47     # Unpack
48     height, width = page_dimensions
49     (xmin, ymin), (xmax, ymax) = geometry
50     # Switch to absolute coords
51     xmin, w = xmin * width, (xmax - xmin) * width
52     ymin, h = ymin * height, (ymax - ymin) * height
53 
54     return patches.Rectangle(
55         (xmin, ymin),
56         w,
57         h,
58         fill=fill,
59         linewidth=linewidth,
60         edgecolor=(*color, alpha),
61         facecolor=(*color, alpha),
62         label=label,
63     )
64 
65 
66 def polygon_patch(
67     geometry: RotatedBbox,
68     page_dimensions: Tuple[int, int],
69     label: Optional[str] = None,
70     color: Tuple[float, float, float] = (0, 0, 0),
71     alpha: float = 0.3,
72     linewidth: int = 2,
73     fill: bool = True,
74 ) -> patches.Polygon:
75     """Create a matplotlib polygon patch for the element
76 
77     Args:
78         geometry: bounding box of the element
79         page_dimensions: dimensions of the Page
80         label: label to display when hovered
81         color: color to draw box
82         alpha: opacity parameter to fill the boxes, 0 = transparent
83         linewidth: line width
84         fill: whether the patch should be filled
85 
86     Returns:
87         a polygon Patch
88     """
89 
90     if len(geometry) != 5 or any(not isinstance(elt, float) for elt in geometry):
91         raise ValueError("invalid geometry format")
92 
93     # Unpack
94     height, width = page_dimensions
95     x, y, w, h, a = geometry
96     # Switch to absolute coords
97     x, w = x * width, w * width
98     y, h = y * height, h * height
99     points = cv2.boxPoints(((x, y), (w, h), a))
100 
101     return patches.Polygon(
102         points,
103         fill=fill,
104         linewidth=linewidth,
105         edgecolor=(*color, alpha),
106         facecolor=(*color, alpha),
107         label=label,
108     )
109 
110 
111 def create_obj_patch(
112     geometry: Union[BoundingBox, RotatedBbox],
113     page_dimensions: Tuple[int, int],
114     **kwargs: Any,
115 ) -> patches.Patch:
116     """Create a matplotlib patch for the element
117 
118     Args:
119         geometry: bounding box (straight or rotated) of the element
120         page_dimensions: dimensions of the page
121 
122     Returns:
123         a matplotlib Patch
124     """
125     if isinstance(geometry, tuple):
126         if len(geometry) == 2:
127             return rect_patch(geometry, page_dimensions, **kwargs)  # type: ignore[arg-type]
128         elif len(geometry) == 5:
129             return polygon_patch(geometry, page_dimensions, **kwargs)  # type: ignore[arg-type]
130 
131     raise ValueError("invalid geometry format")
132 
133 
134 def visualize_page(
135     page: Dict[str, Any],
136     image: np.ndarray,
137     words_only: bool = True,
138     display_artefacts: bool = True,
139     scale: float = 10,
140     interactive: bool = True,
141     add_labels: bool = True,
142     **kwargs: Any,
143 ) -> Figure:
144     """Visualize a full page with predicted blocks, lines and words
145 
146     Example::
147         >>> import numpy as np
148         >>> import matplotlib.pyplot as plt
149         >>> from doctr.utils.visualization import visualize_page
150         >>> from doctr.models import ocr_db_crnn
151         >>> model = ocr_db_crnn(pretrained=True)
152         >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
153         >>> out = model([[input_page]])
154         >>> visualize_page(out[0].pages[0].export(), input_page)
155         >>> plt.show()
156 
157     Args:
158         page: the exported Page of a Document
159         image: np array of the page, needs to have the same shape than page['dimensions']
160         words_only: whether only words should be displayed
161         display_artefacts: whether artefacts should be displayed
162         scale: figsize of the largest windows side
163         interactive: whether the plot should be interactive
164         add_labels: for static plot, adds text labels on top of bounding box
165     """
166     # Get proper scale and aspect ratio
167     h, w = image.shape[:2]
168     size = (scale * w / h, scale) if h > w else (scale, h / w * scale)
169     fig, ax = plt.subplots(figsize=size)
170     # Display the image
171     ax.imshow(image)
172     # hide both axis
173     ax.axis('off')
174 
175     if interactive:
176         artists: List[patches.Patch] = []  # instantiate an empty list of patches (to be drawn on the page)
177 
178     for block in page['blocks']:
179         if not words_only:
180             rect = create_obj_patch(block['geometry'], page['dimensions'],
181                                     label='block', color=(0, 1, 0), linewidth=1, **kwargs)
182             # add patch on figure
183             ax.add_patch(rect)
184             if interactive:
185                 # add patch to cursor's artists
186                 artists.append(rect)
187 
188         for line in block['lines']:
189             if not words_only:
190                 rect = create_obj_patch(line['geometry'], page['dimensions'],
191                                         label='line', color=(1, 0, 0), linewidth=1, **kwargs)
192                 ax.add_patch(rect)
193                 if interactive:
194                     artists.append(rect)
195 
196             for word in line['words']:
197                 rect = create_obj_patch(word['geometry'], page['dimensions'],
198                                         label=f"{word['value']} (confidence: {word['confidence']:.2%})",
199                                         color=(0, 0, 1), **kwargs)
200                 ax.add_patch(rect)
201                 if interactive:
202                     artists.append(rect)
203                 elif add_labels:
204                     if len(word['geometry']) == 5:
205                         text_loc = (
206                             int(page['dimensions'][1] * (word['geometry'][0] - word['geometry'][2] / 2)),
207                             int(page['dimensions'][0] * (word['geometry'][1] - word['geometry'][3] / 2))
208                         )
209                     else:
210                         text_loc = (
211                             int(page['dimensions'][1] * word['geometry'][0][0]),
212                             int(page['dimensions'][0] * word['geometry'][0][1])
213                         )
214                     ax.text(
215                         *text_loc,
216                         word['value'],
217                         size=10,
218                         alpha=0.5,
219                         color=(0, 0, 1),
220                     )
221 
222         if display_artefacts:
223             for artefact in block['artefacts']:
224                 rect = create_obj_patch(
225                     artefact['geometry'],
226                     page['dimensions'],
227                     label='artefact',
228                     color=(0.5, 0.5, 0.5),
229                     linewidth=1,
230                     **kwargs
231                 )
232                 ax.add_patch(rect)
233                 if interactive:
234                     artists.append(rect)
235 
236     if interactive:
237         # Create mlp Cursor to hover patches in artists
238         mplcursors.Cursor(artists, hover=2).connect("add", lambda sel: sel.annotation.set_text(sel.artist.get_label()))
239     fig.tight_layout(pad=0.)
240 
241     return fig
242 
243 
244 def synthetize_page(
245     page: Dict[str, Any],
246     draw_proba: bool = False,
247     font_size: int = 13,
248 ) -> np.ndarray:
249     """Draw a the content of the element page (OCR response) on a blank page.
250 
251     Args:
252         page: exported Page object to represent
253         draw_proba: if True, draw words in colors to represent confidence. Blue: p=1, red: p=0
254         font_size: size of the font, default font = 13
255 
256     Return:
257         A np array (drawn page)
258     """
259     # Draw template
260     h, w = page["dimensions"]
261     response = 255 * np.ones((h, w, 3), dtype=np.int32)
262 
263     # Draw each word
264     for block in page["blocks"]:
265         for line in block["lines"]:
266             for word in line["words"]:
267                 # Get aboslute word geometry
268                 (xmin, ymin), (xmax, ymax) = word["geometry"]
269                 xmin, xmax = int(w * xmin), int(w * xmax)
270                 ymin, ymax = int(h * ymin), int(h * ymax)
271 
272                 # White drawing context adapted to font size, 0.75 factor to convert pts --> pix
273                 h_box, w_box = ymax - ymin, xmax - xmin
274                 h_font, w_font = font_size, int(font_size * w_box / (h_box * 0.75))
275                 img = Image.new('RGB', (w_font, h_font), color=(255, 255, 255))
276                 d = ImageDraw.Draw(img)
277 
278                 # Draw in black the value of the word
279                 d.text((0, 0), word["value"], font=ImageFont.load_default(), fill=(0, 0, 0))
280 
281                 # Resize back to box size
282                 img = img.resize((w_box, h_box), Image.NEAREST)
283 
284                 # Colorize if draw_proba
285                 if draw_proba:
286                     p = int(255 * word["confidence"])
287                     mask = np.where(np.array(img) == 0, 1, 0)
288                     proba = np.array([255 - p, 0, p])
289                     color = mask * proba[np.newaxis, np.newaxis, :]
290                     white_mask = 255 * (1 - mask)
291                     img = color + white_mask
292 
293                 # Write to response page
294                 response[ymin:ymax, xmin:xmax, :] = np.array(img)
295 
296     return response
297
```
--- 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/doctr/utils/visualization.py b/doctr/utils/visualization.py
--- a/doctr/utils/visualization.py
+++ b/doctr/utils/visualization.py
@@ -8,13 +8,14 @@
 import matplotlib.patches as patches
 import mplcursors
 from PIL import ImageFont, ImageDraw, Image
+from copy import deepcopy
 import numpy as np
 import cv2
 from typing import Tuple, List, Dict, Any, Union, Optional
 
 from .common_types import BoundingBox, RotatedBbox
 
-__all__ = ['visualize_page', 'synthetize_page']
+__all__ = ['visualize_page', 'synthetize_page', 'draw_boxes']
 
 
 def rect_patch(
@@ -294,3 +295,34 @@
                 response[ymin:ymax, xmin:xmax, :] = np.array(img)
 
     return response
+
+
+def draw_boxes(
+    boxes: np.ndarray,
+    image: np.ndarray,
+    color: Optional[Tuple] = None,
+    **kwargs
+) -> None:
+    """Draw an array of relative straight boxes on an image
+
+    Args:
+        boxes: array of relative boxes, of shape (*, 4)
+        image: np array, float32 or uint8
+    """
+    h, w = image.shape[:2]
+    # Convert boxes to absolute coords
+    _boxes = deepcopy(boxes)
+    _boxes[:, [0, 2]] *= w
+    _boxes[:, [1, 3]] *= h
+    _boxes = _boxes.astype(np.int32)
+    for box in _boxes.tolist():
+        xmin, ymin, xmax, ymax = box
+        image = cv2.rectangle(
+            image,
+            (xmin, ymin),
+            (xmax, ymax),
+            color=color if isinstance(color, tuple) else (0, 0, 255),
+            thickness=2
+        )
+    plt.imshow(image)
+    plt.plot(**kwargs)

{"golden_diff": "diff --git a/doctr/utils/visualization.py b/doctr/utils/visualization.py\n--- a/doctr/utils/visualization.py\n+++ b/doctr/utils/visualization.py\n@@ -8,13 +8,14 @@\n import matplotlib.patches as patches\n import mplcursors\n from PIL import ImageFont, ImageDraw, Image\n+from copy import deepcopy\n import numpy as np\n import cv2\n from typing import Tuple, List, Dict, Any, Union, Optional\n \n from .common_types import BoundingBox, RotatedBbox\n \n-__all__ = ['visualize_page', 'synthetize_page']\n+__all__ = ['visualize_page', 'synthetize_page', 'draw_boxes']\n \n \n def rect_patch(\n@@ -294,3 +295,34 @@\n                 response[ymin:ymax, xmin:xmax, :] = np.array(img)\n \n     return response\n+\n+\n+def draw_boxes(\n+    boxes: np.ndarray,\n+    image: np.ndarray,\n+    color: Optional[Tuple] = None,\n+    **kwargs\n+) -> None:\n+    \"\"\"Draw an array of relative straight boxes on an image\n+\n+    Args:\n+        boxes: array of relative boxes, of shape (*, 4)\n+        image: np array, float32 or uint8\n+    \"\"\"\n+    h, w = image.shape[:2]\n+    # Convert boxes to absolute coords\n+    _boxes = deepcopy(boxes)\n+    _boxes[:, [0, 2]] *= w\n+    _boxes[:, [1, 3]] *= h\n+    _boxes = _boxes.astype(np.int32)\n+    for box in _boxes.tolist():\n+        xmin, ymin, xmax, ymax = box\n+        image = cv2.rectangle(\n+            image,\n+            (xmin, ymin),\n+            (xmax, ymax),\n+            color=color if isinstance(color, tuple) else (0, 0, 255),\n+            thickness=2\n+        )\n+    plt.imshow(image)\n+    plt.plot(**kwargs)\n", "issue": "[utils] Add visualization capabilities for independent tasks\nVisualization is end-to-end for the moment dynamic, but this means that a static version is not currently available, nor that there is a visualization option for text detection or text recognition only. We should discuss and add visualization for the following blocks:\r\n- [ ] Text detection: display bounding boxes of detected items over the image\r\n- [ ] Text recognition: display the label and confidence in a corner of the crop\n", "before_files": [{"content": "# Copyright (C) 2021, Mindee.\n\n# This program is licensed under the Apache License version 2.\n# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.\n\nimport matplotlib.pyplot as plt\nfrom matplotlib.figure import Figure\nimport matplotlib.patches as patches\nimport mplcursors\nfrom PIL import ImageFont, ImageDraw, Image\nimport numpy as np\nimport cv2\nfrom typing import Tuple, List, Dict, Any, Union, Optional\n\nfrom .common_types import BoundingBox, RotatedBbox\n\n__all__ = ['visualize_page', 'synthetize_page']\n\n\ndef rect_patch(\n    geometry: BoundingBox,\n    page_dimensions: Tuple[int, int],\n    label: Optional[str] = None,\n    color: Tuple[float, float, float] = (0, 0, 0),\n    alpha: float = 0.3,\n    linewidth: int = 2,\n    fill: bool = True,\n) -> patches.Rectangle:\n    \"\"\"Create a matplotlib rectangular patch for the element\n\n    Args:\n        geometry: bounding box of the element\n        page_dimensions: dimensions of the Page\n        label: label to display when hovered\n        color: color to draw box\n        alpha: opacity parameter to fill the boxes, 0 = transparent\n        linewidth: line width\n        fill: whether the patch should be filled\n\n    Returns:\n        a rectangular Patch\n    \"\"\"\n\n    if len(geometry) != 2 or any(not isinstance(elt, tuple) or len(elt) != 2 for elt in geometry):\n        raise ValueError(\"invalid geometry format\")\n\n    # Unpack\n    height, width = page_dimensions\n    (xmin, ymin), (xmax, ymax) = geometry\n    # Switch to absolute coords\n    xmin, w = xmin * width, (xmax - xmin) * width\n    ymin, h = ymin * height, (ymax - ymin) * height\n\n    return patches.Rectangle(\n        (xmin, ymin),\n        w,\n        h,\n        fill=fill,\n        linewidth=linewidth,\n        edgecolor=(*color, alpha),\n        facecolor=(*color, alpha),\n        label=label,\n    )\n\n\ndef polygon_patch(\n    geometry: RotatedBbox,\n    page_dimensions: Tuple[int, int],\n    label: Optional[str] = None,\n    color: Tuple[float, float, float] = (0, 0, 0),\n    alpha: float = 0.3,\n    linewidth: int = 2,\n    fill: bool = True,\n) -> patches.Polygon:\n    \"\"\"Create a matplotlib polygon patch for the element\n\n    Args:\n        geometry: bounding box of the element\n        page_dimensions: dimensions of the Page\n        label: label to display when hovered\n        color: color to draw box\n        alpha: opacity parameter to fill the boxes, 0 = transparent\n        linewidth: line width\n        fill: whether the patch should be filled\n\n    Returns:\n        a polygon Patch\n    \"\"\"\n\n    if len(geometry) != 5 or any(not isinstance(elt, float) for elt in geometry):\n        raise ValueError(\"invalid geometry format\")\n\n    # Unpack\n    height, width = page_dimensions\n    x, y, w, h, a = geometry\n    # Switch to absolute coords\n    x, w = x * width, w * width\n    y, h = y * height, h * height\n    points = cv2.boxPoints(((x, y), (w, h), a))\n\n    return patches.Polygon(\n        points,\n        fill=fill,\n        linewidth=linewidth,\n        edgecolor=(*color, alpha),\n        facecolor=(*color, alpha),\n        label=label,\n    )\n\n\ndef create_obj_patch(\n    geometry: Union[BoundingBox, RotatedBbox],\n    page_dimensions: Tuple[int, int],\n    **kwargs: Any,\n) -> patches.Patch:\n    \"\"\"Create a matplotlib patch for the element\n\n    Args:\n        geometry: bounding box (straight or rotated) of the element\n        page_dimensions: dimensions of the page\n\n    Returns:\n        a matplotlib Patch\n    \"\"\"\n    if isinstance(geometry, tuple):\n        if len(geometry) == 2:\n            return rect_patch(geometry, page_dimensions, **kwargs)  # type: ignore[arg-type]\n        elif len(geometry) == 5:\n            return polygon_patch(geometry, page_dimensions, **kwargs)  # type: ignore[arg-type]\n\n    raise ValueError(\"invalid geometry format\")\n\n\ndef visualize_page(\n    page: Dict[str, Any],\n    image: np.ndarray,\n    words_only: bool = True,\n    display_artefacts: bool = True,\n    scale: float = 10,\n    interactive: bool = True,\n    add_labels: bool = True,\n    **kwargs: Any,\n) -> Figure:\n    \"\"\"Visualize a full page with predicted blocks, lines and words\n\n    Example::\n        >>> import numpy as np\n        >>> import matplotlib.pyplot as plt\n        >>> from doctr.utils.visualization import visualize_page\n        >>> from doctr.models import ocr_db_crnn\n        >>> model = ocr_db_crnn(pretrained=True)\n        >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)\n        >>> out = model([[input_page]])\n        >>> visualize_page(out[0].pages[0].export(), input_page)\n        >>> plt.show()\n\n    Args:\n        page: the exported Page of a Document\n        image: np array of the page, needs to have the same shape than page['dimensions']\n        words_only: whether only words should be displayed\n        display_artefacts: whether artefacts should be displayed\n        scale: figsize of the largest windows side\n        interactive: whether the plot should be interactive\n        add_labels: for static plot, adds text labels on top of bounding box\n    \"\"\"\n    # Get proper scale and aspect ratio\n    h, w = image.shape[:2]\n    size = (scale * w / h, scale) if h > w else (scale, h / w * scale)\n    fig, ax = plt.subplots(figsize=size)\n    # Display the image\n    ax.imshow(image)\n    # hide both axis\n    ax.axis('off')\n\n    if interactive:\n        artists: List[patches.Patch] = []  # instantiate an empty list of patches (to be drawn on the page)\n\n    for block in page['blocks']:\n        if not words_only:\n            rect = create_obj_patch(block['geometry'], page['dimensions'],\n                                    label='block', color=(0, 1, 0), linewidth=1, **kwargs)\n            # add patch on figure\n            ax.add_patch(rect)\n            if interactive:\n                # add patch to cursor's artists\n                artists.append(rect)\n\n        for line in block['lines']:\n            if not words_only:\n                rect = create_obj_patch(line['geometry'], page['dimensions'],\n                                        label='line', color=(1, 0, 0), linewidth=1, **kwargs)\n                ax.add_patch(rect)\n                if interactive:\n                    artists.append(rect)\n\n            for word in line['words']:\n                rect = create_obj_patch(word['geometry'], page['dimensions'],\n                                        label=f\"{word['value']} (confidence: {word['confidence']:.2%})\",\n                                        color=(0, 0, 1), **kwargs)\n                ax.add_patch(rect)\n                if interactive:\n                    artists.append(rect)\n                elif add_labels:\n                    if len(word['geometry']) == 5:\n                        text_loc = (\n                            int(page['dimensions'][1] * (word['geometry'][0] - word['geometry'][2] / 2)),\n                            int(page['dimensions'][0] * (word['geometry'][1] - word['geometry'][3] / 2))\n                        )\n                    else:\n                        text_loc = (\n                            int(page['dimensions'][1] * word['geometry'][0][0]),\n                            int(page['dimensions'][0] * word['geometry'][0][1])\n                        )\n                    ax.text(\n                        *text_loc,\n                        word['value'],\n                        size=10,\n                        alpha=0.5,\n                        color=(0, 0, 1),\n                    )\n\n        if display_artefacts:\n            for artefact in block['artefacts']:\n                rect = create_obj_patch(\n                    artefact['geometry'],\n                    page['dimensions'],\n                    label='artefact',\n                    color=(0.5, 0.5, 0.5),\n                    linewidth=1,\n                    **kwargs\n                )\n                ax.add_patch(rect)\n                if interactive:\n                    artists.append(rect)\n\n    if interactive:\n        # Create mlp Cursor to hover patches in artists\n        mplcursors.Cursor(artists, hover=2).connect(\"add\", lambda sel: sel.annotation.set_text(sel.artist.get_label()))\n    fig.tight_layout(pad=0.)\n\n    return fig\n\n\ndef synthetize_page(\n    page: Dict[str, Any],\n    draw_proba: bool = False,\n    font_size: int = 13,\n) -> np.ndarray:\n    \"\"\"Draw a the content of the element page (OCR response) on a blank page.\n\n    Args:\n        page: exported Page object to represent\n        draw_proba: if True, draw words in colors to represent confidence. Blue: p=1, red: p=0\n        font_size: size of the font, default font = 13\n\n    Return:\n        A np array (drawn page)\n    \"\"\"\n    # Draw template\n    h, w = page[\"dimensions\"]\n    response = 255 * np.ones((h, w, 3), dtype=np.int32)\n\n    # Draw each word\n    for block in page[\"blocks\"]:\n        for line in block[\"lines\"]:\n            for word in line[\"words\"]:\n                # Get aboslute word geometry\n                (xmin, ymin), (xmax, ymax) = word[\"geometry\"]\n                xmin, xmax = int(w * xmin), int(w * xmax)\n                ymin, ymax = int(h * ymin), int(h * ymax)\n\n                # White drawing context adapted to font size, 0.75 factor to convert pts --> pix\n                h_box, w_box = ymax - ymin, xmax - xmin\n                h_font, w_font = font_size, int(font_size * w_box / (h_box * 0.75))\n                img = Image.new('RGB', (w_font, h_font), color=(255, 255, 255))\n                d = ImageDraw.Draw(img)\n\n                # Draw in black the value of the word\n                d.text((0, 0), word[\"value\"], font=ImageFont.load_default(), fill=(0, 0, 0))\n\n                # Resize back to box size\n                img = img.resize((w_box, h_box), Image.NEAREST)\n\n                # Colorize if draw_proba\n                if draw_proba:\n                    p = int(255 * word[\"confidence\"])\n                    mask = np.where(np.array(img) == 0, 1, 0)\n                    proba = np.array([255 - p, 0, p])\n                    color = mask * proba[np.newaxis, np.newaxis, :]\n                    white_mask = 255 * (1 - mask)\n                    img = color + white_mask\n\n                # Write to response page\n                response[ymin:ymax, xmin:xmax, :] = np.array(img)\n\n    return response\n", "path": "doctr/utils/visualization.py"}], "after_files": [{"content": "# Copyright (C) 2021, Mindee.\n\n# This program is licensed under the Apache License version 2.\n# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.\n\nimport matplotlib.pyplot as plt\nfrom matplotlib.figure import Figure\nimport matplotlib.patches as patches\nimport mplcursors\nfrom PIL import ImageFont, ImageDraw, Image\nfrom copy import deepcopy\nimport numpy as np\nimport cv2\nfrom typing import Tuple, List, Dict, Any, Union, Optional\n\nfrom .common_types import BoundingBox, RotatedBbox\n\n__all__ = ['visualize_page', 'synthetize_page', 'draw_boxes']\n\n\ndef rect_patch(\n    geometry: BoundingBox,\n    page_dimensions: Tuple[int, int],\n    label: Optional[str] = None,\n    color: Tuple[float, float, float] = (0, 0, 0),\n    alpha: float = 0.3,\n    linewidth: int = 2,\n    fill: bool = True,\n) -> patches.Rectangle:\n    \"\"\"Create a matplotlib rectangular patch for the element\n\n    Args:\n        geometry: bounding box of the element\n        page_dimensions: dimensions of the Page\n        label: label to display when hovered\n        color: color to draw box\n        alpha: opacity parameter to fill the boxes, 0 = transparent\n        linewidth: line width\n        fill: whether the patch should be filled\n\n    Returns:\n        a rectangular Patch\n    \"\"\"\n\n    if len(geometry) != 2 or any(not isinstance(elt, tuple) or len(elt) != 2 for elt in geometry):\n        raise ValueError(\"invalid geometry format\")\n\n    # Unpack\n    height, width = page_dimensions\n    (xmin, ymin), (xmax, ymax) = geometry\n    # Switch to absolute coords\n    xmin, w = xmin * width, (xmax - xmin) * width\n    ymin, h = ymin * height, (ymax - ymin) * height\n\n    return patches.Rectangle(\n        (xmin, ymin),\n        w,\n        h,\n        fill=fill,\n        linewidth=linewidth,\n        edgecolor=(*color, alpha),\n        facecolor=(*color, alpha),\n        label=label,\n    )\n\n\ndef polygon_patch(\n    geometry: RotatedBbox,\n    page_dimensions: Tuple[int, int],\n    label: Optional[str] = None,\n    color: Tuple[float, float, float] = (0, 0, 0),\n    alpha: float = 0.3,\n    linewidth: int = 2,\n    fill: bool = True,\n) -> patches.Polygon:\n    \"\"\"Create a matplotlib polygon patch for the element\n\n    Args:\n        geometry: bounding box of the element\n        page_dimensions: dimensions of the Page\n        label: label to display when hovered\n        color: color to draw box\n        alpha: opacity parameter to fill the boxes, 0 = transparent\n        linewidth: line width\n        fill: whether the patch should be filled\n\n    Returns:\n        a polygon Patch\n    \"\"\"\n\n    if len(geometry) != 5 or any(not isinstance(elt, float) for elt in geometry):\n        raise ValueError(\"invalid geometry format\")\n\n    # Unpack\n    height, width = page_dimensions\n    x, y, w, h, a = geometry\n    # Switch to absolute coords\n    x, w = x * width, w * width\n    y, h = y * height, h * height\n    points = cv2.boxPoints(((x, y), (w, h), a))\n\n    return patches.Polygon(\n        points,\n        fill=fill,\n        linewidth=linewidth,\n        edgecolor=(*color, alpha),\n        facecolor=(*color, alpha),\n        label=label,\n    )\n\n\ndef create_obj_patch(\n    geometry: Union[BoundingBox, RotatedBbox],\n    page_dimensions: Tuple[int, int],\n    **kwargs: Any,\n) -> patches.Patch:\n    \"\"\"Create a matplotlib patch for the element\n\n    Args:\n        geometry: bounding box (straight or rotated) of the element\n        page_dimensions: dimensions of the page\n\n    Returns:\n        a matplotlib Patch\n    \"\"\"\n    if isinstance(geometry, tuple):\n        if len(geometry) == 2:\n            return rect_patch(geometry, page_dimensions, **kwargs)  # type: ignore[arg-type]\n        elif len(geometry) == 5:\n            return polygon_patch(geometry, page_dimensions, **kwargs)  # type: ignore[arg-type]\n\n    raise ValueError(\"invalid geometry format\")\n\n\ndef visualize_page(\n    page: Dict[str, Any],\n    image: np.ndarray,\n    words_only: bool = True,\n    display_artefacts: bool = True,\n    scale: float = 10,\n    interactive: bool = True,\n    add_labels: bool = True,\n    **kwargs: Any,\n) -> Figure:\n    \"\"\"Visualize a full page with predicted blocks, lines and words\n\n    Example::\n        >>> import numpy as np\n        >>> import matplotlib.pyplot as plt\n        >>> from doctr.utils.visualization import visualize_page\n        >>> from doctr.models import ocr_db_crnn\n        >>> model = ocr_db_crnn(pretrained=True)\n        >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)\n        >>> out = model([[input_page]])\n        >>> visualize_page(out[0].pages[0].export(), input_page)\n        >>> plt.show()\n\n    Args:\n        page: the exported Page of a Document\n        image: np array of the page, needs to have the same shape than page['dimensions']\n        words_only: whether only words should be displayed\n        display_artefacts: whether artefacts should be displayed\n        scale: figsize of the largest windows side\n        interactive: whether the plot should be interactive\n        add_labels: for static plot, adds text labels on top of bounding box\n    \"\"\"\n    # Get proper scale and aspect ratio\n    h, w = image.shape[:2]\n    size = (scale * w / h, scale) if h > w else (scale, h / w * scale)\n    fig, ax = plt.subplots(figsize=size)\n    # Display the image\n    ax.imshow(image)\n    # hide both axis\n    ax.axis('off')\n\n    if interactive:\n        artists: List[patches.Patch] = []  # instantiate an empty list of patches (to be drawn on the page)\n\n    for block in page['blocks']:\n        if not words_only:\n            rect = create_obj_patch(block['geometry'], page['dimensions'],\n                                    label='block', color=(0, 1, 0), linewidth=1, **kwargs)\n            # add patch on figure\n            ax.add_patch(rect)\n            if interactive:\n                # add patch to cursor's artists\n                artists.append(rect)\n\n        for line in block['lines']:\n            if not words_only:\n                rect = create_obj_patch(line['geometry'], page['dimensions'],\n                                        label='line', color=(1, 0, 0), linewidth=1, **kwargs)\n                ax.add_patch(rect)\n                if interactive:\n                    artists.append(rect)\n\n            for word in line['words']:\n                rect = create_obj_patch(word['geometry'], page['dimensions'],\n                                        label=f\"{word['value']} (confidence: {word['confidence']:.2%})\",\n                                        color=(0, 0, 1), **kwargs)\n                ax.add_patch(rect)\n                if interactive:\n                    artists.append(rect)\n                elif add_labels:\n                    if len(word['geometry']) == 5:\n                        text_loc = (\n                            int(page['dimensions'][1] * (word['geometry'][0] - word['geometry'][2] / 2)),\n                            int(page['dimensions'][0] * (word['geometry'][1] - word['geometry'][3] / 2))\n                        )\n                    else:\n                        text_loc = (\n                            int(page['dimensions'][1] * word['geometry'][0][0]),\n                            int(page['dimensions'][0] * word['geometry'][0][1])\n                        )\n                    ax.text(\n                        *text_loc,\n                        word['value'],\n                        size=10,\n                        alpha=0.5,\n                        color=(0, 0, 1),\n                    )\n\n        if display_artefacts:\n            for artefact in block['artefacts']:\n                rect = create_obj_patch(\n                    artefact['geometry'],\n                    page['dimensions'],\n                    label='artefact',\n                    color=(0.5, 0.5, 0.5),\n                    linewidth=1,\n                    **kwargs\n                )\n                ax.add_patch(rect)\n                if interactive:\n                    artists.append(rect)\n\n    if interactive:\n        # Create mlp Cursor to hover patches in artists\n        mplcursors.Cursor(artists, hover=2).connect(\"add\", lambda sel: sel.annotation.set_text(sel.artist.get_label()))\n    fig.tight_layout(pad=0.)\n\n    return fig\n\n\ndef synthetize_page(\n    page: Dict[str, Any],\n    draw_proba: bool = False,\n    font_size: int = 13,\n) -> np.ndarray:\n    \"\"\"Draw a the content of the element page (OCR response) on a blank page.\n\n    Args:\n        page: exported Page object to represent\n        draw_proba: if True, draw words in colors to represent confidence. Blue: p=1, red: p=0\n        font_size: size of the font, default font = 13\n\n    Return:\n        A np array (drawn page)\n    \"\"\"\n    # Draw template\n    h, w = page[\"dimensions\"]\n    response = 255 * np.ones((h, w, 3), dtype=np.int32)\n\n    # Draw each word\n    for block in page[\"blocks\"]:\n        for line in block[\"lines\"]:\n            for word in line[\"words\"]:\n                # Get aboslute word geometry\n                (xmin, ymin), (xmax, ymax) = word[\"geometry\"]\n                xmin, xmax = int(w * xmin), int(w * xmax)\n                ymin, ymax = int(h * ymin), int(h * ymax)\n\n                # White drawing context adapted to font size, 0.75 factor to convert pts --> pix\n                h_box, w_box = ymax - ymin, xmax - xmin\n                h_font, w_font = font_size, int(font_size * w_box / (h_box * 0.75))\n                img = Image.new('RGB', (w_font, h_font), color=(255, 255, 255))\n                d = ImageDraw.Draw(img)\n\n                # Draw in black the value of the word\n                d.text((0, 0), word[\"value\"], font=ImageFont.load_default(), fill=(0, 0, 0))\n\n                # Resize back to box size\n                img = img.resize((w_box, h_box), Image.NEAREST)\n\n                # Colorize if draw_proba\n                if draw_proba:\n                    p = int(255 * word[\"confidence\"])\n                    mask = np.where(np.array(img) == 0, 1, 0)\n                    proba = np.array([255 - p, 0, p])\n                    color = mask * proba[np.newaxis, np.newaxis, :]\n                    white_mask = 255 * (1 - mask)\n                    img = color + white_mask\n\n                # Write to response page\n                response[ymin:ymax, xmin:xmax, :] = np.array(img)\n\n    return response\n\n\ndef draw_boxes(\n    boxes: np.ndarray,\n    image: np.ndarray,\n    color: Optional[Tuple] = None,\n    **kwargs\n) -> None:\n    \"\"\"Draw an array of relative straight boxes on an image\n\n    Args:\n        boxes: array of relative boxes, of shape (*, 4)\n        image: np array, float32 or uint8\n    \"\"\"\n    h, w = image.shape[:2]\n    # Convert boxes to absolute coords\n    _boxes = deepcopy(boxes)\n    _boxes[:, [0, 2]] *= w\n    _boxes[:, [1, 3]] *= h\n    _boxes = _boxes.astype(np.int32)\n    for box in _boxes.tolist():\n        xmin, ymin, xmax, ymax = box\n        image = cv2.rectangle(\n            image,\n            (xmin, ymin),\n            (xmax, ymax),\n            color=color if isinstance(color, tuple) else (0, 0, 255),\n            thickness=2\n        )\n    plt.imshow(image)\n    plt.plot(**kwargs)\n", "path": "doctr/utils/visualization.py"}]}