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

first implementation of the microservice. #1

Merged
merged 2 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
73 changes: 20 additions & 53 deletions README.md
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't forget to add the

_Check the [related documentation](https://docs.swiss-ai-center.ch/reference/services/image-draw-bounding-boxes) for more information._

to the README

Original file line number Diff line number Diff line change
@@ -1,53 +1,20 @@
# Create a new service (generic) template

This repository contains the Python + FastAPI template to create a service
without a model or from an existing model compatible with the Core engine.

Please read the documentation at
<https://docs.swiss-ai-center.ch/how-to-guides/how-to-create-a-new-service> to
understand how to use this template.

## Guidelines

TODO: Add instructions on how to edit this template.

### Publishing and deploying using a CI/CD pipeline

This is the recommended way to publish and deploy your service if you have
access to GitHub Actions or GitLab CI.

TODO

### Publishing and deploying manually

This is the recommended way to publish and deploy your service if you do not
have access to GitHub Actions or GitLab CI or do not want to use these services.

TODO

## Checklist

These checklists allow you to ensure everything is set up correctly.

### Common tasks

- [ ] Rename the project in the [`pyproject.toml`](./pyproject.toml) file
- [x] Add files that must be ignored to the [`.gitignore`](.gitignore) configuration file
- [ ] TODO

### Publishing and deploying using a CI/CD pipeline

> [!NOTE]
> This checklist is specific to the _Publishing and deploying using a CI/CD
> pipeline_ section.

- [x] Add the environment variables
- [ ] TODO

### Publishing and deploying manually

> [!NOTE]
> This checklist is specific to the _Publishing and deploying manually_ section.

- [x] Edit the [`.env`](.env) configuration file
- [ ] TODO
# Image Draw Bounding Boxes
This microservice draws boxes on an image. It is intended to work with the
text recognition OCR service. The bounding boxes are passed in a JSON file that corresponds to the output of
the text recognition service. Namely, it must have the following structure:
```
{"boxes":[
"position":{
"left": ...,
"top": ...,
"width": ...,
"height": ...,
},
{"position": ...},
...
]}
```
Where the values in "left", "top", "width" and "height" are given in pixels.
The bounding boxes are drawn as red unfilled rectangles at the coordinates specified by the 4 fields above.

The JSON file may contain additional fields; they will be ignored.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[project]
name = "service" # Edit this and remove comment
name = "image-draw-bounding-boxes"

[tool.pytest.ini_options]
pythonpath = [".", "src"]
Expand Down
57 changes: 57 additions & 0 deletions requirements-all.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
aiobotocore==2.12.1
aiofiles==23.2.1
aiohappyeyeballs==2.4.0
aiohttp==3.10.5
aioitertools==0.11.0
aiosignal==1.3.1
annotated-types==0.7.0
anyio==4.4.0
async-timeout==4.0.3
attrs==24.2.0
botocore==1.34.51
certifi==2024.7.4
click==8.1.7
common_code @ git+https://github.com/swiss-ai-center/common-code.git@e8bfec8bc00995aa2a0b022bcac03780d38856af
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be removed

coverage==7.6.1
dnspython==2.6.1
email_validator==2.1.1
exceptiongroup==1.2.2
fastapi==0.110.0
flake8==7.0.0
frozenlist==1.4.1
h11==0.14.0
httpcore==1.0.5
httpx==0.27.0
idna==3.8
iniconfig==2.0.0
jmespath==1.0.1
MarkupSafe==2.1.5
mccabe==0.7.0
multidict==6.0.5
packaging==24.1
pillow==10.4.0
pip==22.0.2
pluggy==1.5.0
pycodestyle==2.11.1
pydantic==2.8.2
pydantic-settings==2.2.1
pydantic_core==2.20.1
pyflakes==3.2.0
pytest==8.1.1
pytest-asyncio==0.23.5.post1
pytest-cov==4.1.0
pytest_httpserver==1.0.10
python-dateutil==2.9.0.post0
python-dotenv==1.0.1
PyYAML==6.0.1
setuptools==59.6.0
six==1.16.0
sniffio==1.3.1
starlette==0.36.3
tomli==2.0.1
typing_extensions==4.12.2
urllib3==2.0.7
uvicorn==0.28.0
Werkzeug==3.0.4
wrapt==1.16.0
yarl==1.9.4
4 changes: 3 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
common-code[test] @ git+https://github.com/swiss-ai-center/common-code.git@main
pillow~=10.4.0
pip~=22.0.2
setuptools~=59.6.0
Empty file added src/draw_boxes/__init__.py
Empty file.
14 changes: 14 additions & 0 deletions src/draw_boxes/draw_boxes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from PIL import Image, ImageDraw
import io


def draw_bounding_boxes(image, boxes):
imageStream = io.BytesIO(image)
image = Image.open(imageStream)
image = ImageDraw.Draw(image)
for elt in boxes:
box = elt['position']
x, y, w, h = box['left'], box['top'], box['width'], box['height']
shape = [x, y, x + w, y + h]
image.rectangle(shape, fill=None, outline="red")
return image._image
45 changes: 28 additions & 17 deletions src/main.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import asyncio
import time

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import RedirectResponse
Expand All @@ -19,15 +20,16 @@
from contextlib import asynccontextmanager

# Imports required by the service's model
# TODO: 1. ADD REQUIRED IMPORTS (ALSO IN THE REQUIREMENTS.TXT)
from draw_boxes.draw_boxes import draw_bounding_boxes
import io
import json

settings = get_settings()


class MyService(Service):
# TODO: 2. CHANGE THIS DESCRIPTION
"""
My service model
bounding boxes drawing service
"""

# Any additional fields must be excluded for Pydantic to work
Expand All @@ -36,14 +38,12 @@ class MyService(Service):

def __init__(self):
super().__init__(
# TODO: 3. CHANGE THE SERVICE NAME AND SLUG
name="My Service",
slug="my-service",
name="Image Draw Bounding Boxes",
slug="image-draw-bounding-boxes",
url=settings.service_url,
summary=api_summary,
description=api_description,
status=ServiceStatus.AVAILABLE,
# TODO: 4. CHANGE THE INPUT AND OUTPUT FIELDS, THE TAGS AND THE HAS_AI VARIABLE
data_in_fields=[
FieldDescription(
name="image",
Expand All @@ -52,10 +52,14 @@ def __init__(self):
FieldDescriptionType.IMAGE_JPEG,
],
),
FieldDescription(
name="data",
type=[FieldDescriptionType.APPLICATION_JSON]
)
],
data_out_fields=[
FieldDescription(
name="result", type=[FieldDescriptionType.APPLICATION_JSON]
name="image-with-boxes", type=[FieldDescriptionType.IMAGE_PNG, FieldDescriptionType.IMAGE_JPEG]
),
],
tags=[
Expand All @@ -70,18 +74,26 @@ def __init__(self):
)
self._logger = get_logger(settings)

# TODO: 5. CHANGE THE PROCESS METHOD (CORE OF THE SERVICE)
def process(self, data):
# NOTE that the data is a dictionary with the keys being the field names set in the data_in_fields
# The objects in the data variable are always bytes. It is necessary to convert them to the desired type
# before using them.
# raw = data["image"].data
# input_type = data["image"].type
# ... do something with the raw data
img = data['image'].data
img_format = data['image'].type
boxes_data = json.loads(data['data'].data)

img_boxes = draw_bounding_boxes(image=img, boxes=boxes_data['boxes'])
img_type = str(data['image'].type).split('_')[1]
img_byte_arr = io.BytesIO()
img_boxes.save(img_byte_arr, format=img_type)
img_byte_arr = img_byte_arr.getvalue()

# NOTE that the result must be a dictionary with the keys being the field names set in the data_out_fields
return {
"result": TaskData(data=..., type=FieldDescriptionType.APPLICATION_JSON)
"image-with-boxes": TaskData(data=img_byte_arr, type=img_format)
}


Expand Down Expand Up @@ -135,19 +147,18 @@ async def announce():
await service_service.graceful_shutdown(my_service, engine_url)


# TODO: 6. CHANGE THE API DESCRIPTION AND SUMMARY
api_description = """My service
bla bla bla...
api_description = """This microservice draws boxes on an image. It is intended to work with the
text recognition OCR service. The bounding boxes are passed in a JSON file that corresponds to the output of
the text recognition service.
"""
api_summary = """My service
bla bla bla...
api_summary = """This microservice draws boxes on an image. It is intended to work with the
text recognition OCR service.
"""

# Define the FastAPI application with information
# TODO: 7. CHANGE THE API TITLE, VERSION, CONTACT AND LICENSE
app = FastAPI(
lifespan=lifespan,
title="Sample Service API.",
title="Image Draw Bounding Boxes Service API.",
description=api_description,
version="0.0.1",
contact={
Expand Down
Loading