Skip to content
Merged
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
78 changes: 39 additions & 39 deletions .github/workflows/cd.yaml
Original file line number Diff line number Diff line change
@@ -1,53 +1,53 @@
name: CD

on:
push:
tags:
- '*'
release:
types: [published]

jobs:
build:
name: Build distribution
release-build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.10"
- name: Install pypa/build
run: >-
python3 -m
pip install
build
--user
- name: Build a binary wheel and a source tarball
run: python3 -m build
- name: Store the distribution packages
uses: actions/upload-artifact@v3
with:
name: python-package-distributions
path: dist/

publish-to-pypi:
name: >-
Publish Python distribution 📦 to PyPI
if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes
needs:
- build
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.x"

- name: Build release distributions
run: |
# NOTE: put your own distribution build steps here.
python -m pip install build
python -m build

- name: Upload distributions
uses: actions/upload-artifact@v4
with:
name: release-dists
path: dist/

pypi-publish:
runs-on: ubuntu-latest

needs:
- release-build

permissions:
# IMPORTANT: this permission is mandatory for trusted publishing
id-token: write

# Dedicated environments with protections for publishing are strongly recommended.
environment:
name: pypi
url: https://pypi.org/p/wpodnet-pytorch
permissions:
id-token: write # IMPORTANT: mandatory for trusted publishing

steps:
- name: Download all the dists
uses: actions/download-artifact@v3
with:
name: python-package-distributions
path: dist/
- name: Publish distribution to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
- name: Retrieve release distributions
uses: actions/download-artifact@v4
with:
name: release-dists
path: dist/

- name: Publish release distributions to PyPI
uses: pypa/gh-action-pypi-publish@6f7e8d9c0b1a2c3d4e5f6a7b8c9d0e1f2a3b4c5d
36 changes: 13 additions & 23 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -1,38 +1,28 @@
name: CI

on: [push]
on: [push, pull_request]

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.10"
- name: Install dependencies
run: |
pip install --upgrade pip
pip install -r requirements.txt
- name: Lint with Ruff
run: |
pip install ruff
ruff -- --format=github .
continue-on-error: true
- uses: actions/checkout@v4
- uses: astral-sh/ruff-action@v3
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: "3.10"
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
pip install --upgrade pip
pip install -r requirements.txt
python -m pip install --upgrade pip
pip install -e .[test]
- name: Test with pytest
run: |
pip install pytest pytest-cov pytest-mock
python -m pytest --junitxml=junit/test-results.xml --cov --cov-report=xml --cov-report=html
pytest --junitxml=junit/test-results.xml --cov --cov-report=xml --cov-report=html
105 changes: 66 additions & 39 deletions predict.py
Original file line number Diff line number Diff line change
@@ -1,89 +1,116 @@
import errno
from argparse import ArgumentParser, ArgumentTypeError
from pathlib import Path
from typing import List, Union

import torch
from PIL import Image, UnidentifiedImageError

from wpodnet.backend import Predictor
from wpodnet.model import WPODNet
from wpodnet.stream import ImageStreamer
from wpodnet import Predictor, load_wpodnet_from_checkpoint

if __name__ == '__main__':

def list_image_paths(p: Union[str, Path]) -> List[Path]:
"""
List all images in a directory.

Args:
path (Union[str, Path]): The path to the directory containing images.

Returns:
Generator[Image.Image]: A generator of PIL Image objects.
"""
p = Path(p)
if not p.is_dir():
raise FileNotFoundError(errno.ENOTDIR, "No such directory", args.save_annotated)

image_paths: List[Path] = []
for f in p.glob("**/*"):
try:
with Image.open(f) as image:
image.verify()
image_paths.append(f)
except UnidentifiedImageError:
pass
return image_paths


if __name__ == "__main__":
parser = ArgumentParser()
parser.add_argument("source", type=str, help="the path to the image")
parser.add_argument(
'source',
type=str,
help='the path to the image'
"-w", "--weight", type=str, required=True, help="the path to the model weight"
)
parser.add_argument(
'-w', '--weight',
type=str,
required=True,
help='the path to the model weight'
)
parser.add_argument(
'--scale',
"--scale",
type=float,
default=1.0,
help='adjust the scaling ratio. default to 1.0.'
help="adjust the scaling ratio. default to 1.0.",
)
parser.add_argument(
'--save-annotated',
"--save-annotated",
type=str,
help='save the annotated image at the given folder'
help="save the annotated image at the given folder",
)
parser.add_argument(
'--save-warped',
type=str,
help='save the warped image at the given folder'
"--save-warped", type=str, help="save the warped image at the given folder"
)
args = parser.parse_args()

if args.scale <= 0.0:
raise ArgumentTypeError(message='scale must be greater than 0.0')
raise ArgumentTypeError(message="scale must be greater than 0.0")

if args.save_annotated is not None:
save_annotated = Path(args.save_annotated)
if not save_annotated.is_dir():
raise FileNotFoundError(errno.ENOTDIR, 'No such directory', args.save_annotated)
raise FileNotFoundError(
errno.ENOTDIR, "No such directory", args.save_annotated
)
else:
save_annotated = None

if args.save_warped is not None:
save_warped = Path(args.save_warped)
if not save_warped.is_dir():
raise FileNotFoundError(errno.ENOTDIR, 'No such directory', args.save_warped)
raise FileNotFoundError(
errno.ENOTDIR, "No such directory", args.save_warped
)
else:
save_warped = None

# Prepare for the model
device = 'cuda' if torch.cuda.is_available() else 'cpu'
model = WPODNet()
model.to(device)

checkpoint = torch.load(args.weight)
model.load_state_dict(checkpoint)
device = "cuda" if torch.cuda.is_available() else "cpu"
model = load_wpodnet_from_checkpoint(args.weight).to(device)

predictor = Predictor(model)

streamer = ImageStreamer(args.source)
for i, image in enumerate(streamer):
source = Path(args.source)
if source.is_file():
image_paths = [source]
elif source.is_dir():
image_paths = list_image_paths(source)
else:
raise FileNotFoundError(errno.ENOENT, "No such file or directory", args.source)

for i, image_path in enumerate(image_paths):
image = Image.open(image_path)
prediction = predictor.predict(image, scaling_ratio=args.scale)

print(f'Prediction #{i}')
print(' bounds', prediction.bounds.tolist())
print(' confidence', prediction.confidence)
print(f"Prediction #{i}")
print(" bounds", prediction.bounds)
print(" confidence", prediction.confidence)

if save_annotated:
annotated_path = save_annotated / Path(image.filename).name
annotated = prediction.annotate()
annotated.save(annotated_path)
print(f'Saved the annotated image at {annotated_path}')

canvas = image.copy()
prediction.annotate(canvas, outline="red")
canvas.save(annotated_path)
print(f"Saved the annotated image at {annotated_path}")

if save_warped:
warped_path = save_warped / Path(image.filename).name
warped = prediction.warp()
warped = prediction.warp(image)
warped.save(warped_path)
print(f'Saved the warped image at {warped_path}')
print(f"Saved the warped image at {warped_path}")

print()
70 changes: 59 additions & 11 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
[build-system]
requires = ["setuptools"]
requires = ["setuptools>=64", "setuptools-scm[toml]>=8"]
build-backend = "setuptools.build_meta"

[project]
name = "wpodnet-pytorch"
dynamic = ["dependencies", "version"]
dynamic = ["version"]
description = "The implementation of ECCV 2018 paper \"License Plate Detection and Recognition in Unconstrained Scenarios\" in PyTorch"
readme = "README.md"
requires-python = ">=3.7"
requires-python = ">=3.8"
keywords = [
"python",
"ai",
Expand Down Expand Up @@ -41,18 +41,66 @@ classifiers = [
"Operating System :: MacOS",
"Operating System :: Microsoft :: Windows",
]
dependencies = ["numpy", "Pillow", "torch", "torchvision"]

[tool.setuptools]
packages = { find = { where = ["."], include = ["wpodnet", "wpodnet.*"] } }
packages.find.include = ["wpodnet"]

[tool.setuptools.dynamic]
dependencies = { file = "requirements.txt" }
version = { attr = "wpodnet.__version__" }
[tool.setuptools_scm]
fallback_version = "0.1.0"

[project.optional-dependencies]
dev = [
"pytest"
]
test = ["pytest", "pytest-cov", "pooch"]

[project.urls]
"Source" = "https://github.com/Pandede/WPODNet-Pytorch"
Source = "https://github.com/Pandede/WPODNet-Pytorch"

[tool.ruff]
target-version = "py38"

[tool.ruff.lint]
select = [
"E", # pycodestyle (error)
"F", # pyflakes
"B", # bugbear
"B9",
"C4", # flake8-comprehensions
"SIM", # flake8-simplify
"I", # isort
"UP", # pyupgrade
"PIE", # flake8-pie
"PGH", # pygrep-hooks
"PYI", # flake8-pyi
"RUF",
]
ignore = [
# only relevant if you run a script with `python -0`,
# which seems unlikely for any of the scripts in this repo
"B011",
# Leave it to the formatter to split long lines and
# the judgement of all of us.
"E501",
]

[tool.ruff.lint.per-file-ignores]
"tests/**" = ["D"]

[tool.ruff.lint.pydocstyle]
convention = "google"


[tool.pytest.ini_options]
testpaths = ["tests"]

[tool.coverage.run]
source = ["wpodnet"]

[tool.coverage.report]
show_missing = true
exclude_lines = [
"return NotImplemented",
"pragma: no cover",
"pragma: deprecated",
"pragma: develop",
"pass",
]
Loading
Loading