patchcraft 0.5.0__tar.gz → 0.5.1__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.
- {patchcraft-0.5.0 → patchcraft-0.5.1}/.gitignore +4 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/CHANGELOG.md +121 -3
- patchcraft-0.5.1/CONTRIBUTING.md +270 -0
- patchcraft-0.5.1/MANIFEST.in +37 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/PKG-INFO +31 -30
- {patchcraft-0.5.0 → patchcraft-0.5.1}/README.md +14 -13
- {patchcraft-0.5.0 → patchcraft-0.5.1}/README.pt-BR.md +14 -13
- {patchcraft-0.5.0 → patchcraft-0.5.1}/README.pypi.md +15 -13
- patchcraft-0.5.1/accel/Cargo.lock +232 -0
- patchcraft-0.5.1/accel/Cargo.toml +32 -0
- patchcraft-0.5.1/accel/README.md +48 -0
- patchcraft-0.5.1/accel/src/kernel.rs +252 -0
- patchcraft-0.5.1/accel/src/lib.rs +88 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/docs/AUXILIARY.md +11 -6
- {patchcraft-0.5.0 → patchcraft-0.5.1}/docs/GUIDE.md +34 -23
- patchcraft-0.5.1/docs/PERFORMANCE.md +116 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/docs/ROADMAP.md +2 -1
- {patchcraft-0.5.0 → patchcraft-0.5.1}/docs/SCOPE.md +9 -6
- {patchcraft-0.5.0 → patchcraft-0.5.1}/docs/superpowers/plans/2026-09-01-fase3-g1-predicado.md +2 -2
- patchcraft-0.5.1/pyproject.toml +130 -0
- patchcraft-0.5.1/setup.cfg +4 -0
- patchcraft-0.5.1/setup.py +81 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/src/patchcraft/__init__.py +15 -1
- {patchcraft-0.5.0 → patchcraft-0.5.1}/src/patchcraft/_accel.py +12 -11
- patchcraft-0.5.1/src/patchcraft/_version.py +24 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/src/patchcraft/reconstruct.py +1 -1
- patchcraft-0.5.1/src/patchcraft.egg-info/PKG-INFO +188 -0
- patchcraft-0.5.1/src/patchcraft.egg-info/SOURCES.txt +76 -0
- patchcraft-0.5.1/src/patchcraft.egg-info/dependency_links.txt +1 -0
- patchcraft-0.5.1/src/patchcraft.egg-info/requires.txt +13 -0
- patchcraft-0.5.1/src/patchcraft.egg-info/scm_file_list.json +77 -0
- patchcraft-0.5.1/src/patchcraft.egg-info/scm_version.json +8 -0
- patchcraft-0.5.1/src/patchcraft.egg-info/top_level.txt +1 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/tests/test_accel.py +10 -10
- {patchcraft-0.5.0 → patchcraft-0.5.1}/tests/test_import.py +1 -1
- patchcraft-0.5.1/tools/benchmark.py +165 -0
- patchcraft-0.5.1/tools/check_dist.py +209 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/uv.lock +26 -10
- patchcraft-0.5.0/.github/workflows/release.yml +0 -203
- patchcraft-0.5.0/.github/workflows/test.yml +0 -93
- patchcraft-0.5.0/CONTRIBUTING.md +0 -161
- patchcraft-0.5.0/pyproject.toml +0 -112
- {patchcraft-0.5.0 → patchcraft-0.5.1}/.python-version +0 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/LICENSE +0 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/docs/ADR/0001-patch-extraction-api.md +0 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/docs/ADR/0002-patchify-transform.md +0 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/docs/ADR/0003-reversibility-classes.md +0 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/docs/FOCO-1.0.md +0 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/docs/STUDIES/2026-08-04-patch-techniques.md +0 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/docs/THEORY.md +0 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/docs/USAGE.md +0 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/docs/superpowers/plans/2026-09-01-fase1-bugs-performance.md +0 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/docs/superpowers/plans/2026-09-01-fase2-acel.md +0 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/docs/superpowers/specs/2026-09-01-fase1-bugs-performance-design.md +0 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/docs/superpowers/specs/2026-09-01-fase2-acel-design.md +0 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/docs/superpowers/specs/2026-09-01-fase3-g1-predicado-design.md +0 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/src/patchcraft/_foldgeom.py +0 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/src/patchcraft/cache.py +0 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/src/patchcraft/extract.py +0 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/src/patchcraft/geometry.py +0 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/src/patchcraft/metrics.py +0 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/src/patchcraft/pair.py +0 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/src/patchcraft/py.typed +0 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/src/patchcraft/resize.py +0 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/src/patchcraft/stitch.py +0 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/tests/__init__.py +0 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/tests/_datasets.py +0 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/tests/_rng.py +0 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/tests/conftest.py +0 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/tests/test_cache.py +0 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/tests/test_datasets_helper.py +0 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/tests/test_exactness.py +0 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/tests/test_extract.py +0 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/tests/test_geometry.py +0 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/tests/test_metrics.py +0 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/tests/test_pair.py +0 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/tests/test_public_api.py +0 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/tests/test_reconstruct.py +0 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/tests/test_reference.py +0 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/tests/test_resize.py +0 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/tests/test_rng.py +0 -0
- {patchcraft-0.5.0 → patchcraft-0.5.1}/tests/test_stitch.py +0 -0
|
@@ -6,6 +6,121 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.5.1] - 2026-09-02
|
|
10
|
+
|
|
11
|
+
> Numbered `0.6.0` while it was being built. Corrected before release: the
|
|
12
|
+
> middle digit is the compatibility boundary, and this release changes no
|
|
13
|
+
> signature, no name and no output value. See the versioning policy and its
|
|
14
|
+
> errata in [`CONTRIBUTING.md`](CONTRIBUTING.md).
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- **The native accelerator ships inside this wheel.** It was going to be a
|
|
19
|
+
second PyPI project, `patchcraft-accel`, reached through
|
|
20
|
+
`pip install patchcraft[accel]`. That project was never published and now
|
|
21
|
+
never will be. A release produces one sdist and six wheels for the single
|
|
22
|
+
`patchcraft` project: five tagged `cp312-abi3-<platform>` with the Rust
|
|
23
|
+
kernel compiled in as `patchcraft._accel_native`, and one `py3-none-any`
|
|
24
|
+
for every other platform. Installers prefer the most specific compatible
|
|
25
|
+
tag, so `pip install patchcraft` picks up the accelerator where one exists
|
|
26
|
+
and the pure wheel everywhere else, with no extra to remember. The pattern
|
|
27
|
+
and its universal fallback follow `coverage`, which publishes the same two
|
|
28
|
+
shapes for one version.
|
|
29
|
+
- The `accel` extra is removed. Nothing depended on it, because it never
|
|
30
|
+
reached the index.
|
|
31
|
+
- The accelerator is now on by default where a wheel carries it, rather than
|
|
32
|
+
opt-in. The two paths are bit-identical by test, and `PATCHCRAFT_ACCEL=0`
|
|
33
|
+
still forces the pure one at runtime.
|
|
34
|
+
- Build backend moves from hatchling to setuptools with setuptools-rust, which
|
|
35
|
+
is what makes one project able to emit both shapes of wheel. `setup.py`
|
|
36
|
+
holds the single decision, driven by `PATCHCRAFT_PURE_PYTHON` and
|
|
37
|
+
`PATCHCRAFT_REQUIRE_EXTENSION`; everything else stays declarative in
|
|
38
|
+
`pyproject.toml`. Installing from the sdist compiles the extension when a
|
|
39
|
+
Rust toolchain is present and degrades to a pure install when it is not.
|
|
40
|
+
- License metadata follows PEP 639: the SPDX expression `MIT` with
|
|
41
|
+
`license-files`, and the superseded `License :: OSI Approved` classifier is
|
|
42
|
+
gone. setuptools rejects carrying both.
|
|
43
|
+
|
|
44
|
+
### Added
|
|
45
|
+
|
|
46
|
+
- **The version comes from the git tag.** `setuptools-scm` derives it at build
|
|
47
|
+
time, so no file in the source states a version, the release procedure loses
|
|
48
|
+
its bump step, and a tag and a release can no longer disagree.
|
|
49
|
+
`patchcraft.__version__` still works: the build writes a generated
|
|
50
|
+
`_version.py` that `__init__.py` imports, which costs nothing at import time,
|
|
51
|
+
with `importlib.metadata` as a fallback for a tree that was never built.
|
|
52
|
+
Measured before choosing: `importlib.metadata` alone was the wrong default,
|
|
53
|
+
because it took 56 ms on first call and reported a stale version in an
|
|
54
|
+
editable install.
|
|
55
|
+
- `docs/PERFORMANCE.md`, which is where the accelerator's measurements live.
|
|
56
|
+
It carries the test machine, the versions, the date, the reproduction
|
|
57
|
+
command and what the numbers do not say, and its table is pasted from
|
|
58
|
+
`tools/benchmark.py --markdown` rather than retyped.
|
|
59
|
+
- The Rust kernel is now always built in release mode, including in an
|
|
60
|
+
editable install. It was following the build command, so a development
|
|
61
|
+
checkout compiled a debug kernel: measured here, that turned a 14x speedup
|
|
62
|
+
into 2.1x, which made local benchmarks meaningless and made the accelerated
|
|
63
|
+
CI job exercise code no user runs.
|
|
64
|
+
- `patchcraft.__version__` is re-exported explicitly, so `mypy --strict` in a
|
|
65
|
+
consuming project still accepts it. Moving it out of a literal and into an
|
|
66
|
+
import had quietly made it an implicit re-export, which strict mode rejects.
|
|
67
|
+
- `tools/benchmark.py` measures the accelerated fold against the pure one and
|
|
68
|
+
refuses to report a timing unless the two results are bit-identical. The
|
|
69
|
+
numbers in the documentation come from it, and it is the reproduction
|
|
70
|
+
command they cite.
|
|
71
|
+
- Every build checkout uses `fetch-depth: 0`. The default shallow clone hides
|
|
72
|
+
the tags, and setuptools-scm's fallback for that is a silent
|
|
73
|
+
`0.1.dev1+g<sha>`, a version that walks backwards rather than an error.
|
|
74
|
+
- `tools/check_dist.py` grew `--expect-version` and `--require-universal`, and
|
|
75
|
+
it now rejects any artifact carrying a local version segment. PyPI refuses
|
|
76
|
+
those on upload, so this turns a late and confusing server-side rejection
|
|
77
|
+
into an early local one. It also fixes a bug in the job that checks the
|
|
78
|
+
native wheels, which demanded a universal wheel that job never has.
|
|
79
|
+
- Linux aarch64 wheels, which the previous four-target matrix did not cover.
|
|
80
|
+
- `tools/check_dist.py`, run before every upload. It fails the release if a
|
|
81
|
+
platform wheel lost its extension, which `optional=True` would otherwise
|
|
82
|
+
let through as a silently slow wheel, or if the universal wheel gained one.
|
|
83
|
+
- The versioning policy now names the resolver rule it follows, which is that
|
|
84
|
+
`0.y` is the compatibility boundary in Cargo, npm and PEP 440 alike, and it
|
|
85
|
+
carries an errata for the two releases numbered before the rule was written.
|
|
86
|
+
- Version numbers are gone from the documentation surface. The three cover
|
|
87
|
+
pages carry none at all, since the PyPI badge already shows the current
|
|
88
|
+
release, and the manual keeps only the one the citation entry needs. The
|
|
89
|
+
release archaeology those pages used to recite lives here, and the rest is
|
|
90
|
+
in the git history.
|
|
91
|
+
- `MANIFEST.in`, which puts `accel/` in the sdist so the source path can build
|
|
92
|
+
the extension, and keeps `lab/`, `.superpowers/` and cargo's `target/` out.
|
|
93
|
+
|
|
94
|
+
- **Corrected: the hand-rolled and library tile-and-blend results are not
|
|
95
|
+
bit-identical.** The three cover pages and GUIDE section 1 claimed they were,
|
|
96
|
+
and the GUIDE block asserted it. Re-measured: they agree to 1.17e-05 on a
|
|
97
|
+
value in [0, 1], differing on 24500 of 49152 elements. The cause is the 0.3.0
|
|
98
|
+
separable denominator, where `stitch` sums two 1-D window folds while the
|
|
99
|
+
hand-rolled twin folds a replicated 2-D kernel. `stitch(weight="uniform")`
|
|
100
|
+
is still bit-identical to `reconstruct`. Nothing about the round-trip
|
|
101
|
+
contract changed; the documentation was simply asserting more than the code
|
|
102
|
+
delivered.
|
|
103
|
+
- Documentation corrections found by auditing every page against the code:
|
|
104
|
+
the changelog's reference links did not define the two most recent versions;
|
|
105
|
+
`docs/AUXILIARY.md` listed wheel contents that predate three releases;
|
|
106
|
+
`docs/SCOPE.md` still promised "pure-Python single-threaded", which the
|
|
107
|
+
rayon-parallel kernel contradicts; `docs/ROADMAP.md` still named hatchling;
|
|
108
|
+
GUIDE said the library "is not faster", that the no-zstandard branch is
|
|
109
|
+
untested, and that CI is six cells; the cover pages pointed at `USAGE.md` as
|
|
110
|
+
a walkthrough of 20 symbols when it covers 18 and says so. `CONTRIBUTING.md`
|
|
111
|
+
told a contributor to run `pytest` after a `uv sync` that does not install it.
|
|
112
|
+
|
|
113
|
+
### Notes
|
|
114
|
+
|
|
115
|
+
- No signature, no name and no output value changed. The public surface is the
|
|
116
|
+
same 20 names, still pinned by `tests/test_public_api.py`.
|
|
117
|
+
- Measured on torch 2.14, CPU, mean over the dev machine: `reconstruct` at
|
|
118
|
+
3x512x512 p=32 s=16 goes 11.4 ms to 2.5 ms (4.5x), at 3x2048x2048 p=64 s=32
|
|
119
|
+
477.9 ms to 31.8 ms (15.0x); `stitch[hann]` 21.6 ms to 7.0 ms (3.1x) and
|
|
120
|
+
488.7 ms to 38.8 ms (12.6x). The gain is algorithmic rather than a thread
|
|
121
|
+
count: the pure `F.fold` measured between 365 and 465 ms on that largest
|
|
122
|
+
case at 4, 8, 16 and 36 torch threads.
|
|
123
|
+
|
|
9
124
|
## [0.5.0] - 2026-09-02
|
|
10
125
|
|
|
11
126
|
### Added
|
|
@@ -472,9 +587,12 @@ First public release. Public API stable; signatures will only change in 1.x.
|
|
|
472
587
|
- [`README.md`](README.md) covers installation, the car-vs-track metaphor,
|
|
473
588
|
validation lab.
|
|
474
589
|
|
|
475
|
-
[Unreleased]: https://github.com/LeoPR/PatchCraft/compare/v0.
|
|
476
|
-
[0.
|
|
477
|
-
[0.
|
|
590
|
+
[Unreleased]: https://github.com/LeoPR/PatchCraft/compare/v0.5.1...HEAD
|
|
591
|
+
[0.5.1]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.5.1
|
|
592
|
+
[0.5.0]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.5.0
|
|
593
|
+
|
|
594
|
+
<!-- 0.4.0 and 0.3.0 are deliberately unlinked: they are source milestones that
|
|
595
|
+
were never tagged and never released, as their own entries say. -->
|
|
478
596
|
[0.2.2]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.2.2
|
|
479
597
|
[0.2.1]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.2.1
|
|
480
598
|
[0.2.0]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.2.0
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# Contributing to PatchCraft
|
|
2
|
+
|
|
3
|
+
If you've cloned this repo and want to run the test suite, understand how the project is laid out, or follow the validation conventions, this is the page. For installation and usage, see the [README](README.md).
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Run tests
|
|
8
|
+
|
|
9
|
+
The test runner and the linters live in the `dev` extra, so a plain `uv sync`
|
|
10
|
+
does not install them:
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
uv sync --locked --extra cache --extra dev
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
pytest
|
|
18
|
+
pytest -m "not gpu" # skip GPU-requiring tests
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
CI runs the full suite on every push and PR. See [`.github/workflows/test.yml`](.github/workflows/test.yml). Matrix is `{ubuntu-latest, windows-latest} × {python 3.12, 3.13, 3.14}` for the pure path, plus a second job on both operating systems for the accelerated one. To run the same checks locally before pushing:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
ruff check src tests
|
|
25
|
+
mypy --strict src
|
|
26
|
+
pytest -m "not gpu"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## The two paths
|
|
32
|
+
|
|
33
|
+
The overlapping fold in `reconstruct` and `stitch` has a Rust implementation
|
|
34
|
+
in [`accel/`](accel/), compiled into the wheel on the platforms CI builds for.
|
|
35
|
+
Everything else runs the same operation in torch. The two are bit-identical by
|
|
36
|
+
test, so a contribution has to keep both green rather than pick one.
|
|
37
|
+
|
|
38
|
+
Which one your checkout runs depends on whether the extension got built:
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
python -c "import patchcraft; print(patchcraft.accel_available())"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`uv sync --extra cache --extra dev` builds it when a Rust toolchain is on
|
|
45
|
+
PATH, always in release mode. Two environment variables override that, and CI
|
|
46
|
+
sets both explicitly rather than relying on what happens to be installed:
|
|
47
|
+
|
|
48
|
+
| Variable | Effect |
|
|
49
|
+
|---|---|
|
|
50
|
+
| `PATCHCRAFT_PURE_PYTHON=1` | Skip the extension. How the universal wheel is built. |
|
|
51
|
+
| `PATCHCRAFT_REQUIRE_EXTENSION=1` | Make a Rust build failure fatal instead of degrading to the pure path. |
|
|
52
|
+
| `PATCHCRAFT_ACCEL=0` | Runtime only: ignore an extension that is already built. |
|
|
53
|
+
|
|
54
|
+
The Rust kernel has its own tests, which need no Python at all:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
cargo test --manifest-path accel/Cargo.toml
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
One local wrinkle worth knowing. If your checkout sits under a path with
|
|
61
|
+
non-ASCII characters, `setuptools-rust` mis-decodes the artifact path that
|
|
62
|
+
cargo reports and the copy step fails. Point the build at an ASCII directory
|
|
63
|
+
and it works:
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
CARGO_TARGET_DIR=/tmp/pc-target uv sync --extra cache --extra dev
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Layout
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
PatchCraft/
|
|
75
|
+
├── pyproject.toml package metadata, build backend, cibuildwheel targets
|
|
76
|
+
├── setup.py the one build decision: with or without the Rust extension
|
|
77
|
+
├── tools/check_dist.py gate: extension present, versions agree, tag respected
|
|
78
|
+
├── tools/benchmark.py accelerated vs pure, and proves they agree first
|
|
79
|
+
├── MANIFEST.in what the sdist carries
|
|
80
|
+
├── README.md the call page, canonical, English
|
|
81
|
+
├── README.pt-BR.md the same call page in Portuguese
|
|
82
|
+
├── README.pypi.md the PyPI page (long_description), links absolute
|
|
83
|
+
├── CONTRIBUTING.md this file
|
|
84
|
+
├── CHANGELOG.md Keep-a-Changelog format
|
|
85
|
+
├── LICENSE MIT
|
|
86
|
+
├── .python-version 3.13
|
|
87
|
+
├── .gitignore ignores archive/, venvs, caches, outputs
|
|
88
|
+
├── .github/workflows/
|
|
89
|
+
│ ├── test.yml matrix CI on PRs/main, plus the accelerated job
|
|
90
|
+
│ └── release.yml publishes to PyPI on vX.Y.Z tag push (Trusted Publishing)
|
|
91
|
+
├── src/patchcraft/ library core, one-image-at-a-time primitives
|
|
92
|
+
│ ├── __init__.py re-exports the full public API and the generated version
|
|
93
|
+
│ ├── extract.py patches via strided view or F.unfold; Patchify (ADR 0002)
|
|
94
|
+
│ ├── reconstruct.py inverse via F.fold + count map, closed-form fast path
|
|
95
|
+
│ ├── stitch.py weighted reassembly for modified patches
|
|
96
|
+
│ ├── geometry.py pre-flight: num_patches, tilings, scale_factor, paired_tilings
|
|
97
|
+
│ ├── metrics.py patch_metrics, per_patch_mse, per_patch_psnr
|
|
98
|
+
│ ├── pair.py LR↔HR pairing; PatchPair, PatchMeta
|
|
99
|
+
│ ├── resize.py resize with PIL or torch backends
|
|
100
|
+
│ ├── cache.py content-addressed disk cache
|
|
101
|
+
│ ├── _accel.py bridge to the optional native accelerator, falls back silently
|
|
102
|
+
│ └── _foldgeom.py shared fold-geometry validation for reconstruct and stitch
|
|
103
|
+
├── accel/ the Rust crate compiled into this wheel (pyo3, setuptools-rust)
|
|
104
|
+
│ ├── Cargo.toml crate metadata; the version there is internal
|
|
105
|
+
│ ├── src/kernel.rs the gather-fold kernel, pure Rust, cargo-tested
|
|
106
|
+
│ ├── src/lib.rs pyo3 glue, exports patchcraft._accel_native
|
|
107
|
+
│ └── README.md how to build it, and how to check it is active
|
|
108
|
+
├── tests/ pytest suite (contract tests for src/)
|
|
109
|
+
│ ├── test_extract.py extract + Patchify
|
|
110
|
+
│ ├── test_reconstruct.py
|
|
111
|
+
│ ├── test_stitch.py
|
|
112
|
+
│ ├── test_geometry.py num_patches + tilings + scale_factor + paired_tilings
|
|
113
|
+
│ ├── test_metrics.py
|
|
114
|
+
│ ├── test_pair.py
|
|
115
|
+
│ ├── test_resize.py
|
|
116
|
+
│ ├── test_cache.py
|
|
117
|
+
│ ├── test_exactness.py falsifies the count-map predicate over the legal space
|
|
118
|
+
│ ├── test_reference.py naive loop-based reference for the fast paths
|
|
119
|
+
│ ├── test_public_api.py freezes the 20 names, their signatures and carrier fields
|
|
120
|
+
│ ├── test_accel.py native path, skipped unless the extension was built
|
|
121
|
+
│ ├── test_datasets_helper.py label_subset
|
|
122
|
+
│ ├── test_import.py
|
|
123
|
+
│ ├── _rng.py audited round-trip helpers (data generation, bit equality)
|
|
124
|
+
│ ├── test_rng.py tests for those helpers
|
|
125
|
+
│ └── _datasets.py dev-only fixtures (MNIST, etc), NOT public API
|
|
126
|
+
├── lab/ ephemeral experiments; see lab/README.md
|
|
127
|
+
│ ├── README.md bench rules (tracked; the scripts themselves are not)
|
|
128
|
+
│ └── .gitignore ignores everything else (tracked)
|
|
129
|
+
├── docs/
|
|
130
|
+
│ ├── GUIDE.md the manual, every README claim measured with its output
|
|
131
|
+
│ ├── PERFORMANCE.md what the accelerator is worth, and how to re-measure
|
|
132
|
+
│ ├── USAGE.md REPL walkthrough of every public symbol (behind, see B4)
|
|
133
|
+
│ ├── SCOPE.md responsibilities matrix + parallelization analysis
|
|
134
|
+
│ ├── AUXILIARY.md tests/_datasets, lab/, Z:\ conventions (NOT part of the wheel)
|
|
135
|
+
│ ├── THEORY.md distilled design + §9 condition contract; §0 binding scope
|
|
136
|
+
│ ├── ROADMAP.md milestone plan
|
|
137
|
+
│ ├── FOCO-1.0.md what 1.0 freezes and the blockers in the way
|
|
138
|
+
│ ├── ADR/ one file per decision that shaped the API
|
|
139
|
+
│ ├── STUDIES/ background reading behind those decisions
|
|
140
|
+
│ └── superpowers/ the design specs and implementation plans per work phase
|
|
141
|
+
└── archive/ reference-only; gitignored (pruned 2026-05-17, only HISTORY.md kept)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Validation lab
|
|
147
|
+
|
|
148
|
+
The library is "one image in, one tensor out" by design, but you only know it works once you run it end-to-end on real images. That happens in two places, neither of which is part of the shipped package:
|
|
149
|
+
|
|
150
|
+
- [`tests/`](tests/) holds the formal pytest suite that defines the contract from [`docs/THEORY.md`](docs/THEORY.md) §9.
|
|
151
|
+
- [`lab/`](lab/) holds ephemeral scripts and notebooks for fast hypothesis-checking. See [`lab/README.md`](lab/README.md) for the bench rules; outputs go to `Z:\outputs\patchcraft\` (off-tree).
|
|
152
|
+
|
|
153
|
+
Datasets used by tests/lab are downloaded lazily into `Z:\caches\datasets\<name>\` on first use; they do not ship with the package and are never bundled into the wheel.
|
|
154
|
+
|
|
155
|
+
The dev framework that makes this all work (fixtures, dataset helpers, `Z:\` conventions) is documented in [`docs/AUXILIARY.md`](docs/AUXILIARY.md).
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Versioning policy
|
|
160
|
+
|
|
161
|
+
The scheme is [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html),
|
|
162
|
+
expressed in the [PEP 440](https://peps.python.org/pep-0440/) form that PyPI
|
|
163
|
+
accepts. Two rules matter more than the arithmetic, because both were broken
|
|
164
|
+
once and both cost work to undo.
|
|
165
|
+
|
|
166
|
+
**The number names a published artifact, not a unit of work.** Finishing a
|
|
167
|
+
phase, merging a branch and landing a refactor are not releases, and none of
|
|
168
|
+
them touches `__version__`. Everything between releases accumulates under
|
|
169
|
+
`[Unreleased]` in [`CHANGELOG.md`](CHANGELOG.md), and the number is assigned
|
|
170
|
+
in the release commit itself. Versions 0.3.0 and 0.4.0 came from the opposite
|
|
171
|
+
habit: each internal work phase minted a minor, so three numbers existed
|
|
172
|
+
before any of them reached PyPI.
|
|
173
|
+
|
|
174
|
+
**A published version is frozen.** PyPI refuses a filename it has already
|
|
175
|
+
seen, so a version that shipped can only be superseded, never edited. That is
|
|
176
|
+
also why a failed pipeline never costs a number: `skip-existing` makes the
|
|
177
|
+
upload a no-op and the same tag can be re-run.
|
|
178
|
+
|
|
179
|
+
**While the project is pre-1.0, the middle digit is the compatibility
|
|
180
|
+
boundary.** SemVer itself leaves `0.y.z` open, saying only that "anything MAY
|
|
181
|
+
change at any time", so the meaning comes from the resolvers instead, and they
|
|
182
|
+
agree: Cargo expands `^0.2.3` to `>=0.2.3, <0.3.0`, npm does the same, and
|
|
183
|
+
PEP 440's `~=0.5.0` means `>=0.5.0, <0.6.0`. In every one of them a `0.y` bump
|
|
184
|
+
is the announcement that something may break, and a `0.y.z` bump is the
|
|
185
|
+
promise that nothing does.
|
|
186
|
+
|
|
187
|
+
That fixes what each digit is for here:
|
|
188
|
+
|
|
189
|
+
| Change | Bump | Why |
|
|
190
|
+
|---|---|---|
|
|
191
|
+
| An output value, a signature, a name or documented behaviour changes | `0.Y.0` | Resolvers stop at the y boundary, so this is where a break belongs |
|
|
192
|
+
| New functionality that breaks nothing, or a fix | `0.y.Z` | Anyone pinned to the y-series should get it |
|
|
193
|
+
| Documentation, tests, CI, packaging or tooling only | `0.y.Z` | Still a release if it is published, still no break |
|
|
194
|
+
| Nothing published | nothing | Accumulate under `[Unreleased]` |
|
|
195
|
+
|
|
196
|
+
After 1.0 the ordinary reading applies: breaking is major, additive is minor,
|
|
197
|
+
fixed is patch. What 1.0 freezes is written in
|
|
198
|
+
[`docs/FOCO-1.0.md`](docs/FOCO-1.0.md).
|
|
199
|
+
|
|
200
|
+
### Errata
|
|
201
|
+
|
|
202
|
+
The rule above was written after the fact, and two releases predate it.
|
|
203
|
+
|
|
204
|
+
`0.4.0` should have been `0.3.1`. It added `accel_available()` and nothing
|
|
205
|
+
else: no signature changed, and the accelerated path was bit-exact against the
|
|
206
|
+
pure one. Additive and non-breaking is a z-bump under the boundary rule, and
|
|
207
|
+
minting a y announced a break that never happened.
|
|
208
|
+
|
|
209
|
+
`0.5.1` was briefly numbered `0.6.0` before release. It changes only how the
|
|
210
|
+
package is built and shipped, so it is a z-bump.
|
|
211
|
+
|
|
212
|
+
Two others were right and are worth recording as the shape to copy. `0.3.0`
|
|
213
|
+
moved `stitch` output by ULPs through a different summation order, which is an
|
|
214
|
+
output change and belongs at a y boundary. `0.5.0` made `tilings` return 73
|
|
215
|
+
specs where it returned 100, which is squarely a break.
|
|
216
|
+
|
|
217
|
+
The accelerator in [`accel/`](accel/) has no version of its own to manage. It
|
|
218
|
+
is compiled into this wheel, so it ships when `patchcraft` ships. The version
|
|
219
|
+
in `accel/Cargo.toml` is internal and nothing reads it; what the Python side
|
|
220
|
+
actually checks is `_ABI_VERSION` in `accel/src/lib.rs`, and a mismatch there
|
|
221
|
+
means a silent fall back to the pure path rather than a crash.
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Where the version comes from
|
|
226
|
+
|
|
227
|
+
The git tag, and nowhere else. `setuptools-scm` derives it at build time, so
|
|
228
|
+
no file in the source states a version and there is no literal to forget.
|
|
229
|
+
`v0.5.1` produces `0.5.1`; the leading `v` is stripped for you.
|
|
230
|
+
|
|
231
|
+
At build time setuptools-scm writes `src/patchcraft/_version.py`, which
|
|
232
|
+
`__init__.py` imports and re-exports as `patchcraft.__version__`. That file is
|
|
233
|
+
generated and gitignored, so a checkout that was never built falls back to
|
|
234
|
+
`importlib.metadata` and then to `0+unknown`. The fallback exists so an
|
|
235
|
+
un-built tree still imports; it is not a supported way to learn the version.
|
|
236
|
+
|
|
237
|
+
Anything other than a clean tree sitting exactly on a tag produces a version
|
|
238
|
+
with a local segment, like `0.5.2.dev0+gb4edfd81.d20260902` from a dirty tree
|
|
239
|
+
or `0.1.dev1+gb4edfd81` from a checkout whose tags were never fetched. PyPI
|
|
240
|
+
refuses local segments outright, so such a build can never be published by
|
|
241
|
+
accident, and `tools/check_dist.py` rejects it before the upload rather than
|
|
242
|
+
letting the upload fail.
|
|
243
|
+
|
|
244
|
+
That last point is why every `actions/checkout` that builds uses
|
|
245
|
+
`fetch-depth: 0`. The default shallow clone hides the tags, and the failure is
|
|
246
|
+
silent: a version that walks backwards rather than an error.
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Releasing (maintainer only)
|
|
251
|
+
|
|
252
|
+
1. Close the `[Unreleased]` section in [`CHANGELOG.md`](CHANGELOG.md) as `[X.Y.Z] YYYY-MM-DD`.
|
|
253
|
+
2. Update the `version` field of the BibTeX entry in [`docs/GUIDE.md`](docs/GUIDE.md) section 9. It is the one version string left in the documentation, because a citation needs a concrete one.
|
|
254
|
+
3. Update [`docs/ROADMAP.md`](docs/ROADMAP.md) milestone checkboxes.
|
|
255
|
+
4. Commit: `release: vX.Y.Z`.
|
|
256
|
+
5. Tag + push: `git tag -a vX.Y.Z -m "..."` then `git push origin vX.Y.Z`.
|
|
257
|
+
6. `release.yml` fires automatically: validates, builds, publishes to PyPI via Trusted Publishing, then creates the GitHub Release with every artifact attached.
|
|
258
|
+
|
|
259
|
+
There is no version to bump in step 4, and no way for a tag and a release to
|
|
260
|
+
disagree. The `validate` job still cross-checks that setuptools-scm derived
|
|
261
|
+
exactly what the tag says, which catches a dirty or shallow checkout before
|
|
262
|
+
anything is built.
|
|
263
|
+
|
|
264
|
+
One release produces one sdist and six wheels: five `cp312-abi3-<platform>`
|
|
265
|
+
wheels with the Rust extension inside, and one `py3-none-any` wheel for
|
|
266
|
+
everywhere else. `tools/check_dist.py` runs before the upload and fails the
|
|
267
|
+
release if a platform wheel lost its extension, if the universal wheel gained
|
|
268
|
+
one, if the artifacts disagree about the version, or if any of them is not the
|
|
269
|
+
version the tag names. Publishing is a single project, so there is nothing to
|
|
270
|
+
register on PyPI beyond the publisher that already exists.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# The sdist is the reproducible source snapshot: the product (`src/`, `accel/`)
|
|
2
|
+
# plus the track that proves it works (`tests/`, `docs/`). `accel/` has to be
|
|
3
|
+
# here, because an sdist build compiles it when a Rust toolchain is present.
|
|
4
|
+
#
|
|
5
|
+
# The `prune` lines below are load-bearing rather than decorative, and the
|
|
6
|
+
# reason changed with setuptools-scm. Its file finder offers every git-tracked
|
|
7
|
+
# file to the sdist, so this file stopped being an allowlist and became a
|
|
8
|
+
# blocklist over everything git knows about. Measured: with no prune for it,
|
|
9
|
+
# `.vscode/settings.json` reached the sdist.
|
|
10
|
+
#
|
|
11
|
+
# Note that `lab/` is only *mostly* ignored: `lab/README.md` and
|
|
12
|
+
# `lab/.gitignore` are tracked, so the finder offers them and the prune is what
|
|
13
|
+
# stops them. The rest of `lab/` is scratch work that no commit can reproduce,
|
|
14
|
+
# and patchcraft-0.2.0.tar.gz shipped three such files to PyPI.
|
|
15
|
+
#
|
|
16
|
+
# Adding a tracked directory to this repository ships it to PyPI by default.
|
|
17
|
+
# `tools/check_dist.py` holds the matching allowlist and will fail the release
|
|
18
|
+
# for anything unexpected, which is the reminder to come back here.
|
|
19
|
+
|
|
20
|
+
include LICENSE
|
|
21
|
+
include CHANGELOG.md CONTRIBUTING.md
|
|
22
|
+
include README.md README.pt-BR.md README.pypi.md
|
|
23
|
+
include pyproject.toml setup.py uv.lock .python-version
|
|
24
|
+
|
|
25
|
+
recursive-include src *.py
|
|
26
|
+
include src/patchcraft/py.typed
|
|
27
|
+
recursive-include tests *.py
|
|
28
|
+
recursive-include docs *.md
|
|
29
|
+
recursive-include accel *.rs *.toml *.lock *.md
|
|
30
|
+
prune accel/target
|
|
31
|
+
recursive-include tools *.py
|
|
32
|
+
|
|
33
|
+
prune lab
|
|
34
|
+
prune archive
|
|
35
|
+
prune .superpowers
|
|
36
|
+
prune .github
|
|
37
|
+
prune .vscode
|
|
@@ -1,45 +1,44 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: patchcraft
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.1
|
|
4
4
|
Summary: Image patch extraction, reconstruction, pairing and seam-aware stitching for super-resolution and dataset pipelines.
|
|
5
|
+
Author: Leonardo Marques de Souza
|
|
6
|
+
License-Expression: MIT
|
|
5
7
|
Project-URL: Homepage, https://github.com/LeoPR/PatchCraft
|
|
6
8
|
Project-URL: Repository, https://github.com/LeoPR/PatchCraft
|
|
7
9
|
Project-URL: Issues, https://github.com/LeoPR/PatchCraft/issues
|
|
8
10
|
Project-URL: Changelog, https://github.com/LeoPR/PatchCraft/blob/main/CHANGELOG.md
|
|
9
11
|
Project-URL: Documentation, https://github.com/LeoPR/PatchCraft/blob/main/docs/GUIDE.md
|
|
10
|
-
|
|
11
|
-
License-Expression: MIT
|
|
12
|
-
License-File: LICENSE
|
|
13
|
-
Keywords: dataset,image,patches,super-resolution,torch
|
|
12
|
+
Keywords: image,patches,super-resolution,dataset,torch
|
|
14
13
|
Classifier: Development Status :: 4 - Beta
|
|
15
14
|
Classifier: Intended Audience :: Developers
|
|
16
15
|
Classifier: Intended Audience :: Science/Research
|
|
17
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
18
16
|
Classifier: Operating System :: OS Independent
|
|
19
17
|
Classifier: Programming Language :: Python :: 3
|
|
20
18
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
21
19
|
Classifier: Programming Language :: Python :: 3.12
|
|
22
20
|
Classifier: Programming Language :: Python :: 3.13
|
|
23
21
|
Classifier: Programming Language :: Python :: 3.14
|
|
24
|
-
Classifier:
|
|
22
|
+
Classifier: Programming Language :: Rust
|
|
25
23
|
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
24
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
26
25
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
27
26
|
Classifier: Typing :: Typed
|
|
28
27
|
Requires-Python: >=3.12
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
License-File: LICENSE
|
|
29
30
|
Requires-Dist: numpy>=1.26
|
|
30
|
-
Requires-Dist: pillow>=10
|
|
31
31
|
Requires-Dist: torch>=2.6
|
|
32
|
-
|
|
33
|
-
Requires-Dist: patchcraft-accel>=0.1.0; extra == 'accel'
|
|
32
|
+
Requires-Dist: pillow>=10
|
|
34
33
|
Provides-Extra: cache
|
|
35
|
-
Requires-Dist: zstandard>=0.22; extra ==
|
|
34
|
+
Requires-Dist: zstandard>=0.22; extra == "cache"
|
|
36
35
|
Provides-Extra: dev
|
|
37
|
-
Requires-Dist:
|
|
38
|
-
Requires-Dist: pytest-cov; extra ==
|
|
39
|
-
Requires-Dist:
|
|
40
|
-
Requires-Dist:
|
|
41
|
-
Requires-Dist: torchvision>=0.20; extra ==
|
|
42
|
-
|
|
36
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
37
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
38
|
+
Requires-Dist: ruff>=0.5; extra == "dev"
|
|
39
|
+
Requires-Dist: mypy>=1.10; extra == "dev"
|
|
40
|
+
Requires-Dist: torchvision>=0.20; extra == "dev"
|
|
41
|
+
Dynamic: license-file
|
|
43
42
|
|
|
44
43
|
# PatchCraft
|
|
45
44
|
|
|
@@ -81,16 +80,17 @@ pip install patchcraft
|
|
|
81
80
|
pip install "patchcraft[cache]" # adds zstandard, which compresses Cache payloads
|
|
82
81
|
```
|
|
83
82
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
and
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
pip install patchcraft[accel]
|
|
90
|
-
```
|
|
83
|
+
There is nothing else to install for speed. On Windows x64, Linux x86_64 and
|
|
84
|
+
aarch64, and both macOS architectures, the wheel carries a Rust accelerator for
|
|
85
|
+
the overlapping fold, which is where `reconstruct` and `stitch` spend their
|
|
86
|
+
time. Every other platform gets the universal wheel and runs the pure-torch
|
|
87
|
+
paths, which return the same values.
|
|
91
88
|
|
|
92
|
-
`patchcraft.accel_available()` reports at runtime
|
|
93
|
-
|
|
89
|
+
`patchcraft.accel_available()` reports at runtime which one you got, and
|
|
90
|
+
`PATCHCRAFT_ACCEL=0` in the environment forces the pure path. On the overlapping
|
|
91
|
+
fold it is worth between 2.6x and 14x on the machine it was measured on, which
|
|
92
|
+
the [performance page](https://github.com/LeoPR/PatchCraft/blob/main/docs/PERFORMANCE.md)
|
|
93
|
+
reports in full.
|
|
94
94
|
|
|
95
95
|
The distribution name and the import name are both `patchcraft`. The cache extra is optional, because `Cache` works without `zstandard` as well and simply stores its payload uncompressed.
|
|
96
96
|
|
|
@@ -144,7 +144,7 @@ assert scrambled.shape == patches.shape # the right shape
|
|
|
144
144
|
assert not torch.equal(scrambled, patches) # and the wrong pixels
|
|
145
145
|
```
|
|
146
146
|
|
|
147
|
-
The saving is real on the other side too. Tiling an image, running a per-patch model and blending the result back with a Hann window took 17 non-blank lines by hand against 3 with `extract` and `stitch`, and the two outputs
|
|
147
|
+
The saving is real on the other side too. Tiling an image, running a per-patch model and blending the result back with a Hann window took 17 non-blank lines by hand against 3 with `extract` and `stitch`, and the two outputs agreed to about 1.2e-05 on a value in [0, 1].
|
|
148
148
|
|
|
149
149
|
## The geometry has to cover the image
|
|
150
150
|
|
|
@@ -170,14 +170,15 @@ The everyday shorthand is that `stride == patch_size` and `stride == patch_size
|
|
|
170
170
|
|
|
171
171
|
## Status
|
|
172
172
|
|
|
173
|
-
This
|
|
173
|
+
This is pre-1.0, so both the output values and the API shape can still move. While the leading digit is zero the middle one is the compatibility boundary, which makes a new `0.y.z` safe to take and a new `0.y` the place where a change is allowed to land, and the [changelog](https://github.com/LeoPR/PatchCraft/blob/main/CHANGELOG.md) records each one with the measurement behind it. The suite collects 1571 tests and passes on Python 3.12, 3.13 and 3.14, on Ubuntu and on Windows alike.
|
|
174
174
|
|
|
175
175
|
Two limits are worth knowing before you depend on it. Every figure on this page was measured on CPU, and no CUDA path has ever executed in the test matrix, so the pipeline does preserve the device you hand it while the exactness numbers stay unverified on GPU. The other limit is that no external project has consumed the published API in real use yet, and that consumption is this project's own stated gate for calling the shape settled.
|
|
176
176
|
|
|
177
177
|
## Documentation
|
|
178
178
|
|
|
179
179
|
- [Guide](https://github.com/LeoPR/PatchCraft/blob/main/docs/GUIDE.md), the manual, with every figure on this page shown as runnable code
|
|
180
|
-
- [
|
|
180
|
+
- [Performance](https://github.com/LeoPR/PatchCraft/blob/main/docs/PERFORMANCE.md), what the native accelerator is worth and how to re-measure it
|
|
181
|
+
- [Usage](https://github.com/LeoPR/PatchCraft/blob/main/docs/USAGE.md), a walkthrough of the public surface, captured against an older release
|
|
181
182
|
- [Theory](https://github.com/LeoPR/PatchCraft/blob/main/docs/THEORY.md), the math and the per-function contract
|
|
182
183
|
- [Scope](https://github.com/LeoPR/PatchCraft/blob/main/docs/SCOPE.md), the line between this library and your pipeline
|
|
183
184
|
- [Repository](https://github.com/LeoPR/PatchCraft), [issues](https://github.com/LeoPR/PatchCraft/issues) and [contributing](https://github.com/LeoPR/PatchCraft/blob/main/CONTRIBUTING.md)
|
|
@@ -39,16 +39,16 @@ pip install patchcraft
|
|
|
39
39
|
pip install "patchcraft[cache]" # adds zstandard, which compresses Cache payloads
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
and
|
|
42
|
+
There is nothing else to install for speed. On Windows x64, Linux x86_64 and
|
|
43
|
+
aarch64, and both macOS architectures, the wheel carries a Rust accelerator for
|
|
44
|
+
the overlapping fold, which is where `reconstruct` and `stitch` spend their
|
|
45
|
+
time. Every other platform gets the universal wheel and runs the pure-torch
|
|
46
|
+
paths, which return the same values.
|
|
45
47
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
`patchcraft.accel_available()` reports at runtime whether the accelerator is
|
|
51
|
-
active; `PATCHCRAFT_ACCEL=0` in the environment forces the pure path.
|
|
48
|
+
`patchcraft.accel_available()` reports at runtime which one you got, and
|
|
49
|
+
`PATCHCRAFT_ACCEL=0` in the environment forces the pure path. On the overlapping
|
|
50
|
+
fold it is worth between 2.6x and 14x here, measured in
|
|
51
|
+
[docs/PERFORMANCE.md](docs/PERFORMANCE.md).
|
|
52
52
|
|
|
53
53
|
The distribution name and the import name are both `patchcraft`. The runtime dependencies are `torch>=2.6`, `numpy>=1.26` and `pillow>=10`, and the supported Python versions are in [the guide](docs/GUIDE.md#9-install-details-and-citation) together with the note you need before you install a GPU wheel.
|
|
54
54
|
|
|
@@ -77,7 +77,7 @@ That is the whole loop. You extract, you do your work per patch, and then you co
|
|
|
77
77
|
|
|
78
78
|
The two functions answer two different questions, so choosing between them is the first decision you make.
|
|
79
79
|
|
|
80
|
-
`reconstruct` is the inverse. It assumes the patches still hold the pixels `extract` gave you, it divides by the count map, and on
|
|
80
|
+
`reconstruct` is the inverse. It assumes the patches still hold the pixels `extract` gave you, it divides by the count map, and on the geometries described further down it hands the image back unchanged.
|
|
81
81
|
|
|
82
82
|
`stitch` is for patches a model rewrote. Neighbouring patches now disagree about the pixels they share, and uniform averaging leaves that disagreement visible as a grid of seams, so `stitch` weights each patch by a window that fades toward its border.
|
|
83
83
|
|
|
@@ -108,7 +108,7 @@ assert not torch.equal(scrambled, patches) # and the wrong pix
|
|
|
108
108
|
|
|
109
109
|
The second one is a stride that does not cover the image. On a 128 by 128 image with `patch=32, stride=20` the grid stops at pixel 112, which leaves 3840 of the 16384 pixels at zero, and a hand-rolled `fold` returns that partly black image without complaining.
|
|
110
110
|
|
|
111
|
-
Writing the tile-and-blend loop by hand costs 17 non-blank lines against 3 here, and the two results
|
|
111
|
+
Writing the tile-and-blend loop by hand costs 17 non-blank lines against 3 here, and the two results agree to about 1.2e-05 on a value in [0, 1], the two routes differing only in the order they sum a separable window. [The guide](docs/GUIDE.md#1-why-not-unfold-and-fold-directly) runs both versions side by side.
|
|
112
112
|
|
|
113
113
|
## The geometry has to cover the image
|
|
114
114
|
|
|
@@ -144,7 +144,7 @@ The round trip is exact when every value in the count map is a power of two, whi
|
|
|
144
144
|
|
|
145
145
|
## Status
|
|
146
146
|
|
|
147
|
-
**0.
|
|
147
|
+
**Pre-1.0**, and the badge above is the current release. While the leading digit is zero the middle one is the compatibility boundary, so `0.y.z` is safe to take and a new `0.y` is where an output value or a signature is allowed to move. [CHANGELOG.md](CHANGELOG.md) records every such change with the measurement behind it.
|
|
148
148
|
|
|
149
149
|
1571 tests collected and CI is green on {Ubuntu, Windows} x {Python 3.12, 3.13, 3.14}, with `ruff check` and `mypy --strict` in the same run. The package is typed and ships `py.typed`.
|
|
150
150
|
|
|
@@ -155,7 +155,8 @@ No external project has consumed it yet, and no CUDA path in this library has ev
|
|
|
155
155
|
| If you want | Open |
|
|
156
156
|
|---|---|
|
|
157
157
|
| The manual: every argument above, measured, with its output | [docs/GUIDE.md](docs/GUIDE.md) |
|
|
158
|
-
|
|
|
158
|
+
| What the native accelerator is worth, and how to re-measure it | [docs/PERFORMANCE.md](docs/PERFORMANCE.md) |
|
|
159
|
+
| A walkthrough of the public surface, captured against an older release | [docs/USAGE.md](docs/USAGE.md) |
|
|
159
160
|
| The line between this library and your pipeline | [docs/SCOPE.md](docs/SCOPE.md) |
|
|
160
161
|
| The math, the decisions and the per-function contract | [docs/THEORY.md](docs/THEORY.md) |
|
|
161
162
|
| Why the API looks like this | [docs/ADR/](docs/ADR) |
|