Skip to content

PaddleOCR as a Backend Service for Label Studio Supporting Interactive BBOX OCR in 80 Languages #466

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

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
11 changes: 11 additions & 0 deletions label_studio_ml/examples/paddleocr/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Dockerfile
README.md
*.pyc
*.pyo
*.pyd
__pycache__
.pytest_cache
.idea
docker-compose.yml
data
logs
30 changes: 30 additions & 0 deletions label_studio_ml/examples/paddleocr/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
FROM python:3.10-slim-bullseye

WORKDIR /tmp
COPY requirements.txt .

ENV PYTHONUNBUFFERED=True \
PORT=${PORT:-9090} \
PIP_CACHE_DIR=/.cache

# Installing libGL
RUN apt-get update && apt-get install -y \
libgl1 libgomp1 libglib2.0-0 libsm6 libxrender1 libxext6


RUN pip install --upgrade pip \
&& pip install -r requirements.txt


#COPY uwsgi.ini /etc/uwsgi/
COPY supervisord.conf /etc/supervisor/conf.d/

WORKDIR /app

COPY * /app/

EXPOSE 9090

CMD ["/usr/local/bin/supervisord", \
"-c", \
"/etc/supervisor/conf.d/supervisord.conf"]
213 changes: 213 additions & 0 deletions label_studio_ml/examples/paddleocr/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
## Interactive BBOX OCR using PaddleOCR
Using an OCR engine for Interactive ML-Assisted Labelling, this functionality
can speed up annotation for layout detection, classification and recognition
models.

PaddleOCR is used for OCR but minimal adaptation is needed to connect other OCR
engines or models.

PaddleOCR supports 80 languages. refer to https://github.com/Mushroomcat9998/PaddleOCR/blob/main/doc/doc_en/multi_languages_en.md#language_abbreviations

Tested againt Label Studio 1.10.1, with basic support for both Label Studio
Local File Storage and S3-compatible storage, with a example data storage with
Minio.

### Setup process
0. Download and install Docker with Docker Compose. For MacOS and Windows users,
we suggest using Docker Desktop. You will also need to have git installed.

1. Launch LabelStudio.

```
docker run -it \
-p 8080:8080 \
-v `pwd`/mydata:/label-studio/data \
heartexlabs/label-studio:latest
```

Optionally, you may enable local file serving in Label Studio

```
docker run -it \
-p 8080:8080 \
-v `pwd`/mydata:/label-studio/data \
--env LABEL_STUDIO_LOCAL_FILES_SERVING_ENABLED=true \
--env LABEL_STUDIO_LOCAL_FILES_DOCUMENT_ROOT=/label-studio/data/images \
heartexlabs/label-studio:latest
```
If you're using local file serving, be sure to get a copy of the API token from
Label Studio to connect the model.

2. Create a new project for PaddleOCR. In the project **Settings** set up the **Labeling Interface**.

Fill in the following template code. It's important to specify `smart="true"` in RectangleLabels.
```
<View>
<Image name="image" value="$ocr" zoom="true" zoomControl="false"
rotateControl="true" width="100%" height="100%"
maxHeight="auto" maxWidth="auto"/>

<RectangleLabels name="bbox" toName="image" strokeWidth="1" smart="true">
<Label value="Label1" background="green"/>
<Label value="Label2" background="blue"/>
<Label value="Label3" background="red"/>
</RectangleLabels>

<TextArea name="transcription" toName="image"
editable="true" perRegion="true" required="false"
maxSubmissions="1" rows="5" placeholder="Recognized Text"
displayMode="region-list"/>
</View>
```

3. Download the Label Studio Machine Learning backend backend repository.
```
git clone https://github.com/humansignal/label-studio-ml-backend
cd label-studio-ml-backend/label_studio_ml/examples/paddleocr
```

4. Configure the backend and the Minio server by editing the `example.env` file. If you opted to use Label Studio
Local File Storage, be sure to set the `LABEL_STUDIO_HOST` and `LABEL_STUDIO_ACCESS_TOKEN` variables. If you're
using the Minio storage example, set the `MINIO_ROOT_USER` AND `MINIO_ROOT_PASSWORD` variables, and make the
`AWS_ACCESS_KEY_ID` AND `AWS_SECRET_ACCESS_KEY` variables equal to those values. You may optionally connect to your
own AWS cloud storage by setting those variables. Note that you may need to make additional software changes to the
`paddleocr_ch.py` file to match your particular infrastructure configuration.

```
LABEL_STUDIO_HOST=http://host.docker.internal:8080
LABEL_STUDIO_ACCESS_TOKEN=<optional token for local file access>

AWS_ACCESS_KEY_ID=<set to MINIO_ROOT_USER for minio example>
AWS_SECRET_ACCESS_KEY=<set to MINIO_ROOT_PASSWORD for minio example>
AWS_ENDPOINT=http://host.docker.internal:9000

MINIO_ROOT_USER=<username>
MINIO_ROOT_PASSWORD=<password>
MINIO_API_CORS_ALLOW_ORIGIN=*

OCR_LANGUAGE=<Language Abbreviation ch,en,fr,japan> # support 80 languages. refer to https://github.com/Mushroomcat9998/PaddleOCR/blob/main/doc/doc_en/multi_languages_en.md#language_abbreviations
```

5. Start the PaddleOCR and minio servers.

```
# build image
sudo docker build . -t paddleocr-backend:latest
# or
sudo docker compose build .

# only start paddleocr-backend
sudo docker compose up paddleocr-backend
# or and start with minio
docker compose up -d
# shutdown container
docker compose down


# or docker pull image from docker-hub
docker pull blazordevlab/paddleocr-backend:latest

```
below is my docker-compose file include label-studio,minio and paddleocr-backend

```
version: "3.9"

x-logging:
logging: &default-logging
driver: "local"
options:
max-size: "10m"
max-file: "3"

services:
label-studio:
container_name: label-studio
image: heartexlabs/label-studio:latest
restart: unless-stopped
ports:
- "8080:8080"
depends_on:
- minio
environment:
- JSON_LOG=1
- LOG_LEVEL=DEBUG
volumes:
- label-studio-data:/label-studio/data

# not replicated setup for test setup, use a proper aws S3 compatible cluster in production
minio:
container_name: minio
image: bitnami/minio:latest
restart: unless-stopped
logging: *default-logging
ports:
- "9000:9000"
- "9001:9001"
volumes:
- minio-data:/data
- minio-certs:/certs
# configure env vars in .env file or your systems environment
environment:
- MINIO_ROOT_USER=${MINIO_ROOT_USER:-minio_admin_do_not_use_in_production}
- MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD:-minio_admin_do_not_use_in_production}
- MINIO_PROMETHEUS_AUTH_TYPE=${MINIO_PROMETHEUS_AUTH_TYPE:-public}
paddleocr-backend:
container_name: paddleocr-backend
image: blazordevlab/paddleocr-backend:latest
environment:
- LABEL_STUDIO_HOST=${LABEL_STUDIO_HOST:-http://label-studio:8080}
- LABEL_STUDIO_ACCESS_TOKEN=${LABEL_STUDIO_ACCESS_TOKEN}
- AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
- AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
- AWS_ENDPOINT=${AWS_ENDPOINT:-http://minio:9000}
- MINIO_ROOT_USER=${MINIO_ROOT_USER:-minio_admin_do_not_use_in_production}
- MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD:-minio_admin_do_not_use_in_production}
- MINIO_API_CORS_ALLOW_ORIGIN=${MINIO_API_CORS_ALLOW_ORIGIN:-*}
- OCR_LANGUAGE=${OCR_LANGUAGE:-ch}
ports:
- 9090:9090
volumes:
- paddleocr-backend-data:/data
- paddleocr-backend-logs:/tmp
volumes:
label-studio-data:
minio-data:
minio-certs:
paddleocr-backend-data:
paddleocr-backend-logs:
```

7. Upload tasks.

If you're using the Label Studio Local File Storage option, upload images
directly to Label Studio using the Label Studio interface.

If you're using minio for task storage, log into the minio control panel at
`http://localhost:9001`. Create a new bucket, making a note of the name, and
upload your tasks to minio. Set the visibility of the tasks to be public.
Furtner configuration of your cloud storage is beyond the scope of this
tutorial, and you will want to configure your storage according to your
particular needs.

8. If using minio, In the project **Settings**, set up the **Cloud storage**.

Add your source S3 storage by connecting to the S3 Endpoint
`http://host.docker.internal:9000`, using the bucket name from the previous
step, and Access Key ID and Secret Access Key as configured in the previous
steps. For the minio example, uncheck **Use pre-signed URLS**. Check the
connection and save the storage.

9. Open the **Machine Learning** settings and click **Add Model**.

Add the URL `http://host.docker.internal:9090` and save the model as an ML backend.

10. To use this functionality, activate `Auto-Annotation` and use `Autotdetect` rectangle for drawing boxes

Example below :

![ls_demo_ocr](https://github.com/HumanSignal/label-studio-ml-backend/assets/1549611/fcc44c8b-12fd-495c-b0b4-9d5c0ceb2ed2)

Reference links :
- https://labelstud.io/blog/Improve-OCR-quality-with-Tesseract-and-Label-Studio.html
- https://labelstud.io/blog/release-130.html
129 changes: 129 additions & 0 deletions label_studio_ml/examples/paddleocr/_wsgi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import json
import os
import argparse
import logging
import logging.config

logging.config.dictConfig({
"version": 1,
"formatters": {
"standard": {
"format": "[%(asctime)s] [%(levelname)s] [%(name)s::%(funcName)s::%(lineno)d] %(message)s"
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"level": "DEBUG",
"stream": "ext://sys.stdout",
"formatter": "standard"
}
},
"root": {
"level": "ERROR",
"handlers": [
"console"
],
"propagate": True
}
})

from label_studio_ml.api import init_app
from paddleocr_ch import paddleocrLabeling


_DEFAULT_CONFIG_PATH = os.path.join(os.path.dirname(__file__), 'config.json')


def get_kwargs_from_config(config_path=_DEFAULT_CONFIG_PATH):
if not os.path.exists(config_path):
return dict()
with open(config_path) as f:
config = json.load(f)
assert isinstance(config, dict)
return config


if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Label studio')
parser.add_argument(
'-p', '--port', dest='port', type=int, default=9090,
help='Server port')
parser.add_argument(
'--host', dest='host', type=str, default='0.0.0.0',
help='Server host')
parser.add_argument(
'--kwargs', '--with', dest='kwargs', metavar='KEY=VAL', nargs='+', type=lambda kv: kv.split('='),
help='Additional LabelStudioMLBase model initialization kwargs')
parser.add_argument(
'-d', '--debug', dest='debug', action='store_true',
help='Switch debug mode')
parser.add_argument(
'--log-level', dest='log_level', choices=['DEBUG', 'INFO', 'WARNING', 'ERROR'], default=None,
help='Logging level')
parser.add_argument(
'--model-dir', dest='model_dir', default=os.path.dirname(__file__),
help='Directory where models are stored (relative to the project directory)')
parser.add_argument(
'--check', dest='check', action='store_true',
help='Validate model instance before launching server')

parser.add_argument('--basic-auth-user',
default=os.environ.get('ML_SERVER_BASIC_AUTH_USER', None),
help='Basic auth user')

parser.add_argument('--basic-auth-pass',
default=os.environ.get('ML_SERVER_BASIC_AUTH_PASS', None),
help='Basic auth pass')

args = parser.parse_args()

# setup logging level
if args.log_level:
logging.root.setLevel(args.log_level)

def isfloat(value):
try:
float(value)
return True
except ValueError:
return False

def parse_kwargs():
param = dict()
for k, v in args.kwargs:
if v.isdigit():
param[k] = int(v)
elif v == 'True' or v == 'true':
param[k] = True
elif v == 'False' or v == 'False':
param[k] = False
elif isfloat(v):
param[k] = float(v)
else:
param[k] = v
return param

kwargs = get_kwargs_from_config()

if args.kwargs:
kwargs.update(parse_kwargs())

if args.check:
print('Check "' + paddleocrLabeling.__name__ + '" instance creation..')
model = paddleocrLabeling(**kwargs)

app = init_app(
model_class=paddleocrLabeling,
basic_auth_user=args.basic_auth_user,
basic_auth_pass=args.basic_auth_pass,
**kwargs
)

app.run(host=args.host, port=args.port, debug=args.debug)

else:
# for uWSGI use
app = init_app(
model_class=paddleocrLabeling
)
Loading