hexatess-code 0.3.0__tar.gz → 0.4.0__tar.gz

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.

Potentially problematic release.


This version of hexatess-code might be problematic. Click here for more details.

Files changed (34) hide show
  1. {hexatess_code-0.3.0/src/hexatess_code.egg-info → hexatess_code-0.4.0}/PKG-INFO +50 -14
  2. {hexatess_code-0.3.0 → hexatess_code-0.4.0}/README.md +49 -13
  3. {hexatess_code-0.3.0 → hexatess_code-0.4.0}/pyproject.toml +1 -1
  4. {hexatess_code-0.3.0 → hexatess_code-0.4.0}/src/hexatess/__init__.py +12 -8
  5. {hexatess_code-0.3.0 → hexatess_code-0.4.0}/src/hexatess/camera.py +434 -157
  6. {hexatess_code-0.3.0 → hexatess_code-0.4.0}/src/hexatess/cli.py +21 -9
  7. hexatess_code-0.4.0/src/hexatess/decoder.py +110 -0
  8. {hexatess_code-0.3.0 → hexatess_code-0.4.0}/src/hexatess/encoder.py +39 -6
  9. {hexatess_code-0.3.0 → hexatess_code-0.4.0}/src/hexatess/header.py +27 -3
  10. {hexatess_code-0.3.0 → hexatess_code-0.4.0/src/hexatess_code.egg-info}/PKG-INFO +50 -14
  11. {hexatess_code-0.3.0 → hexatess_code-0.4.0}/tests/test_camera.py +12 -0
  12. {hexatess_code-0.3.0 → hexatess_code-0.4.0}/tests/test_codec.py +72 -1
  13. {hexatess_code-0.3.0 → hexatess_code-0.4.0}/tests/test_conformance.py +6 -3
  14. {hexatess_code-0.3.0 → hexatess_code-0.4.0}/tests/test_header.py +32 -0
  15. hexatess_code-0.3.0/src/hexatess/decoder.py +0 -75
  16. {hexatess_code-0.3.0 → hexatess_code-0.4.0}/LICENSE +0 -0
  17. {hexatess_code-0.3.0 → hexatess_code-0.4.0}/setup.cfg +0 -0
  18. {hexatess_code-0.3.0 → hexatess_code-0.4.0}/src/hexatess/galois.py +0 -0
  19. {hexatess_code-0.3.0 → hexatess_code-0.4.0}/src/hexatess/geometry.py +0 -0
  20. {hexatess_code-0.3.0 → hexatess_code-0.4.0}/src/hexatess/masks.py +0 -0
  21. {hexatess_code-0.3.0 → hexatess_code-0.4.0}/src/hexatess/reedsolomon.py +0 -0
  22. {hexatess_code-0.3.0 → hexatess_code-0.4.0}/src/hexatess/render.py +0 -0
  23. {hexatess_code-0.3.0 → hexatess_code-0.4.0}/src/hexatess/resilience.py +0 -0
  24. {hexatess_code-0.3.0 → hexatess_code-0.4.0}/src/hexatess_code.egg-info/SOURCES.txt +0 -0
  25. {hexatess_code-0.3.0 → hexatess_code-0.4.0}/src/hexatess_code.egg-info/dependency_links.txt +0 -0
  26. {hexatess_code-0.3.0 → hexatess_code-0.4.0}/src/hexatess_code.egg-info/entry_points.txt +0 -0
  27. {hexatess_code-0.3.0 → hexatess_code-0.4.0}/src/hexatess_code.egg-info/requires.txt +0 -0
  28. {hexatess_code-0.3.0 → hexatess_code-0.4.0}/src/hexatess_code.egg-info/top_level.txt +0 -0
  29. {hexatess_code-0.3.0 → hexatess_code-0.4.0}/tests/test_galois.py +0 -0
  30. {hexatess_code-0.3.0 → hexatess_code-0.4.0}/tests/test_geometry.py +0 -0
  31. {hexatess_code-0.3.0 → hexatess_code-0.4.0}/tests/test_masks.py +0 -0
  32. {hexatess_code-0.3.0 → hexatess_code-0.4.0}/tests/test_reedsolomon.py +0 -0
  33. {hexatess_code-0.3.0 → hexatess_code-0.4.0}/tests/test_render.py +0 -0
  34. {hexatess_code-0.3.0 → hexatess_code-0.4.0}/tests/test_resilience.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hexatess-code
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Hexatess Code - an experimental 2D barcode on a hexagonal grid with Reed-Solomon error correction
5
5
  Author: The Hexatess Code Authors
6
6
  License: MIT License
@@ -115,10 +115,12 @@ text, stats = decode(grid) # ('Hello, Hexatess!', {...})
115
115
  > specification and reference implementation are solid and heavily
116
116
  > tested (2,500+ tests, conformance vectors). A **camera decoder**
117
117
  > (`hexatess.camera`, optional `[camera]` extra) already reads symbols
118
- > from real photographs — printed labels, foil transparencies, tilted
119
- > and rotated shots. See the roadmap below. Adopting a young format
120
- > is a deliberate bet; the [full format specification](SPECIFICATION.md)
121
- > is the insurance.
118
+ > from real photographs in about a second — printed labels, foil
119
+ > transparencies, tilted and rotated shots. Since spec v0.3 payload
120
+ > text is zlib-compressed automatically, so long texts fit into
121
+ > considerably smaller symbols. See the roadmap below. Adopting a
122
+ > young format is a deliberate bet; the [full format
123
+ > specification](SPECIFICATION.md) is the insurance.
122
124
 
123
125
  ## Installation
124
126
 
@@ -141,19 +143,43 @@ hexatess --demo # demo symbol + robustness statistics
141
143
  hexatess decode-photo photo1.jpg photo2.jpg # read symbols from photos
142
144
  ```
143
145
 
146
+ Payload text is zlib-compressed automatically when that saves space
147
+ (`--no-compress` disables it; the header flag keeps decoders fully
148
+ backward compatible).
149
+
150
+ ## Payload compression (spec v0.3)
151
+
152
+ One header bit marks the payload as a zlib stream. The encoder applies
153
+ it only when it strictly helps, and decoders inflate transparently —
154
+ symbols without the flag are byte-identical to v0.2. What that means
155
+ in practice (EC 30 unless noted):
156
+
157
+ | payload | raw | stored | symbol |
158
+ |---|---|---|---|
159
+ | 80 digits | 80 B | 21 B | rmax 17 → 11 |
160
+ | `"X" × 250` | 250 B | 12 B | rmax 30 → 10 |
161
+ | 849-byte Slovene paragraph | 849 B | 203 B | would not fit → rmax 28 |
162
+ | short strings (≤ ~30 B) | — | unchanged | overhead wins |
163
+
164
+ The maximum *stored* capacity is unchanged (329 bytes at EC 5), so
165
+ incompressible data behaves exactly as before.
166
+
144
167
  ## API
145
168
 
146
169
  | Function | Description |
147
170
  |---|---|
148
- | `encode(text, ec_pct=30, mask_id="auto", min_rings=None)` | UTF-8 text → `(grid, params)`; `grid` maps axial `(q, r)` to `0/1` |
149
- | `decode(grid)` | grid → `(text, stats)`; RS-corrects transparently |
171
+ | `encode(text, ec_pct=30, mask_id="auto", min_rings=None, compress="auto")` | UTF-8 text → `(grid, params)`; `grid` maps axial `(q, r)` to `0/1` |
172
+ | `decode(grid)` | grid → `(text, stats)`; RS-corrects and inflates transparently |
150
173
  | `render(grid, path, size_px=18, ...)` | grid → PNG (pointy-top hexagons, quiet zone, supersampling) |
151
174
  | `sample_grid_from_image(path, rmax, ...)` | ideal re-sampling of a rendered PNG (self-test helper) |
152
175
  | `run_tests(...)` | noise/blob robustness statistics |
153
176
  | `hexatess.camera.decode_photo(path)` | photograph → `(text, stats)`; finder detection, perspective handling, adaptive sampling (optional `[camera]` extra) |
154
177
 
155
178
  `params` / `stats` contain `rmax` (radius in rings), `mask`, `ec`,
156
- `blocks` (list of `(data_bytes, ecc_bytes)`) and `data_len`.
179
+ `blocks` (list of `(data_bytes, ecc_bytes)`), `data_len` (stored
180
+ length) and `compressed`; `stats` also reports `repair_bits` (the RS
181
+ correction ledger) and, for camera decodes, `sector` and
182
+ `finder_hits`.
157
183
 
158
184
  ## Error-correction budget
159
185
 
@@ -174,10 +200,14 @@ concentrate inside whole bytes.
174
200
 
175
201
  ## Implement it in your own language
176
202
 
203
+ A **pure-JavaScript encoder** already ships in this repository — see
204
+ [`javascript/`](javascript/) (zero dependencies, byte-identical to the
205
+ Python reference for uncompressed symbols, includes a browser demo).
206
+
177
207
  The format is deliberately **specification-first**: everything needed
178
208
  for an independent implementation is in
179
209
  [`SPECIFICATION.md`](SPECIFICATION.md), and
180
- [`test_vectors/vectors_v0.2.json`](test_vectors/vectors_v0.2.json)
210
+ [`test_vectors/vectors_v0.3.json`](test_vectors/vectors_v0.3.json)
181
211
  contains fixed inputs/outputs (grids, headers, damaged symbols, expected
182
212
  results) to verify conformance. If your Rust/Go/JS decoder passes the
183
213
  vectors, it speaks Hexatess Code.
@@ -187,12 +217,18 @@ vectors, it speaks Hexatess Code.
187
217
  1. ~~v0.2/0.3 — camera decoding~~ **done (v0.3.0):** `hexatess.camera`
188
218
  reads symbols from photographs — bullseye detection, homography +
189
219
  correction-field warp handling, adaptive sampling; validated on
190
- printed foil with curl and glare.
191
- 2. **Erasure decoding:** declare blob-occluded modules as
220
+ printed foil with curl and glare. **v0.3.1:** ≈10× faster
221
+ (a typical 12 MP photo now takes about a second) plus stable
222
+ outer-ring sampling and mis-decode-proof pose selection.
223
+ 2. ~~v0.3 — payload compression~~ **done (v0.3.1):** zlib flag bit in
224
+ the header, applied automatically when it helps.
225
+ 3. **Erasure decoding:** declare blob-occluded modules as
192
226
  erasures → doubles correctable symbol counts.
193
- 3. **JavaScript/TypeScript SDK** + online playground (generate a code
194
- in the browser in 10 seconds).
195
- 4. Larger radii / capacity beyond 329 bytes (breaking header change).
227
+ 4. **JavaScript/TypeScript SDK** + online playground (generate a code
228
+ in the browser in 10 seconds) — **encoder done:**
229
+ [`javascript/`](javascript/); decoder + hosted playground next.
230
+ 5. Larger radii / capacity beyond 329 stored bytes (breaking header
231
+ change).
196
232
 
197
233
  Contributions welcome — see [CONTRIBUTING.md](CONTRIBUTING.md).
198
234
 
@@ -51,10 +51,12 @@ text, stats = decode(grid) # ('Hello, Hexatess!', {...})
51
51
  > specification and reference implementation are solid and heavily
52
52
  > tested (2,500+ tests, conformance vectors). A **camera decoder**
53
53
  > (`hexatess.camera`, optional `[camera]` extra) already reads symbols
54
- > from real photographs — printed labels, foil transparencies, tilted
55
- > and rotated shots. See the roadmap below. Adopting a young format
56
- > is a deliberate bet; the [full format specification](SPECIFICATION.md)
57
- > is the insurance.
54
+ > from real photographs in about a second — printed labels, foil
55
+ > transparencies, tilted and rotated shots. Since spec v0.3 payload
56
+ > text is zlib-compressed automatically, so long texts fit into
57
+ > considerably smaller symbols. See the roadmap below. Adopting a
58
+ > young format is a deliberate bet; the [full format
59
+ > specification](SPECIFICATION.md) is the insurance.
58
60
 
59
61
  ## Installation
60
62
 
@@ -77,19 +79,43 @@ hexatess --demo # demo symbol + robustness statistics
77
79
  hexatess decode-photo photo1.jpg photo2.jpg # read symbols from photos
78
80
  ```
79
81
 
82
+ Payload text is zlib-compressed automatically when that saves space
83
+ (`--no-compress` disables it; the header flag keeps decoders fully
84
+ backward compatible).
85
+
86
+ ## Payload compression (spec v0.3)
87
+
88
+ One header bit marks the payload as a zlib stream. The encoder applies
89
+ it only when it strictly helps, and decoders inflate transparently —
90
+ symbols without the flag are byte-identical to v0.2. What that means
91
+ in practice (EC 30 unless noted):
92
+
93
+ | payload | raw | stored | symbol |
94
+ |---|---|---|---|
95
+ | 80 digits | 80 B | 21 B | rmax 17 → 11 |
96
+ | `"X" × 250` | 250 B | 12 B | rmax 30 → 10 |
97
+ | 849-byte Slovene paragraph | 849 B | 203 B | would not fit → rmax 28 |
98
+ | short strings (≤ ~30 B) | — | unchanged | overhead wins |
99
+
100
+ The maximum *stored* capacity is unchanged (329 bytes at EC 5), so
101
+ incompressible data behaves exactly as before.
102
+
80
103
  ## API
81
104
 
82
105
  | Function | Description |
83
106
  |---|---|
84
- | `encode(text, ec_pct=30, mask_id="auto", min_rings=None)` | UTF-8 text → `(grid, params)`; `grid` maps axial `(q, r)` to `0/1` |
85
- | `decode(grid)` | grid → `(text, stats)`; RS-corrects transparently |
107
+ | `encode(text, ec_pct=30, mask_id="auto", min_rings=None, compress="auto")` | UTF-8 text → `(grid, params)`; `grid` maps axial `(q, r)` to `0/1` |
108
+ | `decode(grid)` | grid → `(text, stats)`; RS-corrects and inflates transparently |
86
109
  | `render(grid, path, size_px=18, ...)` | grid → PNG (pointy-top hexagons, quiet zone, supersampling) |
87
110
  | `sample_grid_from_image(path, rmax, ...)` | ideal re-sampling of a rendered PNG (self-test helper) |
88
111
  | `run_tests(...)` | noise/blob robustness statistics |
89
112
  | `hexatess.camera.decode_photo(path)` | photograph → `(text, stats)`; finder detection, perspective handling, adaptive sampling (optional `[camera]` extra) |
90
113
 
91
114
  `params` / `stats` contain `rmax` (radius in rings), `mask`, `ec`,
92
- `blocks` (list of `(data_bytes, ecc_bytes)`) and `data_len`.
115
+ `blocks` (list of `(data_bytes, ecc_bytes)`), `data_len` (stored
116
+ length) and `compressed`; `stats` also reports `repair_bits` (the RS
117
+ correction ledger) and, for camera decodes, `sector` and
118
+ `finder_hits`.
93
119
 
94
120
  ## Error-correction budget
95
121
 
@@ -110,10 +136,14 @@ concentrate inside whole bytes.
110
136
 
111
137
  ## Implement it in your own language
112
138
 
139
+ A **pure-JavaScript encoder** already ships in this repository — see
140
+ [`javascript/`](javascript/) (zero dependencies, byte-identical to the
141
+ Python reference for uncompressed symbols, includes a browser demo).
142
+
113
143
  The format is deliberately **specification-first**: everything needed
114
144
  for an independent implementation is in
115
145
  [`SPECIFICATION.md`](SPECIFICATION.md), and
116
- [`test_vectors/vectors_v0.2.json`](test_vectors/vectors_v0.2.json)
146
+ [`test_vectors/vectors_v0.3.json`](test_vectors/vectors_v0.3.json)
117
147
  contains fixed inputs/outputs (grids, headers, damaged symbols, expected
118
148
  results) to verify conformance. If your Rust/Go/JS decoder passes the
119
149
  vectors, it speaks Hexatess Code.
@@ -123,12 +153,18 @@ vectors, it speaks Hexatess Code.
123
153
  1. ~~v0.2/0.3 — camera decoding~~ **done (v0.3.0):** `hexatess.camera`
124
154
  reads symbols from photographs — bullseye detection, homography +
125
155
  correction-field warp handling, adaptive sampling; validated on
126
- printed foil with curl and glare.
127
- 2. **Erasure decoding:** declare blob-occluded modules as
156
+ printed foil with curl and glare. **v0.3.1:** ≈10× faster
157
+ (a typical 12 MP photo now takes about a second) plus stable
158
+ outer-ring sampling and mis-decode-proof pose selection.
159
+ 2. ~~v0.3 — payload compression~~ **done (v0.3.1):** zlib flag bit in
160
+ the header, applied automatically when it helps.
161
+ 3. **Erasure decoding:** declare blob-occluded modules as
128
162
  erasures → doubles correctable symbol counts.
129
- 3. **JavaScript/TypeScript SDK** + online playground (generate a code
130
- in the browser in 10 seconds).
131
- 4. Larger radii / capacity beyond 329 bytes (breaking header change).
163
+ 4. **JavaScript/TypeScript SDK** + online playground (generate a code
164
+ in the browser in 10 seconds) — **encoder done:**
165
+ [`javascript/`](javascript/); decoder + hosted playground next.
166
+ 5. Larger radii / capacity beyond 329 stored bytes (breaking header
167
+ change).
132
168
 
133
169
  Contributions welcome — see [CONTRIBUTING.md](CONTRIBUTING.md).
134
170
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "hexatess-code"
7
- version = "0.3.0"
7
+ version = "0.4.0"
8
8
  description = "Hexatess Code - an experimental 2D barcode on a hexagonal grid with Reed-Solomon error correction"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -1,10 +1,12 @@
1
1
  """Hexatess Code - an experimental 2D barcode on a hexagonal grid.
2
2
 
3
- Reference implementation of specification v0.2. The symbol is a
3
+ Reference implementation of specification v0.3. The symbol is a
4
4
  hexagonal lattice with a hexagonal bullseye finder, an
5
5
  orientation key ring, spiral serialization from the centre outwards,
6
6
  a Reed-Solomon protected header and a continuously selectable
7
- error-correction budget of 5-90 percent (Aztec-style).
7
+ error-correction budget of 5-90 percent (Aztec-style). Since spec
8
+ v0.3 the payload can optionally be zlib-compressed (one header flag
9
+ bit; applied automatically whenever it saves space).
8
10
 
9
11
  Quick start
10
12
  -----------
@@ -18,13 +20,13 @@ Quick start
18
20
  'Hello, Hexatess!'
19
21
 
20
22
  See SPECIFICATION.md in the repository for the full format
21
- specification, and test_vectors/vectors_v0.2.json for conformance
23
+ specification, and test_vectors/vectors_v0.3.json for conformance
22
24
  data usable by independent implementations.
23
25
  """
24
26
 
25
27
  from __future__ import annotations
26
28
 
27
- from .decoder import decode
29
+ from .decoder import decode, payload_to_text
28
30
  from .encoder import encode
29
31
  from .geometry import (
30
32
  DIRS,
@@ -37,6 +39,7 @@ from .geometry import (
37
39
  from .header import (
38
40
  BULLSEYE_RINGS,
39
41
  BLOCK_DATA_MAX,
42
+ COMPRESSED_FLAG,
40
43
  DATA_RING0,
41
44
  KEY_RING,
42
45
  MAX_DATA_BYTES,
@@ -51,14 +54,15 @@ from .header import (
51
54
  pack_mode,
52
55
  plan_blocks,
53
56
  unpack_mode,
57
+ unpack_mode_ex,
54
58
  )
55
59
  from .masks import evaluate_mask, mask_bit, mask_payload, select_mask
56
60
  from .reedsolomon import rs_correct_msg, rs_encode_msg
57
61
  from .render import render, sample_grid_from_image
58
62
  from .resilience import add_blob_damage, add_random_noise, run_tests
59
63
 
60
- __version__ = "0.3.0"
61
- SPEC_VERSION = "0.2"
64
+ __version__ = "0.4.0"
65
+ SPEC_VERSION = "0.3"
62
66
 
63
67
  __all__ = [
64
68
  # high-level API
@@ -69,7 +73,7 @@ __all__ = [
69
73
  "ring_capacity",
70
74
  # framing
71
75
  "bytes_to_bits", "bits_to_bytes", "pack_mode", "unpack_mode",
72
- "plan_blocks",
76
+ "unpack_mode_ex", "plan_blocks", "payload_to_text",
73
77
  # masks
74
78
  "mask_bit", "mask_payload", "evaluate_mask", "select_mask",
75
79
  # error correction
@@ -77,7 +81,7 @@ __all__ = [
77
81
  # constants
78
82
  "BULLSEYE_RINGS", "KEY_RING", "DATA_RING0", "MAX_RINGS",
79
83
  "BLOCK_DATA_MAX", "MODE_BYTES", "MODE_ECC", "MODE_BITS",
80
- "MIN_EC_PCT", "MAX_EC_PCT", "MAX_DATA_BYTES",
84
+ "MIN_EC_PCT", "MAX_EC_PCT", "MAX_DATA_BYTES", "COMPRESSED_FLAG",
81
85
  # meta
82
86
  "__version__", "SPEC_VERSION",
83
87
  ]