Skip to content

Commit 41cd211

Browse files
committed
docs: adding docstrings and type annotations
1 parent 0903a24 commit 41cd211

11 files changed

Lines changed: 442 additions & 45 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
[![Test with PyTest](https://github.com/mdevolde/aztec_tool/workflows/Test%20with%20PyTest/badge.svg)](https://github.com/mdevolde/aztec_tool/actions)
44
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
55

6-
*A fast, pure‑Python Aztec Code reader with auto‑orientation and ReedSolomon correction.*
6+
*A fast, pure‑Python Aztec Code reader with auto‑orientation and Reed-Solomon correction.*
77

88
-------------
99
## Table of content

TODO.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,4 @@
22
- Allow to decode Aztec barcodes from other sources than images
33
- Auto cropping Aztec barcodes from images
44
- Add a `CONTRIBUTING.md`
5-
- Adding better type annotations
6-
- Adding docstrings
5+
- Allow to decode Aztec barcodes from PNG "without white" images

aztec_tool/__init__.py

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import toml
33
from importlib.metadata import PackageNotFoundError
44
from pathlib import Path
5-
from typing import Any
5+
from typing import Any, Union, Optional
66

77
from .decoder import AztecDecoder
88
from .exceptions import (
@@ -52,13 +52,50 @@
5252

5353

5454
def decode(
55-
image_path: str | Path,
55+
image_path: Union[str, Path],
5656
*,
57-
auto_orient: bool = True,
58-
auto_correct: bool = True,
59-
mode_auto_correct: bool = True,
57+
auto_orient: Optional[bool] = True,
58+
auto_correct: Optional[bool] = True,
59+
mode_auto_correct: Optional[bool] = True,
6060
**kwargs: Any,
6161
) -> str:
62+
"""Decode an Aztec Code image in **one line**.
63+
64+
This convenience wrapper instantiates :class:`~aztec_tool.decoder.AztecDecoder`
65+
and returns its :pyattr:`~aztec_tool.decoder.AztecDecoder.message`
66+
property. All keyword arguments are forwarded unchanged.
67+
68+
Parameters
69+
----------
70+
image_path : Union[str, pathlib.Path]
71+
Path to the cropped image containing the Aztec symbol.
72+
auto_orient : Optional[bool], default ``True``
73+
Auto-rotate the matrix to the canonical orientation.
74+
auto_correct : Optional[bool], default ``True``
75+
Apply Reed-Solomon correction on the *data* code-words.
76+
mode_auto_correct : Optional[bool], default ``True``
77+
Apply Reed-Solomon correction on the *mode* message.
78+
**kwargs
79+
Reserved for future options, currently ignored.
80+
81+
Returns
82+
-------
83+
str
84+
The decoded user message.
85+
86+
Raises
87+
------
88+
InvalidParameterError
89+
The image path is invalid or the file cannot be opened.
90+
BullseyeDetectionError, OrientationError, ReedSolomonError, …
91+
Any exception propagated by the underlying decoder phases.
92+
93+
Examples
94+
--------
95+
>>> from aztec_tool import decode
96+
>>> decode("ticket.png")
97+
'EVENT: Concert\\nROW 12 SEAT 34'
98+
"""
6299
return AztecDecoder(
63100
image_path,
64101
auto_orient=auto_orient,

aztec_tool/codewords.py

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from functools import cached_property
33
import numpy as np
44
import reedsolo
5+
from typing import List, Optional
56

67
from .tables import TableManager
78
from .enums import ReadingDirection, AztecTableType, AztecType
@@ -18,6 +19,45 @@
1819

1920

2021
class CodewordReader:
22+
"""Read the data spiral, apply Reed-Solomon correction and decode code-words.
23+
24+
Parameters
25+
----------
26+
matrix : numpy.ndarray
27+
Square binary matrix (0/1) representing the Aztec symbol.
28+
layers : int
29+
Number of data layers (excluding the bull's-eye).
30+
data_words : int
31+
Expected count of data code-words (script does *not* include ECC words).
32+
aztec_type : AztecType
33+
``AztecType.COMPACT`` or ``AztecType.FULL`` according to the spec.
34+
auto_correct : Optional[bool], default ``True``
35+
If *True*, a Reed-Solomon pass is executed before high-level decoding.
36+
37+
Attributes
38+
----------
39+
bitmap : numpy.ndarray
40+
Raw bit-stream extracted from the symbol (before ECC correction).
41+
corrected_bits : List[int]
42+
Bit-stream after Reed-Solomon decoding and bit-stuff removal.
43+
decoded_string : str
44+
Final user message built with the Aztec shift/latch tables.
45+
46+
Raises
47+
------
48+
InvalidParameterError
49+
One of the constructor arguments is incoherent (e.g. *layers* < 1).
50+
BitReadError
51+
Data spiral extraction failed (index out of matrix or empty result).
52+
ReedSolomonError
53+
The Reed-Solomon decoder could not correct the symbol.
54+
BitStuffingError
55+
Stuffed/padding bits do not follow the spec rules.
56+
SymbolDecodeError
57+
An index maps to no entry in the current character table.
58+
StreamTerminationError
59+
Premature end of bit-stream (e.g. incomplete Byte-shift segment).
60+
"""
2161

2262
PRIM_POLY = {
2363
6: 0x43, # x^6 + x^5 + 1
@@ -32,8 +72,8 @@ def __init__(
3272
layers: int,
3373
data_words: int,
3474
aztec_type: AztecType,
35-
auto_correct: bool = True,
36-
):
75+
auto_correct: Optional[bool] = True,
76+
) -> None:
3777
if layers < 1:
3878
raise InvalidParameterError("layers must be ≥ 1")
3979
if matrix.ndim != 2 or matrix.shape[0] != matrix.shape[1]:
@@ -49,7 +89,7 @@ def __init__(
4989
self.aztec_type = aztec_type
5090
self.auto_correct = auto_correct
5191

52-
def _is_reference(self, r, c):
92+
def _is_reference(self, r: int, c: int) -> bool:
5393
centre = self.matrix.shape[0] // 2
5494
return (r - centre) % 16 == 0 or (c - centre) % 16 == 0
5595

@@ -153,7 +193,7 @@ def _read_bits(self) -> np.ndarray:
153193
def bitmap(self) -> np.ndarray:
154194
return self._read_bits()
155195

156-
def _correct(self) -> list:
196+
def _correct(self) -> List[int]:
157197
if self.layers <= 2:
158198
cw_size = 6
159199
elif self.layers <= 8:
@@ -200,18 +240,20 @@ def _correct(self) -> list:
200240
return corrected_bits
201241

202242
@cached_property
203-
def corrected_bits(self) -> list[int]:
243+
def corrected_bits(self) -> List[int]:
204244
return self._correct()
205245

206246
@classmethod
207-
def _bits_to_int(cls, bits) -> int:
247+
def _bits_to_int(cls, bits: List[int]) -> int:
208248
return int("".join(str(b) for b in bits), 2)
209249

210250
@classmethod
211-
def _bits_to_bytes(cls, bits) -> bytes:
251+
def _bits_to_bytes(cls, bits: List[int]) -> bytes:
212252
return bytes(cls._bits_to_int(bits[i : i + 8]) for i in range(0, len(bits), 8))
213253

214-
def _remove_stuff_bits(self, bits, cw_size, data_words):
254+
def _remove_stuff_bits(
255+
self, bits: List[int], cw_size: int, data_words: int
256+
) -> List[int]:
215257
cleaned = []
216258
i = 0
217259
words_seen = 0

aztec_tool/decoder.py

Lines changed: 76 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from __future__ import annotations
22
from functools import cached_property
33
from pathlib import Path
4+
from typing import Union, Optional, Tuple, List, Dict
5+
import numpy as np
46

57
from .matrix import AztecMatrix
68
from .detection import BullseyeDetector
@@ -14,13 +16,74 @@
1416

1517

1618
class AztecDecoder:
19+
"""High-level façade that decodes an Aztec symbol from an image in **one line**.
20+
21+
This class orchestrates all lower-level components:
22+
:class:`AztecMatrix`, :class:`BullseyeDetector`,
23+
:class:`OrientationManager`, :class:`ModeReader` and
24+
:class:`CodewordReader`.
25+
26+
Parameters
27+
----------
28+
image_path : Union[str, Path]
29+
Path to the image file **already cropped** to the Aztec symbol.
30+
auto_orient : Optional[bool], default ``True``
31+
If *True*, the matrix is rotated automatically so that orientation
32+
patterns match the canonical position (black-white corner pattern).
33+
auto_correct : Optional[bool], default ``True``
34+
Apply Reed-Solomon correction on the *data* code-words before
35+
high-level decoding. Disable it for debugging corrupted symbols.
36+
mode_auto_correct : Optional[bool], default ``True``
37+
Apply Reed-Solomon correction on the *mode message* (layers, data
38+
words, ecc bits).
39+
40+
Attributes
41+
----------
42+
matrix : numpy.ndarray
43+
Final, possibly rotated, binary matrix (0/1) of the symbol.
44+
aztec_type : AztecType
45+
``COMPACT`` or ``FULL`` deduced from the bull's-eye.
46+
bullseye_bounds : Tuple[int, int, int, int]
47+
Coordinates of the bull's-eye corners.
48+
mode_info : Dict[str, Union[int, List[int]]]
49+
Parsed mode fields - keys ``layers``, ``data_words``, ``ecc_bits``.
50+
bitmap : numpy.ndarray
51+
Raw bit-stream extracted from the data spiral (before ECC).
52+
corrected_bits : List[int]
53+
Bit-stream after Reed-Solomon correction and bit-stuff removal.
54+
message : str
55+
Decoded user message (lazy property, evaluated once).
56+
57+
Raises
58+
------
59+
InvalidParameterError
60+
*image_path* does not point to an existing file.
61+
BullseyeDetectionError, OrientationError, BitReadError, etc.
62+
Any lower-level exception is propagated so the caller can catch
63+
precisely the failing phase.
64+
65+
Examples
66+
--------
67+
>>> from aztec_tool import AztecDecoder
68+
>>> dec = AztecDecoder("ticket.png")
69+
>>> dec.message # same as dec.decode()
70+
'EVENT: Concert
71+
ROW 12 SEAT 34'
72+
73+
A one-liner helper is also available:
74+
75+
>>> from aztec_tool import decode
76+
>>> decode("hello.png")
77+
'Hello, world!'
78+
"""
79+
1780
def __init__(
1881
self,
19-
image_path: str | Path,
82+
image_path: Union[str, Path],
2083
*,
21-
auto_orient: bool = True,
22-
auto_correct: bool = True,
23-
mode_auto_correct: bool = True,
84+
auto_orient: Optional[bool] = True,
85+
auto_correct: Optional[bool] = True,
86+
mode_auto_correct: Optional[bool] = True,
2487
) -> None:
2588
self.image_path = Path(image_path)
2689
if not self.image_path.exists():
@@ -32,42 +95,42 @@ def __init__(
3295
self._mode_auto_correct = mode_auto_correct
3396

3497
@cached_property
35-
def _raw_matrix(self):
98+
def _raw_matrix(self) -> np.ndarray:
3699
return AztecMatrix(str(self.image_path)).matrix
37100

38101
@cached_property
39102
def _bullseye(self) -> BullseyeDetector:
40103
return BullseyeDetector(self._raw_matrix)
41104

42105
@cached_property
43-
def bullseye_bounds(self):
106+
def bullseye_bounds(self) -> Tuple[int, int, int, int]:
44107
return self._bullseye.bounds
45108

46109
@cached_property
47110
def aztec_type(self) -> AztecType:
48111
return self._bullseye.aztec_type
49112

50113
@cached_property
51-
def matrix(self):
114+
def matrix(self) -> np.ndarray:
52115
if not self._auto_orient:
53116
return self._raw_matrix
54117
return OrientationManager(
55118
self._raw_matrix, self.bullseye_bounds
56119
).rotate_if_needed()
57120

58121
@cached_property
59-
def _mode(self):
122+
def _mode(self) -> ModeReader:
60123
bullseye = BullseyeDetector(self.matrix)
61124
return ModeReader(
62125
self.matrix, bullseye.bounds, bullseye.aztec_type, self._mode_auto_correct
63126
)
64127

65128
@cached_property
66-
def mode_info(self):
129+
def mode_info(self) -> Dict[str, Union[int, List[int]]]:
67130
return self._mode.mode_fields
68131

69132
@cached_property
70-
def _codewords(self):
133+
def _codewords(self) -> CodewordReader:
71134
return CodewordReader(
72135
self.matrix,
73136
self.mode_info["layers"],
@@ -77,16 +140,17 @@ def _codewords(self):
77140
)
78141

79142
@cached_property
80-
def bitmap(self):
143+
def bitmap(self) -> np.ndarray:
81144
return self._codewords.bitmap
82145

83146
@cached_property
84-
def corrected_bits(self):
147+
def corrected_bits(self) -> List[int]:
85148
return self._codewords.corrected_bits
86149

87150
@cached_property
88151
def message(self) -> str:
89152
return self._codewords.decoded_string
90153

91154
def decode(self) -> str:
155+
"""Return the decoded user message (alias of :pyattr:`message`)."""
92156
return self.message

0 commit comments

Comments
 (0)