patchcraft 0.2.2__tar.gz → 0.5.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.
- {patchcraft-0.2.2 → patchcraft-0.5.0}/.github/workflows/release.yml +76 -1
- {patchcraft-0.2.2 → patchcraft-0.5.0}/.github/workflows/test.yml +43 -1
- {patchcraft-0.2.2 → patchcraft-0.5.0}/.gitignore +3 -0
- {patchcraft-0.2.2 → patchcraft-0.5.0}/CHANGELOG.md +164 -23
- patchcraft-0.5.0/CONTRIBUTING.md +161 -0
- {patchcraft-0.2.2 → patchcraft-0.5.0}/PKG-INFO +38 -6
- {patchcraft-0.2.2 → patchcraft-0.5.0}/README.md +34 -5
- {patchcraft-0.2.2 → patchcraft-0.5.0}/README.pt-BR.md +34 -5
- {patchcraft-0.2.2 → patchcraft-0.5.0}/README.pypi.md +34 -5
- {patchcraft-0.2.2 → patchcraft-0.5.0}/docs/AUXILIARY.md +2 -2
- {patchcraft-0.2.2 → patchcraft-0.5.0}/docs/GUIDE.md +23 -20
- {patchcraft-0.2.2 → patchcraft-0.5.0}/docs/SCOPE.md +10 -7
- {patchcraft-0.2.2 → patchcraft-0.5.0}/docs/STUDIES/2026-08-04-patch-techniques.md +2 -1
- {patchcraft-0.2.2 → patchcraft-0.5.0}/docs/THEORY.md +6 -5
- {patchcraft-0.2.2 → patchcraft-0.5.0}/docs/USAGE.md +16 -8
- patchcraft-0.5.0/docs/superpowers/plans/2026-09-01-fase1-bugs-performance.md +1179 -0
- patchcraft-0.5.0/docs/superpowers/plans/2026-09-01-fase2-acel.md +1643 -0
- patchcraft-0.5.0/docs/superpowers/plans/2026-09-01-fase3-g1-predicado.md +1705 -0
- patchcraft-0.5.0/docs/superpowers/specs/2026-09-01-fase1-bugs-performance-design.md +151 -0
- patchcraft-0.5.0/docs/superpowers/specs/2026-09-01-fase2-acel-design.md +168 -0
- patchcraft-0.5.0/docs/superpowers/specs/2026-09-01-fase3-g1-predicado-design.md +260 -0
- {patchcraft-0.2.2 → patchcraft-0.5.0}/pyproject.toml +14 -2
- {patchcraft-0.2.2 → patchcraft-0.5.0}/src/patchcraft/__init__.py +3 -1
- patchcraft-0.5.0/src/patchcraft/_accel.py +115 -0
- patchcraft-0.5.0/src/patchcraft/_foldgeom.py +87 -0
- {patchcraft-0.2.2 → patchcraft-0.5.0}/src/patchcraft/cache.py +7 -0
- {patchcraft-0.2.2 → patchcraft-0.5.0}/src/patchcraft/extract.py +32 -1
- {patchcraft-0.2.2 → patchcraft-0.5.0}/src/patchcraft/geometry.py +10 -2
- {patchcraft-0.2.2 → patchcraft-0.5.0}/src/patchcraft/metrics.py +21 -13
- patchcraft-0.5.0/src/patchcraft/reconstruct.py +137 -0
- {patchcraft-0.2.2 → patchcraft-0.5.0}/src/patchcraft/resize.py +6 -3
- {patchcraft-0.2.2 → patchcraft-0.5.0}/src/patchcraft/stitch.py +102 -112
- patchcraft-0.5.0/tests/_rng.py +108 -0
- patchcraft-0.5.0/tests/test_accel.py +223 -0
- {patchcraft-0.2.2 → patchcraft-0.5.0}/tests/test_cache.py +22 -0
- patchcraft-0.5.0/tests/test_exactness.py +164 -0
- {patchcraft-0.2.2 → patchcraft-0.5.0}/tests/test_extract.py +62 -1
- {patchcraft-0.2.2 → patchcraft-0.5.0}/tests/test_geometry.py +39 -11
- {patchcraft-0.2.2 → patchcraft-0.5.0}/tests/test_import.py +6 -0
- {patchcraft-0.2.2 → patchcraft-0.5.0}/tests/test_metrics.py +47 -0
- patchcraft-0.5.0/tests/test_public_api.py +249 -0
- {patchcraft-0.2.2 → patchcraft-0.5.0}/tests/test_reconstruct.py +89 -30
- patchcraft-0.5.0/tests/test_reference.py +92 -0
- {patchcraft-0.2.2 → patchcraft-0.5.0}/tests/test_resize.py +11 -0
- patchcraft-0.5.0/tests/test_rng.py +109 -0
- {patchcraft-0.2.2 → patchcraft-0.5.0}/tests/test_stitch.py +91 -20
- patchcraft-0.5.0/uv.lock +1172 -0
- patchcraft-0.2.2/CONTRIBUTING.md +0 -98
- patchcraft-0.2.2/src/patchcraft/reconstruct.py +0 -155
- patchcraft-0.2.2/uv.lock +0 -1041
- {patchcraft-0.2.2 → patchcraft-0.5.0}/.python-version +0 -0
- {patchcraft-0.2.2 → patchcraft-0.5.0}/LICENSE +0 -0
- {patchcraft-0.2.2 → patchcraft-0.5.0}/docs/ADR/0001-patch-extraction-api.md +0 -0
- {patchcraft-0.2.2 → patchcraft-0.5.0}/docs/ADR/0002-patchify-transform.md +0 -0
- {patchcraft-0.2.2 → patchcraft-0.5.0}/docs/ADR/0003-reversibility-classes.md +0 -0
- {patchcraft-0.2.2 → patchcraft-0.5.0}/docs/FOCO-1.0.md +0 -0
- {patchcraft-0.2.2 → patchcraft-0.5.0}/docs/ROADMAP.md +0 -0
- {patchcraft-0.2.2 → patchcraft-0.5.0}/src/patchcraft/pair.py +0 -0
- {patchcraft-0.2.2 → patchcraft-0.5.0}/src/patchcraft/py.typed +0 -0
- {patchcraft-0.2.2 → patchcraft-0.5.0}/tests/__init__.py +0 -0
- {patchcraft-0.2.2 → patchcraft-0.5.0}/tests/_datasets.py +0 -0
- {patchcraft-0.2.2 → patchcraft-0.5.0}/tests/conftest.py +0 -0
- {patchcraft-0.2.2 → patchcraft-0.5.0}/tests/test_datasets_helper.py +0 -0
- {patchcraft-0.2.2 → patchcraft-0.5.0}/tests/test_pair.py +0 -0
|
@@ -64,6 +64,46 @@ jobs:
|
|
|
64
64
|
name: dist
|
|
65
65
|
path: dist/
|
|
66
66
|
|
|
67
|
+
build-accel:
|
|
68
|
+
name: accel wheel (${{ matrix.target }})
|
|
69
|
+
runs-on: ${{ matrix.os }}
|
|
70
|
+
needs: validate
|
|
71
|
+
strategy:
|
|
72
|
+
fail-fast: false
|
|
73
|
+
matrix:
|
|
74
|
+
include:
|
|
75
|
+
# macos-13 is the Intel runner; macos-latest is arm64.
|
|
76
|
+
- os: windows-latest
|
|
77
|
+
target: x86_64-pc-windows-msvc
|
|
78
|
+
- os: ubuntu-latest
|
|
79
|
+
target: x86_64-unknown-linux-gnu
|
|
80
|
+
- os: macos-latest
|
|
81
|
+
target: aarch64-apple-darwin
|
|
82
|
+
- os: macos-13
|
|
83
|
+
target: x86_64-apple-darwin
|
|
84
|
+
steps:
|
|
85
|
+
- uses: actions/checkout@v4
|
|
86
|
+
|
|
87
|
+
- name: Build accel wheel
|
|
88
|
+
uses: PyO3/maturin-action@v1
|
|
89
|
+
with:
|
|
90
|
+
target: ${{ matrix.target }}
|
|
91
|
+
args: --release --out dist-accel --manifest-path accel/Cargo.toml
|
|
92
|
+
|
|
93
|
+
- name: Build accel sdist (linux only)
|
|
94
|
+
# The sdist is OS-independent; one is enough. Building it from source
|
|
95
|
+
# requires a Rust toolchain -- documented in accel/README.md.
|
|
96
|
+
if: matrix.target == 'x86_64-unknown-linux-gnu'
|
|
97
|
+
run: pipx run maturin sdist --manifest-path accel/Cargo.toml --out dist-accel
|
|
98
|
+
|
|
99
|
+
- name: Check accel artifacts
|
|
100
|
+
run: pipx run twine check dist-accel/*
|
|
101
|
+
|
|
102
|
+
- uses: actions/upload-artifact@v4
|
|
103
|
+
with:
|
|
104
|
+
name: accel-${{ matrix.target }}
|
|
105
|
+
path: dist-accel/
|
|
106
|
+
|
|
67
107
|
publish-pypi:
|
|
68
108
|
name: publish to PyPI (Trusted Publishing)
|
|
69
109
|
runs-on: ubuntu-latest
|
|
@@ -94,10 +134,38 @@ jobs:
|
|
|
94
134
|
# Workflow: release.yml
|
|
95
135
|
# Environment: pypi
|
|
96
136
|
|
|
137
|
+
publish-accel:
|
|
138
|
+
name: publish patchcraft-accel to PyPI (Trusted Publishing)
|
|
139
|
+
runs-on: ubuntu-latest
|
|
140
|
+
needs: build-accel
|
|
141
|
+
environment:
|
|
142
|
+
name: pypi
|
|
143
|
+
url: https://pypi.org/project/patchcraft-accel/
|
|
144
|
+
permissions:
|
|
145
|
+
id-token: write # required for OIDC token exchange with PyPI
|
|
146
|
+
steps:
|
|
147
|
+
- uses: actions/download-artifact@v4
|
|
148
|
+
with:
|
|
149
|
+
pattern: accel-*
|
|
150
|
+
path: dist-accel
|
|
151
|
+
merge-multiple: true
|
|
152
|
+
- name: Publish via Trusted Publishing
|
|
153
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
154
|
+
with:
|
|
155
|
+
skip-existing: true
|
|
156
|
+
packages-dir: dist-accel/
|
|
157
|
+
# FIRST RELEASE REQUIRES a one-time "pending publisher" config on
|
|
158
|
+
# pypi.org (user action, not this repo's):
|
|
159
|
+
# PyPI project name: patchcraft-accel
|
|
160
|
+
# Owner: LeoPR
|
|
161
|
+
# Repository: PatchCraft
|
|
162
|
+
# Workflow: release.yml
|
|
163
|
+
# Environment: pypi
|
|
164
|
+
|
|
97
165
|
github-release:
|
|
98
166
|
name: create GitHub Release with assets
|
|
99
167
|
runs-on: ubuntu-latest
|
|
100
|
-
needs: publish-pypi
|
|
168
|
+
needs: [publish-pypi, build-accel]
|
|
101
169
|
permissions:
|
|
102
170
|
contents: write # required to create a Release
|
|
103
171
|
steps:
|
|
@@ -106,6 +174,11 @@ jobs:
|
|
|
106
174
|
with:
|
|
107
175
|
name: dist
|
|
108
176
|
path: dist/
|
|
177
|
+
- uses: actions/download-artifact@v4
|
|
178
|
+
with:
|
|
179
|
+
pattern: accel-*
|
|
180
|
+
path: dist-accel
|
|
181
|
+
merge-multiple: true
|
|
109
182
|
- name: Extract version from tag
|
|
110
183
|
id: ver
|
|
111
184
|
run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
|
|
@@ -123,6 +196,8 @@ jobs:
|
|
|
123
196
|
files: |
|
|
124
197
|
dist/*.whl
|
|
125
198
|
dist/*.tar.gz
|
|
199
|
+
dist-accel/*.whl
|
|
200
|
+
dist-accel/*.tar.gz
|
|
126
201
|
fail_on_unmatched_files: true
|
|
127
202
|
draft: false
|
|
128
203
|
prerelease: false
|
|
@@ -18,7 +18,7 @@ jobs:
|
|
|
18
18
|
fail-fast: false
|
|
19
19
|
matrix:
|
|
20
20
|
os: [ubuntu-latest, windows-latest]
|
|
21
|
-
python: ["3.12", "3.13"]
|
|
21
|
+
python: ["3.12", "3.13", "3.14"]
|
|
22
22
|
env:
|
|
23
23
|
# Force uv to use the matrix Python in every uv* invocation
|
|
24
24
|
# (otherwise `uv run` re-resolves to its default, which is the
|
|
@@ -49,3 +49,45 @@ jobs:
|
|
|
49
49
|
|
|
50
50
|
- name: Pytest (skip GPU markers)
|
|
51
51
|
run: uv run pytest -m "not gpu"
|
|
52
|
+
|
|
53
|
+
test-accel:
|
|
54
|
+
name: ${{ matrix.os }} / py3.12 / accel
|
|
55
|
+
runs-on: ${{ matrix.os }}
|
|
56
|
+
env:
|
|
57
|
+
UV_PYTHON: "3.12"
|
|
58
|
+
strategy:
|
|
59
|
+
fail-fast: false
|
|
60
|
+
matrix:
|
|
61
|
+
os: [ubuntu-latest, windows-latest]
|
|
62
|
+
steps:
|
|
63
|
+
- uses: actions/checkout@v4
|
|
64
|
+
|
|
65
|
+
- name: Install uv
|
|
66
|
+
uses: astral-sh/setup-uv@v3
|
|
67
|
+
with:
|
|
68
|
+
enable-cache: true
|
|
69
|
+
|
|
70
|
+
- name: Install Rust
|
|
71
|
+
uses: dtolnay/rust-toolchain@stable
|
|
72
|
+
|
|
73
|
+
- name: Set up Python
|
|
74
|
+
run: uv python install
|
|
75
|
+
|
|
76
|
+
- name: Install project with dev + cache extras
|
|
77
|
+
run: uv sync --locked --extra cache --extra dev
|
|
78
|
+
|
|
79
|
+
- name: Rust unit tests
|
|
80
|
+
# The venv's interpreter on PATH lets the pyo3 build script resolve
|
|
81
|
+
# libpython (extension-module is off outside maturin builds).
|
|
82
|
+
run: uv run cargo test --manifest-path accel/Cargo.toml
|
|
83
|
+
|
|
84
|
+
- name: Build + install the accelerator
|
|
85
|
+
run: |
|
|
86
|
+
uv pip install maturin
|
|
87
|
+
uv run maturin develop --manifest-path accel/Cargo.toml --release
|
|
88
|
+
|
|
89
|
+
- name: Accelerator is detected
|
|
90
|
+
run: uv run python -c "import patchcraft; assert patchcraft.accel_available()"
|
|
91
|
+
|
|
92
|
+
- name: Pytest (accelerated mode)
|
|
93
|
+
run: uv run pytest -m "not gpu"
|
|
@@ -6,8 +6,165 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.5.0] - 2026-09-02
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Python 3.14 is supported and declared. The classifiers list it, the CI
|
|
14
|
+
matrix runs the full suite plus `ruff check` and `mypy --strict` against it
|
|
15
|
+
on Ubuntu and Windows, and all six cells are green. The floor stays at 3.12
|
|
16
|
+
because `cache.py` uses PEP 695 generic syntax, which 3.11 cannot parse.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- `tilings`/`paired_tilings` no longer emit degenerate single-patch overlap
|
|
21
|
+
specs: with one patch the stride is unobservable and the spec duplicated
|
|
22
|
+
the exact tile. `tilings((28, 28), allow_overlap=True)` now returns 73
|
|
23
|
+
specs instead of 100; `paired_tilings((14, 14), (28, 28), allow_overlap=True)`
|
|
24
|
+
returns 27 instead of 40.
|
|
25
|
+
- The development lock moved to torch 2.14.0 and torchvision 0.29.0, with the
|
|
26
|
+
linters and the rest of the toolchain along with it. The full
|
|
27
|
+
126,736-geometry exactness sweep passes on torch 2.14, so the count-map
|
|
28
|
+
contract is unchanged by the upgrade. Runtime requirements are untouched and
|
|
29
|
+
stay at `torch>=2.6`.
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- **Public retraction: the documented exactness boundary was wrong.** The old
|
|
34
|
+
wording (`k_max <= 4`, "`stride == patch_size / 2`", "within ~1 ULP" outside
|
|
35
|
+
the rule) was measured false: the error grows with the pixel's coverage
|
|
36
|
+
count (up to 19 ULP at count 81 in float32). The correct contract
|
|
37
|
+
(ADR 0003): the `extract`/`reconstruct` round trip is bit-exact iff every
|
|
38
|
+
value of the overlap count map is a power of two, which is always true
|
|
39
|
+
at `stride == patch_size`, and outside the rule the per-pixel error is
|
|
40
|
+
bounded by `(k+1)·eps·|v|`, with `k` the pixel's coverage count.
|
|
41
|
+
Docstrings and docs (SCOPE, THEORY, GUIDE, USAGE, READMEs) now state this
|
|
42
|
+
form; THEORY §9.1 records the extract-truncates / reconstruct-rejects
|
|
43
|
+
asymmetry.
|
|
44
|
+
- Packaging: the sdist no longer carries `.superpowers/`, an untracked
|
|
45
|
+
directory of agent tooling that hatchling swept in (62 files). The same
|
|
46
|
+
class of leak as the `lab/` files that reached PyPI in 0.2.0.
|
|
47
|
+
- Documentation: GUIDE section 6 still printed the pre-0.5.0 enumeration
|
|
48
|
+
output and described the single-patch duplicates as an open wart; section 7
|
|
49
|
+
asserted `__version__ == "0.4.0"`; section 8 and the BibTeX entry quoted
|
|
50
|
+
0.4.0 and its test counts. All four now match the release.
|
|
51
|
+
- Prose: the em-dashes that entered the docstrings, docs and tests during
|
|
52
|
+
0.3.0 to 0.5.0 are rewritten as ordinary sentences, per the project's
|
|
53
|
+
writing rule.
|
|
54
|
+
- Test suite: round-trip assertions now run on seeded full-mantissa noise
|
|
55
|
+
(integer ramps and widened-float32 data could mask ULP-level errors); a
|
|
56
|
+
falsification suite (`tests/test_exactness.py`) enumerates the 126,736
|
|
57
|
+
legal geometries, samples 256 (seeded; full sweep via
|
|
58
|
+
`PATCHCRAFT_SWEEP_FULL=1`), and pins both halves of the predicate; a naive
|
|
59
|
+
loop-based reference (`tests/test_reference.py`) cross-checks the fast
|
|
60
|
+
paths; the 20-name public surface is frozen by `tests/test_public_api.py`;
|
|
61
|
+
the no-`zstandard` cache branch is now covered.
|
|
62
|
+
|
|
63
|
+
### Notes
|
|
64
|
+
|
|
65
|
+
- No signature changed. The public surface is exactly the 20 names of 0.4.0,
|
|
66
|
+
now pinned by test.
|
|
67
|
+
|
|
68
|
+
## [0.4.0] - 2026-09-01
|
|
69
|
+
|
|
70
|
+
> Source milestone, never uploaded to PyPI. Its contents reached
|
|
71
|
+
> users inside 0.5.0. See the versioning policy in
|
|
72
|
+
> [`CONTRIBUTING.md`](CONTRIBUTING.md) for why the number was minted
|
|
73
|
+
> early and why that no longer happens.
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
### Added
|
|
77
|
+
|
|
78
|
+
- Optional native accelerator `patchcraft-accel` (`pip install patchcraft[accel]`):
|
|
79
|
+
a Rust/pyo3 kernel for the overlap fold of `reconstruct`/`stitch`. Prebuilt
|
|
80
|
+
abi3 wheels (Python 3.12+) for Windows x64, Linux x86_64 (manylinux), macOS
|
|
81
|
+
arm64 and macOS x86_64; self-contained, no system dependencies, no torch
|
|
82
|
+
linkage. When it is absent, ABI-mismatched, ineligible (CUDA/integer
|
|
83
|
+
tensors), or disabled via `PATCHCRAFT_ACCEL=0`, every path falls back
|
|
84
|
+
silently to pure torch.
|
|
85
|
+
- `patchcraft.accel_available()` reports whether the accelerator is active.
|
|
86
|
+
|
|
87
|
+
### Performance
|
|
88
|
+
|
|
89
|
+
- Overlap fold with the accelerator, mean of `lab/bench_phase2.py` on the
|
|
90
|
+
dev machine: reconstruct 3x512x512 p=32 s=16 17.727 ms -> 2.609 ms (6.80x);
|
|
91
|
+
stitch[hann] same geometry 23.714 ms -> 8.894 ms (2.67x); 3x2048x2048 p=64
|
|
92
|
+
s=32 reconstruct 469.814 ms -> 32.458 ms (14.47x). The accelerated `stitch`
|
|
93
|
+
also skips the full pre-multiply pass over the patches tensor.
|
|
94
|
+
|
|
95
|
+
### Notes
|
|
96
|
+
|
|
97
|
+
- Numeric equivalence accel vs pure: bit-exact (`torch.equal`) across the
|
|
98
|
+
test grid (overlap x rectangular x stride∤patch x C in {1,3,4} x f32/f64).
|
|
99
|
+
- Linux validation: wheel
|
|
100
|
+
`patchcraft_accel-0.1.0-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl`
|
|
101
|
+
installed and the test suite passed accelerated on Ubuntu 26.04 (WSL),
|
|
102
|
+
built via the manylinux maturin container.
|
|
103
|
+
|
|
104
|
+
## [0.3.0] - 2026-09-01
|
|
105
|
+
|
|
106
|
+
> Source milestone, never uploaded to PyPI. Its contents reached
|
|
107
|
+
> users inside 0.5.0. See the versioning policy in
|
|
108
|
+
> [`CONTRIBUTING.md`](CONTRIBUTING.md) for why the number was minted
|
|
109
|
+
> early and why that no longer happens.
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
### Performance
|
|
113
|
+
|
|
114
|
+
- `extract` uses a strided-window view (`Tensor.unfold`) for `dilation == 1`,
|
|
115
|
+
one copy instead of im2col + permute-contiguous: 13-21x faster on CPU,
|
|
116
|
+
bit-exact. `dilation > 1` still uses `F.unfold`.
|
|
117
|
+
- `reconstruct` skips `F.fold` entirely on non-overlapping grids (a pure
|
|
118
|
+
rearrangement, 27x faster on CPU) and computes the overlap count map in
|
|
119
|
+
closed form O(H+W) instead of folding a tensor of ones.
|
|
120
|
+
- `stitch` builds its denominator from two 1-D window folds (the kernels are
|
|
121
|
+
separable) instead of a second 2-D `F.fold`. `uniform` stays bit-exact vs
|
|
122
|
+
`reconstruct`; `hann`/`gaussian` may differ by ULPs (summation order).
|
|
123
|
+
- `patch_metrics`/`per_patch_mse` materialize one f64 tensor instead of
|
|
124
|
+
three and sync the device once instead of three times.
|
|
125
|
+
|
|
9
126
|
### Fixed
|
|
10
127
|
|
|
128
|
+
- `resize` with `backend="pil"` clamped integer tensors to the uint8 range
|
|
129
|
+
before casting; values outside [0, 255] used to wrap (300 became 44).
|
|
130
|
+
- Removed the machine-local `cache_dir = "Z:\caches\pytest"` from
|
|
131
|
+
`pyproject.toml` (created a literal `Z:\caches\...` directory on
|
|
132
|
+
Linux/macOS).
|
|
133
|
+
|
|
134
|
+
### Documentation
|
|
135
|
+
|
|
136
|
+
- Corrected stale 0.2.1 version strings on all cover pages and in GUIDE.
|
|
137
|
+
- Corrected the gaussian-kernel floor claim in `stitch` (exp(-4) at corners,
|
|
138
|
+
not exp(-2) everywhere).
|
|
139
|
+
- Corrected the bit-exactness rule in THEORY/USAGE (power-of-two count map).
|
|
140
|
+
- SCOPE §4.4 no longer claims `reconstruct` lacks a dtype guard.
|
|
141
|
+
- CHANGELOG: link targets for [Unreleased], [0.2.2], [0.2.1].
|
|
142
|
+
|
|
143
|
+
### Internal
|
|
144
|
+
|
|
145
|
+
- `reconstruct`/`stitch` share one fold-geometry validator
|
|
146
|
+
(`patchcraft._foldgeom.check_fold_geometry`); error messages unchanged.
|
|
147
|
+
|
|
148
|
+
## [0.2.2] - 2026-08-30
|
|
149
|
+
|
|
150
|
+
Documentation release. No behaviour changes, so every call that worked on 0.2.1
|
|
151
|
+
works identically here, and the only file under `src/` that changed is a
|
|
152
|
+
docstring.
|
|
153
|
+
|
|
154
|
+
### Fixed
|
|
155
|
+
|
|
156
|
+
- **`reconstruct`'s docstring claimed the overlap round trip returns the
|
|
157
|
+
original image.** It said each pixel comes back as "the average of all patches
|
|
158
|
+
covering it, the same as the original when patches came from `extract`
|
|
159
|
+
unmodified". The second half is false. Measured on a 16x16 image with
|
|
160
|
+
`patch_size=4, stride=1`, unmodified patches straight from `extract` come back
|
|
161
|
+
with `torch.equal` False and a maximum absolute error of 2.4e-07 in float32.
|
|
162
|
+
|
|
163
|
+
The docstring now states the real rule: the round trip is bit-exact when every
|
|
164
|
+
value in the count map is a power of two, because dividing a float by a power
|
|
165
|
+
of two is the one division that never rounds. It also says that widening the
|
|
166
|
+
dtype does not rescue it, since the deciding axis is the geometry rather than
|
|
167
|
+
the precision.
|
|
11
168
|
- **`pair()`'s docstring double-applied the stride, and could silently
|
|
12
169
|
misplace every patch for a caller who trusted it.** It said patch `k` has
|
|
13
170
|
its top-left at LR pixel `(row * sh_lr, col * sw_lr)`, but `PatchMeta.row`
|
|
@@ -37,32 +194,11 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
37
194
|
Reported from an API review done for the QSR prototype, which uses patch
|
|
38
195
|
position as a context feature.
|
|
39
196
|
|
|
40
|
-
## [0.2.2] 2026-08-30
|
|
41
|
-
|
|
42
|
-
Documentation release. No behaviour changes, so every call that worked on 0.2.1
|
|
43
|
-
works identically here, and the only file under `src/` that changed is a
|
|
44
|
-
docstring.
|
|
45
|
-
|
|
46
|
-
### Fixed
|
|
47
|
-
|
|
48
|
-
- **`reconstruct`'s docstring claimed the overlap round trip returns the
|
|
49
|
-
original image.** It said each pixel comes back as "the average of all patches
|
|
50
|
-
covering it, the same as the original when patches came from `extract`
|
|
51
|
-
unmodified". The second half is false. Measured on a 16x16 image with
|
|
52
|
-
`patch_size=4, stride=1`, unmodified patches straight from `extract` come back
|
|
53
|
-
with `torch.equal` False and a maximum absolute error of 2.4e-07 in float32.
|
|
54
|
-
|
|
55
|
-
The docstring now states the real rule: the round trip is bit-exact when every
|
|
56
|
-
value in the count map is a power of two, because dividing a float by a power
|
|
57
|
-
of two is the one division that never rounds. It also says that widening the
|
|
58
|
-
dtype does not rescue it, since the deciding axis is the geometry rather than
|
|
59
|
-
the precision.
|
|
60
|
-
|
|
61
197
|
### Changed
|
|
62
198
|
|
|
63
199
|
- **The README is now a call page and the manual moved to `docs/GUIDE.md`.** The
|
|
64
200
|
README had reached 864 lines, which made it a manual printed on the front
|
|
65
|
-
door. It is
|
|
201
|
+
door. It is 156 lines now, and it answers what the library is, where you are
|
|
66
202
|
getting into, and which of `reconstruct` or `stitch` you want. Nothing was
|
|
67
203
|
discarded: every measurement, table and long example moved into the guide,
|
|
68
204
|
which gained its own table of contents.
|
|
@@ -233,7 +369,7 @@ kernel so each pixel "trusts" patches closer to its center more.
|
|
|
233
369
|
on overlap). With `"hann"` it strongly suppresses seams at the
|
|
234
370
|
cost of zeroing image corners that fall on Hann's edge-weight-zero
|
|
235
371
|
region (documented artifact). With `"gaussian"`
|
|
236
|
-
(`sigma = max(1,
|
|
372
|
+
(per-axis `sigma = max(1.0, n / 4.0)`) it blends smoothly with no
|
|
237
373
|
corner artifact.
|
|
238
374
|
|
|
239
375
|
Floating-point patches only, because window kernels are float-valued and we
|
|
@@ -336,5 +472,10 @@ First public release. Public API stable; signatures will only change in 1.x.
|
|
|
336
472
|
- [`README.md`](README.md) covers installation, the car-vs-track metaphor,
|
|
337
473
|
validation lab.
|
|
338
474
|
|
|
475
|
+
[Unreleased]: https://github.com/LeoPR/PatchCraft/compare/v0.4.0...HEAD
|
|
476
|
+
[0.4.0]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.4.0
|
|
477
|
+
[0.3.0]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.3.0
|
|
478
|
+
[0.2.2]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.2.2
|
|
479
|
+
[0.2.1]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.2.1
|
|
339
480
|
[0.2.0]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.2.0
|
|
340
481
|
[0.1.0]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.1.0
|
|
@@ -0,0 +1,161 @@
|
|
|
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
|
+
```
|
|
10
|
+
pytest
|
|
11
|
+
pytest -m "not gpu" # skip GPU-requiring tests
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
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}`. To run the same checks locally before pushing:
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
ruff check src tests
|
|
18
|
+
mypy --strict src
|
|
19
|
+
pytest -m "not gpu"
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Layout
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
PatchCraft/
|
|
28
|
+
├── pyproject.toml package metadata, build backend (hatchling)
|
|
29
|
+
├── README.md the call page, canonical, English
|
|
30
|
+
├── README.pt-BR.md the same call page in Portuguese
|
|
31
|
+
├── README.pypi.md the PyPI page (long_description), links absolute
|
|
32
|
+
├── CONTRIBUTING.md this file
|
|
33
|
+
├── CHANGELOG.md Keep-a-Changelog format
|
|
34
|
+
├── LICENSE MIT
|
|
35
|
+
├── .python-version 3.13
|
|
36
|
+
├── .gitignore ignores archive/, venvs, caches, outputs
|
|
37
|
+
├── .github/workflows/
|
|
38
|
+
│ ├── test.yml matrix CI on PRs/main, plus the accelerated job
|
|
39
|
+
│ └── release.yml publishes to PyPI on vX.Y.Z tag push (Trusted Publishing)
|
|
40
|
+
├── src/patchcraft/ library core, one-image-at-a-time primitives
|
|
41
|
+
│ ├── __init__.py re-exports the full public API, holds __version__
|
|
42
|
+
│ ├── extract.py patches via strided view or F.unfold; Patchify (ADR 0002)
|
|
43
|
+
│ ├── reconstruct.py inverse via F.fold + count map, closed-form fast path
|
|
44
|
+
│ ├── stitch.py weighted reassembly for modified patches
|
|
45
|
+
│ ├── geometry.py pre-flight: num_patches, tilings, scale_factor, paired_tilings
|
|
46
|
+
│ ├── metrics.py patch_metrics, per_patch_mse, per_patch_psnr
|
|
47
|
+
│ ├── pair.py LR↔HR pairing; PatchPair, PatchMeta
|
|
48
|
+
│ ├── resize.py resize with PIL or torch backends
|
|
49
|
+
│ ├── cache.py content-addressed disk cache
|
|
50
|
+
│ ├── _accel.py bridge to the optional native accelerator, falls back silently
|
|
51
|
+
│ └── _foldgeom.py shared fold-geometry validation for reconstruct and stitch
|
|
52
|
+
├── accel/ patchcraft-accel, a separate distribution (Rust, pyo3/maturin)
|
|
53
|
+
│ ├── Cargo.toml crate metadata, holds the accel version
|
|
54
|
+
│ ├── src/kernel.rs the gather-fold kernel, pure Rust, cargo-tested
|
|
55
|
+
│ ├── src/lib.rs pyo3 glue
|
|
56
|
+
│ └── README.md how to build it from source
|
|
57
|
+
├── tests/ pytest suite (contract tests for src/)
|
|
58
|
+
│ ├── test_extract.py extract + Patchify
|
|
59
|
+
│ ├── test_reconstruct.py
|
|
60
|
+
│ ├── test_stitch.py
|
|
61
|
+
│ ├── test_geometry.py num_patches + tilings + scale_factor + paired_tilings
|
|
62
|
+
│ ├── test_metrics.py
|
|
63
|
+
│ ├── test_pair.py
|
|
64
|
+
│ ├── test_resize.py
|
|
65
|
+
│ ├── test_cache.py
|
|
66
|
+
│ ├── test_exactness.py falsifies the count-map predicate over the legal space
|
|
67
|
+
│ ├── test_reference.py naive loop-based reference for the fast paths
|
|
68
|
+
│ ├── test_public_api.py freezes the 20 names, their signatures and carrier fields
|
|
69
|
+
│ ├── test_accel.py native path, skipped unless the accelerator is installed
|
|
70
|
+
│ ├── test_datasets_helper.py label_subset
|
|
71
|
+
│ ├── test_import.py
|
|
72
|
+
│ ├── _rng.py audited round-trip helpers (data generation, bit equality)
|
|
73
|
+
│ ├── test_rng.py tests for those helpers
|
|
74
|
+
│ └── _datasets.py dev-only fixtures (MNIST, etc), NOT public API
|
|
75
|
+
├── lab/ ephemeral experiments; see lab/README.md
|
|
76
|
+
│ ├── README.md bench rules (tracked)
|
|
77
|
+
│ ├── usage_demo.py regenerates the live REPL outputs in docs/USAGE.md
|
|
78
|
+
│ └── .gitignore ignores everything else (tracked)
|
|
79
|
+
├── docs/
|
|
80
|
+
│ ├── GUIDE.md the manual, every README claim measured with its output
|
|
81
|
+
│ ├── USAGE.md REPL walkthrough of every public symbol (behind, see B4)
|
|
82
|
+
│ ├── SCOPE.md responsibilities matrix + parallelization analysis
|
|
83
|
+
│ ├── AUXILIARY.md tests/_datasets, lab/, Z:\ conventions (NOT part of the wheel)
|
|
84
|
+
│ ├── THEORY.md distilled design + §9 condition contract; §0 binding scope
|
|
85
|
+
│ ├── ROADMAP.md milestone plan
|
|
86
|
+
│ ├── FOCO-1.0.md what 1.0 freezes and the blockers in the way
|
|
87
|
+
│ ├── ADR/ one file per decision that shaped the API
|
|
88
|
+
│ ├── STUDIES/ background reading behind those decisions
|
|
89
|
+
│ └── superpowers/ the design specs and implementation plans per work phase
|
|
90
|
+
└── archive/ reference-only; gitignored (pruned 2026-05-17, only HISTORY.md kept)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Validation lab
|
|
96
|
+
|
|
97
|
+
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:
|
|
98
|
+
|
|
99
|
+
- [`tests/`](tests/) holds the formal pytest suite that defines the contract from [`docs/THEORY.md`](docs/THEORY.md) §9.
|
|
100
|
+
- [`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).
|
|
101
|
+
|
|
102
|
+
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.
|
|
103
|
+
|
|
104
|
+
The dev framework that makes this all work (fixtures, dataset helpers, `Z:\` conventions) is documented in [`docs/AUXILIARY.md`](docs/AUXILIARY.md).
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Versioning policy
|
|
109
|
+
|
|
110
|
+
The scheme is [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html),
|
|
111
|
+
expressed in the [PEP 440](https://peps.python.org/pep-0440/) form that PyPI
|
|
112
|
+
accepts. Two rules matter more than the arithmetic, because both were broken
|
|
113
|
+
once and both cost work to undo.
|
|
114
|
+
|
|
115
|
+
**The number names a published artifact, not a unit of work.** Finishing a
|
|
116
|
+
phase, merging a branch and landing a refactor are not releases, and none of
|
|
117
|
+
them touches `__version__`. Everything between releases accumulates under
|
|
118
|
+
`[Unreleased]` in [`CHANGELOG.md`](CHANGELOG.md), and the number is assigned
|
|
119
|
+
in the release commit itself. Versions 0.3.0 and 0.4.0 came from the opposite
|
|
120
|
+
habit: each internal work phase minted a minor, so three numbers existed
|
|
121
|
+
before any of them reached PyPI.
|
|
122
|
+
|
|
123
|
+
**A published version is frozen.** PyPI refuses a filename it has already
|
|
124
|
+
seen, so a version that shipped can only be superseded, never edited. That is
|
|
125
|
+
also why a failed pipeline never costs a number: `skip-existing` makes the
|
|
126
|
+
upload a no-op and the same tag can be re-run.
|
|
127
|
+
|
|
128
|
+
Which component moves, while the project is pre-1.0 and SemVer leaves `0.y.z`
|
|
129
|
+
open:
|
|
130
|
+
|
|
131
|
+
| Change | Bump |
|
|
132
|
+
|---|---|
|
|
133
|
+
| Any output value, signature, name or documented behaviour changes | minor, `0.Y.0` |
|
|
134
|
+
| A fix that changes no documented behaviour and no signature | patch, `0.y.Z` |
|
|
135
|
+
| Documentation, tests, CI or tooling only | nothing |
|
|
136
|
+
|
|
137
|
+
After 1.0 the ordinary reading applies: breaking is major, additive is minor,
|
|
138
|
+
fixed is patch. What 1.0 freezes is written in
|
|
139
|
+
[`docs/FOCO-1.0.md`](docs/FOCO-1.0.md).
|
|
140
|
+
|
|
141
|
+
The accelerator in [`accel/`](accel/) is a separate distribution with its own
|
|
142
|
+
version in `accel/Cargo.toml`. It moves on its own schedule, and the two
|
|
143
|
+
numbers are not meant to track each other.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Releasing (maintainer only)
|
|
148
|
+
|
|
149
|
+
1. Bump `__version__` in [`src/patchcraft/__init__.py`](src/patchcraft/__init__.py).
|
|
150
|
+
2. Close the `[Unreleased]` section in [`CHANGELOG.md`](CHANGELOG.md) as `[X.Y.Z] YYYY-MM-DD`.
|
|
151
|
+
3. Sweep the version strings the docs quote, which live in
|
|
152
|
+
[`docs/GUIDE.md`](docs/GUIDE.md) (the provenance note, the section 7
|
|
153
|
+
assertion, section 8 and the BibTeX entry) and in the Status block of the
|
|
154
|
+
three cover pages.
|
|
155
|
+
4. Update [`docs/ROADMAP.md`](docs/ROADMAP.md) milestone checkboxes.
|
|
156
|
+
5. Commit: `release: vX.Y.Z`.
|
|
157
|
+
6. Tag + push: `git tag -a vX.Y.Z -m "..."` then `git push origin vX.Y.Z`.
|
|
158
|
+
7. `release.yml` fires automatically: validates → builds → publishes to PyPI via Trusted Publishing → creates GitHub Release with `.whl` + `.tar.gz`.
|
|
159
|
+
|
|
160
|
+
The tag must match `__version__`, and the `validate` job fails the run if it
|
|
161
|
+
does not.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: patchcraft
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Image patch extraction, reconstruction, pairing and seam-aware stitching for super-resolution and dataset pipelines.
|
|
5
5
|
Project-URL: Homepage, https://github.com/LeoPR/PatchCraft
|
|
6
6
|
Project-URL: Repository, https://github.com/LeoPR/PatchCraft
|
|
@@ -20,6 +20,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
20
20
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
21
21
|
Classifier: Programming Language :: Python :: 3.12
|
|
22
22
|
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
24
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
24
25
|
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
25
26
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
@@ -28,6 +29,8 @@ Requires-Python: >=3.12
|
|
|
28
29
|
Requires-Dist: numpy>=1.26
|
|
29
30
|
Requires-Dist: pillow>=10
|
|
30
31
|
Requires-Dist: torch>=2.6
|
|
32
|
+
Provides-Extra: accel
|
|
33
|
+
Requires-Dist: patchcraft-accel>=0.1.0; extra == 'accel'
|
|
31
34
|
Provides-Extra: cache
|
|
32
35
|
Requires-Dist: zstandard>=0.22; extra == 'cache'
|
|
33
36
|
Provides-Extra: dev
|
|
@@ -51,6 +54,24 @@ PatchCraft takes a single `(C, H, W)` float tensor, cuts it into a stack of patc
|
|
|
51
54
|
|
|
52
55
|
The scope is one image at a time, and that is worth knowing before you install anything, because it is the constraint that decides whether PatchCraft fits your problem at all. There is no batching across images, no `Dataset`, no `DataLoader` and no training loop, so multi-image work stays in your own `for` loop, in your `torch.vmap`, or in your `DataLoader` calling this once per item.
|
|
53
56
|
|
|
57
|
+
```
|
|
58
|
+
one image the patch stack one image again
|
|
59
|
+
(1, 4, 4) (4, 1, 2, 2), row-major (1, 4, 4)
|
|
60
|
+
|
|
61
|
+
+-----+-----+ +-----+ +-----+ +-----+-----+
|
|
62
|
+
| A A | B B | | A A | | B B | | A A | B B |
|
|
63
|
+
| A A | B B | extract | A A | | B B | reconstruct | A A | B B |
|
|
64
|
+
+-----+-----+ ----------> +--p0-+ +--p1-+ ----------> +-----+-----+
|
|
65
|
+
| C C | D D | patch_size=2 +-----+ +-----+ stride=2 | C C | D D |
|
|
66
|
+
| C C | D D | stride=2 | C C | | D D | | C C | D D |
|
|
67
|
+
+-----+-----+ | C C | | D D | +-----+-----+
|
|
68
|
+
+--p2-+ +--p3-+
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The image goes out as a stack of patches, you do your work on the stack, and it comes back as one
|
|
72
|
+
image. That last arrow has two doors: `reconstruct` when the patches are untouched, and `stitch`
|
|
73
|
+
when a model rewrote them and the seams need to fade.
|
|
74
|
+
|
|
54
75
|
This page is the short one. The manual is [docs/GUIDE.md](https://github.com/LeoPR/PatchCraft/blob/main/docs/GUIDE.md), which carries the measurements, the tables and the long examples.
|
|
55
76
|
|
|
56
77
|
## Install
|
|
@@ -60,7 +81,18 @@ pip install patchcraft
|
|
|
60
81
|
pip install "patchcraft[cache]" # adds zstandard, which compresses Cache payloads
|
|
61
82
|
```
|
|
62
83
|
|
|
63
|
-
|
|
84
|
+
Or with the optional native accelerator (prebuilt wheels for Windows x64,
|
|
85
|
+
Linux x86_64, macOS arm64 and macOS x86_64; the package stays pure-Python
|
|
86
|
+
and fully functional without it):
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
pip install patchcraft[accel]
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`patchcraft.accel_available()` reports at runtime whether the accelerator is
|
|
93
|
+
active; `PATCHCRAFT_ACCEL=0` in the environment forces the pure path.
|
|
94
|
+
|
|
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.
|
|
64
96
|
|
|
65
97
|
## Sixty seconds
|
|
66
98
|
|
|
@@ -118,7 +150,7 @@ The saving is real on the other side too. Tiling an image, running a per-patch m
|
|
|
118
150
|
|
|
119
151
|
`extract` follows whatever grid you hand it, but `reconstruct` and `stitch` refuse a grid that does not cover the image exactly, rather than returning a plausible tensor built on missing pixels. On a 128x128 image with `patch_size=32` and `stride=20` the grid reaches only 112x112, which leaves 3840 of the 16384 pixels at zero, and the error message names that covered extent instead of hiding it.
|
|
120
152
|
|
|
121
|
-
The answer is to pick a legal geometry rather than to pad the image into one, because padding synthesizes pixels you never had. `tilings(image_shape)` enumerates the legal geometries from the shape alone and allocates nothing while it does so, so you can call it before you have committed to anything: a 28x28 image has 5 exact tilings, and
|
|
153
|
+
The answer is to pick a legal geometry rather than to pad the image into one, because padding synthesizes pixels you never had. `tilings(image_shape)` enumerates the legal geometries from the shape alone and allocates nothing while it does so, so you can call it before you have committed to anything: a 28x28 image has 5 exact tilings, and 73 of them once `allow_overlap=True` lets the patches overlap.
|
|
122
154
|
|
|
123
155
|
Two narrower questions have their own entry points. `num_patches` takes a geometry you already have in mind and returns the grid it implies, and `paired_tilings` is the one to reach for when a low-resolution image and a high-resolution image have to stay aligned patch for patch.
|
|
124
156
|
|
|
@@ -132,20 +164,20 @@ It helps when you tile one image for an inference pass too large to run in a sin
|
|
|
132
164
|
|
|
133
165
|
The round trip is exact when every value in the count map is a power of two. The reason is that reconstruction divides each pixel by the number of patches that covered it, and dividing a float by a power of two is the one division that never rounds.
|
|
134
166
|
|
|
135
|
-
That makes the geometry the deciding axis rather than the dtype, so `float64` is not a safe harbour: outside the rule
|
|
167
|
+
That makes the geometry the deciding axis rather than the dtype, so `float64` is not a safe harbour: outside the rule the per-pixel error is bounded by `(k+1)·eps·|v|`, with `k` the pixel's coverage count. A wider float buys a smaller miss and never exactness.
|
|
136
168
|
|
|
137
169
|
The everyday shorthand is that `stride == patch_size` and `stride == patch_size / 2` always satisfy the rule. Both are sufficient conditions rather than necessary ones, so a geometry outside them can still be exact, and the [guide](https://github.com/LeoPR/PatchCraft/blob/main/docs/GUIDE.md#4-when-the-round-trip-is-bit-for-bit) carries the sweep that measures it.
|
|
138
170
|
|
|
139
171
|
## Status
|
|
140
172
|
|
|
141
|
-
This page documents 0.
|
|
173
|
+
This page documents 0.5.0, which is pre-1.0, so both the output values and the API shape can still change in a minor release, and the [changelog](https://github.com/LeoPR/PatchCraft/blob/main/CHANGELOG.md) is where each of those changes is recorded 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.
|
|
142
174
|
|
|
143
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.
|
|
144
176
|
|
|
145
177
|
## Documentation
|
|
146
178
|
|
|
147
179
|
- [Guide](https://github.com/LeoPR/PatchCraft/blob/main/docs/GUIDE.md), the manual, with every figure on this page shown as runnable code
|
|
148
|
-
- [Usage](https://github.com/LeoPR/PatchCraft/blob/main/docs/USAGE.md), a walkthrough of each of the
|
|
180
|
+
- [Usage](https://github.com/LeoPR/PatchCraft/blob/main/docs/USAGE.md), a walkthrough of each of the 20 public symbols
|
|
149
181
|
- [Theory](https://github.com/LeoPR/PatchCraft/blob/main/docs/THEORY.md), the math and the per-function contract
|
|
150
182
|
- [Scope](https://github.com/LeoPR/PatchCraft/blob/main/docs/SCOPE.md), the line between this library and your pipeline
|
|
151
183
|
- [Repository](https://github.com/LeoPR/PatchCraft), [issues](https://github.com/LeoPR/PatchCraft/issues) and [contributing](https://github.com/LeoPR/PatchCraft/blob/main/CONTRIBUTING.md)
|