captix 1.0.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.
- captix-1.0.0/.gitignore +23 -0
- captix-1.0.0/CHANGELOG.md +50 -0
- captix-1.0.0/LICENSE +21 -0
- captix-1.0.0/PKG-INFO +261 -0
- captix-1.0.0/README.md +187 -0
- captix-1.0.0/pyproject.toml +112 -0
- captix-1.0.0/src/captix/__init__.py +62 -0
- captix-1.0.0/src/captix/annotate.py +70 -0
- captix-1.0.0/src/captix/captcha.py +111 -0
- captix-1.0.0/src/captix/data.py +65 -0
- captix-1.0.0/src/captix/dataset.py +207 -0
- captix-1.0.0/src/captix/decrypt.py +231 -0
- captix-1.0.0/src/captix/download/__init__.py +26 -0
- captix-1.0.0/src/captix/download/_base.py +153 -0
- captix-1.0.0/src/captix/download/_sources.py +248 -0
- captix-1.0.0/src/captix/download/cli.py +92 -0
- captix-1.0.0/src/captix/fit.py +193 -0
- captix-1.0.0/src/captix/hub.py +207 -0
- captix-1.0.0/src/captix/load.py +93 -0
- captix-1.0.0/src/captix/metrics.py +14 -0
- captix-1.0.0/src/captix/model.py +111 -0
- captix-1.0.0/tests/conftest.py +39 -0
- captix-1.0.0/tests/test_annotate.py +44 -0
- captix-1.0.0/tests/test_captcha.py +49 -0
- captix-1.0.0/tests/test_check_branch_name.py +39 -0
- captix-1.0.0/tests/test_dataset.py +114 -0
- captix-1.0.0/tests/test_decrypt.py +68 -0
- captix-1.0.0/tests/test_download.py +432 -0
- captix-1.0.0/tests/test_fit.py +17 -0
- captix-1.0.0/tests/test_hub.py +242 -0
- captix-1.0.0/tests/test_load.py +39 -0
- captix-1.0.0/tests/test_metrics.py +20 -0
- captix-1.0.0/tests/test_model.py +35 -0
captix-1.0.0/.gitignore
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Python-generated files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[oc]
|
|
4
|
+
build/
|
|
5
|
+
dist/
|
|
6
|
+
wheels/
|
|
7
|
+
*.egg-info
|
|
8
|
+
|
|
9
|
+
# Virtual environments
|
|
10
|
+
.venv
|
|
11
|
+
notebooks/data
|
|
12
|
+
notebooks/data_merged
|
|
13
|
+
notebooks/data_live
|
|
14
|
+
notebooks/*.pt
|
|
15
|
+
docs/notebooks/data
|
|
16
|
+
docs/notebooks/data_merged
|
|
17
|
+
docs/notebooks/data_live
|
|
18
|
+
docs/notebooks/*.pt
|
|
19
|
+
.claude/
|
|
20
|
+
|
|
21
|
+
.env
|
|
22
|
+
# Isolated agent worktrees
|
|
23
|
+
.worktrees/
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `txtcaptcha` are documented here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project
|
|
5
|
+
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
- Notebooks moved under `docs/notebooks/` and surfaced on the docs site.
|
|
11
|
+
|
|
12
|
+
## [0.1.0] - 2026-04-11
|
|
13
|
+
|
|
14
|
+
Initial public release.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- CRNN+CTC architecture (`txtcaptcha.CRNN`) with ResNet-style CNN backbone,
|
|
18
|
+
2-layer BiLSTM and CTC head. Handles variable-width input and variable
|
|
19
|
+
label lengths out of the box.
|
|
20
|
+
- `read_captcha`, `Captcha`, `annotate` for loading/labeling images.
|
|
21
|
+
- `CaptchaDataset`, `transform_image`, `encode_label`, `decode_indices`,
|
|
22
|
+
`pad_collate` for building training pipelines.
|
|
23
|
+
- `fit_model` training loop with validation split, CTC loss and early
|
|
24
|
+
stopping.
|
|
25
|
+
- `decrypt()` with three decoding modes:
|
|
26
|
+
- greedy CTC (default),
|
|
27
|
+
- fixed-length exact DP via `length=N`,
|
|
28
|
+
- decode-time masking via `mask=` (list, regex, or `re.Pattern`).
|
|
29
|
+
- `save_model` / `load_model` with automatic CPU fallback when CUDA is
|
|
30
|
+
unavailable.
|
|
31
|
+
- Dataset fetchers: `available_datasets`, `download_dataset`.
|
|
32
|
+
- Live captcha fetchers via `download_captchas` CLI and the
|
|
33
|
+
`txtcaptcha.download` subpackage (10 Brazilian court / tax sources).
|
|
34
|
+
- Hugging Face Hub integration:
|
|
35
|
+
- `save_pretrained`, `from_pretrained`, `push_to_hub`, `DEFAULT_REPO_ID`,
|
|
36
|
+
- safetensors + `config.json` + model card layout,
|
|
37
|
+
- `decrypt()` auto-downloads the unified checkpoint from
|
|
38
|
+
[`jtrecenti/txtcaptcha-crnn`](https://huggingface.co/jtrecenti/txtcaptcha-crnn)
|
|
39
|
+
on first call and caches it.
|
|
40
|
+
- Unified pretrained checkpoint published on the Hub at
|
|
41
|
+
`jtrecenti/txtcaptcha-crnn` (~89% captcha-level accuracy across ten
|
|
42
|
+
Brazilian court datasets).
|
|
43
|
+
- Notebooks: `train_unified_model.ipynb`, `eval_per_dataset.ipynb`,
|
|
44
|
+
`eval_per_dataset_live.ipynb`.
|
|
45
|
+
- GitHub Actions:
|
|
46
|
+
- CI running pytest on push/PR,
|
|
47
|
+
- PyPI publishing via trusted publishing on GitHub releases,
|
|
48
|
+
- Quarto + quartodoc site deployed to GitHub Pages on pushes to `main`.
|
|
49
|
+
|
|
50
|
+
[0.1.0]: https://github.com/jtrecenti/txtcaptcha/releases/tag/v0.1.0
|
captix-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Julio Trecenti
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
captix-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: captix
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Read, annotate, train and decrypt text captchas with a CRNN+CTC model.
|
|
5
|
+
Project-URL: Homepage, https://github.com/Diaszano/captix
|
|
6
|
+
Project-URL: Documentation, https://Diaszano.github.io/captix/
|
|
7
|
+
Project-URL: Repository, https://github.com/Diaszano/captix
|
|
8
|
+
Project-URL: Issues, https://github.com/Diaszano/captix/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/Diaszano/captix/blob/main/CHANGELOG.md
|
|
10
|
+
Project-URL: Model (HF Hub), https://huggingface.co/jtrecenti/txtcaptcha-crnn
|
|
11
|
+
Author-email: Julio Trecenti <jtrecenti@gmail.com>
|
|
12
|
+
Maintainer-email: Julio Trecenti <jtrecenti@gmail.com>
|
|
13
|
+
License: MIT License
|
|
14
|
+
|
|
15
|
+
Copyright (c) 2026 Julio Trecenti
|
|
16
|
+
|
|
17
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
18
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
19
|
+
in the Software without restriction, including without limitation the rights
|
|
20
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
21
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
22
|
+
furnished to do so, subject to the following conditions:
|
|
23
|
+
|
|
24
|
+
The above copyright notice and this permission notice shall be included in all
|
|
25
|
+
copies or substantial portions of the Software.
|
|
26
|
+
|
|
27
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
28
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
29
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
30
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
31
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
32
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
33
|
+
SOFTWARE.
|
|
34
|
+
License-File: LICENSE
|
|
35
|
+
Keywords: captcha,crnn,ctc,deep-learning,image-to-text,ocr,pytorch
|
|
36
|
+
Classifier: Development Status :: 4 - Beta
|
|
37
|
+
Classifier: Intended Audience :: Developers
|
|
38
|
+
Classifier: Intended Audience :: Science/Research
|
|
39
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
40
|
+
Classifier: Operating System :: OS Independent
|
|
41
|
+
Classifier: Programming Language :: Python :: 3
|
|
42
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
43
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
44
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
45
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
46
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
47
|
+
Classifier: Topic :: Scientific/Engineering :: Image Recognition
|
|
48
|
+
Requires-Python: >=3.10
|
|
49
|
+
Requires-Dist: huggingface-hub>=0.20
|
|
50
|
+
Requires-Dist: matplotlib>=3.8
|
|
51
|
+
Requires-Dist: numpy>=1.24
|
|
52
|
+
Requires-Dist: pandas>=2.0
|
|
53
|
+
Requires-Dist: pillow>=10.0
|
|
54
|
+
Requires-Dist: python-dotenv>=1.0
|
|
55
|
+
Requires-Dist: requests>=2.31
|
|
56
|
+
Requires-Dist: safetensors>=0.4
|
|
57
|
+
Requires-Dist: torch>=2.2
|
|
58
|
+
Requires-Dist: torchvision>=0.17
|
|
59
|
+
Requires-Dist: tqdm>=4.66
|
|
60
|
+
Provides-Extra: dev
|
|
61
|
+
Requires-Dist: build>=1.2; extra == 'dev'
|
|
62
|
+
Requires-Dist: pre-commit<5,>=4.6; extra == 'dev'
|
|
63
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
64
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
65
|
+
Requires-Dist: python-semantic-release==10.7.0; extra == 'dev'
|
|
66
|
+
Requires-Dist: twine>=5.0; extra == 'dev'
|
|
67
|
+
Provides-Extra: docs
|
|
68
|
+
Requires-Dist: griffe<1; extra == 'docs'
|
|
69
|
+
Requires-Dist: quartodoc>=0.7; extra == 'docs'
|
|
70
|
+
Provides-Extra: notebook
|
|
71
|
+
Requires-Dist: ipykernel>=6.29; extra == 'notebook'
|
|
72
|
+
Requires-Dist: jupyter>=1.0; extra == 'notebook'
|
|
73
|
+
Description-Content-Type: text/markdown
|
|
74
|
+
|
|
75
|
+
# Captix
|
|
76
|
+
|
|
77
|
+
[](https://pypi.org/project/captix/)
|
|
78
|
+
[](https://pypi.org/project/captix/)
|
|
79
|
+
[](LICENSE)
|
|
80
|
+
[](https://huggingface.co/jtrecenti/txtcaptcha-crnn)
|
|
81
|
+
[](https://Diaszano.github.io/captix/)
|
|
82
|
+
|
|
83
|
+
Read, annotate, train and decrypt **text captchas in images** with a modern
|
|
84
|
+
CRNN + CTC pipeline in PyTorch.
|
|
85
|
+
|
|
86
|
+
Captix ships:
|
|
87
|
+
|
|
88
|
+
- a CRNN architecture that handles **arbitrary input sizes** and
|
|
89
|
+
**variable-length labels**,
|
|
90
|
+
- the full alphanumeric vocabulary `0-9a-zA-Z` (62 classes + CTC blank),
|
|
91
|
+
- **decode-time masking** so a single trained model can be restricted per
|
|
92
|
+
site (e.g. `mask="[0-9]"`),
|
|
93
|
+
- **fixed-length decoding** via `length=N` for sites with a known length,
|
|
94
|
+
- a pretrained unified model hosted on the
|
|
95
|
+
[Hugging Face Hub](https://huggingface.co/jtrecenti/txtcaptcha-crnn) with
|
|
96
|
+
~89% captcha-level accuracy across ten Brazilian court captcha datasets.
|
|
97
|
+
|
|
98
|
+
## Installation
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
pip install captix
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Or from source with [`uv`](https://docs.astral.sh/uv/):
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
git clone https://github.com/Diaszano/captix
|
|
108
|
+
cd captix
|
|
109
|
+
uv sync --extra dev
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The distribution installs as `captix`; Python imports use `captix`.
|
|
113
|
+
|
|
114
|
+
## Quick start
|
|
115
|
+
|
|
116
|
+
The first `decrypt` call downloads the pretrained model from the Hugging Face
|
|
117
|
+
Hub into `~/.cache/huggingface/hub`; subsequent calls are free.
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
from captix import read_captcha, decrypt
|
|
121
|
+
|
|
122
|
+
cap = read_captcha("path/to/captcha.png")
|
|
123
|
+
print(decrypt(cap)) # greedy, variable length
|
|
124
|
+
print(decrypt(cap, mask="[0-9]")) # digits only
|
|
125
|
+
print(decrypt(cap, length=5)) # force exactly 5 chars
|
|
126
|
+
print(decrypt(cap, mask=list("abcdef0123"))) # explicit allowed set
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Pin a specific release or load a different Hub repo explicitly:
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
from captix import from_pretrained
|
|
133
|
+
|
|
134
|
+
model = from_pretrained("jtrecenti/txtcaptcha-crnn", revision="v0.1.0")
|
|
135
|
+
print(decrypt(cap, model=model))
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Training your own model
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
from captix import fit_model, save_model, download_dataset
|
|
142
|
+
|
|
143
|
+
data_dir = download_dataset("tjmg", "data")
|
|
144
|
+
model, history = fit_model(
|
|
145
|
+
data_dir,
|
|
146
|
+
epochs=30,
|
|
147
|
+
batch_size=64,
|
|
148
|
+
case_sensitive=False,
|
|
149
|
+
)
|
|
150
|
+
save_model(model, "tjmg.pt")
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Publishing your own model to the Hub
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
from captix import push_to_hub
|
|
157
|
+
|
|
158
|
+
push_to_hub(
|
|
159
|
+
model,
|
|
160
|
+
repo_id="your-username/your-captcha-model",
|
|
161
|
+
model_card="# My captcha model\n\nTrained on ...",
|
|
162
|
+
tag="v0.1.0",
|
|
163
|
+
)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Public API
|
|
167
|
+
|
|
168
|
+
| Function | Purpose |
|
|
169
|
+
|---|---|
|
|
170
|
+
| `read_captcha(files, lab_in_path=False)` | Load image(s) into a `Captcha` object. |
|
|
171
|
+
| `Captcha` | Container with `images`, `labels`, `paths`, `plot()`. |
|
|
172
|
+
| `annotate(files, labels=None, ...)` | Interactive/batch labeling (filename convention). |
|
|
173
|
+
| `CaptchaDataset(root, vocab, height, case_sensitive)` | PyTorch dataset over a folder of `<id>_<label>.<ext>` files. |
|
|
174
|
+
| `transform_image(files, height=32)` | Load + resize + width-pad for batching. |
|
|
175
|
+
| `encode_label`, `decode_indices` | Vocab ↔ tensor (CTC blank index 0). |
|
|
176
|
+
| `pad_collate` | DataLoader collate fn for variable-width batching. |
|
|
177
|
+
| `CRNN(vocab, ...)` | CNN + BiLSTM + linear head. |
|
|
178
|
+
| `fit_model(dir, ...)` | Training loop with CTC loss + early stopping. |
|
|
179
|
+
| `decrypt(files, model=None, mask=None, case_sensitive=True, length=None)` | Predict labels; auto-downloads the pretrained model when `model=None`. |
|
|
180
|
+
| `save_model`, `load_model` | Local checkpoint persistence. |
|
|
181
|
+
| `from_pretrained`, `save_pretrained`, `push_to_hub` | Hugging Face Hub integration. |
|
|
182
|
+
| `download_dataset`, `available_datasets` | Fetch labeled training datasets. |
|
|
183
|
+
| `download_captchas` (CLI) | Download live, unlabeled captchas from 10 Brazilian sources. |
|
|
184
|
+
| `sequence_accuracy(preds, targets)` | Exact-match accuracy metric. |
|
|
185
|
+
|
|
186
|
+
Full API reference: **<https://Diaszano.github.io/captix/>**.
|
|
187
|
+
|
|
188
|
+
## Architecture
|
|
189
|
+
|
|
190
|
+
`CRNN` is a Convolutional Recurrent Neural Network:
|
|
191
|
+
|
|
192
|
+
1. **CNN backbone** — ResNet-style basic blocks (`64 → 128 → 256 → 256`
|
|
193
|
+
channels) with strided pooling. Down-samples height by `8` and width by
|
|
194
|
+
`4`, preserving width resolution for the sequence dimension.
|
|
195
|
+
2. **Adaptive pool** — collapses the remaining height to 1, producing a
|
|
196
|
+
width-indexed sequence of feature vectors.
|
|
197
|
+
3. **BiLSTM** — 2-layer bidirectional LSTM (hidden 256).
|
|
198
|
+
4. **Linear head** — projects to `len(vocab) + 1` logits per timestep (the
|
|
199
|
+
extra slot is the CTC blank).
|
|
200
|
+
5. **CTC loss** — handles variable-length targets, no per-position softmax.
|
|
201
|
+
|
|
202
|
+
Variable image dimensions are handled by resizing height to 32 at load time,
|
|
203
|
+
preserving the aspect-ratio width, and padding widths within each batch via
|
|
204
|
+
`pad_collate`. CRNN+CTC is the de-facto baseline for short-text scene-text
|
|
205
|
+
recognition — lighter than transformer OCR (e.g. TrOCR) and consistently
|
|
206
|
+
strong on short captcha images.
|
|
207
|
+
|
|
208
|
+
## Variable-length labels
|
|
209
|
+
|
|
210
|
+
CRNN + CTC handles variable label lengths natively. The convolutional stack
|
|
211
|
+
emits `T` logits per image; CTC collapsing (remove consecutive repeats, then
|
|
212
|
+
remove blanks) turns any path into a string of arbitrary length between 0
|
|
213
|
+
and `T`. Training mixes 4-char and 5-char labels in the same batch — no
|
|
214
|
+
length head, no padding tokens.
|
|
215
|
+
|
|
216
|
+
The downside of greedy CTC is that a confident wrong timestep can yield a
|
|
217
|
+
prediction of the wrong length. When you know the expected length, pass
|
|
218
|
+
`length=` to switch to an exact dynamic-programming search over CTC paths
|
|
219
|
+
that collapse to exactly that many characters:
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
decrypt(cap) # greedy
|
|
223
|
+
decrypt(cap, length=5) # force 5 chars
|
|
224
|
+
decrypt(cap, length=4, mask="[0-9]") # combine with masking
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
The DP runs in `O(T · L · |vocab|)` per image, tracks the best path for
|
|
228
|
+
every `(collapsed_count, last_index)` state and reconstructs the argmax. It
|
|
229
|
+
is strictly at least as good as greedy when the true length is known and
|
|
230
|
+
never emits a wrong-length prediction.
|
|
231
|
+
|
|
232
|
+
## Decode-time masking
|
|
233
|
+
|
|
234
|
+
`decrypt(..., mask=...)` zeros out forbidden vocabulary logits before CTC
|
|
235
|
+
decoding, so the same trained model can be specialized per site:
|
|
236
|
+
|
|
237
|
+
```python
|
|
238
|
+
decrypt(cap, mask=["a", "b", "c", "1", "2", "3"]) # explicit list
|
|
239
|
+
decrypt(cap, mask="[0-9a-z]") # regex char-class
|
|
240
|
+
decrypt(cap, mask="[A-Z]", case_sensitive=True) # uppercase only
|
|
241
|
+
decrypt(cap, mask="[a-z]", case_sensitive=False) # output lowercased
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## Notebooks
|
|
245
|
+
|
|
246
|
+
- `docs/notebooks/train_unified_model.ipynb` — downloads every dataset, merges
|
|
247
|
+
them and trains the unified CRNN. Designed for a cloud GPU machine.
|
|
248
|
+
- `docs/notebooks/eval_per_dataset.ipynb` — per-dataset accuracy on a held-out
|
|
249
|
+
split.
|
|
250
|
+
- `docs/notebooks/eval_per_dataset_live.ipynb` — predictions on freshly
|
|
251
|
+
downloaded, unlabeled captchas (overfit check).
|
|
252
|
+
|
|
253
|
+
## Tests
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
uv run pytest
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## License
|
|
260
|
+
|
|
261
|
+
[MIT](LICENSE) © Julio Trecenti
|
captix-1.0.0/README.md
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# Captix
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/captix/)
|
|
4
|
+
[](https://pypi.org/project/captix/)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://huggingface.co/jtrecenti/txtcaptcha-crnn)
|
|
7
|
+
[](https://Diaszano.github.io/captix/)
|
|
8
|
+
|
|
9
|
+
Read, annotate, train and decrypt **text captchas in images** with a modern
|
|
10
|
+
CRNN + CTC pipeline in PyTorch.
|
|
11
|
+
|
|
12
|
+
Captix ships:
|
|
13
|
+
|
|
14
|
+
- a CRNN architecture that handles **arbitrary input sizes** and
|
|
15
|
+
**variable-length labels**,
|
|
16
|
+
- the full alphanumeric vocabulary `0-9a-zA-Z` (62 classes + CTC blank),
|
|
17
|
+
- **decode-time masking** so a single trained model can be restricted per
|
|
18
|
+
site (e.g. `mask="[0-9]"`),
|
|
19
|
+
- **fixed-length decoding** via `length=N` for sites with a known length,
|
|
20
|
+
- a pretrained unified model hosted on the
|
|
21
|
+
[Hugging Face Hub](https://huggingface.co/jtrecenti/txtcaptcha-crnn) with
|
|
22
|
+
~89% captcha-level accuracy across ten Brazilian court captcha datasets.
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install captix
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Or from source with [`uv`](https://docs.astral.sh/uv/):
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
git clone https://github.com/Diaszano/captix
|
|
34
|
+
cd captix
|
|
35
|
+
uv sync --extra dev
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The distribution installs as `captix`; Python imports use `captix`.
|
|
39
|
+
|
|
40
|
+
## Quick start
|
|
41
|
+
|
|
42
|
+
The first `decrypt` call downloads the pretrained model from the Hugging Face
|
|
43
|
+
Hub into `~/.cache/huggingface/hub`; subsequent calls are free.
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from captix import read_captcha, decrypt
|
|
47
|
+
|
|
48
|
+
cap = read_captcha("path/to/captcha.png")
|
|
49
|
+
print(decrypt(cap)) # greedy, variable length
|
|
50
|
+
print(decrypt(cap, mask="[0-9]")) # digits only
|
|
51
|
+
print(decrypt(cap, length=5)) # force exactly 5 chars
|
|
52
|
+
print(decrypt(cap, mask=list("abcdef0123"))) # explicit allowed set
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Pin a specific release or load a different Hub repo explicitly:
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from captix import from_pretrained
|
|
59
|
+
|
|
60
|
+
model = from_pretrained("jtrecenti/txtcaptcha-crnn", revision="v0.1.0")
|
|
61
|
+
print(decrypt(cap, model=model))
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Training your own model
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from captix import fit_model, save_model, download_dataset
|
|
68
|
+
|
|
69
|
+
data_dir = download_dataset("tjmg", "data")
|
|
70
|
+
model, history = fit_model(
|
|
71
|
+
data_dir,
|
|
72
|
+
epochs=30,
|
|
73
|
+
batch_size=64,
|
|
74
|
+
case_sensitive=False,
|
|
75
|
+
)
|
|
76
|
+
save_model(model, "tjmg.pt")
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Publishing your own model to the Hub
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from captix import push_to_hub
|
|
83
|
+
|
|
84
|
+
push_to_hub(
|
|
85
|
+
model,
|
|
86
|
+
repo_id="your-username/your-captcha-model",
|
|
87
|
+
model_card="# My captcha model\n\nTrained on ...",
|
|
88
|
+
tag="v0.1.0",
|
|
89
|
+
)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Public API
|
|
93
|
+
|
|
94
|
+
| Function | Purpose |
|
|
95
|
+
|---|---|
|
|
96
|
+
| `read_captcha(files, lab_in_path=False)` | Load image(s) into a `Captcha` object. |
|
|
97
|
+
| `Captcha` | Container with `images`, `labels`, `paths`, `plot()`. |
|
|
98
|
+
| `annotate(files, labels=None, ...)` | Interactive/batch labeling (filename convention). |
|
|
99
|
+
| `CaptchaDataset(root, vocab, height, case_sensitive)` | PyTorch dataset over a folder of `<id>_<label>.<ext>` files. |
|
|
100
|
+
| `transform_image(files, height=32)` | Load + resize + width-pad for batching. |
|
|
101
|
+
| `encode_label`, `decode_indices` | Vocab ↔ tensor (CTC blank index 0). |
|
|
102
|
+
| `pad_collate` | DataLoader collate fn for variable-width batching. |
|
|
103
|
+
| `CRNN(vocab, ...)` | CNN + BiLSTM + linear head. |
|
|
104
|
+
| `fit_model(dir, ...)` | Training loop with CTC loss + early stopping. |
|
|
105
|
+
| `decrypt(files, model=None, mask=None, case_sensitive=True, length=None)` | Predict labels; auto-downloads the pretrained model when `model=None`. |
|
|
106
|
+
| `save_model`, `load_model` | Local checkpoint persistence. |
|
|
107
|
+
| `from_pretrained`, `save_pretrained`, `push_to_hub` | Hugging Face Hub integration. |
|
|
108
|
+
| `download_dataset`, `available_datasets` | Fetch labeled training datasets. |
|
|
109
|
+
| `download_captchas` (CLI) | Download live, unlabeled captchas from 10 Brazilian sources. |
|
|
110
|
+
| `sequence_accuracy(preds, targets)` | Exact-match accuracy metric. |
|
|
111
|
+
|
|
112
|
+
Full API reference: **<https://Diaszano.github.io/captix/>**.
|
|
113
|
+
|
|
114
|
+
## Architecture
|
|
115
|
+
|
|
116
|
+
`CRNN` is a Convolutional Recurrent Neural Network:
|
|
117
|
+
|
|
118
|
+
1. **CNN backbone** — ResNet-style basic blocks (`64 → 128 → 256 → 256`
|
|
119
|
+
channels) with strided pooling. Down-samples height by `8` and width by
|
|
120
|
+
`4`, preserving width resolution for the sequence dimension.
|
|
121
|
+
2. **Adaptive pool** — collapses the remaining height to 1, producing a
|
|
122
|
+
width-indexed sequence of feature vectors.
|
|
123
|
+
3. **BiLSTM** — 2-layer bidirectional LSTM (hidden 256).
|
|
124
|
+
4. **Linear head** — projects to `len(vocab) + 1` logits per timestep (the
|
|
125
|
+
extra slot is the CTC blank).
|
|
126
|
+
5. **CTC loss** — handles variable-length targets, no per-position softmax.
|
|
127
|
+
|
|
128
|
+
Variable image dimensions are handled by resizing height to 32 at load time,
|
|
129
|
+
preserving the aspect-ratio width, and padding widths within each batch via
|
|
130
|
+
`pad_collate`. CRNN+CTC is the de-facto baseline for short-text scene-text
|
|
131
|
+
recognition — lighter than transformer OCR (e.g. TrOCR) and consistently
|
|
132
|
+
strong on short captcha images.
|
|
133
|
+
|
|
134
|
+
## Variable-length labels
|
|
135
|
+
|
|
136
|
+
CRNN + CTC handles variable label lengths natively. The convolutional stack
|
|
137
|
+
emits `T` logits per image; CTC collapsing (remove consecutive repeats, then
|
|
138
|
+
remove blanks) turns any path into a string of arbitrary length between 0
|
|
139
|
+
and `T`. Training mixes 4-char and 5-char labels in the same batch — no
|
|
140
|
+
length head, no padding tokens.
|
|
141
|
+
|
|
142
|
+
The downside of greedy CTC is that a confident wrong timestep can yield a
|
|
143
|
+
prediction of the wrong length. When you know the expected length, pass
|
|
144
|
+
`length=` to switch to an exact dynamic-programming search over CTC paths
|
|
145
|
+
that collapse to exactly that many characters:
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
decrypt(cap) # greedy
|
|
149
|
+
decrypt(cap, length=5) # force 5 chars
|
|
150
|
+
decrypt(cap, length=4, mask="[0-9]") # combine with masking
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The DP runs in `O(T · L · |vocab|)` per image, tracks the best path for
|
|
154
|
+
every `(collapsed_count, last_index)` state and reconstructs the argmax. It
|
|
155
|
+
is strictly at least as good as greedy when the true length is known and
|
|
156
|
+
never emits a wrong-length prediction.
|
|
157
|
+
|
|
158
|
+
## Decode-time masking
|
|
159
|
+
|
|
160
|
+
`decrypt(..., mask=...)` zeros out forbidden vocabulary logits before CTC
|
|
161
|
+
decoding, so the same trained model can be specialized per site:
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
decrypt(cap, mask=["a", "b", "c", "1", "2", "3"]) # explicit list
|
|
165
|
+
decrypt(cap, mask="[0-9a-z]") # regex char-class
|
|
166
|
+
decrypt(cap, mask="[A-Z]", case_sensitive=True) # uppercase only
|
|
167
|
+
decrypt(cap, mask="[a-z]", case_sensitive=False) # output lowercased
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Notebooks
|
|
171
|
+
|
|
172
|
+
- `docs/notebooks/train_unified_model.ipynb` — downloads every dataset, merges
|
|
173
|
+
them and trains the unified CRNN. Designed for a cloud GPU machine.
|
|
174
|
+
- `docs/notebooks/eval_per_dataset.ipynb` — per-dataset accuracy on a held-out
|
|
175
|
+
split.
|
|
176
|
+
- `docs/notebooks/eval_per_dataset_live.ipynb` — predictions on freshly
|
|
177
|
+
downloaded, unlabeled captchas (overfit check).
|
|
178
|
+
|
|
179
|
+
## Tests
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
uv run pytest
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## License
|
|
186
|
+
|
|
187
|
+
[MIT](LICENSE) © Julio Trecenti
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "captix"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "Read, annotate, train and decrypt text captchas with a CRNN+CTC model."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
license = { file = "LICENSE" }
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "Julio Trecenti", email = "jtrecenti@gmail.com" },
|
|
10
|
+
]
|
|
11
|
+
maintainers = [
|
|
12
|
+
{ name = "Julio Trecenti", email = "jtrecenti@gmail.com" },
|
|
13
|
+
]
|
|
14
|
+
keywords = [
|
|
15
|
+
"captcha",
|
|
16
|
+
"ocr",
|
|
17
|
+
"crnn",
|
|
18
|
+
"ctc",
|
|
19
|
+
"pytorch",
|
|
20
|
+
"deep-learning",
|
|
21
|
+
"image-to-text",
|
|
22
|
+
]
|
|
23
|
+
classifiers = [
|
|
24
|
+
"Development Status :: 4 - Beta",
|
|
25
|
+
"Intended Audience :: Developers",
|
|
26
|
+
"Intended Audience :: Science/Research",
|
|
27
|
+
"License :: OSI Approved :: MIT License",
|
|
28
|
+
"Operating System :: OS Independent",
|
|
29
|
+
"Programming Language :: Python :: 3",
|
|
30
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
31
|
+
"Programming Language :: Python :: 3.10",
|
|
32
|
+
"Programming Language :: Python :: 3.11",
|
|
33
|
+
"Programming Language :: Python :: 3.12",
|
|
34
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
35
|
+
"Topic :: Scientific/Engineering :: Image Recognition",
|
|
36
|
+
]
|
|
37
|
+
dependencies = [
|
|
38
|
+
"torch>=2.2",
|
|
39
|
+
"torchvision>=0.17",
|
|
40
|
+
"Pillow>=10.0",
|
|
41
|
+
"numpy>=1.24",
|
|
42
|
+
"requests>=2.31",
|
|
43
|
+
"tqdm>=4.66",
|
|
44
|
+
"matplotlib>=3.8",
|
|
45
|
+
"pandas>=2.0",
|
|
46
|
+
"huggingface-hub>=0.20",
|
|
47
|
+
"safetensors>=0.4",
|
|
48
|
+
"python-dotenv>=1.0",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
[project.optional-dependencies]
|
|
52
|
+
dev = [
|
|
53
|
+
"pytest>=8.0",
|
|
54
|
+
"pytest-cov>=5.0",
|
|
55
|
+
"build>=1.2",
|
|
56
|
+
"twine>=5.0",
|
|
57
|
+
"pre-commit>=4.6,<5",
|
|
58
|
+
"python-semantic-release==10.7.0",
|
|
59
|
+
]
|
|
60
|
+
notebook = [
|
|
61
|
+
"jupyter>=1.0",
|
|
62
|
+
"ipykernel>=6.29",
|
|
63
|
+
]
|
|
64
|
+
docs = [
|
|
65
|
+
"quartodoc>=0.7",
|
|
66
|
+
"griffe<1",
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
[project.urls]
|
|
70
|
+
Homepage = "https://github.com/Diaszano/captix"
|
|
71
|
+
Documentation = "https://Diaszano.github.io/captix/"
|
|
72
|
+
Repository = "https://github.com/Diaszano/captix"
|
|
73
|
+
Issues = "https://github.com/Diaszano/captix/issues"
|
|
74
|
+
Changelog = "https://github.com/Diaszano/captix/blob/main/CHANGELOG.md"
|
|
75
|
+
"Model (HF Hub)" = "https://huggingface.co/jtrecenti/txtcaptcha-crnn"
|
|
76
|
+
|
|
77
|
+
[project.scripts]
|
|
78
|
+
download_captchas = "captix.download.cli:main"
|
|
79
|
+
|
|
80
|
+
[build-system]
|
|
81
|
+
requires = ["hatchling"]
|
|
82
|
+
build-backend = "hatchling.build"
|
|
83
|
+
|
|
84
|
+
[tool.hatch.build.targets.wheel]
|
|
85
|
+
packages = ["src/captix"]
|
|
86
|
+
|
|
87
|
+
[tool.hatch.build.targets.sdist]
|
|
88
|
+
include = [
|
|
89
|
+
"src/captix",
|
|
90
|
+
"tests",
|
|
91
|
+
"README.md",
|
|
92
|
+
"LICENSE",
|
|
93
|
+
"CHANGELOG.md",
|
|
94
|
+
"pyproject.toml",
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
[tool.semantic_release]
|
|
98
|
+
version_toml = ["pyproject.toml:project.version"]
|
|
99
|
+
version_variables = ["src/captix/__init__.py:__version__"]
|
|
100
|
+
tag_format = "v{version}"
|
|
101
|
+
commit_parser = "conventional"
|
|
102
|
+
allow_zero_version = true
|
|
103
|
+
major_on_zero = true
|
|
104
|
+
commit_message = "chore(release): {version}"
|
|
105
|
+
|
|
106
|
+
[tool.semantic_release.branches.main]
|
|
107
|
+
match = "main"
|
|
108
|
+
|
|
109
|
+
[tool.pytest.ini_options]
|
|
110
|
+
testpaths = ["tests"]
|
|
111
|
+
addopts = "-q"
|
|
112
|
+
pythonpath = ["."]
|