Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added: New OMR Feature for Checkbox Detection #468

Open
wants to merge 144 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
144 commits
Select commit Hold shift + click to select a range
15cc1c7
Optimize spans/annotations data structures
mexicat Oct 11, 2023
5a729be
Fix tests
mexicat Oct 12, 2023
f2b7bbf
Optimize regex tokenizer
mexicat Oct 12, 2023
dcec882
Merge branch 'master' of https://github.com/konfuzio-ai/konfuzio-sdk …
mexicat Oct 20, 2023
34e686a
Test saving models with BentoML
mexicat Oct 22, 2023
700abc8
Better extraction service
mexicat Oct 26, 2023
1c5dfec
Response example
mexicat Jan 17, 2024
50c7acd
Added: BentoML into the requirements, test file stub
iftwigs Jan 29, 2024
0d719b3
Changed: structure of extraction service's response
iftwigs Jan 29, 2024
cb9d2a7
Changed: saving method of base model
iftwigs Jan 29, 2024
f5a8419
Added: save() method for the bento ML
iftwigs Feb 1, 2024
bfdc00b
Changed: saving for bento ML, added: loading
iftwigs Feb 1, 2024
11d4ed7
Added: build bento archives
mexicat Feb 1, 2024
d435378
Changed: locked packages, Added: tests for saving bento AIs
iftwigs Feb 2, 2024
a2a2b11
Merge branch 'master' of https://github.com/konfuzio-ai/konfuzio-sdk …
mexicat Feb 3, 2024
6a80130
Fixed: Mistakenly replaced file
mexicat Feb 3, 2024
0d2cd42
Changed: Keep schemas together with services for now
mexicat Feb 3, 2024
8b2dfcb
Added: Tests for bento-based extraction AI
iftwigs Feb 5, 2024
e326442
Changed: structure of output schema
iftwigs Feb 6, 2024
f67c8bc
Changed: structure of output schema
iftwigs Feb 6, 2024
307ebde
Merge branch 'master' into bentoml-experiments
iftwigs Feb 6, 2024
a39b80f
Changed: schema structure
iftwigs Feb 8, 2024
6be8859
Added: logging subprocess to utils, tests for bentoml passing
iftwigs Feb 9, 2024
1c480ff
Merge branch 'bentoml-experiments' of https://github.com/konfuzio-ai/…
iftwigs Feb 9, 2024
cf84a48
Merge branch 'master' into bentoml-experiments
iftwigs Feb 9, 2024
c5cf708
Changed: wrong input test
iftwigs Feb 12, 2024
ad4a767
Merge branch 'bentoml-experiments' of https://github.com/konfuzio-ai/…
iftwigs Feb 12, 2024
faafc53
Changed:dependency check
iftwigs Feb 12, 2024
99d29d0
Added: entrypoint_methods for other AIs
iftwigs Feb 12, 2024
2a05e4e
Changed: testing strategy
iftwigs Feb 12, 2024
edc5dde
Changed: structure of Bento ML-related components
iftwigs Feb 15, 2024
52733fc
Added: documentation on Bento
iftwigs Feb 20, 2024
ec6e76c
Merge branch 'master' into bentoml-experiments
iftwigs Feb 20, 2024
96036dc
Changed: run Bento save tests only on one python version for saving time
iftwigs Feb 20, 2024
f4dcb75
Changed: versioning check logic in request and response processing me…
iftwigs Feb 21, 2024
3f947c7
Changed: modules of bento and tests for it
iftwigs Feb 21, 2024
214db56
Changed: skip long test
iftwigs Feb 21, 2024
4303ec9
Changed: what gets included into bento
iftwigs Feb 23, 2024
394b40a
Changed: schemas for Bbox
iftwigs Feb 26, 2024
d0cf642
Changed: relative imports, inclusions of files
iftwigs Feb 27, 2024
5a6a12f
Fixed: bentoml build imports
mexicat Feb 27, 2024
3ecd200
Merge branch 'bentoml-experiments' of https://github.com/konfuzio-ai/…
iftwigs Feb 27, 2024
41b8d9e
Changed: update bento version
mexicat Feb 27, 2024
323af83
Added: name class attribute in schemas
iftwigs Feb 27, 2024
4a5e15e
Added: OMR bentoml integration
nengelmann Feb 29, 2024
edec5ad
Merge pull request #467 from konfuzio-ai/bentoml-experiments
nengelmann Feb 29, 2024
3650208
Added: Bentoml schemas for OMR version 20240215
nengelmann Feb 29, 2024
78fafb6
Merge branch 'master' into bentoml-experiments
iftwigs Mar 6, 2024
694f386
Merge branch 'master' into bentoml-experiments
iftwigs Mar 11, 2024
44f28b7
Changed: structures of response and request
iftwigs Mar 20, 2024
e02c580
Added: convert_response_to_annotations
mexicat Apr 3, 2024
52718a2
Merge branch 'master' of https://github.com/konfuzio-ai/konfuzio-sdk …
mexicat Apr 3, 2024
f2af87e
Fixed: convert_document_to_request and bboxes
mexicat Apr 3, 2024
f220ed8
Added: bottom calculation in Bbox
mexicat Apr 3, 2024
8963e1c
Fixed: a typo in variables docstring
iftwigs Apr 3, 2024
a7bc151
Changed: construction of request
iftwigs Apr 3, 2024
b5d2a70
Changed: use temporary branch in bento build
mexicat Apr 3, 2024
7ff7e8a
Fixed: typing of bbox id
iftwigs Apr 4, 2024
edde7d1
Merge branch 'bentoml-experiments' of https://github.com/konfuzio-ai/…
iftwigs Apr 4, 2024
7aa3a13
Fixed: include konfuzio_sdk.bento in setup.py
mexicat Apr 4, 2024
175caa7
Changed: use embedded project when preparing requests
mexicat Apr 4, 2024
72a9c96
Changed: common error message
mexicat Apr 4, 2024
d40ac50
Changed: test for a request with auto-converted document
iftwigs Apr 4, 2024
9086908
Merge branch 'bentoml-experiments' of https://github.com/konfuzio-ai/…
iftwigs Apr 4, 2024
17b4a4d
Fixed: float silently converted into integers
mexicat Apr 5, 2024
6b7caec
Merge branch 'master' into bentoml-experiments
zypriafl Apr 5, 2024
2a3a2f2
Added: testing for converting response to annotations
iftwigs Apr 8, 2024
30f4e63
Merge branch 'master' into bentoml-experiments
iftwigs Apr 9, 2024
7ccf28e
Added: Project method to recreate category and label info inside bent…
iftwigs Apr 9, 2024
5c8a598
Merge branch 'master' into bentoml-experiments
iftwigs Apr 10, 2024
2bd7551
Changes: inclusions into the bento
iftwigs Apr 10, 2024
e8e73ad
Fixed: don't shadow schema from BaseModel
mexicat Apr 11, 2024
7217c7d
Merge branch 'bentoml-experiments' of https://github.com/konfuzio-ai/…
iftwigs Apr 11, 2024
db16833
Fixed: requires attributes
mexicat Apr 15, 2024
5f7e84a
Added: segmentation in ExtractRequest20240117Page
mexicat Apr 15, 2024
b0c9002
Changed: only pass page_number in ExtractRequest20240117
mexicat Apr 15, 2024
5401539
Fixed: page_number in schema
mexicat Apr 15, 2024
688e7f2
Merge branch 'bentoml-experiments' of https://github.com/konfuzio-ai/…
iftwigs Apr 15, 2024
a56e88c
Fixed: metadata generator breaking pickling pipeline, pathless output…
iftwigs Apr 15, 2024
481db71
Merge branch 'master' into bentoml-experiments
iftwigs Apr 15, 2024
78109af
Fixed: generating a temporary json file
iftwigs Apr 15, 2024
1d017ad
Merge branch 'bentoml-experiments' of https://github.com/konfuzio-ai/…
iftwigs Apr 15, 2024
d79fc8c
Fixed: tests for bentoml
iftwigs Apr 15, 2024
cbbdf29
Changed: update bentoml
mexicat Apr 19, 2024
e4d662c
Merge branch 'bentoml-experiments' of https://github.com/konfuzio-ai/…
mexicat Apr 19, 2024
8604a98
Changed: consistent pickle/bento naming
mexicat Apr 19, 2024
6fb4b3a
Merge branch 'master' into bentoml-experiments
iftwigs Apr 19, 2024
7230e34
Fixed: tests broken by a new property in Base Model
iftwigs Apr 22, 2024
2fa33b2
Fixed: project metadata filename
mexicat Apr 29, 2024
4d7091e
Added: project_metadata endpoint in Bento services
mexicat Apr 29, 2024
856356f
Merge branch 'bentoml-experiments' of https://github.com/konfuzio-ai/…
mexicat Apr 29, 2024
c2386c2
Added: konfuzio_sdk.bento.extraction in setup.py
mexicat May 3, 2024
78b5dfb
Changed: use bento services of runners
mexicat May 6, 2024
b60b621
Fixed: move pages declaration inside schema if block
mexicat May 6, 2024
d5e843f
Changed: use pydantic 2
mexicat May 6, 2024
dce00d2
Changed: stricter pydantic version range
mexicat May 6, 2024
0cd3df1
Changed: only use basic dependencies for bento build
mexicat May 7, 2024
fab9766
Fixed: test for schema validation
iftwigs May 7, 2024
669e47b
Changed: use bento_metadata as labels
mexicat May 8, 2024
61e091d
Fixed: missing name in create_project_metadata_dict
mexicat May 10, 2024
20df3dd
Changed: Schemas
nengelmann May 23, 2024
784f363
Merge pull request #494 from konfuzio-ai/bentoml-experiments
nengelmann May 23, 2024
ba1762d
Added: ID mappings in convert_response_to_annotations
mexicat Jun 4, 2024
0bf6daf
Fixed: enforce int keys in mappings
mexicat Jun 4, 2024
6d0dde3
Added: documentation on how to save Extraction AI using Bento
iftwigs Jun 10, 2024
a494d06
Added: documentation on how to save Extraction AI using Bento
iftwigs Jun 10, 2024
fdb46f8
Merge branch 'master' of https://github.com/konfuzio-ai/konfuzio-sdk …
mexicat Jun 11, 2024
25eafab
Merge conflicts resolved
iftwigs Jun 12, 2024
b5728ae
Merge branch 'bentoml-experiments' of https://github.com/konfuzio-ai/…
iftwigs Jun 12, 2024
a7a7f36
Fixed: pydantic version in settings_importer
mexicat Jun 12, 2024
982de10
Merge branch 'master' into bentoml-experiments
mexicat Jun 13, 2024
8e668c5
Merge branch 'bentoml-experiments' of https://github.com/konfuzio-ai/…
iftwigs Jun 13, 2024
3d7f4b4
Fixed: test value
iftwigs Jun 14, 2024
de37215
Adding: Tests
nengelmann Jun 17, 2024
f928926
Changed: bento tutorial
iftwigs Jun 17, 2024
1cbf562
Changed: upgrade bentoml version
mexicat Jun 17, 2024
cb2eb51
Fixed: handle image bytes in bento serializers
mexicat Jun 18, 2024
024f335
Remove custom objects in bento build
nengelmann Jun 18, 2024
8fd04f6
Added: testing for image bytes
iftwigs Jun 18, 2024
de5e4bf
Change: Bento pulled instead of build
nengelmann Jun 19, 2024
49e0774
Merge branch 'bentoml-experiments' into bentoml-omr
nengelmann Jun 19, 2024
125f330
Changed: Schema imports
nengelmann Jun 19, 2024
b281c10
Fixed: bento service name
mexicat Jun 20, 2024
3d6cac6
Fixed: bento service name, again, maybe
mexicat Jun 20, 2024
9f45b36
Added: convert_response_to_checkbox_annotations
mexicat Jun 26, 2024
4c4e51f
Fixed: setting annotation.metadata['checkbox']
mexicat Jun 26, 2024
9b33015
Changed: init parameters for metadata and is_linked_to_checkbox
mexicat Jul 1, 2024
b84d966
Update directory structure, add hexbyte validator, etc
nengelmann Jul 3, 2024
fd3d4bd
Update and add tests for OMR
nengelmann Jul 3, 2024
75e47b2
Add OMR to extra dependencies
nengelmann Jul 3, 2024
bf24940
Merge branch 'master' into bentoml-experiments
zypriafl Jul 3, 2024
72f6e95
Update docstrings, fix typos, add type hints, small refactoring
nengelmann Jul 3, 2024
ae8b383
Merge branch 'bentoml-experiments' into bentoml-omr
nengelmann Jul 3, 2024
32781f0
Changed: added a temporary xfail to tests for uploading containerized…
iftwigs Jul 3, 2024
29c9a60
Added: API reference for the Bento functionality
iftwigs Jul 3, 2024
f94101c
Add bbox pair plot function
nengelmann Jul 4, 2024
1f5cc64
Add tests for bbox pairing class
nengelmann Jul 4, 2024
e9ef633
Merge branch 'bentoml-experiments' into bentoml-omr
nengelmann Jul 4, 2024
f8eb8af
Update Bbox pairing documentation
nengelmann Jul 5, 2024
bbcb5a0
Update sourcecode.rst
nengelmann Jul 8, 2024
0487306
Update link to konfuzio_sdk dependency
nengelmann Jul 10, 2024
e6d2c0d
Merge branch 'master' into bentoml-omr
nengelmann Jul 10, 2024
0fe9df8
Fixed: OMR serialization
mexicat Jul 11, 2024
c1fb881
Fixed: missing module in setup.py
mexicat Jul 12, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added docs/_static/img/bbox_pairs.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 13 additions & 1 deletion docs/sdk/sourcecode.rst
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,17 @@ Utils
.. autofunction:: get_spans_from_bbox
.. autofunction:: normalize_name

BboxPairing
---------------------

`[source] <https://github.com/konfuzio-ai/konfuzio-sdk/blob/master/konfuzio_sdk/trainer/omr.py>`__

.. automodule:: konfuzio_sdk.trainer.omr

.. autoclass:: BboxPairing
:members:
:noindex:

Tokenizers
=====================

Expand Down Expand Up @@ -485,4 +496,5 @@ Containerization utils
.. autofunction:: prepare_request
.. autofunction:: process_response
.. autofunction:: convert_document_to_request
.. autofunction:: convert_response_to_annotations
.. autofunction:: convert_response_to_annotations

151 changes: 147 additions & 4 deletions docs/sdk/tutorials/information_extraction/index_nb.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ from konfuzio_sdk.data import Project

project = Project(id_=TEST_PROJECT_ID)
category = project.get_category_by_id(TEST_PAYSLIPS_CATEGORY_ID)
categorization_pipeline = CustomExtractionAI(category)
extraction_pipeline = CustomExtractionAI(category)
```

Then, create a sample test Document to run the extraction on.
Expand All @@ -127,18 +127,25 @@ print(sample_document.text)

Run the extraction of a Document and print the extracted Annotations.
```python
extracted = categorization_pipeline.extract(sample_document)
extracted = extraction_pipeline.extract(sample_document)
for annotation in extracted.annotations(use_correct=False):
print(annotation.offset_string)
```

Now we can save the AI and check that it is possible to load it afterwards.
Now we can save the AI and check that it is possible to load it afterwards. There are two different ways to save an
Extraction AI: using the native `save()` method of AbstractExtractionAI that saves a model into an `lz4`-compressed
pickle file and using Bento model and using `save_bento()` method that creates a containerized instance of an Extraction
AI model in `bento` format, allowing the AI to run independently of the server environment.

To save the model to a compressed pickle file, use the following command:
```python
pickle_model_path = categorization_pipeline.save()
extraction_pipeline_loaded = CustomExtractionAI.load_model(pickle_model_path)
```

The custom Extraction AI we just prepared inherits from AbstractExtractionAI, which in turn inherits from [BaseModel](sourcecode.html#base-model). `BaseModel` provides `save` method that saves a model into a compressed pickle file that can be directly uploaded to the Konfuzio Server (see [Upload Extraction or Category AI to target instance](https://help.konfuzio.com/tutorials/migrate-trained-ai-to-an-new-project-to-annotate-documents-faster/index.html#upload-extraction-or-category-ai-to-target-instance)).
The custom Extraction AI we just prepared inherits from AbstractExtractionAI, which in turn inherits from
[BaseModel](sourcecode.html#base-model). `BaseModel` provides `save` method that saves a model into a compressed
pickle file that can be directly uploaded to the Konfuzio Server (see [Upload Extraction or Category AI to target instance](https://help.konfuzio.com/tutorials/migrate-trained-ai-to-an-new-project-to-annotate-documents-faster/index.html#upload-extraction-or-category-ai-to-target-instance)).

Activating the uploaded AI on the web interface will enable the custom pipeline on your self-hosted installation.

Expand All @@ -148,6 +155,142 @@ on app), you need to enable creating them in the Superuser Project settings if y
If you have the Superuser rights, it is also possible to upload the AI from your local machine using the
`upload_ai_model()` as described in [Upload your AI](https://dev.konfuzio.com/sdk/tutorials/upload-your-ai/index.html).

### Create and containerize a custom Extraction AI into the Bento container

It is also possible to save the model using [Bento](https://www.bentoml.com/). Let's take the code we used in the
previous tutorial and modify it to support Bento archiving.

We will add several new methods: `build_bento()` allows building the Bento archive that can later be uploaded to
Konfuzio app or an on-prem installation, as well as served and used locally. `entrypoint_methods()` is a property that
defines what methods will be exposed in a resulting Bento model. `bento_metadata()` defines what will be saved in the
model as metadata.

```python
import json
import re
import shutil
import tempfile

import bentoml

from konfuzio_sdk.data import Document, Span, Annotation, Label
from konfuzio_sdk.trainer.information_extraction import AbstractExtractionAI

class CustomExtractionAI(AbstractExtractionAI):
def extract(self, document: Document) -> Document:
document = super().extract(document)
label_set = document.category.default_label_set
label_name = 'Date'
if label_name in [label.name for label in document.category.labels]:
label = document.project.get_label_by_name(label_name)
else:
label = Label(text=label_name, project=document.project, label_sets=[label_set])
annotation_set = document.default_annotation_set
for re_match in re.finditer(r'(\d+/\d+/\d+)', document.text, flags=re.MULTILINE):
span = Span(start_offset=re_match.span(1)[0], end_offset=re_match.span(1)[1])

_ = Annotation(
document=document,
label=label,
annotation_set=annotation_set,
confidence=1.0,
spans=[span],
)
return document

@property
def entrypoint_methods(self) -> dict:
return {
'extract': {'batchable': False},
'evaluate': {'batchable': False},
}

@property
def bento_metadata(self) -> dict:
return {
'requires_images': getattr(self, 'requires_images', False),
'requires_segmentation': getattr(self, 'requires_segmentation', False),
'requires_text': getattr(self, 'requires_text', False),
'request': 'ExtractRequest20240117',
'response': 'ExtractResponse20240117',
}

def build_bento(self, bento_model):
bento_module_dir = 'konfuzio-sdk/konfuzio_sdk/bento/extraction'
dict_metadata = self.project.create_project_metadata_dict()

with tempfile.TemporaryDirectory() as temp_dir:
shutil.copytree(bento_module_dir, temp_dir + '/extraction')
with open(f'{temp_dir}/categories_and_label_data.json5', 'w') as f:
json.dump(dict_metadata, f, indent=2, sort_keys=True)
with open(f'{temp_dir}/AI_MODEL_NAME', 'w') as f:
f.write(self._pkl_name)

built_bento = bentoml.bentos.build(
name=f"extraction_{self.category.id_ if self.category else '0'}",
service=f'extraction/rfextractionai_service.py:ExtractionService',
include=[
'extraction/*.py',
'categories_and_label_data.json5',
'AI_MODEL_NAME',
],
labels=self.bento_metadata,
python={
'packages': [
'https://github.com/konfuzio-ai/konfuzio-sdk/archive/refs/heads/bentoml-experiments.zip#egg=konfuzio-sdk'
],
'lock_packages': True,
},
build_ctx=temp_dir,
models=[str(bento_model.tag)],
)

return built_bento
```

Let's initialize the AI:

```python
from konfuzio_sdk.data import Project

project = Project(id_=YOUR_PROJECT_ID)
category = project.get_category_by_id(YOUR_CATEGORY_ID)
extraction_pipeline = CustomExtractionAI(category)
```

To save the model to the `bento` format, run the following code:
```python
bento, path_to_bento = CustomExtractionAI.save_bento() # you can specify the path via output_dir argument
```

Check that the Bento was successfully saved via the following command:
```commandline tags=["skip-execution", "nbval-skip"]
bentoml list
```

Later, the saved Bento file can be uploaded to server or served locally. To serve locally, you need to know the
name and the version of the Bento:

```python
bento_name = bento.tag.name
bento_version = bento.tag.version
print('Bento name: ' + bento_name)
print('Bento version: ' + bento_version)
```

Then you can serve it via the command:

```commandline tags=["skip-execution", "nbval-skip"]
bentoml serve name:version # for example, extraction_11:2qytjiwhoc7flhbp
```

After that, you can check the Swagger for the Bento on `0.0.0.0:3000` and send requests to the available endpoint(s).

To run a Bento instance as a container and test it, use a following command:

```commandline tags=["skip-execution", "nbval-skip"]
bentoml containerize name:version # for example, extraction_11:2qytjiwhoc7flhbp
```

### The Paragraph Custom Extraction AI
In [the Paragraph Tokenizer tutorial](https://dev.konfuzio.com/sdk/tutorials/tokenizers/index.html#paragraph-tokenization), we saw how we can use the Paragraph Tokenizer in `detectron` mode and with the `create_detectron_labels` option to segment a Document and create `figure`, `table`, `list`, `text` and `title` Annotations.
Expand Down
1 change: 1 addition & 0 deletions konfuzio_sdk/bento/extraction/schemas.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Define pydantic models for request and response from the Extraction AI."""

from typing import Any, Dict, List, Optional, Tuple, Union

from pydantic import BaseModel, PlainSerializer, PlainValidator, WithJsonSchema, errors
Expand Down
3 changes: 3 additions & 0 deletions konfuzio_sdk/bento/extraction/utils.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
"""Utility functions for adapting Konfuzio concepts to be used with Pydantic models."""

from typing import Optional

from pydantic import BaseModel

from konfuzio_sdk.data import Annotation, AnnotationSet, Document, Page, Project, Span


from .schemas import ExtractRequest20240117, ExtractRequest20240117Page, ExtractResponse20240117

NOT_IMPLEMENTED_ERROR_MESSAGE = (
Expand Down Expand Up @@ -138,6 +140,7 @@ def convert_document_to_request(document: Document, schema: BaseModel = ExtractR
},
pages=pages,
)

else:
raise NotImplementedError(NOT_IMPLEMENTED_ERROR_MESSAGE)
return converted
Expand Down
Empty file.
144 changes: 144 additions & 0 deletions konfuzio_sdk/bento/omr/checkboxdetector_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
"""Checkbox Detection BentoML service."""

import logging
import os
from io import BytesIO
from typing import Any

import bentoml
import numpy as np
from fastapi import FastAPI
from omr.schemas import CheckboxRequest20240523, CheckboxResponse20240523
from PIL import Image

# import from the built bento directory src/trainer/omr.py
from trainer.omr import CheckboxDetectorUtils

from konfuzio_sdk.trainer.omr import BboxPairing

# load ai model name AI_MODEL_NAME file in parent directory
ai_model_name_file = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'AI_MODEL_NAME')
ai_model_name = open(ai_model_name_file).read().strip()

app = FastAPI()
logger = logging.getLogger(__name__)


@bentoml.service
@bentoml.mount_asgi_app(app, path='/v1')
class CheckboxService:
"""Checkbox Detection BentoML service."""

def __init__(self) -> None:
"""Load the checkbox detection model and serve it."""
self.extraction_model = bentoml.torchscript.load_model(ai_model_name + ':latest')
self.detector_utils = CheckboxDetectorUtils()
self.bbox_pairing = BboxPairing()

@bentoml.api(input_spec=CheckboxRequest20240523)
async def extract(self, **request: Any) -> CheckboxResponse20240523:
"""Detect checkboxes on every page image, map the checkbox to the according Annotation and return the result as meta data.

:param request: The request containing the page images and annotations according to the specified schema.
:returns: The response containing the meta data of the detected checkboxes according to the specified schema.
"""

request = CheckboxRequest20240523(**request)

metadata = []

for page in request.pages:
page_image = Image.open(BytesIO(page.image))
page_image = page_image.convert('RGB')

annotations = [a for a in request.annotations if a.page_id == page.page_id]

image_size = page_image.size
page_size = (page.width, page.height)

# convert the annotation bbox from page to image coordinates
annotation_boxes = [
(coords_page2img(a.bbox.x0, a.bbox.x1, a.bbox.y0, a.bbox.y1, page_size, image_size))
for a in annotations
]
annotation_boxes = [(x0, y0, x1, y1) for x0, x1, y0, y1 in annotation_boxes]
annotation_boxes = np.array(annotation_boxes)

# compute the checkbox detection
image_tensor = self.detector_utils.preprocess(image=page_image, out_shape=(1280, 1280))
outputs = self.extraction_model(image_tensor)
cls_conf, checkboxes = self.detector_utils.postprocess(
outputs, page_image.size, request.detection_threshold
)
checked = [c[0] > c[1] for c in cls_conf]
confidence = [max(c) for c in cls_conf]

# pair the checkboxes to the annotations
ann_boxes_ind, checkbox_ind = self.bbox_pairing.find_pairs(annotation_boxes, checkboxes)

# convert the checkboxes from image coordinates to document coordinates
checkboxes = [coords_img2page(x0, x1, y0, y1, page_size, image_size) for x0, y0, x1, y1 in checkboxes]
checkboxes = [{'x0': x0, 'x1': x1, 'y0': y0, 'y1': y1} for x0, x1, y0, y1 in checkboxes]

# update the metadata of the annotations with the checkbox information
for ann_idx, chbx_idx in zip(ann_boxes_ind, checkbox_ind):
chbx_meta = {
'is_checked': checked[chbx_idx],
'bbox': checkboxes[chbx_idx],
'confidence': float(confidence[chbx_idx]),
}
a_id_ = annotations[ann_idx].annotation_id
metadata.append({'annotation_id': a_id_, 'checkbox': chbx_meta})

return CheckboxResponse20240523(metadata=metadata)


def coords_img2page(x0: int, x1: int, y0: int, y1: int, page_shape: tuple, image_shape: tuple) -> tuple:
"""Convert and scale bbox coordinates from image to page.

:param x0: The x0 coordinate of the bbox in image coordinates.
:param x1: The x1 coordinate of the bbox in image coordinates.
:param y0: The y0 coordinate of the bbox in image coordinates.
:param y1: The y1 coordinate of the bbox in image coordinates.
:param page_shape: The shape of the page image (width, height).
:param image_shape: The shape of the image (width, height).
:returns: The converted and scaled bbox coordinates in page coordinates.
"""
(page_w, page_h) = page_shape
(image_w, image_h) = image_shape

scale_y = page_h / image_h
scale_x = page_w / image_w

# scale
y0, y1 = y0 * scale_y, y1 * scale_y
x0, x1 = x0 * scale_x, x1 * scale_x
# convert
y0, y1 = page_h - y1, page_h - y0

return int(x0), int(x1), int(y0), int(y1)


def coords_page2img(x0: int, x1: int, y0: int, y1: int, page_shape: tuple, image_shape: tuple) -> tuple:
"""Convert and scale bbox coordinates from page to image coordinates.

:param x0: The x0 coordinate of the bbox in image coordinates.
:param x1: The x1 coordinate of the bbox in image coordinates.
:param y0: The y0 coordinate of the bbox in image coordinates.
:param y1: The y1 coordinate of the bbox in image coordinates.
:param page_shape: The shape of the page image (width, height).
:param image_shape: The shape of the image (width, height).
:returns: The converted and scaled bbox coordinates in image coordinates.
"""
(page_w, page_h) = page_shape
(image_w, image_h) = image_shape

scale_y = image_h / page_h
scale_x = image_w / page_w
# scale
y0, y1 = y0 * scale_y, y1 * scale_y
x0, x1 = x0 * scale_x, x1 * scale_x
# convert
y0, y1 = image_h - y1, image_h - y0

return int(x0), int(x1), int(y0), int(y1)
Loading
Loading