patchcraft 0.5.5__tar.gz → 0.5.7__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.7}/.pre-commit-config.yaml +2 -2
- {patchcraft-0.5.5 → patchcraft-0.5.7}/AGENTS.md +5 -3
- {patchcraft-0.5.5 → patchcraft-0.5.7}/CHANGELOG.md +194 -1
- {patchcraft-0.5.5 → patchcraft-0.5.7}/CITATION.cff +5 -6
- {patchcraft-0.5.5 → patchcraft-0.5.7}/CONTRIBUTING.md +7 -6
- {patchcraft-0.5.5 → patchcraft-0.5.7}/MANIFEST.in +4 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/MAP.md +5 -1
- {patchcraft-0.5.5/src/patchcraft.egg-info → patchcraft-0.5.7}/PKG-INFO +7 -7
- {patchcraft-0.5.5 → patchcraft-0.5.7}/README.md +4 -4
- {patchcraft-0.5.5 → patchcraft-0.5.7}/README.pt-BR.md +5 -5
- {patchcraft-0.5.5 → patchcraft-0.5.7}/README.pypi.md +6 -6
- {patchcraft-0.5.5 → patchcraft-0.5.7}/STATUS.md +36 -14
- {patchcraft-0.5.5 → patchcraft-0.5.7}/docs/ADR/0003-reversibility-classes.md +7 -2
- {patchcraft-0.5.5 → patchcraft-0.5.7}/docs/ADR/0004-precision-and-effort-parameters.md +8 -4
- patchcraft-0.5.7/docs/ADR/0005-border-policy.md +94 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/docs/FOCO-1.0.md +7 -7
- {patchcraft-0.5.5 → patchcraft-0.5.7}/docs/GUIDE.md +201 -30
- {patchcraft-0.5.5 → patchcraft-0.5.7}/docs/PERFORMANCE.md +1 -1
- {patchcraft-0.5.5 → patchcraft-0.5.7}/docs/ROADMAP.md +7 -1
- {patchcraft-0.5.5 → patchcraft-0.5.7}/docs/SCOPE.md +5 -6
- patchcraft-0.5.7/docs/STUDIES/2026-10-10-border-policy.md +113 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/docs/THEORY.md +33 -12
- {patchcraft-0.5.5 → patchcraft-0.5.7}/docs/USAGE.md +181 -19
- {patchcraft-0.5.5 → patchcraft-0.5.7}/src/patchcraft/_accel.py +12 -3
- {patchcraft-0.5.5 → patchcraft-0.5.7}/src/patchcraft/_version.py +3 -3
- {patchcraft-0.5.5 → patchcraft-0.5.7}/src/patchcraft/extract.py +25 -41
- {patchcraft-0.5.5 → patchcraft-0.5.7}/src/patchcraft/geometry.py +10 -4
- {patchcraft-0.5.5 → patchcraft-0.5.7}/src/patchcraft/reconstruct.py +99 -25
- {patchcraft-0.5.5 → patchcraft-0.5.7}/src/patchcraft/stitch.py +6 -3
- {patchcraft-0.5.5 → patchcraft-0.5.7/src/patchcraft.egg-info}/PKG-INFO +7 -7
- {patchcraft-0.5.5 → patchcraft-0.5.7}/src/patchcraft.egg-info/SOURCES.txt +6 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/src/patchcraft.egg-info/scm_file_list.json +18 -0
- patchcraft-0.5.7/src/patchcraft.egg-info/scm_version.json +8 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/tests/_rng.py +5 -1
- {patchcraft-0.5.5 → patchcraft-0.5.7}/tests/test_accel.py +10 -5
- {patchcraft-0.5.5 → patchcraft-0.5.7}/tests/test_exactness.py +28 -1
- {patchcraft-0.5.5 → patchcraft-0.5.7}/tests/test_extract.py +15 -9
- {patchcraft-0.5.5 → patchcraft-0.5.7}/tests/test_geometry.py +21 -0
- patchcraft-0.5.7/tests/test_integer.py +191 -0
- patchcraft-0.5.7/tests/test_operator.py +144 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/tests/test_reconstruct.py +19 -5
- patchcraft-0.5.7/tests/test_vmap.py +72 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/tools/check_dist.py +3 -0
- patchcraft-0.5.7/tools/compare_border_strategies.py +116 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/tools/compare_exactness_rules.py +31 -1
- {patchcraft-0.5.5 → patchcraft-0.5.7}/tools/make_outreach_figures.py +21 -9
- {patchcraft-0.5.5 → patchcraft-0.5.7}/uv.lock +101 -101
- patchcraft-0.5.5/src/patchcraft.egg-info/scm_version.json +0 -8
- {patchcraft-0.5.5 → patchcraft-0.5.7}/.gitignore +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/.python-version +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/CODE_OF_CONDUCT.md +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/LICENSE +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/SECURITY.md +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/accel/Cargo.lock +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/accel/Cargo.toml +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/accel/README.md +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/accel/src/kernel.rs +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/accel/src/lib.rs +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/docs/ADR/0001-patch-extraction-api.md +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/docs/ADR/0002-patchify-transform.md +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/docs/AUXILIARY.md +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/docs/STUDIES/2026-08-04-patch-techniques.md +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/docs/STUDIES/2026-09-03-precision-measurements.md +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/docs/STUDIES/2026-09-03-strata-conformity.md +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/docs/design/2026-09-01-fase1-bugs-performance.md +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/docs/design/2026-09-01-fase2-acelerador.md +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/docs/design/2026-09-01-fase3-predicado.md +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/pyproject.toml +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/setup.cfg +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/setup.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/src/patchcraft/__init__.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/src/patchcraft/_foldgeom.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/src/patchcraft/cache.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/src/patchcraft/metrics.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/src/patchcraft/pair.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/src/patchcraft/py.typed +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/src/patchcraft/resize.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/src/patchcraft.egg-info/dependency_links.txt +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/src/patchcraft.egg-info/requires.txt +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/src/patchcraft.egg-info/top_level.txt +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/tests/__init__.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/tests/_datasets.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/tests/conftest.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/tests/test_cache.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/tests/test_citation.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/tests/test_datasets_helper.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/tests/test_docs_usage.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/tests/test_import.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/tests/test_metrics.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/tests/test_pair.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/tests/test_public_api.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/tests/test_reference.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/tests/test_resize.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/tests/test_rng.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/tests/test_stitch.py +0 -0
- {patchcraft-0.5.5 → patchcraft-0.5.7}/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,197 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.5.7] - 2026-10-10
|
|
10
|
+
|
|
11
|
+
Integer images, end to end. `extract` takes every dtype at every dilation,
|
|
12
|
+
and `reconstruct` takes the five integer dtypes and returns them exactly in
|
|
13
|
+
every legal geometry, since integer sums do not round. Every float result is
|
|
14
|
+
unchanged: `extract`'s new single path is bit-identical to the one it
|
|
15
|
+
replaces. The full suite passes on both code paths at the declared floor and
|
|
16
|
+
at the newest releases, and the 126,736-geometry sweep, now with an integer
|
|
17
|
+
round trip, passes on both paths.
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- **Integer images through `extract` and `reconstruct`, exact in every legal
|
|
22
|
+
geometry.** These are steps 1 and 2 of ADR 0004, shipped together as the ADR
|
|
23
|
+
requires. `extract` takes every dtype at every dilation, because it is now
|
|
24
|
+
one strided window view and one copy for all of them: bit-identical to the
|
|
25
|
+
`F.unfold` path it replaced for floats, and 3.9 times faster at dilation 2
|
|
26
|
+
on 3x1024x1024. `reconstruct` takes `uint8`, `int8`, `int16`, `int32` and
|
|
27
|
+
`int64` and returns each pixel's exact mean rounded half to even, the
|
|
28
|
+
rounding `resize` already applies to integers. For patches from `extract`
|
|
29
|
+
that mean is the pixel itself in every legal geometry, with no count-map
|
|
30
|
+
rule, since integer sums do not round. Nothing overflows: up to `int32` the
|
|
31
|
+
sum is taken in `int64`, and `int64` values are split by their pixel's count
|
|
32
|
+
into a quotient and a remainder before they are added. `bool` and complex
|
|
33
|
+
patches are refused with a `ValueError`, `stitch` stays float-only, and the
|
|
34
|
+
accelerator leaves integers to torch. `tests/test_integer.py` checks the
|
|
35
|
+
round trip over the sampled geometry space, all 126,736 geometries under
|
|
36
|
+
`PATCHCRAFT_SWEEP_FULL=1`, for `uint8` and `int64`, and the mean of
|
|
37
|
+
disagreeing patches against Python integers in every dtype; deliberately
|
|
38
|
+
breaking the rounding or the accumulator fails 63 and 90 of its cases.
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
|
|
42
|
+
- THEORY §1 and SCOPE §2.1 described `extract` as built on `F.unfold`, which
|
|
43
|
+
had been true only at dilation above 1 since 0.3.0 and is not true at all
|
|
44
|
+
now. Both describe the strided view and the copy. THEORY §9.1 and §9.2,
|
|
45
|
+
GUIDE §3, USAGE §5, the three READMEs and ADR 0004 state the integer
|
|
46
|
+
contract.
|
|
47
|
+
|
|
48
|
+
## [0.5.6] - 2026-10-10
|
|
49
|
+
|
|
50
|
+
A fix release. `torch.vmap` works again over `extract`, `reconstruct` and
|
|
51
|
+
`stitch`, which it had not since 0.5.0, and `tilings` returns `[]` for an
|
|
52
|
+
image smaller than the default minimum patch instead of raising. No other
|
|
53
|
+
returned value changes. The full suite passes on both code paths at the
|
|
54
|
+
declared floor (Python 3.12, torch 2.6.0, numpy 1.26.0, pillow 10.1.0, Rust
|
|
55
|
+
1.82) and at the newest releases (Python 3.14, torch 2.14.1, numpy 2.5.4,
|
|
56
|
+
pillow 12.3.0), and the 126,736-geometry exactness sweep passes on both paths.
|
|
57
|
+
The rest is documentation, a CI job that now holds the floor to that claim,
|
|
58
|
+
and the CI actions moved to Node 24.
|
|
59
|
+
|
|
60
|
+
### Added
|
|
61
|
+
|
|
62
|
+
- `docs/PAPER/`, the scaffold of an article on computing a mean whose every
|
|
63
|
+
intermediate stays in the input's domain, drawing on PatchCraft's
|
|
64
|
+
patch-grid mean: outline, formal statements with proofs, measurements with
|
|
65
|
+
the scripts that reproduce them, and checked references. It is not
|
|
66
|
+
documentation of the library and does not ship: `MANIFEST.in` prunes it and
|
|
67
|
+
`tools/check_dist.py` fails a release that carries it.
|
|
68
|
+
- **CI tests the declared floor, not only the newest versions.** A new job
|
|
69
|
+
builds the extension with Rust 1.82 and runs the whole suite on both code
|
|
70
|
+
paths at Python 3.12, torch 2.6.0, numpy 1.26.0 and pillow 10.1.0, the
|
|
71
|
+
oldest versions `pyproject.toml` and `Cargo.toml` accept that install on
|
|
72
|
+
3.12; until now the floor was a claim no job exercised. The lock moves to
|
|
73
|
+
the newest runtime releases (torch 2.14.1, torchvision 0.29.1, numpy
|
|
74
|
+
2.5.4), so the other jobs test the top of the range; the development tools
|
|
75
|
+
stay pinned. The first run at the floor found one claim that held only at
|
|
76
|
+
the top: `extract` on an integer tensor raises torch's own error, which is a
|
|
77
|
+
`RuntimeError` at torch 2.6 and its subclass `NotImplementedError` in newer
|
|
78
|
+
releases, while the test, THEORY §9.1 and the PyPI page named the subclass.
|
|
79
|
+
They now name what holds across the range.
|
|
80
|
+
- ADR 0005 is recorded in the ROADMAP as the 1.1 candidate, not scheduled.
|
|
81
|
+
- ADR 0005 (`Proposed`) records how a grid that does not cover the image
|
|
82
|
+
would be padded, borrowed rather than designed: kornia's auto-padding on the
|
|
83
|
+
way in and unpadding on the way back, MONAI's `padding_mode` with `F.pad`'s
|
|
84
|
+
values, the padding split between both sides, and `None` as the default
|
|
85
|
+
that changes no call. Nothing is implemented. The survey and the
|
|
86
|
+
measurements behind it are in `docs/STUDIES/2026-10-10-border-policy.md`,
|
|
87
|
+
reproduced by `tools/compare_border_strategies.py`.
|
|
88
|
+
- **THEORY §2.6 states the operator behind `reconstruct` and `stitch`.**
|
|
89
|
+
`extract` is a selection matrix `U`, the fold is `Uᵀ`, `UᵀU` is the count
|
|
90
|
+
map, `reconstruct` is the least-squares inverse `(UᵀU)⁻¹Uᵀ` and `stitch` the
|
|
91
|
+
window-weighted one, the same weighted overlap-add that inverts an STFT.
|
|
92
|
+
`tests/test_operator.py` pins each statement on both code paths. GUIDE
|
|
93
|
+
gains three recipes built on it, run against 0.5.5: what averaging does to a
|
|
94
|
+
per-patch quantization (§2), a filter applied per tile with a halo, which
|
|
95
|
+
comes back bit-identical to the whole-image filter (§5), and the padded
|
|
96
|
+
round trip for a frame no tiling covers (§6). Section 6 used to rule
|
|
97
|
+
padding out because it puts pixels the frame never had into the edge
|
|
98
|
+
patches; it now offers it with that cost stated.
|
|
99
|
+
|
|
100
|
+
### Fixed
|
|
101
|
+
|
|
102
|
+
- **`torch.vmap` works again over `extract`, `reconstruct` and `stitch`.** The
|
|
103
|
+
documentation names vmap as one of the three ways to batch (README, SCOPE,
|
|
104
|
+
USAGE, THEORY §0 and §9.1), and since 0.5.0 it failed on most calls: `extract`
|
|
105
|
+
at every geometry without dilation and `reconstruct` at `stride ==
|
|
106
|
+
patch_size` asked the tensor for its storage to guard against aliasing, which
|
|
107
|
+
a vmap batched tensor does not have, and with the accelerator every
|
|
108
|
+
overlapping `reconstruct` and `stitch` failed reading a data pointer. Both
|
|
109
|
+
guards are now an unconditional clone, which is the one copy those paths
|
|
110
|
+
always made, and the accelerator treats a tensor without storage as
|
|
111
|
+
ineligible and hands it to the torch path, as its docstring already said.
|
|
112
|
+
Bit-identical to 0.5.5 on ordinary calls in 144 comparisons per path,
|
|
113
|
+
including the degenerate geometries the guard existed for, with no output
|
|
114
|
+
sharing memory with its input and no change in time. `tests/test_vmap.py`
|
|
115
|
+
pins vmap to a plain loop bit for bit, on both paths and in four dtypes; 70
|
|
116
|
+
of its 114 cases fail against 0.5.5. The test helper `bit_equal` gained
|
|
117
|
+
bfloat16.
|
|
118
|
+
- **`tilings` returns `[]` for an image smaller than the default minimum
|
|
119
|
+
patch.** A one-row image raised `min_patch_size (2) > max_patch_size (1)`,
|
|
120
|
+
blaming a bound the caller never passed, while THEORY §9.6 accepts any
|
|
121
|
+
positive shape and an explicit bound that excludes every patch already
|
|
122
|
+
returned `[]`. An explicit `max_patch_size` below `min_patch_size` still
|
|
123
|
+
raises. The docstring no longer promises at least one entry, which was false
|
|
124
|
+
for both cases.
|
|
125
|
+
|
|
126
|
+
### Changed
|
|
127
|
+
|
|
128
|
+
- **CI actions moved off Node 20**, which GitHub has deprecated and was
|
|
129
|
+
forcing onto Node 24: `actions/checkout` v5, `astral-sh/setup-uv` v7,
|
|
130
|
+
`actions/upload-artifact` v6, `actions/download-artifact` v7 and
|
|
131
|
+
`softprops/action-gh-release` v3, each the first major on Node 24 rather
|
|
132
|
+
than the newest. Every input the workflows pass exists in those versions.
|
|
133
|
+
- **USAGE walks a 4×4 image through every call.** Its sections now show
|
|
134
|
+
the pixels, not only the shapes: the patches `extract` returns and their
|
|
135
|
+
order, where each patch lands in `reconstruct`, the per-pixel mean when
|
|
136
|
+
patches disagree, the step `uniform` leaves and the ramp `hann` and
|
|
137
|
+
`gaussian` make, an LR patch beside its HR patch in `pair`, a resize down
|
|
138
|
+
and back up, and the one patch `per_patch_mse` singles out. Every example
|
|
139
|
+
runs as a doctest. GUIDE §1 shows the uncovered band on a 5×5 count map, and
|
|
140
|
+
GUIDE §4 shows the count map beside the pixels that come back different.
|
|
141
|
+
- GUIDE §4 still gave the old reason for the exactness rule, that division by
|
|
142
|
+
a power of two is exact; it now gives the corrected one, as the other pages
|
|
143
|
+
do since the entry below.
|
|
144
|
+
- **The stated reason for the exactness rule is corrected.** The pages said the
|
|
145
|
+
round trip is exact because dividing a float by a power of two never rounds.
|
|
146
|
+
That leaves out the sum: k equal values added one at a time and divided by k
|
|
147
|
+
come back exact for k = 2 and 4, and for k = 8 they do not, since 3x, 5x, 6x
|
|
148
|
+
and 7x round (3,914,684 of the 8,388,608 float32 values in [1, 2) fail,
|
|
149
|
+
`python docs/PAPER/experiments/float_mean.py`). The rule itself stands,
|
|
150
|
+
because a coverage of three on either axis puts a 3 in the map, so over all
|
|
151
|
+
126,736 legal geometries a map of powers of two holds only 1, 2 and 4. The
|
|
152
|
+
READMEs, THEORY §1, the `reconstruct` docstring, ADR 0003 (with a dated
|
|
153
|
+
correction) and the outreach sources now give that reason.
|
|
154
|
+
- Exact inside the rule means equal as values, which is what `torch.equal`
|
|
155
|
+
checks, and THEORY and the `reconstruct` docstring now say so: the fold sums
|
|
156
|
+
into `+0.0`, so on an overlapping grid a `-0.0` comes back `+0.0` and a NaN
|
|
157
|
+
may change its bits. Both paths do the same, and a test now pins it, which
|
|
158
|
+
`torch.equal` could not.
|
|
159
|
+
- THEORY §9.2 said promotion cut the bfloat16 error from 9.3e-03 to 1.9e-03.
|
|
160
|
+
Those two numbers measured the input's cast from float32, not the library:
|
|
161
|
+
on data drawn in bfloat16 the native fold misses by 7.8e-03 and the promoted
|
|
162
|
+
round trip returns the input bit for bit, at counts 3, 6 and 9. THEORY,
|
|
163
|
+
GUIDE and the code comment now say so, and a test pins it.
|
|
164
|
+
- THEORY §2 and §2.5 and USAGE §5 described the `reconstruct` and `stitch`
|
|
165
|
+
implementation from before 0.5.0 (a second fold of ones, a floor on every
|
|
166
|
+
denominator). They now describe the closed-form count map, the separable
|
|
167
|
+
window denominator and the non-overlapping rearrangement. USAGE §5 checks
|
|
168
|
+
the overlapping round trip with `torch.equal` instead of `torch.allclose`.
|
|
169
|
+
- THEORY §9.6 said `tilings` returns a sorted list. Its order is patch size
|
|
170
|
+
ascending with the exact tile first, which `sorted()` does not give, and a
|
|
171
|
+
test now pins it.
|
|
172
|
+
- The accelerator parity tests compare bits instead of `torch.equal`, and
|
|
173
|
+
include `stride == patch_size`, where hann and gaussian still fold now that
|
|
174
|
+
uniform takes the fast path.
|
|
175
|
+
- Errata for 0.5.5, whose entry says it "changes no bit": that holds for
|
|
176
|
+
returned values in eager mode. Under the uniform window at `stride ==
|
|
177
|
+
patch_size`, derivatives pass a `-0.0` or a signaling NaN through unchanged
|
|
178
|
+
and `torch.compile` folds the `+ 0.0` away; the code comment now says so.
|
|
179
|
+
- **Documentation that said more than the code does.** GUIDE called hann the
|
|
180
|
+
cheapest window, where on an overlapping grid the three cost the same; GUIDE
|
|
181
|
+
and the PyPI page said `extract` hands every tensor to `F.unfold`, where only
|
|
182
|
+
an integer one goes there; PERFORMANCE said 25 runs where the benchmark
|
|
183
|
+
takes 15; GUIDE's two bound figures now read as the measured misses inside
|
|
184
|
+
the `(k+1)·eps·|v|` bound, with the shape of the fp16 example stated.
|
|
185
|
+
FOCO-1.0 freezes values rather than bits, the bound rather than "1 ULP",
|
|
186
|
+
and 20 names. CONTRIBUTING and AGENTS name the three version strings a
|
|
187
|
+
release bumps, STATUS among them. The CITATION abstract no longer carries
|
|
188
|
+
development history, and the BibTeX author reads `Marques de Souza`.
|
|
189
|
+
- `tools/compare_exactness_rules.py` also reports the worst miss outside the
|
|
190
|
+
rule: 19.0 ULP in float32, at `(h, w, ph, pw, sh, sw) = (18, 23, 9, 9, 1,
|
|
191
|
+
1)`, on a pixel covered 81 times. CI's ruff step checks `tools/` as well.
|
|
192
|
+
- Outreach: a news source dated 2026-10-10 carries the 0.5.5 table, and the
|
|
193
|
+
2026-09-04 one stays as the record of 0.5.4. The articles follow it, scope
|
|
194
|
+
the 19 ULP figure to the geometry that produced it, and say where each path
|
|
195
|
+
was executed instead of calling the GPU path correct. The post no longer
|
|
196
|
+
says `extract` validates the geometry, the MNIST page states when each of
|
|
197
|
+
the three problems appears, and every reproduction block clones the
|
|
198
|
+
repository, since the wheel carries neither `tools/` nor `tests/`.
|
|
199
|
+
|
|
9
200
|
## [0.5.5] - 2026-09-26
|
|
10
201
|
|
|
11
202
|
A performance release that changes no bit. Every change to `src/` below was
|
|
@@ -1016,7 +1207,9 @@ First public release. Public API stable; signatures will only change in 1.x.
|
|
|
1016
1207
|
- [`README.md`](README.md) covers installation, the car-vs-track metaphor,
|
|
1017
1208
|
validation lab.
|
|
1018
1209
|
|
|
1019
|
-
[Unreleased]: https://github.com/LeoPR/PatchCraft/compare/v0.5.
|
|
1210
|
+
[Unreleased]: https://github.com/LeoPR/PatchCraft/compare/v0.5.7...HEAD
|
|
1211
|
+
[0.5.7]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.5.7
|
|
1212
|
+
[0.5.6]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.5.6
|
|
1020
1213
|
[0.5.5]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.5.5
|
|
1021
1214
|
[0.5.4]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.5.4
|
|
1022
1215
|
[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.7"
|
|
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.7
|
|
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
|
-
|
|
114
|
+
An 8-bit image goes in as it is. `extract` copies, so its patches hold the image's own bytes, and `reconstruct` returns them exactly in every legal geometry, since `k` copies of `v` sum to `k·v` and divide back to `v`; patches that disagree come back as their exact mean rounded half to even. `stitch` stays float-only, because its windows are float weights.
|
|
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,9 +162,9 @@ 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 of a float image 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
|
-
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.
|
|
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. An integer image comes back exact in every legal geometry, because integer sums do not round.
|
|
168
168
|
|
|
169
169
|
The everyday shorthand is that `stride == patch_size` and `stride == patch_size / 2` always satisfy the rule. Both are sufficient conditions rather than necessary ones, so a geometry outside them can still be exact, and the [guide](https://github.com/LeoPR/PatchCraft/blob/main/docs/GUIDE.md#4-when-the-round-trip-is-bit-for-bit) carries the sweep that measures it.
|
|
170
170
|
|
|
@@ -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)
|
|
@@ -83,7 +83,7 @@ The two functions answer two different questions, so choosing between them is th
|
|
|
83
83
|
|
|
84
84
|
| You want | Call | Because |
|
|
85
85
|
|---|---|---|
|
|
86
|
-
| The patches back as an image, untouched | `reconstruct` | Exact inverse of `extract
|
|
86
|
+
| The patches back as an image, untouched | `reconstruct` | Exact inverse of `extract`: always for an integer image, and for a float image when every overlap count is a power of two, which is always true at `stride == patch_size` |
|
|
87
87
|
| A model's output back as an image | `stitch` | Overlapping patches disagree, and the window hides the grid |
|
|
88
88
|
|
|
89
89
|
## Why not unfold and fold directly
|
|
@@ -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 of a float image 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: among floats the axis that decides the answer is the geometry, and [the guide](docs/GUIDE.md#4-when-the-round-trip-is-bit-for-bit) has the sweep that measures it. An integer image comes back exact in every legal geometry, because integer sums do not round.
|
|
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) |
|
|
@@ -83,7 +83,7 @@ O `stitch` é para patches que um modelo reescreveu. Nesse caso os patches vizin
|
|
|
83
83
|
|
|
84
84
|
| Você quer | Chame | Porque |
|
|
85
85
|
|---|---|---|
|
|
86
|
-
| Os patches de volta como imagem, intocados | `reconstruct` | Inverso exato do `extract
|
|
86
|
+
| Os patches de volta como imagem, intocados | `reconstruct` | Inverso exato do `extract`: sempre para uma imagem inteira, e para uma imagem float quando toda contagem de sobreposição é potência de dois, o que sempre vale com `stride == patch_size` |
|
|
87
87
|
| A saída de um modelo de volta como imagem | `stitch` | Patches sobrepostos discordam, e a janela esconde a grade |
|
|
88
88
|
|
|
89
89
|
## Por que não usar `unfold` e `fold` direto
|
|
@@ -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 de uma imagem float é 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: entre os floats, quem decide a resposta é a geometria, e [o manual](docs/GUIDE.md#4-when-the-round-trip-is-bit-for-bit) traz a varredura que mede isso. Uma imagem inteira volta exata em toda geometria legal, porque soma de inteiros não arredonda.
|
|
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
|
-
|
|
72
|
+
An 8-bit image goes in as it is. `extract` copies, so its patches hold the image's own bytes, and `reconstruct` returns them exactly in every legal geometry, since `k` copies of `v` sum to `k·v` and divide back to `v`; patches that disagree come back as their exact mean rounded half to even. `stitch` stays float-only, because its windows are float weights.
|
|
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,9 +120,9 @@ 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 of a float image 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
|
-
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.
|
|
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. An integer image comes back exact in every legal geometry, because integer sums do not round.
|
|
126
126
|
|
|
127
127
|
The everyday shorthand is that `stride == patch_size` and `stride == patch_size / 2` always satisfy the rule. Both are sufficient conditions rather than necessary ones, so a geometry outside them can still be exact, and the [guide](https://github.com/LeoPR/PatchCraft/blob/main/docs/GUIDE.md#4-when-the-round-trip-is-bit-for-bit) carries the sweep that measures it.
|
|
128
128
|
|
|
@@ -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)
|