Repository: mindee/doctr

Issue:
Write a conversion function from Image + bounding boxes to cropped images
The detection block is currently returning a list of bounding boxes, while the recognition block is actually using cropped images. The recognition pre-processing needs to handle this.

**Input**
- images: Numpy-style encoded images (already read)
- bounding boxes: list of tensor of predictions of size N*4 (xmin, ymin, xmax, ymax)

**Output**
- cropped images: N cropped images


Discussion:

Resolving patch:
diff --git a/doctr/models/__init__.py b/doctr/models/__init__.py
--- a/doctr/models/__init__.py
+++ b/doctr/models/__init__.py
@@ -1,3 +1,4 @@
 from .preprocessor import *
 from .detection import *
+from .recognition import *
 from . import utils
diff --git a/doctr/models/recognition/__init__.py b/doctr/models/recognition/__init__.py
new file mode 100644
--- /dev/null
+++ b/doctr/models/recognition/__init__.py
@@ -0,0 +1 @@
+from ._utils import *
diff --git a/doctr/models/recognition/_utils.py b/doctr/models/recognition/_utils.py
new file mode 100644
--- /dev/null
+++ b/doctr/models/recognition/_utils.py
@@ -0,0 +1,34 @@
+# Copyright (C) 2021, Mindee.
+
+# This program is licensed under the Apache License version 2.
+# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
+
+import numpy as np
+from typing import List
+
+__all__ = ['extract_crops']
+
+
+def extract_crops(img: np.ndarray, boxes: np.ndarray) -> List[np.ndarray]:
+    """Created cropped images from list of bounding boxes
+
+    Args:
+        img: input image
+        boxes: bounding boxes of shape (N, 4) where N is the number of boxes, and the relative
+            coordinates (xmin, ymin, xmax, ymax)
+
+    Returns:
+        list of cropped images
+    """
+    if boxes.shape[1] != 4:
+        raise AssertionError("boxes are expected to be relative and in order (xmin, ymin, xmax, ymax)")
+
+    # Project relative coordinates
+    _boxes = boxes.copy()
+    _boxes[:, [0, 2]] *= img.shape[1]
+    _boxes[:, [1, 3]] *= img.shape[0]
+    _boxes = _boxes.round().astype(int)
+    # Add last index
+    _boxes[2:] += 1
+
+    return [img[box[1]: box[3], box[0]: box[2]] for box in _boxes]


Test patch:
diff --git a/test/test_models.py b/test/test_models.py
--- a/test/test_models.py
+++ b/test/test_models.py
@@ -96,3 +96,20 @@ def test_dbmodel():
     assert len(dboutput_train) == 3
     # batch size
     assert all(out.numpy().shape == (8, 640, 640, 1) for out in dboutput_train)
+
+
+def test_extract_crops(mock_pdf):  # noqa: F811
+    doc_img = read_pdf(mock_pdf)[0]
+    num_crops = 2
+    boxes = np.array([[idx / num_crops, idx / num_crops, (idx + 1) / num_crops, (idx + 1) / num_crops]
+                      for idx in range(num_crops)], dtype=np.float32)
+    croped_imgs = models.recognition.extract_crops(doc_img, boxes)
+
+    # Number of crops
+    assert len(croped_imgs) == num_crops
+    # Data type and shape
+    assert all(isinstance(crop, np.ndarray) for crop in croped_imgs)
+    assert all(crop.ndim == 3 for crop in croped_imgs)
+
+    # Identity
+    assert np.all(doc_img == models.recognition.extract_crops(doc_img, np.array([[0, 0, 1, 1]]))[0])