patchcraft 0.5.5__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.5 → patchcraft-0.5.6}/.pre-commit-config.yaml +2 -2
- {patchcraft-0.5.5 → patchcraft-0.5.6}/AGENTS.md +5 -3
- {patchcraft-0.5.5 → patchcraft-0.5.6}/CHANGELOG.md +154 -1
- {patchcraft-0.5.5 → patchcraft-0.5.6}/CITATION.cff +5 -6
- {patchcraft-0.5.5 → patchcraft-0.5.6}/CONTRIBUTING.md +7 -6
- {patchcraft-0.5.5 → patchcraft-0.5.6}/MANIFEST.in +4 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/MAP.md +5 -1
- {patchcraft-0.5.5 → patchcraft-0.5.6}/PKG-INFO +6 -6
- {patchcraft-0.5.5 → patchcraft-0.5.6}/README.md +3 -3
- {patchcraft-0.5.5 → patchcraft-0.5.6}/README.pt-BR.md +4 -4
- {patchcraft-0.5.5 → patchcraft-0.5.6}/README.pypi.md +5 -5
- {patchcraft-0.5.5 → patchcraft-0.5.6}/STATUS.md +26 -13
- {patchcraft-0.5.5 → 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.5 → patchcraft-0.5.6}/docs/FOCO-1.0.md +7 -7
- {patchcraft-0.5.5 → patchcraft-0.5.6}/docs/GUIDE.md +178 -13
- {patchcraft-0.5.5 → patchcraft-0.5.6}/docs/PERFORMANCE.md +1 -1
- {patchcraft-0.5.5 → 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.5 → patchcraft-0.5.6}/docs/THEORY.md +27 -8
- {patchcraft-0.5.5 → patchcraft-0.5.6}/docs/USAGE.md +163 -18
- {patchcraft-0.5.5 → patchcraft-0.5.6}/src/patchcraft/_accel.py +12 -3
- {patchcraft-0.5.5 → patchcraft-0.5.6}/src/patchcraft/_version.py +3 -3
- {patchcraft-0.5.5 → patchcraft-0.5.6}/src/patchcraft/extract.py +13 -8
- {patchcraft-0.5.5 → patchcraft-0.5.6}/src/patchcraft/geometry.py +10 -4
- {patchcraft-0.5.5 → patchcraft-0.5.6}/src/patchcraft/reconstruct.py +25 -18
- {patchcraft-0.5.5 → patchcraft-0.5.6}/src/patchcraft/stitch.py +6 -3
- {patchcraft-0.5.5 → patchcraft-0.5.6}/src/patchcraft.egg-info/PKG-INFO +6 -6
- {patchcraft-0.5.5 → patchcraft-0.5.6}/src/patchcraft.egg-info/SOURCES.txt +5 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/src/patchcraft.egg-info/scm_file_list.json +17 -0
- patchcraft-0.5.6/src/patchcraft.egg-info/scm_version.json +8 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/tests/_rng.py +1 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/tests/test_accel.py +10 -5
- {patchcraft-0.5.5 → patchcraft-0.5.6}/tests/test_exactness.py +28 -1
- {patchcraft-0.5.5 → patchcraft-0.5.6}/tests/test_extract.py +4 -2
- {patchcraft-0.5.5 → patchcraft-0.5.6}/tests/test_geometry.py +21 -0
- patchcraft-0.5.6/tests/test_operator.py +144 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/tests/test_reconstruct.py +14 -0
- patchcraft-0.5.6/tests/test_vmap.py +72 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/tools/check_dist.py +3 -0
- patchcraft-0.5.6/tools/compare_border_strategies.py +116 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/tools/compare_exactness_rules.py +31 -1
- {patchcraft-0.5.5 → patchcraft-0.5.6}/tools/make_outreach_figures.py +21 -9
- {patchcraft-0.5.5 → patchcraft-0.5.6}/uv.lock +101 -101
- patchcraft-0.5.5/src/patchcraft.egg-info/scm_version.json +0 -8
- {patchcraft-0.5.5 → patchcraft-0.5.6}/.gitignore +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/.python-version +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/CODE_OF_CONDUCT.md +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/LICENSE +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/SECURITY.md +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/accel/Cargo.lock +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/accel/Cargo.toml +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/accel/README.md +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/accel/src/kernel.rs +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/accel/src/lib.rs +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/docs/ADR/0001-patch-extraction-api.md +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/docs/ADR/0002-patchify-transform.md +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/docs/ADR/0004-precision-and-effort-parameters.md +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/docs/AUXILIARY.md +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/docs/SCOPE.md +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/docs/STUDIES/2026-08-04-patch-techniques.md +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/docs/STUDIES/2026-09-03-precision-measurements.md +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/docs/STUDIES/2026-09-03-strata-conformity.md +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/docs/design/2026-09-01-fase1-bugs-performance.md +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/docs/design/2026-09-01-fase2-acelerador.md +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/docs/design/2026-09-01-fase3-predicado.md +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/pyproject.toml +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/setup.cfg +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/setup.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/src/patchcraft/__init__.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/src/patchcraft/_foldgeom.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/src/patchcraft/cache.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/src/patchcraft/metrics.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/src/patchcraft/pair.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/src/patchcraft/py.typed +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/src/patchcraft/resize.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/src/patchcraft.egg-info/dependency_links.txt +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/src/patchcraft.egg-info/requires.txt +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/src/patchcraft.egg-info/top_level.txt +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/tests/__init__.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/tests/_datasets.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/tests/conftest.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/tests/test_cache.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/tests/test_citation.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/tests/test_datasets_helper.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/tests/test_docs_usage.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/tests/test_import.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/tests/test_metrics.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/tests/test_pair.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/tests/test_public_api.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/tests/test_reference.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/tests/test_resize.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/tests/test_rng.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/tests/test_stitch.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.6}/tools/benchmark.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,158 @@ 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
|
+
|
|
9
161
|
## [0.5.5] - 2026-09-26
|
|
10
162
|
|
|
11
163
|
A performance release that changes no bit. Every change to `src/` below was
|
|
@@ -1016,7 +1168,8 @@ First public release. Public API stable; signatures will only change in 1.x.
|
|
|
1016
1168
|
- [`README.md`](README.md) covers installation, the car-vs-track metaphor,
|
|
1017
1169
|
validation lab.
|
|
1018
1170
|
|
|
1019
|
-
[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
|
|
1020
1173
|
[0.5.5]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.5.5
|
|
1021
1174
|
[0.5.4]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.5.4
|
|
1022
1175
|
[0.5.3]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.5.3
|
|
@@ -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
|
|
37
|
+
it, and adopting it would reformat 37 files in a commit that changed no
|
|
38
38
|
behaviour.
|
|
39
39
|
|
|
40
40
|
```
|
|
@@ -153,7 +153,7 @@ PatchCraft/
|
|
|
153
153
|
│ ├── ISSUE_TEMPLATE/ four forms and a chooser; the required fields are what makes a report reproducible
|
|
154
154
|
│ ├── PULL_REQUEST_TEMPLATE.md asks whether a returned value moved, which decides the version bump
|
|
155
155
|
│ └── workflows/
|
|
156
|
-
│ ├── test.yml matrix CI on PRs/main, plus the accelerated
|
|
156
|
+
│ ├── test.yml matrix CI on PRs/main, plus the accelerated and floor jobs
|
|
157
157
|
│ └── release.yml publishes to PyPI on vX.Y.Z tag push (Trusted Publishing)
|
|
158
158
|
├── src/patchcraft/ library core, one-image-at-a-time primitives
|
|
159
159
|
│ ├── __init__.py re-exports the full public API and the generated version
|
|
@@ -207,11 +207,12 @@ PatchCraft/
|
|
|
207
207
|
│ ├── FOCO-1.0.md what 1.0 freezes, and the six blockers, all closed
|
|
208
208
|
│ ├── ADR/ one file per decision; the choice only, evidence in STUDIES/
|
|
209
209
|
│ ├── STUDIES/ background reading behind those decisions
|
|
210
|
-
│
|
|
210
|
+
│ ├── design/ one spec per work phase: the alternatives measured and the decision
|
|
211
|
+
│ └── PAPER/ an article in preparation, with its experiments; not shipped
|
|
211
212
|
├── outreach/ material for presenting the project publicly, not shipped
|
|
212
213
|
│ ├── README.md how it is organized and what to check before publishing
|
|
213
|
-
│ ├── 2026-
|
|
214
|
-
│ ├── 2026-09-
|
|
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
|
|
215
216
|
│ └── linkedin/ post, artigo, the illustrated page and the figures, PT and EN
|
|
216
217
|
└── archive/ reference-only; gitignored (pruned 2026-05-17, only HISTORY.md kept)
|
|
217
218
|
```
|
|
@@ -347,7 +348,7 @@ silent: a version that walks backwards rather than an error.
|
|
|
347
348
|
## Releasing (maintainer only)
|
|
348
349
|
|
|
349
350
|
1. Close the `[Unreleased]` section in [`CHANGELOG.md`](CHANGELOG.md) as `[X.Y.Z] YYYY-MM-DD`.
|
|
350
|
-
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.
|
|
351
352
|
3. Update [`docs/ROADMAP.md`](docs/ROADMAP.md) milestone checkboxes.
|
|
352
353
|
4. Commit: `release: vX.Y.Z`.
|
|
353
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
|
|
@@ -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
|
│
|
|
@@ -58,12 +59,14 @@ PatchCraft/
|
|
|
58
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
|
|
66
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
|
|
67
70
|
│ └── make_outreach_figures.py every outreach figure and generated page, computed
|
|
68
71
|
│
|
|
69
72
|
├── outreach/ ................. material for presenting the project; not shipped
|
|
@@ -82,6 +85,7 @@ PatchCraft/
|
|
|
82
85
|
| **Work on it as an agent**, or learn what this repo insists on | [AGENTS.md](AGENTS.md) |
|
|
83
86
|
| **Use it**: every argument, measured, with its printed output | [docs/GUIDE.md](docs/GUIDE.md) |
|
|
84
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 |
|
|
85
89
|
| Know **what each function accepts and rejects**, precisely | [docs/THEORY.md §9](docs/THEORY.md), which is the arbiter where documents disagree |
|
|
86
90
|
| Know **whether this library is the right tool** | [docs/SCOPE.md](docs/SCOPE.md) |
|
|
87
91
|
| Know **what the native accelerator is worth**, and re-measure it | [docs/PERFORMANCE.md](docs/PERFORMANCE.md), then `python tools/benchmark.py` |
|
|
@@ -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
|
|
@@ -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
|
|
|
@@ -178,7 +178,7 @@ Where it applies today: on GPU the functions accept CUDA tensors and keep the de
|
|
|
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)
|
|
@@ -140,13 +140,13 @@ PatchCraft does not help when you want a batched op over N images, a dataset or
|
|
|
140
140
|
|
|
141
141
|
## When the round trip is bit for bit
|
|
142
142
|
|
|
143
|
-
The round trip is exact when every value in the count map is a power of two, which
|
|
143
|
+
The round trip is exact when every value in the count map is a power of two, which in a legal geometry means every count is 1, 2 or 4: adding two or four copies of a value never rounds, and neither does dividing by 2 or 4. Outside that 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, so float64 is not a safe harbour. The axis that decides the answer is the geometry and not the dtype, and [the guide](docs/GUIDE.md#4-when-the-round-trip-is-bit-for-bit) has the sweep that measures it.
|
|
144
144
|
|
|
145
145
|
## Status
|
|
146
146
|
|
|
147
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
|
+
CI is green on {Ubuntu, Windows} x {Python 3.12, 3.13, 3.14} with the newest torch, and on the declared floor, Python 3.12 with torch 2.6, numpy 1.26, pillow 10.1 and Rust 1.82, with `ruff check` and `mypy --strict` in the same run. The package is typed and ships `py.typed`.
|
|
150
150
|
|
|
151
151
|
On GPU the functions keep the device you hand them, and the Rust kernel is CPU-only, so it does not accelerate there; every figure here is a CPU figure. No external project consumes the library yet, which is the gate for calling the API settled. [The guide](docs/GUIDE.md#8-where-this-applies-today) says where the rest applies.
|
|
152
152
|
|
|
@@ -156,7 +156,7 @@ On GPU the functions keep the device you hand them, and the Rust kernel is CPU-o
|
|
|
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
|
|
159
|
+
| A walkthrough of the public surface, executed as a doctest | [docs/USAGE.md](docs/USAGE.md) |
|
|
160
160
|
| The line between this library and your pipeline | [docs/SCOPE.md](docs/SCOPE.md) |
|
|
161
161
|
| The math, the decisions and the per-function contract | [docs/THEORY.md](docs/THEORY.md) |
|
|
162
162
|
| Why the API looks like this | [docs/ADR/](docs/ADR) |
|
|
@@ -106,7 +106,7 @@ assert scrambled.shape == patches.shape # a forma certa
|
|
|
106
106
|
assert not torch.equal(scrambled, patches) # e os pixels errados
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
-
O segundo é um stride que não cobre a imagem. Numa imagem de 128 por 128 com `patch=32, stride=20`, a grade para no pixel 112, o que deixa 3840 dos 16384 pixels em zero, e um `fold` escrito à mão devolve essa imagem parcialmente preta sem reclamar.
|
|
109
|
+
O segundo é um stride, a distância de um patch para o próximo, que não cobre a imagem. Numa imagem de 128 por 128 com `patch=32, stride=20`, a grade para no pixel 112, o que deixa 3840 dos 16384 pixels em zero, e um `fold` escrito à mão devolve essa imagem parcialmente preta sem reclamar.
|
|
110
110
|
|
|
111
111
|
Escrever o laço de recortar e remontar à mão custa 17 linhas não vazias contra 3 aqui, e os dois resultados concordam em cerca de 1,2e-05 num valor em [0, 1], mais de 300 vezes menos que um degrau de 8 bits, com as duas rotas diferindo só na ordem em que somam uma janela separável. [O manual](docs/GUIDE.md#1-why-not-unfold-and-fold-directly) roda as duas versões lado a lado.
|
|
112
112
|
|
|
@@ -140,13 +140,13 @@ O PatchCraft não ajuda quando você quer uma operação em lote sobre N imagens
|
|
|
140
140
|
|
|
141
141
|
## Quando o round-trip é bit a bit
|
|
142
142
|
|
|
143
|
-
O round-trip é exato quando todo valor do mapa de cobertura é potência de dois,
|
|
143
|
+
O round-trip é exato quando todo valor do mapa de cobertura é potência de dois, o que numa geometria legal quer dizer que toda contagem é 1, 2 ou 4: somar duas ou quatro cópias de um valor nunca arredonda, e dividir por 2 ou por 4 também não. Fora dessa regra, o erro por pixel é limitado por (k+1)·eps·|v|, com k a contagem de cobertura do pixel. Um float mais largo compra um erro menor e nunca a exatidão, então o float64 não é um porto seguro. Quem decide a resposta é a geometria, e não o dtype. [O manual](docs/GUIDE.md#4-when-the-round-trip-is-bit-for-bit) traz a varredura que mede isso.
|
|
144
144
|
|
|
145
145
|
## Estado
|
|
146
146
|
|
|
147
147
|
**Pré-1.0**, e o badge acima mostra a versão atual. Enquanto o primeiro dígito for zero, quem marca a fronteira de compatibilidade é o do meio: um `0.y.z` novo é seguro de pegar, e é num `0.y` novo que um valor de saída ou uma assinatura pode mudar. O [CHANGELOG.md](CHANGELOG.md) registra cada uma dessas mudanças com a medição por trás dela.
|
|
148
148
|
|
|
149
|
-
|
|
149
|
+
A CI está verde em {Ubuntu, Windows} x {Python 3.12, 3.13, 3.14} com o torch mais novo, e no piso declarado, Python 3.12 com torch 2.6, numpy 1.26, pillow 10.1 e Rust 1.82, com `ruff check` e `mypy --strict` na mesma execução. O pacote é tipado e distribui o `py.typed`.
|
|
150
150
|
|
|
151
151
|
Em GPU as funções preservam o dispositivo que você passa, e o kernel Rust é só de CPU, então não acelera ali; todo número desta página é de CPU. Nenhum projeto externo consome a biblioteca ainda, que é o critério para dizer a API assentada. [O manual](docs/GUIDE.md#8-where-this-applies-today) diz onde o resto se aplica.
|
|
152
152
|
|
|
@@ -156,7 +156,7 @@ Em GPU as funções preservam o dispositivo que você passa, e o kernel Rust é
|
|
|
156
156
|
|---|---|
|
|
157
157
|
| O manual: cada argumento acima, medido, com a saída | [docs/GUIDE.md](docs/GUIDE.md) |
|
|
158
158
|
| Quanto vale o acelerador nativo, e como remedir | [docs/PERFORMANCE.md](docs/PERFORMANCE.md) |
|
|
159
|
-
| Um passeio
|
|
159
|
+
| Um passeio pela superfície pública, executado como doctest | [docs/USAGE.md](docs/USAGE.md) |
|
|
160
160
|
| A linha entre esta biblioteca e o seu pipeline | [docs/SCOPE.md](docs/SCOPE.md) |
|
|
161
161
|
| A matemática, as decisões e o contrato por função | [docs/THEORY.md](docs/THEORY.md) |
|
|
162
162
|
| Por que a API tem essa cara | [docs/ADR/](docs/ADR) |
|
|
@@ -69,7 +69,7 @@ blended = stitch(edited, image.shape, stride=16, weight="hann")
|
|
|
69
69
|
assert blended.shape == image.shape # seams smoothed, geometry preserved
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
-
PatchCraft accepts float tensors only. An 8-bit image has to become `image.float() / 255` before it reaches `extract`, because `extract`
|
|
72
|
+
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'`.
|
|
73
73
|
|
|
74
74
|
## reconstruct or stitch
|
|
75
75
|
|
|
@@ -86,7 +86,7 @@ Uniform averaging is the default, and it is the option that reports what the mod
|
|
|
86
86
|
|
|
87
87
|
## Why not unfold and fold directly
|
|
88
88
|
|
|
89
|
-
Nothing stops you
|
|
89
|
+
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.
|
|
90
90
|
|
|
91
91
|
```python
|
|
92
92
|
import torch
|
|
@@ -102,7 +102,7 @@ assert scrambled.shape == patches.shape # the right shape
|
|
|
102
102
|
assert not torch.equal(scrambled, patches) # and the wrong pixels
|
|
103
103
|
```
|
|
104
104
|
|
|
105
|
-
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
|
|
105
|
+
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.
|
|
106
106
|
|
|
107
107
|
## The geometry has to cover the image
|
|
108
108
|
|
|
@@ -120,7 +120,7 @@ It helps when you tile one image for an inference pass too large to run in a sin
|
|
|
120
120
|
|
|
121
121
|
## When the round trip is bit for bit
|
|
122
122
|
|
|
123
|
-
The round trip is exact when every value in the count map is a power of two.
|
|
123
|
+
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.
|
|
124
124
|
|
|
125
125
|
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.
|
|
126
126
|
|
|
@@ -136,7 +136,7 @@ Where it applies today: on GPU the functions accept CUDA tensors and keep the de
|
|
|
136
136
|
|
|
137
137
|
- [Guide](https://github.com/LeoPR/PatchCraft/blob/main/docs/GUIDE.md), the manual, with every figure on this page shown as runnable code
|
|
138
138
|
- [Performance](https://github.com/LeoPR/PatchCraft/blob/main/docs/PERFORMANCE.md), what the native accelerator is worth and how to re-measure it
|
|
139
|
-
- [Usage](https://github.com/LeoPR/PatchCraft/blob/main/docs/USAGE.md), a walkthrough of
|
|
139
|
+
- [Usage](https://github.com/LeoPR/PatchCraft/blob/main/docs/USAGE.md), a walkthrough of the public surface, executed as a doctest
|
|
140
140
|
- [Theory](https://github.com/LeoPR/PatchCraft/blob/main/docs/THEORY.md), the math and the per-function contract
|
|
141
141
|
- [Scope](https://github.com/LeoPR/PatchCraft/blob/main/docs/SCOPE.md), the line between this library and your pipeline
|
|
142
142
|
- [Repository](https://github.com/LeoPR/PatchCraft), [issues](https://github.com/LeoPR/PatchCraft/issues) and [contributing](https://github.com/LeoPR/PatchCraft/blob/main/CONTRIBUTING.md)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# STATUS: 2026-
|
|
1
|
+
# STATUS: 2026-10-10
|
|
2
2
|
|
|
3
3
|
> The snapshot. Where things are *right now*, and who each open item waits on.
|
|
4
4
|
> Where things **are** is [MAP.md](MAP.md); the rules are [AGENTS.md](AGENTS.md);
|
|
@@ -6,31 +6,44 @@
|
|
|
6
6
|
|
|
7
7
|
## Where the project is
|
|
8
8
|
|
|
9
|
-
**0.5.
|
|
9
|
+
**0.5.6 is on PyPI**, published through Trusted Publishing on a tag push: one
|
|
10
10
|
sdist, five `cp312-abi3` platform wheels carrying the Rust accelerator, and one
|
|
11
11
|
universal `py3-none-any` wheel. There is no extra to enable and no second
|
|
12
12
|
package.
|
|
13
13
|
|
|
14
|
-
**
|
|
14
|
+
**1818 tests pass** under `pytest -m "not gpu"`, with 32 skipped and 5
|
|
15
15
|
deselected, plus two full-sweep gates behind `PATCHCRAFT_SWEEP_FULL=1`
|
|
16
16
|
that enumerate all 126,736 legal geometries. CI is green on
|
|
17
17
|
{Ubuntu, Windows} x {3.12, 3.13, 3.14} on the pure path, plus a two-OS job that
|
|
18
|
-
builds the Rust kernel and runs the whole suite through it
|
|
18
|
+
builds the Rust kernel and runs the whole suite through it, all at the newest
|
|
19
|
+
torch, and a job at the declared floor (Python 3.12, torch 2.6, numpy 1.26,
|
|
20
|
+
pillow 10.1, Rust 1.82) that runs it on both paths.
|
|
19
21
|
|
|
20
22
|
**The surface is 20 names**, pinned by `tests/test_public_api.py` with
|
|
21
23
|
`inspect.signature`.
|
|
22
24
|
|
|
23
25
|
## What is in flight
|
|
24
26
|
|
|
25
|
-
**Nothing unreleased.** 0.5.
|
|
26
|
-
`
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
**Nothing unreleased.** 0.5.6 is a fix release: `torch.vmap` over `extract`,
|
|
28
|
+
`reconstruct` and `stitch`, which the documentation offers as a way to batch,
|
|
29
|
+
failed on most calls since 0.5.0 and works again, pinned by
|
|
30
|
+
`tests/test_vmap.py`; and `tilings` returns `[]` for an image smaller than the
|
|
31
|
+
default minimum patch instead of raising. With them came the corrected reason
|
|
32
|
+
for the exactness rule, THEORY §2.6 on the operator, the USAGE walk through a
|
|
33
|
+
4×4 image, and a CI job at the declared floor, whose first run found a claim
|
|
34
|
+
that held only at the top. The full 126,736-geometry sweep passes on both
|
|
35
|
+
paths.
|
|
36
|
+
|
|
37
|
+
**ADR 0005 is `Proposed`**: padding a grid that does not cover the image, with
|
|
38
|
+
the design taken from kornia's auto-padding and MONAI's `padding_mode`, one
|
|
39
|
+
keyword with a default that changes no call. Nothing is implemented. The owner
|
|
40
|
+
recorded it in the ROADMAP as the 1.1 candidate rather than scheduling it, so it
|
|
41
|
+
**waits on a consumer**.
|
|
42
|
+
|
|
43
|
+
**`docs/PAPER/` is a scaffold** for an article on means that never leave
|
|
44
|
+
their domain: outline, formal statements, measurements and references, with
|
|
45
|
+
the prose not yet written. It does not ship. Its first open item, a search for
|
|
46
|
+
prior art on the integer carry, **waits on the owner**, as does the writing.
|
|
34
47
|
|
|
35
48
|
## What is blocked, and on whom
|
|
36
49
|
|
|
@@ -58,8 +58,13 @@ be false over part of their accepted input.
|
|
|
58
58
|
**Geometry.** The `extract`/`reconstruct` round trip is bit-exact when every
|
|
59
59
|
value of the overlap coverage map is a power of two, and not otherwise. The
|
|
60
60
|
reason is arithmetic rather than statistical: the fold sums `k` copies of a value
|
|
61
|
-
and divides by `k
|
|
62
|
-
|
|
61
|
+
and divides by `k`. Dividing by a power of two never rounds, and for `k` = 2 and 4
|
|
62
|
+
the sum of equal values is exact too, short of overflow. For `k` = 8 it is not,
|
|
63
|
+
since the partial sums 3x, 5x, 6x and 7x round; the rule holds anyway because a
|
|
64
|
+
coverage of three or more on either axis puts a 3 in the map, so a map of powers
|
|
65
|
+
of two only ever holds 1, 2 and 4, which is what all 126,736 legal geometries
|
|
66
|
+
show. (Corrected 2026-10-10: the earlier wording credited every power of two.)
|
|
67
|
+
Every exact tiling qualifies, since all its counts are 1.
|
|
63
68
|
|
|
64
69
|
**Accumulator.** ADR 0004 measured the sentence above to carry an unstated
|
|
65
70
|
premise, which is the accumulator the library happens to use. Over 76 legal
|