Skip to content

Commit

Permalink
update api tests
Browse files Browse the repository at this point in the history
  • Loading branch information
felixdittrich92 committed Apr 11, 2024
1 parent 6b1ed8b commit 43079b4
Show file tree
Hide file tree
Showing 9 changed files with 404 additions and 85 deletions.
5 changes: 2 additions & 3 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,8 @@ jobs:
python-version: ${{ matrix.python }}
architecture: x64
- name: Build & run docker
run: cd api && docker compose up -d --build
run: cd api && make lock && make run
- name: Ping server
run: wget --spider --tries=12 http://localhost:8080/docs
- name: Run docker test
run: |
docker-compose -f api/docker-compose.yml exec --no-TTY web pytest tests/
run: make test
2 changes: 1 addition & 1 deletion api/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@ test:
docker cp requirements-dev.txt api_web:/app/requirements-dev.txt
docker compose exec -T web pip install -r requirements-dev.txt
docker cp tests api_web:/app/tests
docker compose exec -T web pytest tests/
docker compose exec -T web pytest tests/ -vv
docker compose down
215 changes: 215 additions & 0 deletions api/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,218 @@ async def test_app_asyncio():
# for httpx>=20, follow_redirects=True (cf. https://github.com/encode/httpx/releases/tag/0.20.0)
async with AsyncClient(app=app, base_url="http://test", follow_redirects=True) as ac:
yield ac # testing happens here


@pytest_asyncio.fixture(scope="function")
def mock_detection_response():
return {
"box": {
"name": "117319856-fc35bf00-ae8b-11eb-9b51-ca5aba673466.jpg",
"geometries": [
[0.724609375, 0.1787109375, 0.7900390625, 0.2080078125],
[0.6748046875, 0.1796875, 0.7314453125, 0.20703125],
],
},
"poly": {
"name": "117319856-fc35bf00-ae8b-11eb-9b51-ca5aba673466.jpg",
"geometries": [
[
0.7873152494430542,
0.17740710079669952,
0.7884310483932495,
0.20474515855312347,
0.7244035005569458,
0.20735852420330048,
0.7232877016067505,
0.18002046644687653,
],
[
0.7286394834518433,
0.17740298807621002,
0.7298480272293091,
0.2027825564146042,
0.6746810674667358,
0.20540954172611237,
0.67347252368927,
0.1800299733877182,
],
],
},
}


@pytest_asyncio.fixture(scope="function")
def mock_kie_response():
return {
"box": {
"name": "117319856-fc35bf00-ae8b-11eb-9b51-ca5aba673466.jpg",
"orientation": {"value": None, "confidence": None},
"language": {"value": None, "confidence": None},
"predictions": [
{
"class_name": "words",
"items": [
{
"value": "Hello",
"geometry": [0.7471996155154171, 0.1796875, 0.8272978149561669, 0.20703125],
"confidence": 1,
},
{
"value": "world!",
"geometry": [0.8176307908857315, 0.1787109375, 0.9101580212741838, 0.2080078125],
"confidence": 1,
},
],
}
],
},
"poly": {
"name": "117319856-fc35bf00-ae8b-11eb-9b51-ca5aba673466.jpg",
"orientation": {"value": None, "confidence": None},
"language": {"value": None, "confidence": None},
"predictions": [
{
"class_name": "words",
"items": [
{
"value": "Hello",
"geometry": [
0.7453157305717468,
0.1800299733877182,
0.8233299851417542,
0.17740298807621002,
0.8250390291213989,
0.2027825564146042,
0.7470247745513916,
0.20540954172611237,
],
"confidence": 0.99,
},
{
"value": "world!",
"geometry": [
0.8157618045806885,
0.18002046644687653,
0.9063061475753784,
0.17740710079669952,
0.9078840017318726,
0.20474515855312347,
0.8173396587371826,
0.20735852420330048,
],
"confidence": 1,
},
],
}
],
},
}


@pytest_asyncio.fixture(scope="function")
def mock_ocr_response():
return {
"box": {
"name": "117319856-fc35bf00-ae8b-11eb-9b51-ca5aba673466.jpg",
"orientation": {"value": None, "confidence": None},
"language": {"value": None, "confidence": None},
"items": [
{
"blocks": [
{
"geometry": [0.7471996155154171, 0.1787109375, 0.9101580212741838, 0.2080078125],
"lines": [
{
"geometry": [0.7471996155154171, 0.1787109375, 0.9101580212741838, 0.2080078125],
"words": [
{
"value": "Hello",
"geometry": [0.7471996155154171, 0.1796875, 0.8272978149561669, 0.20703125],
"confidence": 1,
},
{
"value": "world!",
"geometry": [
0.8176307908857315,
0.1787109375,
0.9101580212741838,
0.2080078125,
],
"confidence": 1,
},
],
}
],
}
]
}
],
},
"poly": {
"name": "117319856-fc35bf00-ae8b-11eb-9b51-ca5aba673466.jpg",
"orientation": {"value": None, "confidence": None},
"language": {"value": None, "confidence": None},
"items": [
{
"blocks": [
{
"geometry": [
0.7451040148735046,
0.17927837371826172,
0.9062581658363342,
0.17407986521720886,
0.9072266221046448,
0.2041015625,
0.7460724711418152,
0.20930007100105286,
],
"lines": [
{
"geometry": [
0.7451040148735046,
0.17927837371826172,
0.9062581658363342,
0.17407986521720886,
0.9072266221046448,
0.2041015625,
0.7460724711418152,
0.20930007100105286,
],
"words": [
{
"value": "Hello",
"geometry": [
0.7453157305717468,
0.1800299733877182,
0.8233299851417542,
0.17740298807621002,
0.8250390291213989,
0.2027825564146042,
0.7470247745513916,
0.20540954172611237,
],
"confidence": 0.99,
},
{
"value": "world!",
"geometry": [
0.8157618045806885,
0.18002046644687653,
0.9063061475753784,
0.17740710079669952,
0.9078840017318726,
0.20474515855312347,
0.8173396587371826,
0.20735852420330048,
],
"confidence": 1,
},
],
}
],
}
]
}
],
},
}
65 changes: 47 additions & 18 deletions api/tests/routes/test_detection.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,58 @@
import numpy as np
import pytest
from scipy.optimize import linear_sum_assignment

from doctr.utils.metrics import box_iou

def common_test(json_response, expected_response):
assert isinstance(json_response, list) and len(json_response) == 2
first_pred = json_response[0] # it's enough to test for the first file because the same image is used twice

assert isinstance(first_pred["name"], str)
np.testing.assert_allclose(first_pred["geometries"], expected_response["geometries"], rtol=1e-2)


@pytest.mark.asyncio
async def test_text_detection(test_app_asyncio, mock_detection_image, mock_txt_file):
response = await test_app_asyncio.post("/detection", files={"files": [mock_detection_image] * 2})
async def test_text_detection_box(test_app_asyncio, mock_detection_image, mock_detection_response):
headers = {
"accept": "application/json",
}
params = {"det_arch": "db_resnet50"}
files = [
("files", ("test.jpg", mock_detection_image, "image/jpeg")),
("files", ("test2.jpg", mock_detection_image, "image/jpeg")),
]
response = await test_app_asyncio.post("/detection", params=params, files=files, headers=headers)
assert response.status_code == 200
json_response = response.json()

gt_boxes = np.array([[1240, 430, 1355, 470], [1360, 430, 1495, 470]], dtype=np.float32)
gt_boxes[:, [0, 2]] = gt_boxes[:, [0, 2]] / 1654
gt_boxes[:, [1, 3]] = gt_boxes[:, [1, 3]] / 2339
expected_box_response = mock_detection_response["box"]
common_test(json_response, expected_box_response)

# Check that IoU with GT if reasonable
assert isinstance(json_response, list) and len(json_response) == 2
first_pred = json_response[0] # it's enough to test for the first file because the same image is used twice
assert isinstance(first_pred, dict) and len(first_pred["geometries"]) == gt_boxes.shape[0]
pred_boxes = np.array(first_pred["geometries"])
iou_mat = box_iou(gt_boxes, pred_boxes)
gt_idxs, pred_idxs = linear_sum_assignment(-iou_mat)
is_kept = iou_mat[gt_idxs, pred_idxs] >= 0.8
assert gt_idxs[is_kept].shape[0] == gt_boxes.shape[0]

response = await test_app_asyncio.post("/detection", files={"files": [mock_txt_file]})

@pytest.mark.asyncio
async def test_text_detection_poly(test_app_asyncio, mock_detection_image, mock_detection_response):
headers = {
"accept": "application/json",
}
params = {"det_arch": "db_resnet50", "assume_straight_pages": False}
files = [
("files", ("test.jpg", mock_detection_image, "image/jpeg")),
("files", ("test2.jpg", mock_detection_image, "image/jpeg")),
]
response = await test_app_asyncio.post("/detection", params=params, files=files, headers=headers)
assert response.status_code == 200
json_response = response.json()

expected_poly_response = mock_detection_response["poly"]
common_test(json_response, expected_poly_response)


@pytest.mark.asyncio
async def test_text_detection_invalid_file(test_app_asyncio, mock_txt_file):
headers = {
"accept": "application/json",
}
files = [
("files", ("test.txt", mock_txt_file)),
]
response = await test_app_asyncio.post("/detection", files=files, headers=headers)
assert response.status_code == 400
Loading

0 comments on commit 43079b4

Please sign in to comment.