dotcode-decoder 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
dotcode/rs.py ADDED
@@ -0,0 +1,212 @@
1
+ """
2
+ Reed-Solomon error correction over GF(113)
3
+ """
4
+
5
+ P = 113
6
+ ALPHA = 3
7
+
8
+ DOT_PATTERNS = [
9
+ "101010101","010101011","010101101","010110101","011010101","101010110","101011010","101101010",
10
+ "110101010","010101110","010110110","010111010","011010110","011011010","011101010","100101011",
11
+ "100101101","100110101","101001011","101001101","101010011","101011001","101100101","101101001",
12
+ "110010101","110100101","110101001","001010111","001011011","001011101","001101011","001101101",
13
+ "001110101","010010111","010011011","010011101","010100111","010110011","010111001","011001011",
14
+ "011001101","011010011","011011001","011100101","011101001","100101110","100110110","100111010",
15
+ "101001110","101011100","101100110","101101100","101110010","101110100","110010110","110011010",
16
+ "110100110","110101100","110110010","110110100","111001010","111010010","111010100","001011110",
17
+ "001101110","001110110","001111010","010011110","010111100","011001110","011011100","011100110",
18
+ "011101100","011110010","011110100","100010111","100011011","100011101","100100111","100110011",
19
+ "100111001","101000111","101100011","101110001","110001011","110001101","110010011","110011001",
20
+ "110100011","110110001","111000101","111001001","111010001","000101111","000110111","000111011",
21
+ "000111101","001001111","001100111","001110011","001111001","010001111","011000111","011100011",
22
+ "011110001","100011110","100111100","101111000","110001110","110011100","110111000","111000110",
23
+ "111001100",
24
+ ]
25
+ DOT_TO_VAL = {p: i for i, p in enumerate(DOT_PATTERNS)}
26
+
27
+ def gf_inv(a):
28
+ return pow(a, P-2, P)
29
+
30
+ def gf_add(a, b):
31
+ return (a + b) % P
32
+
33
+ def gf_sub(a, b):
34
+ return (a - b) % P
35
+
36
+ def gf_mul(a, b):
37
+ return (a * b) % P
38
+
39
+ def gf_div(a, b):
40
+ return gf_mul(a, gf_inv(b))
41
+
42
+ def gf_pow(a, k):
43
+ return pow(a, k, P) if k >= 0 else gf_inv(pow(a, -k, P))
44
+
45
+ def ptrim(p):
46
+ i = len(p)-1
47
+ while i > 0 and p[i] % P == 0:
48
+ i -= 1
49
+ return p[:i+1]
50
+
51
+ def pmul(a, b):
52
+ out = [0] * (len(a) + len(b) - 1)
53
+ for i, ai in enumerate(a):
54
+ if ai % P == 0:
55
+ continue
56
+ for j, bj in enumerate(b):
57
+ if bj % P == 0:
58
+ continue
59
+ out[i+j] = gf_add(out[i+j], gf_mul(ai, bj))
60
+ return ptrim(out)
61
+
62
+ def peval(p, x):
63
+ acc = 0
64
+ for c in reversed(p):
65
+ acc = gf_add(gf_mul(acc, x), c)
66
+ return acc
67
+
68
+ def pder(p):
69
+ if len(p) <= 1:
70
+ return [0]
71
+ out = [0] * (len(p)-1)
72
+ for i in range(1, len(p)):
73
+ out[i-1] = gf_mul(p[i], i % P)
74
+ return ptrim(out)
75
+
76
+ def pmod_xk(p, k):
77
+ q = p[:k] if len(p) > k else p[:]
78
+ if len(q) < k:
79
+ q += [0] * (k - len(q))
80
+ return q
81
+
82
+ def syndromes(r, NC):
83
+ S = []
84
+ for i in range(1, NC+1):
85
+ s = 0
86
+ for j, rj in enumerate(r):
87
+ if rj % P == 0:
88
+ continue
89
+ s = gf_add(s, gf_mul(rj, gf_pow(ALPHA, i * j)))
90
+ S.append(s)
91
+ return S
92
+
93
+ def erasure_locator(pos):
94
+ L = [1]
95
+ for j in pos:
96
+ L = pmul(L, [1, gf_sub(0, gf_pow(ALPHA, j))])
97
+ return L
98
+
99
+ def berlekamp_massey(S):
100
+ N = len(S)
101
+ if N == 0:
102
+ return [1]
103
+
104
+ def s(k):
105
+ return S[k] if 0 <= k < N else 0
106
+
107
+ C = [0]*(N+1)
108
+ C[0] = 1
109
+ B = [0]*(N+1)
110
+ B[0] = 1
111
+ L, m, b = 0, 1, 1
112
+
113
+ for n in range(N):
114
+ d = s(n)
115
+ for i in range(1, L+1):
116
+ d = gf_add(d, gf_mul(C[i], s(n-i)))
117
+
118
+ if d % P == 0:
119
+ m += 1
120
+ continue
121
+
122
+ fac = gf_div(d, b)
123
+ sB = [0]*(N+1)
124
+ for k in range(N+1-m):
125
+ sB[k+m] = B[k]
126
+
127
+ Cn = C[:]
128
+ for k in range(N+1):
129
+ Cn[k] = gf_sub(Cn[k], gf_mul(fac, sB[k]))
130
+
131
+ if 2*L <= n:
132
+ L = n+1-L
133
+ B = C[:]
134
+ b = d
135
+ m = 1
136
+ C = Cn
137
+ else:
138
+ m += 1
139
+ C = Cn
140
+
141
+ idx = N
142
+ while idx > 0 and C[idx] % P == 0:
143
+ idx -= 1
144
+ C = C[:idx+1]
145
+
146
+ if C and C[0] % P != 1:
147
+ inv = gf_inv(C[0])
148
+ C = [gf_mul(c, inv) for c in C]
149
+
150
+ return C
151
+
152
+ def rs_correct(mask_sym, vals, ND, NC, era_idx):
153
+ NW = len(vals)
154
+ r = [mask_sym] + [(v if v is not None else 0) for v in vals]
155
+ era = [1 + i for i in era_idx if 0 <= i < NW]
156
+ S = syndromes(r, NC)
157
+
158
+ if all(s % P == 0 for s in S) and not era:
159
+ return [v if v is not None else 0 for v in vals], []
160
+
161
+ if len(era) > NC:
162
+ return None
163
+
164
+ Le = erasure_locator(era)
165
+ SLe = pmod_xk(pmul(S[:], Le), NC)
166
+ Lu = berlekamp_massey(SLe)
167
+ Lam = ptrim(pmul(Le, Lu))
168
+ Om = pmod_xk(pmul(S[:], Lam), NC)
169
+ Ld = pder(Lam)
170
+
171
+ err_pos = []
172
+ err_mag = {}
173
+
174
+ for j in range(len(r)):
175
+ x = gf_pow(ALPHA, -j)
176
+ if peval(Lam, x) % P == 0:
177
+ if j == 0:
178
+ continue
179
+ num = peval(Om, x)
180
+ den = peval(Ld, x)
181
+ if den % P == 0:
182
+ continue
183
+ mag = gf_sub(0, gf_div(num, den))
184
+ if mag % P != 0:
185
+ err_pos.append(j)
186
+ err_mag[j] = mag
187
+
188
+ rc = r[:]
189
+ for j in err_pos:
190
+ rc[j] = gf_sub(rc[j], err_mag[j])
191
+
192
+ Sa = syndromes(rc, NC)
193
+ if any(s % P != 0 for s in Sa):
194
+ return None
195
+
196
+ return rc[1:], [p-1 for p in err_pos]
197
+
198
+ def solve_ND_NC(NW):
199
+ for ND in range(0, NW+1):
200
+ NC = 3 + ((ND+1)//2)
201
+ if ND + NC == NW:
202
+ return (ND, NC)
203
+
204
+ for ND in range(0, NW+1):
205
+ NC = 3 + (ND//2)
206
+ if ND + NC == NW:
207
+ return (ND, NC)
208
+
209
+ return None, None
210
+
211
+ def mask_step(mid):
212
+ return [0, 3, 7, 17][mid]
dotcode/setup.py ADDED
@@ -0,0 +1,26 @@
1
+ from setuptools import find_packages, setup
2
+
3
+ setup(
4
+ name="dotcode-decoder",
5
+ version="1.0.0",
6
+ description="Fast DotCode 2D barcode decoder with iterative regression",
7
+ author="Anish karki",
8
+ packages=find_packages(),
9
+ install_requires=[
10
+ "opencv-python>=4.5.0",
11
+ "numpy>=1.19.0",
12
+ ],
13
+ python_requires=">=3.7",
14
+ classifiers=[
15
+ "Programming Language :: Python :: 3",
16
+ "Programming Language :: Python :: 3.7",
17
+ "Programming Language :: Python :: 3.8",
18
+ "Programming Language :: Python :: 3.9",
19
+ "Programming Language :: Python :: 3.10",
20
+ "License :: OSI Approved :: MIT License",
21
+ "Operating System :: OS Independent",
22
+ "Topic :: Multimedia :: Graphics",
23
+ "Topic :: Scientific/Engineering :: Image Recognition",
24
+ ],
25
+ keywords="barcode dotcode decoder 2d",
26
+ )
dotcode/utils.py ADDED
@@ -0,0 +1,36 @@
1
+ """
2
+ Shared types, constants, and result objects
3
+ """
4
+
5
+ from dataclasses import dataclass
6
+ from enum import Enum
7
+ from typing import Optional
8
+
9
+ # Constants
10
+ GRID_ROWS = 10
11
+ GRID_COLS = 47
12
+ MIN_DOTS = 12
13
+ EARLY_EXIT_CONFIDENCE = 0.30
14
+ REGRESSION_ITERATIONS = 2
15
+
16
+ class DecodeStage(Enum):
17
+ REGRESSION = 1
18
+ ANGLE_SWEEP = 2
19
+ INVERT = 3
20
+
21
+ @dataclass
22
+ class DecodeResult:
23
+ success: bool
24
+ message: Optional[str] = None
25
+ error: Optional[str] = None
26
+ file: Optional[str] = None
27
+ method: Optional[str] = None
28
+ elapsed_ms: float = 0.0
29
+ stage: Optional[DecodeStage] = None
30
+ confidence: float = 0.0
31
+ timed_out: bool = False
32
+
33
+ def __repr__(self):
34
+ if self.success:
35
+ return f"DecodeResult(success=True, msg='{self.message}', time={self.elapsed_ms:.0f}ms)"
36
+ return f"DecodeResult(success=False, error='{self.error}', time={self.elapsed_ms:.0f}ms)"
@@ -0,0 +1,248 @@
1
+ Metadata-Version: 2.4
2
+ Name: dotcode-decoder
3
+ Version: 1.0.0
4
+ Summary: Fast DotCode 2D barcode decoder
5
+ Author: Anish karki
6
+ Keywords: barcode dotcode decoder 2d
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Python :: 3.7
9
+ Classifier: Programming Language :: Python :: 3.8
10
+ Classifier: Programming Language :: Python :: 3.9
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Topic :: Multimedia :: Graphics
15
+ Classifier: Topic :: Scientific/Engineering :: Image Recognition
16
+ Requires-Python: >=3.7
17
+ Description-Content-Type: text/markdown
18
+ Requires-Dist: opencv-python>=4.5.0
19
+ Requires-Dist: numpy>=1.19.0
20
+ Requires-Dist: scipy>=1.7.0
21
+ Dynamic: author
22
+ Dynamic: classifier
23
+ Dynamic: description
24
+ Dynamic: description-content-type
25
+ Dynamic: keywords
26
+ Dynamic: requires-dist
27
+ Dynamic: requires-python
28
+ Dynamic: summary
29
+
30
+ # DotCode Decoder
31
+
32
+ [![Python Version](https://img.shields.io/badge/python-3.7+-blue.svg)](https://python.org)
33
+ [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
34
+ [![PyPI](https://img.shields.io/badge/pypi-v1.0.0-orange.svg)](https://pypi.org/project/dotcode)
35
+
36
+ **Fast, accurate DotCode 2D barcode decoder for industrial and commercial applications.**
37
+
38
+ ---
39
+
40
+ ## 📦 Installation
41
+
42
+ ```bash
43
+ pip install dotcode
44
+ ```
45
+
46
+ Or install from source:
47
+
48
+ ```bash
49
+ git clone https://github.com/yourusername/dotcode.git
50
+ cd dotcode
51
+ pip install -e .
52
+ ```
53
+
54
+ ---
55
+
56
+ ## 🚀 Quick Start
57
+
58
+ ```python
59
+ from dotcode import decode, DecodeResult
60
+
61
+ # Single image
62
+ result = decode("image.jpg")
63
+ print(result.message) # "25E13KXUKSXG1313"
64
+
65
+ # With details
66
+ result: DecodeResult = decode("image.jpg")
67
+ print(f"Message: {result.message}")
68
+ print(f"Time: {result.elapsed_ms:.0f}ms")
69
+ print(f"Confidence: {result.confidence:.2f}")
70
+ print(f"Method: {result.method}")
71
+
72
+ # Batch decode
73
+ results = decode_batch(["img1.jpg", "img2.jpg", "img3.jpg"])
74
+ for r in results:
75
+ print(f"{r.file}: {r.message} ({r.elapsed_ms:.0f}ms)")
76
+ ```
77
+
78
+ ---
79
+
80
+ ## ⚙️ Advanced Usage
81
+
82
+ ```python
83
+ from dotcode import DotCodeDecoder
84
+
85
+ # Custom configuration
86
+ decoder = DotCodeDecoder(
87
+ timeout_ms=500, # Max time per image
88
+ confidence=0.35, # Confidence threshold (0.20-0.40)
89
+ fast_mode=True, # Fast decode (6 angles) or accurate (12 angles)
90
+ use_sauvola=False # Sauvola thresholding for poor lighting
91
+ )
92
+
93
+ # Decode with custom settings
94
+ result = decoder.decode("image.jpg")
95
+ print(result.message)
96
+
97
+ # Disable fast mode for better accuracy on difficult images
98
+ decoder = DotCodeDecoder(fast_mode=False)
99
+ ```
100
+
101
+ ---
102
+
103
+ ## 📊 Performance
104
+
105
+ | Metric | Value |
106
+ |--------|-------|
107
+ | **Accuracy** | **96.2%** (381/396 images) |
108
+ | **Average Speed** | **41ms** per image |
109
+ | **Success Rate** | 96%+ |
110
+ | **Timeout** | 300ms (configurable) |
111
+ | **Stage Distribution** | Regression: 27% \| Angle Sweep: 73% |
112
+
113
+ ### Performance by Stage
114
+
115
+ | Stage | Images | Avg Time | Description |
116
+ |-------|--------|----------|-------------|
117
+ | **Regression** | 103 (27%) | ~20ms | Fast path - direct grid fit |
118
+ | **Angle Sweep** | 278 (73%) | ~45ms | Slower path - angle correction |
119
+ | **Invert Fallback** | 0 | N/A | Last resort for inverted images |
120
+
121
+ ---
122
+
123
+ ## 🔧 How It Works
124
+
125
+ 1. **Dot Detection**
126
+ Extracts dot centroids using adaptive thresholding (Otsu/fixed) with morphological erosion to separate touching dots.
127
+
128
+ 2. **Iterative Regression**
129
+ Straightens tilted/skewed grids using per-row polynomial fitting with early exit when angle stabilizes.
130
+
131
+ 3. **Grid Snapping**
132
+ Fits detected dots to a 10×47 checkerboard grid using k-means clustering (or optional Procrustes + RANSAC for difficult images).
133
+
134
+ 4. **Reed-Solomon Error Correction**
135
+ Corrects errors in the extracted bitstream using Reed-Solomon decoding over GF(113) - handles up to 7 errors per code.
136
+
137
+ 5. **Text Decoding**
138
+ Converts corrected values to readable text using DotCode code sets (A, B, C) with state machine for character encoding.
139
+
140
+ ---
141
+
142
+ ## 📁 Project Structure
143
+
144
+ ```
145
+ dotcode/
146
+ ├── __init__.py # Main exports
147
+ ├── decoder.py # Core decoding logic
148
+ ├── image.py # Image processing (thresholding, blob detection)
149
+ ├── codes.py # DotCode code sets (A, B, C)
150
+ ├── rs.py # Reed-Solomon error correction
151
+ └── utils.py # Utility classes and constants
152
+
153
+ examples/
154
+ ├── basic_usage.py # Simple decode example
155
+ └── batch_usage.py # Batch processing example
156
+
157
+ tests/
158
+ └── test_decoder.py # Unit tests
159
+ ```
160
+
161
+ ---
162
+
163
+ ## 🔬 Algorithm Details
164
+
165
+ ### Reed-Solomon Error Correction
166
+ - Field: **GF(113)**
167
+ - Primitive element: **3**
168
+ - Corrects up to **~5-7 errors** per code
169
+ - Uses Berlekamp-Massey algorithm for error location
170
+
171
+ ### Iterative Regression
172
+ - **2 iterations** (configurable)
173
+ - **Early exit** when angle change < 0.1°
174
+ - **Per-row quadratic fit** for curvature correction
175
+
176
+ ### Threshold Selection
177
+ - **Otsu's method** (automatic)
178
+ - **Fixed thresholds**: 105, 115, 125, 135, 145, 155, 165, 175
179
+ - **Sauvola threshold** (local adaptive) - optional
180
+
181
+ ---
182
+
183
+ ## 🛠️ Requirements
184
+
185
+ - Python 3.7+
186
+ - OpenCV >= 4.5.0
187
+ - NumPy >= 1.19.0
188
+ - SciPy >= 1.7.0 (for KDTree)
189
+
190
+ ---
191
+
192
+ ## 📝 License
193
+
194
+ MIT License - see [LICENSE](LICENSE) file for details.
195
+
196
+ ---
197
+
198
+ ## 🤝 Contributing
199
+
200
+ Contributions are welcome! Please:
201
+
202
+ 1. Fork the repository
203
+ 2. Create a feature branch
204
+ 3. Submit a pull request
205
+
206
+ ### Development Setup
207
+
208
+ ```bash
209
+ # Clone the repository
210
+ git clone https://github.com/yourusername/dotcode.git
211
+ cd dotcode
212
+
213
+ # Install in development mode
214
+ pip install -e .
215
+
216
+ # Run tests
217
+ python test.py
218
+ ```
219
+
220
+ ---
221
+
222
+ ## 📚 References
223
+
224
+ - [DotCode Specification (AIM)](https://www.aimglobal.org/dotcode)
225
+ - [Wikipedia: DotCode](https://en.wikipedia.org/wiki/DotCode)
226
+ - [Reed-Solomon Error Correction](https://en.wikipedia.org/wiki/Reed%E2%80%93Solomon_error_correction)
227
+
228
+ ---
229
+
230
+ ## 📧 Contact
231
+
232
+ For questions or support:
233
+ - GitHub Issues: [https://github.com/yourusername/dotcode/issues](https://github.com/yourusername/dotcode/issues)
234
+ - Email: your.email@example.com
235
+
236
+ ---
237
+
238
+ ## ⭐ Acknowledgments
239
+
240
+ This project builds on research in 2D barcode decoding and industrial computer vision. Special thanks to:
241
+
242
+ - The open-source community for OpenCV, NumPy, and SciPy
243
+ - AIM Global for the DotCode specification
244
+ - Contributors who helped test and improve the decoder
245
+
246
+ ---
247
+
248
+ **Made with ❤️ for industrial automation and barcode enthusiasts.**
@@ -0,0 +1,14 @@
1
+ dotcode/__init__.py,sha256=fg8H7arLGK6Yj_mbvCqSsehUWM_cXSEx4mRNW6mQomQ,256
2
+ dotcode/code.py,sha256=EhVIS3BIMy1esjIrSQcyTM2EOFLrJwDMiD1XkQVMwsE,35755
3
+ dotcode/codes.py,sha256=9I9R9pCj8uV2PFdN3XDIunZMVj5H6QcqopPTrtzxM8M,6445
4
+ dotcode/decoder.py,sha256=eyl4ODb6fLxIvXO1aOR-jfVv-bICqrqsn9-c0iF9I8s,37055
5
+ dotcode/decoder_90.py,sha256=v99mhQX_WS1zuNLC5XiZt_mk5ml_R2FIQKqSzuQhbfQ,15138
6
+ dotcode/image.py,sha256=N7RuvnPHusTEF0RlEbN1wS350qA_-J_IYkY55DmTK7o,87
7
+ dotcode/images.py,sha256=o59vWCrrdUD_CKh4eKdFCn5BDn7LWNEHb2p2LDV8oUw,6255
8
+ dotcode/rs.py,sha256=p6u7kAknqcpVulYigJ-XrfG0E36qqd1LhvoinxWxvSQ,5876
9
+ dotcode/setup.py,sha256=yLk8trMdvHNSAPr47lr1hD2je-kGMdCMsZWZpzzKODo,891
10
+ dotcode/utils.py,sha256=jL8e03M_027OTKzHwHhJACzDBOtuQC6yromc20mL6Cs,920
11
+ dotcode_decoder-1.0.0.dist-info/METADATA,sha256=Xjq8im_7UibDLSXHQ97UPprIxKGiW4UDVFsAVHf5Dnc,6727
12
+ dotcode_decoder-1.0.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
13
+ dotcode_decoder-1.0.0.dist-info/top_level.txt,sha256=2hWaRw8BGxQazH72zDHPMH8vrPyPQe5v81kJKxB7uzo,8
14
+ dotcode_decoder-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ dotcode