patchcraft 0.5.4__tar.gz → 0.5.6__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.4 → patchcraft-0.5.6}/.pre-commit-config.yaml +2 -2
- {patchcraft-0.5.4 → patchcraft-0.5.6}/AGENTS.md +5 -3
- {patchcraft-0.5.4 → patchcraft-0.5.6}/CHANGELOG.md +276 -1
- {patchcraft-0.5.4 → patchcraft-0.5.6}/CITATION.cff +5 -6
- {patchcraft-0.5.4 → patchcraft-0.5.6}/CONTRIBUTING.md +21 -19
- {patchcraft-0.5.4 → patchcraft-0.5.6}/MANIFEST.in +4 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/MAP.md +12 -6
- {patchcraft-0.5.4 → patchcraft-0.5.6}/PKG-INFO +10 -10
- {patchcraft-0.5.4 → patchcraft-0.5.6}/README.md +10 -10
- {patchcraft-0.5.4 → patchcraft-0.5.6}/README.pt-BR.md +9 -9
- {patchcraft-0.5.4 → patchcraft-0.5.6}/README.pypi.md +9 -9
- {patchcraft-0.5.4 → patchcraft-0.5.6}/STATUS.md +27 -12
- {patchcraft-0.5.4 → patchcraft-0.5.6}/docs/ADR/0003-reversibility-classes.md +7 -2
- patchcraft-0.5.6/docs/ADR/0005-border-policy.md +94 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/docs/FOCO-1.0.md +7 -7
- {patchcraft-0.5.4 → patchcraft-0.5.6}/docs/GUIDE.md +201 -44
- {patchcraft-0.5.4 → patchcraft-0.5.6}/docs/PERFORMANCE.md +17 -12
- {patchcraft-0.5.4 → patchcraft-0.5.6}/docs/ROADMAP.md +7 -1
- patchcraft-0.5.6/docs/STUDIES/2026-10-10-border-policy.md +113 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/docs/THEORY.md +27 -8
- {patchcraft-0.5.4 → patchcraft-0.5.6}/docs/USAGE.md +169 -26
- {patchcraft-0.5.4 → patchcraft-0.5.6}/pyproject.toml +5 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/src/patchcraft/_accel.py +12 -3
- {patchcraft-0.5.4 → patchcraft-0.5.6}/src/patchcraft/_version.py +3 -3
- {patchcraft-0.5.4 → patchcraft-0.5.6}/src/patchcraft/extract.py +13 -8
- {patchcraft-0.5.4 → patchcraft-0.5.6}/src/patchcraft/geometry.py +45 -22
- {patchcraft-0.5.4 → patchcraft-0.5.6}/src/patchcraft/metrics.py +5 -3
- {patchcraft-0.5.4 → patchcraft-0.5.6}/src/patchcraft/reconstruct.py +48 -28
- {patchcraft-0.5.4 → patchcraft-0.5.6}/src/patchcraft/stitch.py +37 -11
- {patchcraft-0.5.4 → patchcraft-0.5.6}/src/patchcraft.egg-info/PKG-INFO +10 -10
- {patchcraft-0.5.4 → patchcraft-0.5.6}/src/patchcraft.egg-info/SOURCES.txt +8 -1
- {patchcraft-0.5.4 → patchcraft-0.5.6}/src/patchcraft.egg-info/scm_file_list.json +45 -0
- patchcraft-0.5.6/src/patchcraft.egg-info/scm_version.json +8 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/tests/_rng.py +1 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/tests/test_accel.py +10 -5
- {patchcraft-0.5.4 → patchcraft-0.5.6}/tests/test_exactness.py +28 -1
- {patchcraft-0.5.4 → patchcraft-0.5.6}/tests/test_extract.py +4 -2
- {patchcraft-0.5.4 → patchcraft-0.5.6}/tests/test_geometry.py +21 -0
- patchcraft-0.5.6/tests/test_operator.py +144 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/tests/test_reconstruct.py +14 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/tests/test_stitch.py +14 -0
- patchcraft-0.5.6/tests/test_vmap.py +72 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/tools/benchmark.py +5 -3
- {patchcraft-0.5.4 → patchcraft-0.5.6}/tools/check_dist.py +3 -0
- patchcraft-0.5.6/tools/compare_border_strategies.py +116 -0
- patchcraft-0.5.6/tools/compare_exactness_rules.py +107 -0
- patchcraft-0.5.6/tools/make_outreach_figures.py +982 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/uv.lock +101 -101
- patchcraft-0.5.4/src/patchcraft.egg-info/scm_version.json +0 -8
- {patchcraft-0.5.4 → patchcraft-0.5.6}/.gitignore +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/.python-version +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/CODE_OF_CONDUCT.md +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/LICENSE +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/SECURITY.md +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/accel/Cargo.lock +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/accel/Cargo.toml +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/accel/README.md +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/accel/src/kernel.rs +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/accel/src/lib.rs +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/docs/ADR/0001-patch-extraction-api.md +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/docs/ADR/0002-patchify-transform.md +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/docs/ADR/0004-precision-and-effort-parameters.md +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/docs/AUXILIARY.md +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/docs/SCOPE.md +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/docs/STUDIES/2026-08-04-patch-techniques.md +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/docs/STUDIES/2026-09-03-precision-measurements.md +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/docs/STUDIES/2026-09-03-strata-conformity.md +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/docs/design/2026-09-01-fase1-bugs-performance.md +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/docs/design/2026-09-01-fase2-acelerador.md +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/docs/design/2026-09-01-fase3-predicado.md +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/setup.cfg +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/setup.py +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/src/patchcraft/__init__.py +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/src/patchcraft/_foldgeom.py +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/src/patchcraft/cache.py +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/src/patchcraft/pair.py +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/src/patchcraft/py.typed +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/src/patchcraft/resize.py +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/src/patchcraft.egg-info/dependency_links.txt +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/src/patchcraft.egg-info/requires.txt +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/src/patchcraft.egg-info/top_level.txt +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/tests/__init__.py +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/tests/_datasets.py +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/tests/conftest.py +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/tests/test_cache.py +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/tests/test_citation.py +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/tests/test_datasets_helper.py +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/tests/test_docs_usage.py +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/tests/test_import.py +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/tests/test_metrics.py +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/tests/test_pair.py +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/tests/test_public_api.py +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/tests/test_reference.py +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/tests/test_resize.py +0 -0
- {patchcraft-0.5.4 → patchcraft-0.5.6}/tests/test_rng.py +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Runs the gates CI runs, before the commit rather than after it.
|
|
2
2
|
#
|
|
3
3
|
# Deliberately absent: `ruff-format`. This project has never used it and
|
|
4
|
-
# adopting it here would reformat
|
|
5
|
-
# behaviour. `ruff check` is the gate; formatting is not.
|
|
4
|
+
# adopting it here would reformat about half the tree (CONTRIBUTING.md has the
|
|
5
|
+
# count) in a commit that changed no behaviour. `ruff check` is the gate; formatting is not.
|
|
6
6
|
#
|
|
7
7
|
# `mypy` runs through the project environment rather than through a pre-commit
|
|
8
8
|
# virtualenv, because it needs torch to resolve any annotation in `src/` and
|
|
@@ -128,9 +128,11 @@ connectives; one idea per paragraph. `README.md` is a call page, not a manual:
|
|
|
128
128
|
the manual is [`docs/GUIDE.md`](docs/GUIDE.md), and complex examples belong
|
|
129
129
|
there.
|
|
130
130
|
|
|
131
|
-
Documents that state a version go stale.
|
|
132
|
-
|
|
133
|
-
[`
|
|
131
|
+
Documents that state a version go stale. A release bumps three: the BibTeX
|
|
132
|
+
entry in GUIDE section 9, [`CITATION.cff`](CITATION.cff), and the release
|
|
133
|
+
named at the top of [`STATUS.md`](STATUS.md). [`docs/PERFORMANCE.md`](docs/PERFORMANCE.md)
|
|
134
|
+
names the version it measured, and changes only when it is re-measured. Do
|
|
135
|
+
not add another.
|
|
134
136
|
|
|
135
137
|
## 2. Before you touch anything
|
|
136
138
|
|
|
@@ -6,6 +6,279 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.5.6] - 2026-10-10
|
|
10
|
+
|
|
11
|
+
A fix release. `torch.vmap` works again over `extract`, `reconstruct` and
|
|
12
|
+
`stitch`, which it had not since 0.5.0, and `tilings` returns `[]` for an
|
|
13
|
+
image smaller than the default minimum patch instead of raising. No other
|
|
14
|
+
returned value changes. The full suite passes on both code paths at the
|
|
15
|
+
declared floor (Python 3.12, torch 2.6.0, numpy 1.26.0, pillow 10.1.0, Rust
|
|
16
|
+
1.82) and at the newest releases (Python 3.14, torch 2.14.1, numpy 2.5.4,
|
|
17
|
+
pillow 12.3.0), and the 126,736-geometry exactness sweep passes on both paths.
|
|
18
|
+
The rest is documentation, a CI job that now holds the floor to that claim,
|
|
19
|
+
and the CI actions moved to Node 24.
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- `docs/PAPER/`, the scaffold of an article on computing a mean whose every
|
|
24
|
+
intermediate stays in the input's domain, drawing on PatchCraft's
|
|
25
|
+
patch-grid mean: outline, formal statements with proofs, measurements with
|
|
26
|
+
the scripts that reproduce them, and checked references. It is not
|
|
27
|
+
documentation of the library and does not ship: `MANIFEST.in` prunes it and
|
|
28
|
+
`tools/check_dist.py` fails a release that carries it.
|
|
29
|
+
- **CI tests the declared floor, not only the newest versions.** A new job
|
|
30
|
+
builds the extension with Rust 1.82 and runs the whole suite on both code
|
|
31
|
+
paths at Python 3.12, torch 2.6.0, numpy 1.26.0 and pillow 10.1.0, the
|
|
32
|
+
oldest versions `pyproject.toml` and `Cargo.toml` accept that install on
|
|
33
|
+
3.12; until now the floor was a claim no job exercised. The lock moves to
|
|
34
|
+
the newest runtime releases (torch 2.14.1, torchvision 0.29.1, numpy
|
|
35
|
+
2.5.4), so the other jobs test the top of the range; the development tools
|
|
36
|
+
stay pinned. The first run at the floor found one claim that held only at
|
|
37
|
+
the top: `extract` on an integer tensor raises torch's own error, which is a
|
|
38
|
+
`RuntimeError` at torch 2.6 and its subclass `NotImplementedError` in newer
|
|
39
|
+
releases, while the test, THEORY §9.1 and the PyPI page named the subclass.
|
|
40
|
+
They now name what holds across the range.
|
|
41
|
+
- ADR 0005 is recorded in the ROADMAP as the 1.1 candidate, not scheduled.
|
|
42
|
+
- ADR 0005 (`Proposed`) records how a grid that does not cover the image
|
|
43
|
+
would be padded, borrowed rather than designed: kornia's auto-padding on the
|
|
44
|
+
way in and unpadding on the way back, MONAI's `padding_mode` with `F.pad`'s
|
|
45
|
+
values, the padding split between both sides, and `None` as the default
|
|
46
|
+
that changes no call. Nothing is implemented. The survey and the
|
|
47
|
+
measurements behind it are in `docs/STUDIES/2026-10-10-border-policy.md`,
|
|
48
|
+
reproduced by `tools/compare_border_strategies.py`.
|
|
49
|
+
- **THEORY §2.6 states the operator behind `reconstruct` and `stitch`.**
|
|
50
|
+
`extract` is a selection matrix `U`, the fold is `Uᵀ`, `UᵀU` is the count
|
|
51
|
+
map, `reconstruct` is the least-squares inverse `(UᵀU)⁻¹Uᵀ` and `stitch` the
|
|
52
|
+
window-weighted one, the same weighted overlap-add that inverts an STFT.
|
|
53
|
+
`tests/test_operator.py` pins each statement on both code paths. GUIDE
|
|
54
|
+
gains three recipes built on it, run against 0.5.5: what averaging does to a
|
|
55
|
+
per-patch quantization (§2), a filter applied per tile with a halo, which
|
|
56
|
+
comes back bit-identical to the whole-image filter (§5), and the padded
|
|
57
|
+
round trip for a frame no tiling covers (§6). Section 6 used to rule
|
|
58
|
+
padding out because it puts pixels the frame never had into the edge
|
|
59
|
+
patches; it now offers it with that cost stated.
|
|
60
|
+
|
|
61
|
+
### Fixed
|
|
62
|
+
|
|
63
|
+
- **`torch.vmap` works again over `extract`, `reconstruct` and `stitch`.** The
|
|
64
|
+
documentation names vmap as one of the three ways to batch (README, SCOPE,
|
|
65
|
+
USAGE, THEORY §0 and §9.1), and since 0.5.0 it failed on most calls: `extract`
|
|
66
|
+
at every geometry without dilation and `reconstruct` at `stride ==
|
|
67
|
+
patch_size` asked the tensor for its storage to guard against aliasing, which
|
|
68
|
+
a vmap batched tensor does not have, and with the accelerator every
|
|
69
|
+
overlapping `reconstruct` and `stitch` failed reading a data pointer. Both
|
|
70
|
+
guards are now an unconditional clone, which is the one copy those paths
|
|
71
|
+
always made, and the accelerator treats a tensor without storage as
|
|
72
|
+
ineligible and hands it to the torch path, as its docstring already said.
|
|
73
|
+
Bit-identical to 0.5.5 on ordinary calls in 144 comparisons per path,
|
|
74
|
+
including the degenerate geometries the guard existed for, with no output
|
|
75
|
+
sharing memory with its input and no change in time. `tests/test_vmap.py`
|
|
76
|
+
pins vmap to a plain loop bit for bit, on both paths and in four dtypes; 70
|
|
77
|
+
of its 114 cases fail against 0.5.5. The test helper `bit_equal` gained
|
|
78
|
+
bfloat16.
|
|
79
|
+
- **`tilings` returns `[]` for an image smaller than the default minimum
|
|
80
|
+
patch.** A one-row image raised `min_patch_size (2) > max_patch_size (1)`,
|
|
81
|
+
blaming a bound the caller never passed, while THEORY §9.6 accepts any
|
|
82
|
+
positive shape and an explicit bound that excludes every patch already
|
|
83
|
+
returned `[]`. An explicit `max_patch_size` below `min_patch_size` still
|
|
84
|
+
raises. The docstring no longer promises at least one entry, which was false
|
|
85
|
+
for both cases.
|
|
86
|
+
|
|
87
|
+
### Changed
|
|
88
|
+
|
|
89
|
+
- **CI actions moved off Node 20**, which GitHub has deprecated and was
|
|
90
|
+
forcing onto Node 24: `actions/checkout` v5, `astral-sh/setup-uv` v7,
|
|
91
|
+
`actions/upload-artifact` v6, `actions/download-artifact` v7 and
|
|
92
|
+
`softprops/action-gh-release` v3, each the first major on Node 24 rather
|
|
93
|
+
than the newest. Every input the workflows pass exists in those versions.
|
|
94
|
+
- **USAGE walks a 4×4 image through every call.** Its sections now show
|
|
95
|
+
the pixels, not only the shapes: the patches `extract` returns and their
|
|
96
|
+
order, where each patch lands in `reconstruct`, the per-pixel mean when
|
|
97
|
+
patches disagree, the step `uniform` leaves and the ramp `hann` and
|
|
98
|
+
`gaussian` make, an LR patch beside its HR patch in `pair`, a resize down
|
|
99
|
+
and back up, and the one patch `per_patch_mse` singles out. Every example
|
|
100
|
+
runs as a doctest. GUIDE §1 shows the uncovered band on a 5×5 count map, and
|
|
101
|
+
GUIDE §4 shows the count map beside the pixels that come back different.
|
|
102
|
+
- GUIDE §4 still gave the old reason for the exactness rule, that division by
|
|
103
|
+
a power of two is exact; it now gives the corrected one, as the other pages
|
|
104
|
+
do since the entry below.
|
|
105
|
+
- **The stated reason for the exactness rule is corrected.** The pages said the
|
|
106
|
+
round trip is exact because dividing a float by a power of two never rounds.
|
|
107
|
+
That leaves out the sum: k equal values added one at a time and divided by k
|
|
108
|
+
come back exact for k = 2 and 4, and for k = 8 they do not, since 3x, 5x, 6x
|
|
109
|
+
and 7x round (3,914,684 of the 8,388,608 float32 values in [1, 2) fail,
|
|
110
|
+
`python docs/PAPER/experiments/float_mean.py`). The rule itself stands,
|
|
111
|
+
because a coverage of three on either axis puts a 3 in the map, so over all
|
|
112
|
+
126,736 legal geometries a map of powers of two holds only 1, 2 and 4. The
|
|
113
|
+
READMEs, THEORY §1, the `reconstruct` docstring, ADR 0003 (with a dated
|
|
114
|
+
correction) and the outreach sources now give that reason.
|
|
115
|
+
- Exact inside the rule means equal as values, which is what `torch.equal`
|
|
116
|
+
checks, and THEORY and the `reconstruct` docstring now say so: the fold sums
|
|
117
|
+
into `+0.0`, so on an overlapping grid a `-0.0` comes back `+0.0` and a NaN
|
|
118
|
+
may change its bits. Both paths do the same, and a test now pins it, which
|
|
119
|
+
`torch.equal` could not.
|
|
120
|
+
- THEORY §9.2 said promotion cut the bfloat16 error from 9.3e-03 to 1.9e-03.
|
|
121
|
+
Those two numbers measured the input's cast from float32, not the library:
|
|
122
|
+
on data drawn in bfloat16 the native fold misses by 7.8e-03 and the promoted
|
|
123
|
+
round trip returns the input bit for bit, at counts 3, 6 and 9. THEORY,
|
|
124
|
+
GUIDE and the code comment now say so, and a test pins it.
|
|
125
|
+
- THEORY §2 and §2.5 and USAGE §5 described the `reconstruct` and `stitch`
|
|
126
|
+
implementation from before 0.5.0 (a second fold of ones, a floor on every
|
|
127
|
+
denominator). They now describe the closed-form count map, the separable
|
|
128
|
+
window denominator and the non-overlapping rearrangement. USAGE §5 checks
|
|
129
|
+
the overlapping round trip with `torch.equal` instead of `torch.allclose`.
|
|
130
|
+
- THEORY §9.6 said `tilings` returns a sorted list. Its order is patch size
|
|
131
|
+
ascending with the exact tile first, which `sorted()` does not give, and a
|
|
132
|
+
test now pins it.
|
|
133
|
+
- The accelerator parity tests compare bits instead of `torch.equal`, and
|
|
134
|
+
include `stride == patch_size`, where hann and gaussian still fold now that
|
|
135
|
+
uniform takes the fast path.
|
|
136
|
+
- Errata for 0.5.5, whose entry says it "changes no bit": that holds for
|
|
137
|
+
returned values in eager mode. Under the uniform window at `stride ==
|
|
138
|
+
patch_size`, derivatives pass a `-0.0` or a signaling NaN through unchanged
|
|
139
|
+
and `torch.compile` folds the `+ 0.0` away; the code comment now says so.
|
|
140
|
+
- **Documentation that said more than the code does.** GUIDE called hann the
|
|
141
|
+
cheapest window, where on an overlapping grid the three cost the same; GUIDE
|
|
142
|
+
and the PyPI page said `extract` hands every tensor to `F.unfold`, where only
|
|
143
|
+
an integer one goes there; PERFORMANCE said 25 runs where the benchmark
|
|
144
|
+
takes 15; GUIDE's two bound figures now read as the measured misses inside
|
|
145
|
+
the `(k+1)·eps·|v|` bound, with the shape of the fp16 example stated.
|
|
146
|
+
FOCO-1.0 freezes values rather than bits, the bound rather than "1 ULP",
|
|
147
|
+
and 20 names. CONTRIBUTING and AGENTS name the three version strings a
|
|
148
|
+
release bumps, STATUS among them. The CITATION abstract no longer carries
|
|
149
|
+
development history, and the BibTeX author reads `Marques de Souza`.
|
|
150
|
+
- `tools/compare_exactness_rules.py` also reports the worst miss outside the
|
|
151
|
+
rule: 19.0 ULP in float32, at `(h, w, ph, pw, sh, sw) = (18, 23, 9, 9, 1,
|
|
152
|
+
1)`, on a pixel covered 81 times. CI's ruff step checks `tools/` as well.
|
|
153
|
+
- Outreach: a news source dated 2026-10-10 carries the 0.5.5 table, and the
|
|
154
|
+
2026-09-04 one stays as the record of 0.5.4. The articles follow it, scope
|
|
155
|
+
the 19 ULP figure to the geometry that produced it, and say where each path
|
|
156
|
+
was executed instead of calling the GPU path correct. The post no longer
|
|
157
|
+
says `extract` validates the geometry, the MNIST page states when each of
|
|
158
|
+
the three problems appears, and every reproduction block clones the
|
|
159
|
+
repository, since the wheel carries neither `tools/` nor `tests/`.
|
|
160
|
+
|
|
161
|
+
## [0.5.5] - 2026-09-26
|
|
162
|
+
|
|
163
|
+
A performance release that changes no bit. Every change to `src/` below was
|
|
164
|
+
compared against the 0.5.4 code through integer views, so that `-0.0` and
|
|
165
|
+
`+0.0` or two NaN payloads count as different: 53,716 cases over 1,382
|
|
166
|
+
geometries, four float dtypes, both code paths and gradients, with 0
|
|
167
|
+
differences. The full 126,736-geometry exactness sweep passes on both paths.
|
|
168
|
+
|
|
169
|
+
### Changed
|
|
170
|
+
|
|
171
|
+
- **`stitch` builds its window denominator without a Python loop.** The
|
|
172
|
+
1-D window fold ran one iteration per stride residue, six tensor operations
|
|
173
|
+
each, and that loop dominated `stitch` at every size once the numerator was
|
|
174
|
+
accelerated. All residues are now one cumsum over a `(m, step)` view of the
|
|
175
|
+
zero-padded kernel, which scans each residue in the same order, so the
|
|
176
|
+
output is bit-identical: 0 differences against the previous code over
|
|
177
|
+
38,696 cases, covering three windows, four float dtypes, both code paths and
|
|
178
|
+
gradients. Accelerated `stitch` is 2.7x to 5.1x faster at 512x512 and 1.2x
|
|
179
|
+
to 1.4x at 2048x2048; on the pure path `F.fold` still dominates at 2048.
|
|
180
|
+
|
|
181
|
+
- **`reconstruct` builds its count map with fewer operations, and both
|
|
182
|
+
`reconstruct` and `stitch` divide in place.** The suffix ramp of the
|
|
183
|
+
closed-form count map is the prefix ramp reversed, a square grid reuses the
|
|
184
|
+
H axis for W, and the outer product is taken in the accumulation dtype,
|
|
185
|
+
where multiplying two small exact integers rounds their exact product once,
|
|
186
|
+
as casting the int64 product did. The numerator and the denominator are
|
|
187
|
+
fresh tensors each function owns, so the final clamp and division no longer
|
|
188
|
+
allocate. Bit-identical: 0 differences over 13,820 `reconstruct` and 38,696
|
|
189
|
+
`stitch` cases, and the caller's patches are never mutated or aliased.
|
|
190
|
+
Accelerated `reconstruct` is 1.15x to 1.26x faster, and one call at
|
|
191
|
+
3x2048x2048 with patch 64 and stride 32 allocates 64 MiB instead of 177
|
|
192
|
+
(`stitch` hann: 65 instead of 128).
|
|
193
|
+
|
|
194
|
+
- **`tilings(allow_overlap=True)` visits only the strides that can work.** A
|
|
195
|
+
stride covers both axes exactly when it divides `h - p` and `w - p`, that
|
|
196
|
+
is when it divides their gcd, so the loop runs over the divisors of the gcd
|
|
197
|
+
instead of over every stride below the patch size. The output is the same
|
|
198
|
+
list in the same order: identical in 9,530 cases, every shape from 1x1 to
|
|
199
|
+
69x69 in both modes plus larger ones, and `paired_tilings`, which builds on
|
|
200
|
+
it. 22.8 to 7.3 ms at 512x512, 91 to 9 ms at 1080x1920, 699 to 17 ms at
|
|
201
|
+
4000x3000.
|
|
202
|
+
|
|
203
|
+
- **`stitch` with the uniform window skips the fold on non-overlapping
|
|
204
|
+
grids.** There the fold adds each value, times 1, into a zero-filled buffer
|
|
205
|
+
and divides by a count of 1, which is `0.0 + x`: the value itself, except
|
|
206
|
+
that `-0.0` comes out as `+0.0` and a NaN comes out quiet. The new path
|
|
207
|
+
rearranges the patches and adds `0.0`, which reproduces exactly that; a bare
|
|
208
|
+
rearrangement would have kept `-0.0`, a difference `torch.equal` cannot see
|
|
209
|
+
and an integer-view comparison can. Bit-identical in the full comparison
|
|
210
|
+
and in a dedicated check of zeros of both signs, NaN payloads, infinities,
|
|
211
|
+
extremes and subnormals, and pinned by a new test against `F.fold` itself.
|
|
212
|
+
3.4x to 58x faster depending on size: 146 to 9.4 ms on the pure path at
|
|
213
|
+
3x2048x2048 with patch 32.
|
|
214
|
+
|
|
215
|
+
- docs/PERFORMANCE.md is re-measured on the code that ships, twice, agreeing
|
|
216
|
+
within about 10%. The accelerator's range over the published table is now
|
|
217
|
+
5.8x to 16.5x, against 2.6x to 14x at 0.5.1; the low end moved most, since
|
|
218
|
+
accelerated `stitch` at 512 went from 6.3 to 3.0 ms. The thread sweep behind
|
|
219
|
+
"the gain is algorithmic" was re-run as well: the pure path at 2048 takes
|
|
220
|
+
448 ms at 4 threads and 390 at 36, no better than at 8.
|
|
221
|
+
|
|
222
|
+
- **The manual, the usage page and the three READMEs no longer carry
|
|
223
|
+
development history.** Sentences about what an earlier version did, what a
|
|
224
|
+
first audit found or what used to be printed moved out; where an old
|
|
225
|
+
measurement is the argument for the current rule it stays, in the present
|
|
226
|
+
tense. GUIDE §8 is now "Where this applies today", and each of its items says
|
|
227
|
+
what a reader should do (on GPU it works and does not accelerate; on macOS and
|
|
228
|
+
aarch64, compare once against `PATCHCRAFT_ACCEL=0`), instead of listing what
|
|
229
|
+
the project has not done. The old anchor is updated everywhere it was linked.
|
|
230
|
+
|
|
231
|
+
- The README tagline read "Encode one image into patches, decode it back",
|
|
232
|
+
which invited the compression reading the page then had to head off. It now
|
|
233
|
+
says "Cut one image into patches, and put it back together", followed by one
|
|
234
|
+
sentence on what a patch is for.
|
|
235
|
+
|
|
236
|
+
- GUIDE's hann figure against the model's own patches read 27.14 dB and prints
|
|
237
|
+
27.15 dB on both code paths; the page now quotes what the code prints.
|
|
238
|
+
|
|
239
|
+
- `tools/make_outreach_figures.py` lost 301 unreachable lines left from
|
|
240
|
+
earlier layouts, with every generated file byte-identical before the later
|
|
241
|
+
edits. Every caption that states a result is now checked against it before
|
|
242
|
+
it is drawn, including the refusal at stride 20 and the exact round trip at
|
|
243
|
+
32 and 16. The MNIST grid is drawn at display scale instead of into the
|
|
244
|
+
digit's pixels, where it hid more than a third of a 28x28 image, and the
|
|
245
|
+
patch shown alone is outlined in it.
|
|
246
|
+
|
|
247
|
+
### Added
|
|
248
|
+
|
|
249
|
+
- `tools/compare_exactness_rules.py` measures the exactness rule against the
|
|
250
|
+
obvious looser one, keeping the largest coverage count at 4 or below, over
|
|
251
|
+
the 126,736 geometries `tests/test_exactness.py` enumerates, with the round
|
|
252
|
+
trip on five seeds as ground truth. The power-of-two rule makes no false
|
|
253
|
+
promise and withholds none; `k_max <= 4` promises exactness on 13,870
|
|
254
|
+
geometries that do not come back exact. The outreach article quoted an
|
|
255
|
+
earlier sweep of 14,969 rectangular geometries from a lab script that is not
|
|
256
|
+
in the tree, so no command reproduced it; it now cites this one.
|
|
257
|
+
|
|
258
|
+
### Fixed
|
|
259
|
+
|
|
260
|
+
- docs/PERFORMANCE.md and `tools/benchmark.py` said non-overlapping geometries
|
|
261
|
+
never reach the fold. At 0.5.4 that held for `reconstruct`, which has a
|
|
262
|
+
closed-form path at `stride == patch_size`, and not for `stitch`, which
|
|
263
|
+
folded at every geometry: about 144 ms on the pure path at 3x2048x2048 with
|
|
264
|
+
patch 32, where `reconstruct` returned the same bits in about 6 ms. This
|
|
265
|
+
release gives `stitch` that path under the uniform window, and both texts
|
|
266
|
+
now say which windows still fold and why.
|
|
267
|
+
|
|
268
|
+
- `metrics.py` claimed no allocation beyond the difference and, in a comment,
|
|
269
|
+
that the in-place subtract avoids a float64 copy of `b`. Measured with the
|
|
270
|
+
torch profiler, `a.to(float64).sub_(b)` with `b` in float32 allocates a
|
|
271
|
+
float64 temporary of `b` first, doubling the largest allocation from 188 to
|
|
272
|
+
375 MiB on 94 MiB of input. The comment and the module docstring now say
|
|
273
|
+
what happens; the code is unchanged.
|
|
274
|
+
|
|
275
|
+
- CONTRIBUTING placed the `WeightKind` paragraph between "what each digit is
|
|
276
|
+
for here:" and the table the colon announced, and its errata repeated two
|
|
277
|
+
worked examples word for word. MAP and CONTRIBUTING counted five issue forms
|
|
278
|
+
where there are four, called all-closed 1.0 blockers "still in the way", and
|
|
279
|
+
listed neither generator in `tools/`. STATUS still called the `Cache` fix
|
|
280
|
+
unreleased after 0.5.4 shipped it.
|
|
281
|
+
|
|
9
282
|
## [0.5.4] - 2026-09-04
|
|
10
283
|
|
|
11
284
|
### Fixed
|
|
@@ -895,7 +1168,9 @@ First public release. Public API stable; signatures will only change in 1.x.
|
|
|
895
1168
|
- [`README.md`](README.md) covers installation, the car-vs-track metaphor,
|
|
896
1169
|
validation lab.
|
|
897
1170
|
|
|
898
|
-
[Unreleased]: https://github.com/LeoPR/PatchCraft/compare/v0.5.
|
|
1171
|
+
[Unreleased]: https://github.com/LeoPR/PatchCraft/compare/v0.5.6...HEAD
|
|
1172
|
+
[0.5.6]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.5.6
|
|
1173
|
+
[0.5.5]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.5.5
|
|
899
1174
|
[0.5.4]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.5.4
|
|
900
1175
|
[0.5.3]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.5.3
|
|
901
1176
|
[0.5.2]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.5.2
|
|
@@ -6,8 +6,8 @@ authors:
|
|
|
6
6
|
- given-names: Leonardo
|
|
7
7
|
family-names: Marques de Souza
|
|
8
8
|
email: leonardo.marques.souza@gmail.com
|
|
9
|
-
version: "0.5.
|
|
10
|
-
date-released: "2026-
|
|
9
|
+
version: "0.5.6"
|
|
10
|
+
date-released: "2026-10-10"
|
|
11
11
|
repository-code: "https://github.com/LeoPR/PatchCraft"
|
|
12
12
|
license: MIT
|
|
13
13
|
abstract: >
|
|
@@ -28,10 +28,9 @@ abstract: >
|
|
|
28
28
|
every value of the overlap coverage map is a power of two, always true at
|
|
29
29
|
stride equal to patch size, and outside that rule the per-pixel error is
|
|
30
30
|
bounded by (k+1) times eps times the absolute value, with k the pixel's
|
|
31
|
-
coverage count.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
tries to break both halves of it.
|
|
31
|
+
coverage count. A falsification suite enumerates the 126,736 legal
|
|
32
|
+
geometries of the space independently of the predicate and tries to break
|
|
33
|
+
both halves of it.
|
|
35
34
|
|
|
36
35
|
Five of the six published wheels carry a Rust kernel for the overlapping
|
|
37
36
|
fold, compiled into the wheel itself rather than into a separate package, and
|
|
@@ -34,7 +34,7 @@ is silently inert here. Either make that global hook delegate, or set
|
|
|
34
34
|
whatever else the global hooks were doing.
|
|
35
35
|
|
|
36
36
|
`ruff-format` is deliberately not among the hooks. This project has never used
|
|
37
|
-
it, and adopting it would reformat
|
|
37
|
+
it, and adopting it would reformat 37 files in a commit that changed no
|
|
38
38
|
behaviour.
|
|
39
39
|
|
|
40
40
|
```
|
|
@@ -130,6 +130,8 @@ PatchCraft/
|
|
|
130
130
|
├── setup.py the one build decision: with or without the Rust extension
|
|
131
131
|
├── tools/check_dist.py gate: extension present, versions agree, tag respected
|
|
132
132
|
├── tools/benchmark.py accelerated vs pure, and proves they agree first
|
|
133
|
+
├── tools/compare_exactness_rules.py the power-of-two rule against k_max <= 4, measured
|
|
134
|
+
├── tools/make_outreach_figures.py every outreach figure and generated page, computed
|
|
133
135
|
├── MANIFEST.in what the sdist carries
|
|
134
136
|
├── README.md the call page, canonical, English
|
|
135
137
|
├── README.pt-BR.md the same call page in Portuguese
|
|
@@ -148,10 +150,10 @@ PatchCraft/
|
|
|
148
150
|
├── SECURITY.md how to report, and what the real attack surface is
|
|
149
151
|
├── CODE_OF_CONDUCT.md Contributor Covenant 2.1, verbatim
|
|
150
152
|
├── .github/
|
|
151
|
-
│ ├── ISSUE_TEMPLATE/
|
|
153
|
+
│ ├── ISSUE_TEMPLATE/ four forms and a chooser; the required fields are what makes a report reproducible
|
|
152
154
|
│ ├── PULL_REQUEST_TEMPLATE.md asks whether a returned value moved, which decides the version bump
|
|
153
155
|
│ └── workflows/
|
|
154
|
-
│ ├── test.yml matrix CI on PRs/main, plus the accelerated
|
|
156
|
+
│ ├── test.yml matrix CI on PRs/main, plus the accelerated and floor jobs
|
|
155
157
|
│ └── release.yml publishes to PyPI on vX.Y.Z tag push (Trusted Publishing)
|
|
156
158
|
├── src/patchcraft/ library core, one-image-at-a-time primitives
|
|
157
159
|
│ ├── __init__.py re-exports the full public API and the generated version
|
|
@@ -185,6 +187,9 @@ PatchCraft/
|
|
|
185
187
|
│ ├── test_accel.py native path, skipped unless the extension was built
|
|
186
188
|
│ ├── test_datasets_helper.py label_subset
|
|
187
189
|
│ ├── test_import.py
|
|
190
|
+
│ ├── test_docs_usage.py runs docs/USAGE.md as a doctest
|
|
191
|
+
│ ├── test_citation.py CITATION.cff agrees with the package
|
|
192
|
+
│ ├── conftest.py shared fixtures
|
|
188
193
|
│ ├── _rng.py audited round-trip helpers (data generation, bit equality)
|
|
189
194
|
│ ├── test_rng.py tests for those helpers
|
|
190
195
|
│ └── _datasets.py dev-only fixtures (MNIST, etc), NOT public API
|
|
@@ -199,14 +204,16 @@ PatchCraft/
|
|
|
199
204
|
│ ├── AUXILIARY.md tests/_datasets, lab/, Z:\ conventions (NOT part of the wheel)
|
|
200
205
|
│ ├── THEORY.md distilled design + §9 condition contract; §0 binding scope
|
|
201
206
|
│ ├── ROADMAP.md milestone plan
|
|
202
|
-
│ ├── FOCO-1.0.md what 1.0 freezes and the blockers
|
|
207
|
+
│ ├── FOCO-1.0.md what 1.0 freezes, and the six blockers, all closed
|
|
203
208
|
│ ├── ADR/ one file per decision; the choice only, evidence in STUDIES/
|
|
204
209
|
│ ├── STUDIES/ background reading behind those decisions
|
|
205
|
-
│
|
|
210
|
+
│ ├── design/ one spec per work phase: the alternatives measured and the decision
|
|
211
|
+
│ └── PAPER/ an article in preparation, with its experiments; not shipped
|
|
206
212
|
├── outreach/ material for presenting the project publicly, not shipped
|
|
207
213
|
│ ├── README.md how it is organized and what to check before publishing
|
|
208
|
-
│ ├── 2026-
|
|
209
|
-
│
|
|
214
|
+
│ ├── 2026-10-10-lancamento.md the current dated news source the channel texts derive from
|
|
215
|
+
│ ├── 2026-09-0*-lancamento.md the earlier ones, kept as the records of 0.5.4 and 0.5.1
|
|
216
|
+
│ └── linkedin/ post, artigo, the illustrated page and the figures, PT and EN
|
|
210
217
|
└── archive/ reference-only; gitignored (pruned 2026-05-17, only HISTORY.md kept)
|
|
211
218
|
```
|
|
212
219
|
|
|
@@ -253,12 +260,7 @@ PEP 440's `~=0.5.0` means `>=0.5.0, <0.6.0`. In every one of them a `0.y` bump
|
|
|
253
260
|
is the announcement that something may break, and a `0.y.z` bump is the
|
|
254
261
|
promise that nothing does.
|
|
255
262
|
|
|
256
|
-
That fixes what each digit is for here
|
|
257
|
-
|
|
258
|
-
One consequence worth stating on its own, because a consumer cannot infer it
|
|
259
|
-
from a `Literal`: **`WeightKind` is an open set.** Adding a member is additive
|
|
260
|
-
and lands in a `0.y` before 1.0 and a minor after it; removing or renaming one
|
|
261
|
-
is a break. Code that matches on it exhaustively should keep a fallback arm.
|
|
263
|
+
That fixes what each digit is for here.
|
|
262
264
|
|
|
263
265
|
The question that decides it is not "did a returned value change". A bug fix
|
|
264
266
|
changes returned values by definition, and if that forced a `0.Y.0` then a
|
|
@@ -289,6 +291,11 @@ under any rule keyed on whether values changed:
|
|
|
289
291
|
- `0.5.0` made `tilings` return 73 specs where it returned 100. The
|
|
290
292
|
enumeration's output set is the contract. A **y**.
|
|
291
293
|
|
|
294
|
+
One consequence worth stating on its own, because a consumer cannot infer it
|
|
295
|
+
from a `Literal`: **`WeightKind` is an open set.** Adding a member is additive
|
|
296
|
+
and lands in a `0.y` before 1.0 and a minor after it; removing or renaming one
|
|
297
|
+
is a break. Code that matches on it exhaustively should keep a fallback arm.
|
|
298
|
+
|
|
292
299
|
After 1.0 the ordinary reading applies: breaking is major, additive is minor,
|
|
293
300
|
fixed is patch. What 1.0 freezes is written in
|
|
294
301
|
[`docs/FOCO-1.0.md`](docs/FOCO-1.0.md).
|
|
@@ -305,11 +312,6 @@ minting a y announced a break that never happened.
|
|
|
305
312
|
`0.5.1` was briefly numbered `0.6.0` before release. It changes only how the
|
|
306
313
|
package is built and shipped, so it is a z-bump.
|
|
307
314
|
|
|
308
|
-
Two others were right and are worth recording as the shape to copy. `0.3.0`
|
|
309
|
-
moved `stitch` output by ULPs through a different summation order, which is an
|
|
310
|
-
output change and belongs at a y boundary. `0.5.0` made `tilings` return 73
|
|
311
|
-
specs where it returned 100, which is squarely a break.
|
|
312
|
-
|
|
313
315
|
The accelerator in [`accel/`](accel/) has no version of its own to manage. It
|
|
314
316
|
is compiled into this wheel, so it ships when `patchcraft` ships. The version
|
|
315
317
|
in `accel/Cargo.toml` is internal and nothing reads it; what the Python side
|
|
@@ -346,7 +348,7 @@ silent: a version that walks backwards rather than an error.
|
|
|
346
348
|
## Releasing (maintainer only)
|
|
347
349
|
|
|
348
350
|
1. Close the `[Unreleased]` section in [`CHANGELOG.md`](CHANGELOG.md) as `[X.Y.Z] YYYY-MM-DD`.
|
|
349
|
-
2. Update `version` and `date-released` in [`CITATION.cff`](CITATION.cff),
|
|
351
|
+
2. Update `version` and `date-released` in [`CITATION.cff`](CITATION.cff), the `version` field of the BibTeX entry in [`docs/GUIDE.md`](docs/GUIDE.md) section 9, and the release named at the top of [`STATUS.md`](STATUS.md). Those are the version strings a release bumps, because a citation needs a concrete one and a snapshot names its release; [`docs/PERFORMANCE.md`](docs/PERFORMANCE.md) names the version it measured and changes only when the table is re-measured. `uvx --from cffconvert cffconvert --validate` checks the file.
|
|
350
352
|
3. Update [`docs/ROADMAP.md`](docs/ROADMAP.md) milestone checkboxes.
|
|
351
353
|
4. Commit: `release: vX.Y.Z`.
|
|
352
354
|
5. Tag + push: `git tag -a vX.Y.Z -m "..."` then `git push origin vX.Y.Z`.
|
|
@@ -42,3 +42,7 @@ prune .vscode
|
|
|
42
42
|
# Presentation material, not source. It is tracked, so the file finder offers
|
|
43
43
|
# it and only this line keeps it out.
|
|
44
44
|
prune outreach
|
|
45
|
+
# An article in preparation, not documentation of the library. Without this
|
|
46
|
+
# line its experiment scripts would ship too, since the finder offers every
|
|
47
|
+
# tracked file.
|
|
48
|
+
prune docs/PAPER
|
|
@@ -22,7 +22,7 @@ PatchCraft/
|
|
|
22
22
|
├── CONTRIBUTING.md ........... layout, the two code paths, the versioning rule, releasing
|
|
23
23
|
├── CITATION.cff .............. citation metadata; GitHub reads it for the cite button
|
|
24
24
|
├── LICENSE ................... MIT
|
|
25
|
-
├── SECURITY.md ............... how to report, and the
|
|
25
|
+
├── SECURITY.md ............... how to report, and the five edges of the attack surface
|
|
26
26
|
├── CODE_OF_CONDUCT.md ........ Contributor Covenant 2.1
|
|
27
27
|
│
|
|
28
28
|
├── src/patchcraft/ ........... THE LIBRARY. 20 public names, frozen by test
|
|
@@ -44,6 +44,7 @@ PatchCraft/
|
|
|
44
44
|
├── tests/ .................... the contract, executable
|
|
45
45
|
│ ├── test_exactness.py ..... enumerates 126,736 geometries and tries to FALSIFY the predicate
|
|
46
46
|
│ ├── test_reference.py ..... a naive loop the fast paths must agree with
|
|
47
|
+
│ ├── test_operator.py ...... extract as a matrix U: the fold is U^T, reconstruct and stitch are least squares
|
|
47
48
|
│ ├── test_public_api.py .... freezes the 20 names, their signatures and carrier fields
|
|
48
49
|
│ └── _rng.py ............... audited data generation; integer ramps hid a real defect once
|
|
49
50
|
│
|
|
@@ -55,19 +56,23 @@ PatchCraft/
|
|
|
55
56
|
│ ├── USAGE.md .............. the 20 symbols one at a time; every >>> runs in the suite
|
|
56
57
|
│ ├── AUXILIARY.md .......... test fixtures, lab/, the off-tree conventions
|
|
57
58
|
│ ├── ROADMAP.md ............ milestones, historical
|
|
58
|
-
│ ├── FOCO-1.0.md ........... what 1.0 freezes, and the blockers
|
|
59
|
+
│ ├── FOCO-1.0.md ........... what 1.0 freezes, and the six blockers, all closed
|
|
59
60
|
│ ├── ADR/ .................. one file per decision; the choice, not the research
|
|
60
61
|
│ ├── design/ ............... one spec per work phase: alternatives measured, then chosen
|
|
61
|
-
│
|
|
62
|
+
│ ├── STUDIES/ .............. background reading, and the Strata conformity review
|
|
63
|
+
│ └── PAPER/ ................ an article in preparation, on means that stay in their domain; not shipped
|
|
62
64
|
│
|
|
63
65
|
├── tools/
|
|
64
66
|
│ ├── benchmark.py .......... accelerated vs pure, and proves they agree before timing
|
|
65
|
-
│
|
|
67
|
+
│ ├── check_dist.py ......... release gate: extension present, versions agree, tag respected
|
|
68
|
+
│ ├── compare_exactness_rules.py the power-of-two rule against k_max <= 4, measured
|
|
69
|
+
│ ├── compare_border_strategies.py truncate, pad or move the last window, measured
|
|
70
|
+
│ └── make_outreach_figures.py every outreach figure and generated page, computed
|
|
66
71
|
│
|
|
67
72
|
├── outreach/ ................. material for presenting the project; not shipped
|
|
68
73
|
├── lab/ ...................... scratch experiments; only the README is tracked
|
|
69
74
|
└── .github/
|
|
70
|
-
├── ISSUE_TEMPLATE/ .......
|
|
75
|
+
├── ISSUE_TEMPLATE/ ....... four forms, each asking for the geometry and PATCHCRAFT_ACCEL=0
|
|
71
76
|
├── PULL_REQUEST_TEMPLATE.md
|
|
72
77
|
└── workflows/ ............ test.yml (both code paths) and release.yml (one project, six wheels)
|
|
73
78
|
```
|
|
@@ -80,10 +85,11 @@ PatchCraft/
|
|
|
80
85
|
| **Work on it as an agent**, or learn what this repo insists on | [AGENTS.md](AGENTS.md) |
|
|
81
86
|
| **Use it**: every argument, measured, with its printed output | [docs/GUIDE.md](docs/GUIDE.md) |
|
|
82
87
|
| Know **when the round trip is exact**, as a rule I can evaluate myself | [docs/GUIDE.md §4](docs/GUIDE.md#4-when-the-round-trip-is-bit-for-bit), and [docs/ADR/0003](docs/ADR/0003-reversibility-classes.md) for the contract |
|
|
88
|
+
| Know **what `reconstruct` and `stitch` compute** for patches a model changed, and how to filter or pad around them | [docs/THEORY.md §2.6](docs/THEORY.md), then [docs/GUIDE.md](docs/GUIDE.md) §2, §5 and §6 for the measured recipes |
|
|
83
89
|
| Know **what each function accepts and rejects**, precisely | [docs/THEORY.md §9](docs/THEORY.md), which is the arbiter where documents disagree |
|
|
84
90
|
| Know **whether this library is the right tool** | [docs/SCOPE.md](docs/SCOPE.md) |
|
|
85
91
|
| Know **what the native accelerator is worth**, and re-measure it | [docs/PERFORMANCE.md](docs/PERFORMANCE.md), then `python tools/benchmark.py` |
|
|
86
|
-
| Know **
|
|
92
|
+
| Know **where the library applies today** | [docs/GUIDE.md §8](docs/GUIDE.md#8-where-this-applies-today) |
|
|
87
93
|
| Understand **why the API looks like this** | [docs/ADR/](docs/ADR/), and [docs/STUDIES/](docs/STUDIES/) for the measurements behind them |
|
|
88
94
|
| Understand **why a phase was built the way it was** | [docs/design/](docs/design/) |
|
|
89
95
|
| **Contribute**: run the gates, understand the two code paths | [CONTRIBUTING.md](CONTRIBUTING.md) |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: patchcraft
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.6
|
|
4
4
|
Summary: Image patch extraction, reconstruction, pairing and seam-aware stitching for super-resolution and dataset pipelines.
|
|
5
5
|
Author: Leonardo Marques de Souza
|
|
6
6
|
License-Expression: MIT
|
|
@@ -42,7 +42,7 @@ Dynamic: license-file
|
|
|
42
42
|
|
|
43
43
|
# PatchCraft
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
Cut one image into patches, put it back together, and decide what happens at the seams.
|
|
46
46
|
|
|
47
47
|
[](https://pypi.org/project/patchcraft/)
|
|
48
48
|
[](https://pypi.org/project/patchcraft/)
|
|
@@ -88,7 +88,7 @@ paths, which return the same values.
|
|
|
88
88
|
|
|
89
89
|
`patchcraft.accel_available()` reports at runtime which one you got, and
|
|
90
90
|
`PATCHCRAFT_ACCEL=0` in the environment forces the pure path. On the overlapping
|
|
91
|
-
fold
|
|
91
|
+
fold the accelerator is worth between 5.8x and 16.5x on the machine it was measured on, which
|
|
92
92
|
the [performance page](https://github.com/LeoPR/PatchCraft/blob/main/docs/PERFORMANCE.md)
|
|
93
93
|
reports in full.
|
|
94
94
|
|
|
@@ -111,7 +111,7 @@ blended = stitch(edited, image.shape, stride=16, weight="hann")
|
|
|
111
111
|
assert blended.shape == image.shape # seams smoothed, geometry preserved
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
-
PatchCraft accepts float tensors only. An 8-bit image has to become `image.float() / 255` before it reaches `extract`, because `extract`
|
|
114
|
+
PatchCraft accepts float tensors only. An 8-bit image has to become `image.float() / 255` before it reaches `extract`, because `extract` routes an integer tensor to `F.unfold`, and torch has no integer kernel there: it raises its own error, `"im2col_out_cpu" not implemented for 'Byte'`.
|
|
115
115
|
|
|
116
116
|
## reconstruct or stitch
|
|
117
117
|
|
|
@@ -128,7 +128,7 @@ Uniform averaging is the default, and it is the option that reports what the mod
|
|
|
128
128
|
|
|
129
129
|
## Why not unfold and fold directly
|
|
130
130
|
|
|
131
|
-
Nothing stops you
|
|
131
|
+
Nothing stops you. What PatchCraft adds is the pixel order and the boundary checks, done once with tests around them, and a faster fold where the wheel carries one, because the intuitive reshape after `F.unfold` returns a tensor of the right shape whose pixels are scrambled.
|
|
132
132
|
|
|
133
133
|
```python
|
|
134
134
|
import torch
|
|
@@ -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
|
|
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 costs 17 non-blank lines by hand against 3 with `extract` and `stitch`, and the two outputs agree to about 1.2e-05 on a value in [0, 1], summation-order rounding more than 300 times smaller than one 8-bit step.
|
|
148
148
|
|
|
149
149
|
## The geometry has to cover the image
|
|
150
150
|
|
|
@@ -162,7 +162,7 @@ It helps when you tile one image for an inference pass too large to run in a sin
|
|
|
162
162
|
|
|
163
163
|
## When the round trip is bit for bit
|
|
164
164
|
|
|
165
|
-
The round trip is exact when every value in the count map is a power of two.
|
|
165
|
+
The round trip is exact when every value in the count map is a power of two. Reconstruction adds up the patches covering each pixel and divides by how many there were, and in a legal geometry a map of powers of two holds only the counts 1, 2 and 4, where both steps are exact: adding two or four copies of a value never rounds, and neither does dividing by 2 or 4.
|
|
166
166
|
|
|
167
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.
|
|
168
168
|
|
|
@@ -170,15 +170,15 @@ The everyday shorthand is that `stride == patch_size` and `stride == patch_size
|
|
|
170
170
|
|
|
171
171
|
## Status
|
|
172
172
|
|
|
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
|
|
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 1657 tests and passes on Python 3.12, 3.13 and 3.14, on Ubuntu and on Windows alike.
|
|
174
174
|
|
|
175
|
-
|
|
175
|
+
Where it applies today: on GPU the functions accept CUDA tensors and keep the device, and the Rust kernel is CPU-only, so it does not accelerate there; every figure on this page is a CPU figure, so check exactness on your device before relying on it. No external project consumes the published API yet, which is the gate this project set 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
|
|
181
|
+
- [Usage](https://github.com/LeoPR/PatchCraft/blob/main/docs/USAGE.md), a walkthrough of the public surface, executed as a doctest
|
|
182
182
|
- [Theory](https://github.com/LeoPR/PatchCraft/blob/main/docs/THEORY.md), the math and the per-function contract
|
|
183
183
|
- [Scope](https://github.com/LeoPR/PatchCraft/blob/main/docs/SCOPE.md), the line between this library and your pipeline
|
|
184
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)
|