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

refactor: src layout and pytest #35

Merged
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
3 changes: 2 additions & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,5 @@ jobs:
pip install ".[tests]"
- name: Run mypy
run: |-
mypy --install-types --non-interactive qreader.py
mkdir -p .mypy_cache
mypy --install-types --non-interactive src tests
27 changes: 27 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Test
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
pytest:
name: Run pytest
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.x' # Specify the Python version you are using
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install .[tests]
sudo apt-get install libzbar0 -y
- name: Run pytest
run: |
python -m pytest tests/
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ On **Mac OS X**:
brew install zbar
```

To install the QReader package locally, run pip

```bash
python -m pip install --editable .
```

**NOTE:** If you're running **QReader** in a server with very limited resources, you may want to install the **CPU** version of [**PyTorch**](https://pytorch.org/get-started/locally/), before installing **QReader**. To do so, run: ``pip install torch --no-cache-dir`` (Thanks to [**@cjwalther**](https://github.com/Eric-Canas/QReader/issues/5) for his advice).

## Usage
Expand Down Expand Up @@ -160,6 +166,20 @@ Image: test_draw_64x64.jpeg -> QReader: ('https://github.com/Eric-Canas/QReader'

Note that **QReader** internally uses <a href="https://github.com/NaturalHistoryMuseum/pyzbar" target="_blank">pyzbar</a> as **decoder**. The improved **detection-decoding rate** that **QReader** achieves comes from the combination of different image pre-processing techniques and the <a href="https://github.com/ultralytics/ultralytics" target="_blank">YOLOv8</a> based <a href="https://github.com/Eric-Canas/qrdet" target="_blank">**QR** detector</a> that is able to detect **QR** codes in harder conditions than classical _Computer Vision_ methods.

## Running tests

The tests can be launched via pytest. Make sure you install the test version of the package

```bash
python -m pip install --editable ".[test]"
```

Then, you can run the tests with

```bash
python -m pytest tests/
```

## Benchmark

### Rotation Test
Expand Down
2 changes: 2 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ license_files=LICENSE
ignore_missing_imports = True
[mypy-qrdet.*]
ignore_missing_imports = True
[mypy-qrcode.*]
ignore_missing_imports = True
7 changes: 4 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from setuptools import find_namespace_packages, setup
from setuptools import find_packages, setup

setup(
name="qreader",
version="3.14",
packages=find_namespace_packages(),
packages=find_packages(where="src"),
package_dir={"": "src"},
# expose qreader.py as the unique module
py_modules=["qreader"],
url="https://github.com/Eric-Canas/qreader",
Expand All @@ -21,7 +22,7 @@
"qrdet>=2.5",
],
extras_require={
"tests": ["mypy"],
"tests": ["mypy", "pytest", "qrcode"],
},
classifiers=[
"Development Status :: 5 - Production/Stable",
Expand Down
21 changes: 11 additions & 10 deletions qreader.py → src/qreader/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,11 @@
("shift-jis", "big5") if os.name == "nt" else ("big5", "shift-jis")
)

from numpy.typing import NDArray

CorrectionsType = typing.Literal["cropped_bbox", "corrected_perspective"]
FlavorType = typing.Literal["original", "inverted", "grayscale"]
ReencodeToType = typing.Union[str, typing.Sequence[str], None]


@dataclass(frozen=True)
Expand Down Expand Up @@ -82,7 +85,7 @@ def __init__(
self,
model_size: str = "s",
min_confidence: float = 0.5,
reencode_to: str | tuple[str, ...] | list[str] | None = DEFAULT_REENCODINGS,
reencode_to: ReencodeToType = DEFAULT_REENCODINGS,
weights_folder: str | None = None,
):
"""
Expand Down Expand Up @@ -119,7 +122,7 @@ def __init__(
assert isinstance(
reencode_to, (tuple, list)
), f"reencode_to must be a str, tuple, list or None. Got {type(reencode_to)}"
self.reencode_to = reencode_to
self.reencode_to = tuple(reencode_to)

def detect(
self, image: np.ndarray, is_bgr: bool = False
Expand Down Expand Up @@ -193,12 +196,10 @@ def decode(

def detect_and_decode(
self, image: np.ndarray, return_detections: bool = False, is_bgr: bool = False
) -> (
tuple[
dict[str, np.ndarray | float | tuple[float | int, float | int]], str | None
]
| tuple[str | None, ...]
):
) -> typing.Tuple[
typing.Tuple[str | None, ...],
tuple[dict[str, np.ndarray | float | tuple[float | int, float | int]]] | None,
]:
"""
This method will decode the **QR** codes in the given image and return the decoded strings
(or None, if any of them was detected but not decoded).
Expand All @@ -224,7 +225,7 @@ def detect_and_decode(
if return_detections:
return decoded_qrs, detections
else:
return decoded_qrs
return decoded_qrs, None

def get_detection_result_from_polygon(
self,
Expand Down Expand Up @@ -331,7 +332,7 @@ def _decode_qr_zbar(
results=decodedQR,
)
# For QRs with black background and white foreground, try to invert the image
inverted_image = image = 255 - rescaled_image
inverted_image = np.array(255) - rescaled_image
decodedQR = decodeQR(inverted_image, symbols=[ZBarSymbol.QRCODE])
if len(decodedQR) > 0:
return wrap(
Expand Down
Empty file added tests/__init__.py
Empty file.
Empty file added tests/integration/__init__.py
Empty file.
30 changes: 16 additions & 14 deletions main.py → tests/integration/test_qreader.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
import os

import cv2
import qrcode
from qrdet import BBOX_XYXY

from qreader import QReader

SAMPLE_IMG = os.path.join(
os.path.dirname(__file__), "documentation", "resources", "test_draw_64x64.jpeg"
)


def utf_errors_test():
import qrcode
def test_utf_errors():

qreader = QReader(model_size="n")
image_path = "my_image.png"
Expand All @@ -26,10 +22,21 @@ def utf_errors_test():
print(f"result = {result[0]}")


def decode_test_set():
def test_decode_test_set():
images = [
os.path.join(os.path.dirname(__file__), "testset", filename)
for filename in os.listdir(os.path.join(os.path.dirname(__file__), "testset"))
os.path.join(
os.path.dirname(__file__),
"..",
"..",
"documentation",
"resources",
filename,
)
for filename in os.listdir(
os.path.join(
os.path.dirname(__file__), "..", "..", "documentation", "resources"
)
)
]
# Initialize QReader
detector = QReader(model_size="n")
Expand All @@ -49,8 +56,3 @@ def decode_test_set():
pass
# decoded_qrs = detector.detect_and_decode(image=img, return_detections=False)
print("-------------------")


if __name__ == "__main__":
utf_errors_test()
# decode_test_set()
Empty file added tests/preformance/__init__.py
Empty file.
14 changes: 10 additions & 4 deletions performance_test.py → tests/preformance/test_performance.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,18 @@
from qreader import QReader

SAMPLE_IMG_1 = os.path.join(
os.path.dirname(__file__), "documentation", "resources", "64x64.png"
os.path.dirname(__file__), "..", "..", "documentation", "resources", "64x64.png"
)
SAMPLE_IMG_2 = os.path.join(
os.path.dirname(__file__), "documentation", "resources", "512x512.jpeg"
os.path.dirname(__file__), "..", "..", "documentation", "resources", "512x512.jpeg"
)
SAMPLE_IMG_3 = os.path.join(
os.path.dirname(__file__), "documentation", "resources", "1024x1024.jpeg"
os.path.dirname(__file__),
"..",
"..",
"documentation",
"resources",
"1024x1024.jpeg",
)

PERFORMANCE_TEST_IAMGES = {
Expand All @@ -24,7 +29,8 @@
}
RUNS_TO_AVERAGE, WARMUP_ITERATIONS = 5, 5

if __name__ == "__main__":

def test_performance():
results = {}
for shape, img_path in tqdm(PERFORMANCE_TEST_IAMGES.items()):
# Read the image
Expand Down
Loading