patchcraft 0.5.3__tar.gz → 0.5.5__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.3 → patchcraft-0.5.5}/AGENTS.md +24 -1
- {patchcraft-0.5.3 → patchcraft-0.5.5}/CHANGELOG.md +248 -1
- {patchcraft-0.5.3 → patchcraft-0.5.5}/CITATION.cff +2 -2
- {patchcraft-0.5.3 → patchcraft-0.5.5}/CONTRIBUTING.md +19 -13
- {patchcraft-0.5.3 → patchcraft-0.5.5}/MAP.md +10 -8
- {patchcraft-0.5.3/src/patchcraft.egg-info → patchcraft-0.5.5}/PKG-INFO +6 -6
- {patchcraft-0.5.3 → patchcraft-0.5.5}/README.md +8 -8
- {patchcraft-0.5.3 → patchcraft-0.5.5}/README.pt-BR.md +6 -6
- {patchcraft-0.5.3 → patchcraft-0.5.5}/README.pypi.md +5 -5
- {patchcraft-0.5.3 → patchcraft-0.5.5}/SECURITY.md +13 -6
- patchcraft-0.5.5/STATUS.md +99 -0
- patchcraft-0.5.5/docs/ADR/0003-reversibility-classes.md +139 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/docs/ADR/0004-precision-and-effort-parameters.md +61 -78
- {patchcraft-0.5.3 → patchcraft-0.5.5}/docs/FOCO-1.0.md +1 -1
- {patchcraft-0.5.3 → patchcraft-0.5.5}/docs/GUIDE.md +30 -38
- {patchcraft-0.5.3 → patchcraft-0.5.5}/docs/PERFORMANCE.md +16 -11
- patchcraft-0.5.5/docs/STUDIES/2026-09-03-precision-measurements.md +98 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/docs/THEORY.md +3 -2
- {patchcraft-0.5.3 → patchcraft-0.5.5}/docs/USAGE.md +6 -8
- {patchcraft-0.5.3 → patchcraft-0.5.5}/pyproject.toml +5 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/src/patchcraft/_version.py +3 -3
- {patchcraft-0.5.3 → patchcraft-0.5.5}/src/patchcraft/cache.py +5 -1
- {patchcraft-0.5.3 → patchcraft-0.5.5}/src/patchcraft/geometry.py +38 -19
- {patchcraft-0.5.3 → patchcraft-0.5.5}/src/patchcraft/metrics.py +5 -3
- {patchcraft-0.5.3 → patchcraft-0.5.5}/src/patchcraft/reconstruct.py +28 -12
- {patchcraft-0.5.3 → patchcraft-0.5.5}/src/patchcraft/resize.py +33 -3
- {patchcraft-0.5.3 → patchcraft-0.5.5}/src/patchcraft/stitch.py +34 -11
- {patchcraft-0.5.3 → patchcraft-0.5.5/src/patchcraft.egg-info}/PKG-INFO +6 -6
- {patchcraft-0.5.3 → patchcraft-0.5.5}/src/patchcraft.egg-info/SOURCES.txt +4 -1
- {patchcraft-0.5.3 → patchcraft-0.5.5}/src/patchcraft.egg-info/scm_file_list.json +29 -0
- patchcraft-0.5.5/src/patchcraft.egg-info/scm_version.json +8 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/tests/test_cache.py +29 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/tests/test_resize.py +73 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/tests/test_stitch.py +14 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/tools/benchmark.py +5 -3
- patchcraft-0.5.5/tools/compare_exactness_rules.py +77 -0
- patchcraft-0.5.5/tools/make_outreach_figures.py +970 -0
- patchcraft-0.5.3/STATUS.md +0 -85
- patchcraft-0.5.3/docs/ADR/0003-reversibility-classes.md +0 -108
- patchcraft-0.5.3/src/patchcraft.egg-info/scm_version.json +0 -8
- {patchcraft-0.5.3 → patchcraft-0.5.5}/.gitignore +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/.pre-commit-config.yaml +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/.python-version +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/CODE_OF_CONDUCT.md +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/LICENSE +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/MANIFEST.in +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/accel/Cargo.lock +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/accel/Cargo.toml +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/accel/README.md +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/accel/src/kernel.rs +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/accel/src/lib.rs +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/docs/ADR/0001-patch-extraction-api.md +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/docs/ADR/0002-patchify-transform.md +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/docs/AUXILIARY.md +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/docs/ROADMAP.md +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/docs/SCOPE.md +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/docs/STUDIES/2026-08-04-patch-techniques.md +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/docs/STUDIES/2026-09-03-strata-conformity.md +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/docs/design/2026-09-01-fase1-bugs-performance.md +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/docs/design/2026-09-01-fase2-acelerador.md +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/docs/design/2026-09-01-fase3-predicado.md +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/setup.cfg +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/setup.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/src/patchcraft/__init__.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/src/patchcraft/_accel.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/src/patchcraft/_foldgeom.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/src/patchcraft/extract.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/src/patchcraft/pair.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/src/patchcraft/py.typed +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/src/patchcraft.egg-info/dependency_links.txt +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/src/patchcraft.egg-info/requires.txt +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/src/patchcraft.egg-info/top_level.txt +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/tests/__init__.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/tests/_datasets.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/tests/_rng.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/tests/conftest.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/tests/test_accel.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/tests/test_citation.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/tests/test_datasets_helper.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/tests/test_docs_usage.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/tests/test_exactness.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/tests/test_extract.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/tests/test_geometry.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/tests/test_import.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/tests/test_metrics.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/tests/test_pair.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/tests/test_public_api.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/tests/test_reconstruct.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/tests/test_reference.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/tests/test_rng.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/tools/check_dist.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.5}/uv.lock +0 -0
|
@@ -89,7 +89,30 @@ literal directory of that name on Linux.
|
|
|
89
89
|
`tools/check_dist.py` holds the matching allowlist. Adding a top-level
|
|
90
90
|
directory means updating both. The gate will tell you; do not silence it.
|
|
91
91
|
|
|
92
|
-
### I10.
|
|
92
|
+
### I10. An ADR carries the choice, not the research behind it
|
|
93
|
+
|
|
94
|
+
Before writing one, split what is being said into what is **determined** and
|
|
95
|
+
what is **chosen**, and put only the second in the ADR.
|
|
96
|
+
|
|
97
|
+
A name is determined: which identifier a parameter carries is a fact about the
|
|
98
|
+
ecosystem the caller already imports, and picking one by taste is a guess made
|
|
99
|
+
permanent by a frozen surface. A measurement is determined. A benchmark table is
|
|
100
|
+
determined. Those belong in `docs/STUDIES/`, dated, with the ADR linking to
|
|
101
|
+
them.
|
|
102
|
+
|
|
103
|
+
What is chosen is whether to do the thing at all, when, and what it costs the
|
|
104
|
+
surface. If that part comes out empty, it was never an ADR: it was a finding,
|
|
105
|
+
and a study is where it goes.
|
|
106
|
+
|
|
107
|
+
Both ADRs written in 2026-09 failed this on the first draft, in the same way
|
|
108
|
+
and for the same reason, which is that writing down everything you learned feels
|
|
109
|
+
like thoroughness and reads as a lab notebook. ADR 0003 mixed a rule with a
|
|
110
|
+
vocabulary for six transforms that never shipped; ADR 0004 mixed the names,
|
|
111
|
+
which precedent decides, with whether to spend a frozen surface on them, which
|
|
112
|
+
only the owner decides. Both came out clearer once separated, and both were
|
|
113
|
+
caught by the owner asking what the document was for.
|
|
114
|
+
|
|
115
|
+
### I11. Do not invent what already exists
|
|
93
116
|
|
|
94
117
|
Check for an official or established solution before writing one. The
|
|
95
118
|
accelerator's packaging, the versioning rule, the citation format and the
|
|
@@ -6,6 +6,251 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.5.5] - 2026-09-26
|
|
10
|
+
|
|
11
|
+
A performance release that changes no bit. Every change to `src/` below was
|
|
12
|
+
compared against the 0.5.4 code through integer views, so that `-0.0` and
|
|
13
|
+
`+0.0` or two NaN payloads count as different: 53,716 cases over 1,382
|
|
14
|
+
geometries, four float dtypes, both code paths and gradients, with 0
|
|
15
|
+
differences. The full 126,736-geometry exactness sweep passes on both paths.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- **`stitch` builds its window denominator without a Python loop.** The
|
|
20
|
+
1-D window fold ran one iteration per stride residue, six tensor operations
|
|
21
|
+
each, and that loop dominated `stitch` at every size once the numerator was
|
|
22
|
+
accelerated. All residues are now one cumsum over a `(m, step)` view of the
|
|
23
|
+
zero-padded kernel, which scans each residue in the same order, so the
|
|
24
|
+
output is bit-identical: 0 differences against the previous code over
|
|
25
|
+
38,696 cases, covering three windows, four float dtypes, both code paths and
|
|
26
|
+
gradients. Accelerated `stitch` is 2.7x to 5.1x faster at 512x512 and 1.2x
|
|
27
|
+
to 1.4x at 2048x2048; on the pure path `F.fold` still dominates at 2048.
|
|
28
|
+
|
|
29
|
+
- **`reconstruct` builds its count map with fewer operations, and both
|
|
30
|
+
`reconstruct` and `stitch` divide in place.** The suffix ramp of the
|
|
31
|
+
closed-form count map is the prefix ramp reversed, a square grid reuses the
|
|
32
|
+
H axis for W, and the outer product is taken in the accumulation dtype,
|
|
33
|
+
where multiplying two small exact integers rounds their exact product once,
|
|
34
|
+
as casting the int64 product did. The numerator and the denominator are
|
|
35
|
+
fresh tensors each function owns, so the final clamp and division no longer
|
|
36
|
+
allocate. Bit-identical: 0 differences over 13,820 `reconstruct` and 38,696
|
|
37
|
+
`stitch` cases, and the caller's patches are never mutated or aliased.
|
|
38
|
+
Accelerated `reconstruct` is 1.15x to 1.26x faster, and one call at
|
|
39
|
+
3x2048x2048 with patch 64 and stride 32 allocates 64 MiB instead of 177
|
|
40
|
+
(`stitch` hann: 65 instead of 128).
|
|
41
|
+
|
|
42
|
+
- **`tilings(allow_overlap=True)` visits only the strides that can work.** A
|
|
43
|
+
stride covers both axes exactly when it divides `h - p` and `w - p`, that
|
|
44
|
+
is when it divides their gcd, so the loop runs over the divisors of the gcd
|
|
45
|
+
instead of over every stride below the patch size. The output is the same
|
|
46
|
+
list in the same order: identical in 9,530 cases, every shape from 1x1 to
|
|
47
|
+
69x69 in both modes plus larger ones, and `paired_tilings`, which builds on
|
|
48
|
+
it. 22.8 to 7.3 ms at 512x512, 91 to 9 ms at 1080x1920, 699 to 17 ms at
|
|
49
|
+
4000x3000.
|
|
50
|
+
|
|
51
|
+
- **`stitch` with the uniform window skips the fold on non-overlapping
|
|
52
|
+
grids.** There the fold adds each value, times 1, into a zero-filled buffer
|
|
53
|
+
and divides by a count of 1, which is `0.0 + x`: the value itself, except
|
|
54
|
+
that `-0.0` comes out as `+0.0` and a NaN comes out quiet. The new path
|
|
55
|
+
rearranges the patches and adds `0.0`, which reproduces exactly that; a bare
|
|
56
|
+
rearrangement would have kept `-0.0`, a difference `torch.equal` cannot see
|
|
57
|
+
and an integer-view comparison can. Bit-identical in the full comparison
|
|
58
|
+
and in a dedicated check of zeros of both signs, NaN payloads, infinities,
|
|
59
|
+
extremes and subnormals, and pinned by a new test against `F.fold` itself.
|
|
60
|
+
3.4x to 58x faster depending on size: 146 to 9.4 ms on the pure path at
|
|
61
|
+
3x2048x2048 with patch 32.
|
|
62
|
+
|
|
63
|
+
- docs/PERFORMANCE.md is re-measured on the code that ships, twice, agreeing
|
|
64
|
+
within about 10%. The accelerator's range over the published table is now
|
|
65
|
+
5.8x to 16.5x, against 2.6x to 14x at 0.5.1; the low end moved most, since
|
|
66
|
+
accelerated `stitch` at 512 went from 6.3 to 3.0 ms. The thread sweep behind
|
|
67
|
+
"the gain is algorithmic" was re-run as well: the pure path at 2048 takes
|
|
68
|
+
448 ms at 4 threads and 390 at 36, no better than at 8.
|
|
69
|
+
|
|
70
|
+
- **The manual, the usage page and the three READMEs no longer carry
|
|
71
|
+
development history.** Sentences about what an earlier version did, what a
|
|
72
|
+
first audit found or what used to be printed moved out; where an old
|
|
73
|
+
measurement is the argument for the current rule it stays, in the present
|
|
74
|
+
tense. GUIDE §8 is now "Where this applies today", and each of its items says
|
|
75
|
+
what a reader should do (on GPU it works and does not accelerate; on macOS and
|
|
76
|
+
aarch64, compare once against `PATCHCRAFT_ACCEL=0`), instead of listing what
|
|
77
|
+
the project has not done. The old anchor is updated everywhere it was linked.
|
|
78
|
+
|
|
79
|
+
- The README tagline read "Encode one image into patches, decode it back",
|
|
80
|
+
which invited the compression reading the page then had to head off. It now
|
|
81
|
+
says "Cut one image into patches, and put it back together", followed by one
|
|
82
|
+
sentence on what a patch is for.
|
|
83
|
+
|
|
84
|
+
- GUIDE's hann figure against the model's own patches read 27.14 dB and prints
|
|
85
|
+
27.15 dB on both code paths; the page now quotes what the code prints.
|
|
86
|
+
|
|
87
|
+
- `tools/make_outreach_figures.py` lost 301 unreachable lines left from
|
|
88
|
+
earlier layouts, with every generated file byte-identical before the later
|
|
89
|
+
edits. Every caption that states a result is now checked against it before
|
|
90
|
+
it is drawn, including the refusal at stride 20 and the exact round trip at
|
|
91
|
+
32 and 16. The MNIST grid is drawn at display scale instead of into the
|
|
92
|
+
digit's pixels, where it hid more than a third of a 28x28 image, and the
|
|
93
|
+
patch shown alone is outlined in it.
|
|
94
|
+
|
|
95
|
+
### Added
|
|
96
|
+
|
|
97
|
+
- `tools/compare_exactness_rules.py` measures the exactness rule against the
|
|
98
|
+
obvious looser one, keeping the largest coverage count at 4 or below, over
|
|
99
|
+
the 126,736 geometries `tests/test_exactness.py` enumerates, with the round
|
|
100
|
+
trip on five seeds as ground truth. The power-of-two rule makes no false
|
|
101
|
+
promise and withholds none; `k_max <= 4` promises exactness on 13,870
|
|
102
|
+
geometries that do not come back exact. The outreach article quoted an
|
|
103
|
+
earlier sweep of 14,969 rectangular geometries from a lab script that is not
|
|
104
|
+
in the tree, so no command reproduced it; it now cites this one.
|
|
105
|
+
|
|
106
|
+
### Fixed
|
|
107
|
+
|
|
108
|
+
- docs/PERFORMANCE.md and `tools/benchmark.py` said non-overlapping geometries
|
|
109
|
+
never reach the fold. At 0.5.4 that held for `reconstruct`, which has a
|
|
110
|
+
closed-form path at `stride == patch_size`, and not for `stitch`, which
|
|
111
|
+
folded at every geometry: about 144 ms on the pure path at 3x2048x2048 with
|
|
112
|
+
patch 32, where `reconstruct` returned the same bits in about 6 ms. This
|
|
113
|
+
release gives `stitch` that path under the uniform window, and both texts
|
|
114
|
+
now say which windows still fold and why.
|
|
115
|
+
|
|
116
|
+
- `metrics.py` claimed no allocation beyond the difference and, in a comment,
|
|
117
|
+
that the in-place subtract avoids a float64 copy of `b`. Measured with the
|
|
118
|
+
torch profiler, `a.to(float64).sub_(b)` with `b` in float32 allocates a
|
|
119
|
+
float64 temporary of `b` first, doubling the largest allocation from 188 to
|
|
120
|
+
375 MiB on 94 MiB of input. The comment and the module docstring now say
|
|
121
|
+
what happens; the code is unchanged.
|
|
122
|
+
|
|
123
|
+
- CONTRIBUTING placed the `WeightKind` paragraph between "what each digit is
|
|
124
|
+
for here:" and the table the colon announced, and its errata repeated two
|
|
125
|
+
worked examples word for word. MAP and CONTRIBUTING counted five issue forms
|
|
126
|
+
where there are four, called all-closed 1.0 blockers "still in the way", and
|
|
127
|
+
listed neither generator in `tools/`. STATUS still called the `Cache` fix
|
|
128
|
+
unreleased after 0.5.4 shipped it.
|
|
129
|
+
|
|
130
|
+
## [0.5.4] - 2026-09-04
|
|
131
|
+
|
|
132
|
+
### Fixed
|
|
133
|
+
|
|
134
|
+
- **13 of the 25 resample-mode x integer-dtype combinations raised a raw torch
|
|
135
|
+
error** instead of resizing. THEORY §9.4 accepts any integer tensor on the
|
|
136
|
+
`torch` backend and promises interpolation in float with round + clamp on the
|
|
137
|
+
way back, unconditionally. `_resize_torch` guarded the promotion with
|
|
138
|
+
`mode in {"bilinear", "bicubic"}`, so an integer tensor reached
|
|
139
|
+
`F.interpolate` unpromoted under `nearest`, `area` and `nearest-exact` and
|
|
140
|
+
surfaced `NotImplementedError: "adaptive_avg_pool2d" not implemented for
|
|
141
|
+
'Int'`, naming an internal op the caller never called. The outbound half of
|
|
142
|
+
the same function already did the right thing for every integer dtype with no
|
|
143
|
+
condition on the mode, so only the inbound guard was ever wrong.
|
|
144
|
+
|
|
145
|
+
The suite could not catch it because the two parametrizations that would have
|
|
146
|
+
never crossed: `test_torch_accepts_all_resamples` sweeps all five modes on
|
|
147
|
+
`torch.rand`, and every integer test used the default mode. The regression is
|
|
148
|
+
that crossing, 50 cases, plus the upsampling direction because `area` is
|
|
149
|
+
`adaptive_avg_pool2d` and behaves differently going up.
|
|
150
|
+
|
|
151
|
+
Fixing it by promoting everything through `float32` would have traded the
|
|
152
|
+
loud error for the silent truncation this library exists to prevent, so the
|
|
153
|
+
promotion width is now derived from the dtype's range: the narrowest float
|
|
154
|
+
that represents it exactly, `float32` for `uint8`, `int8` and `int16` and
|
|
155
|
+
`float64` for `int32`. That mirrors `patchcraft.metrics`, which already
|
|
156
|
+
promotes to `float64` for the same reason. **This also fixes a silent defect
|
|
157
|
+
that predates the one above**: on the bilinear path an `int32` tensor was
|
|
158
|
+
already being promoted through `float32`, so `2**24 + 1` came back as
|
|
159
|
+
`2**24`. Measured on 2026-09-04, on every mode. `int64` remains the one dtype
|
|
160
|
+
no float covers, and §9.4 now says so rather than implying otherwise.
|
|
161
|
+
|
|
162
|
+
- **`Cache("~/cache")` created a directory literally named `~`** in the working
|
|
163
|
+
directory instead of using the home directory. `Path(root)` was used without
|
|
164
|
+
`expanduser`. Every established library expands and then stops: verified that
|
|
165
|
+
`torch.hub.set_dir` calls `expanduser` and validates nothing, that pytest's
|
|
166
|
+
cacheprovider does not validate, and that pip passes `--cache-dir` through.
|
|
167
|
+
`Cache` now does the same one thing they do.
|
|
168
|
+
|
|
169
|
+
The question that found this was whether to reject a `namespace` containing
|
|
170
|
+
`..`. The answer is no, and the reason is the owner's: it is not this
|
|
171
|
+
library's job to invent path security, and no library in the ecosystem does
|
|
172
|
+
it. What it was missing was the one thing they all do. `SECURITY.md` and
|
|
173
|
+
THEORY §9.5 now say that the path is the caller's and that only `~` is
|
|
174
|
+
expanded.
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
### Added
|
|
178
|
+
|
|
179
|
+
- The rule that produced the two rescopings is recorded as invariant I10 in
|
|
180
|
+
`AGENTS.md`: an ADR carries the choice, not the research behind it. Before
|
|
181
|
+
writing one, split what is determined from what is chosen and keep only the
|
|
182
|
+
second; if the chosen part comes out empty it was a finding, and a dated
|
|
183
|
+
study is where it goes. Both ADRs written this month failed it on the first
|
|
184
|
+
draft, in the same way, and both were caught by the owner asking what the
|
|
185
|
+
document was for. `docs/ADR/0001` and `0002` were checked and do not have the
|
|
186
|
+
problem: they are context, decision, consequences and alternatives, at a
|
|
187
|
+
hundred lines each.
|
|
188
|
+
- **ADR 0004 separates what is determined from what is chosen, and what is left
|
|
189
|
+
is nothing.** The owner's point settled the first half: naming is a fact about
|
|
190
|
+
the ecosystem the caller already imports, not a preference of this project,
|
|
191
|
+
and choosing by taste would be a guess made permanent by a frozen surface. So
|
|
192
|
+
the three names are stated as determined by precedent, verified on 2026-09-03
|
|
193
|
+
against the installed libraries: `numpy.sum` and `torch.sum` take `dtype=`,
|
|
194
|
+
`torch.div` takes `rounding_mode=` and documents exactly `None`, `"trunc"` and
|
|
195
|
+
`"floor"`, both `interpolate` and torchvision's `resize` take `antialias=`,
|
|
196
|
+
and `numpy.ufunc.accumulate` exists, which is the collision that rules out
|
|
197
|
+
`accumulate=`.
|
|
198
|
+
|
|
199
|
+
That narrowing left one apparent question, whether to add the knobs before the
|
|
200
|
+
1.0 freeze or after, on the stated ground that afterwards each costs a minor
|
|
201
|
+
release. Applying I10 to it dissolved it, because the premise is false: every
|
|
202
|
+
one of these is a keyword-only argument with a default, so adding one is
|
|
203
|
+
additive and can never be a major, and `reconstruct` has no positional-only
|
|
204
|
+
parameters to make the order matter. Nothing about adding one later is harder
|
|
205
|
+
than adding one now. With the premise gone, YAGNI, the absence of any external
|
|
206
|
+
consumer and Strata §9's default verdict all point the same way, and the ADR's
|
|
207
|
+
own table had already marked every knob "after 1.0" before the question was
|
|
208
|
+
written on top of it. The document now records a design that is not built, and
|
|
209
|
+
stays `Proposed` because none of it is implemented rather than because
|
|
210
|
+
anything is pending.
|
|
211
|
+
- `docs/STUDIES/2026-09-03-precision-measurements.md`. ADR 0004 was carrying
|
|
212
|
+
its own evidence, which made a 227-line document that read as a lab notebook
|
|
213
|
+
where a decision was wanted. The measurements moved here, the ADR kept the
|
|
214
|
+
decision, the names and the order, and it is 176 lines. Each measurement now
|
|
215
|
+
says what it settled, so a number can be checked without reading the argument
|
|
216
|
+
that used it.
|
|
217
|
+
|
|
218
|
+
### Changed
|
|
219
|
+
|
|
220
|
+
- **ADR 0004 step 0 is done**, which the ADR names as the only item that must
|
|
221
|
+
land before 1.0: the `reconstruct` docstring's "widening the dtype does not
|
|
222
|
+
help" now says *input* dtype, and that widening the accumulator does help,
|
|
223
|
+
with the measurement. It was the one live site; earlier counts of seven
|
|
224
|
+
included historical and study files that are not edited.
|
|
225
|
+
- **`WeightKind` is declared an open set** in the GUIDE symbol table and in
|
|
226
|
+
CONTRIBUTING's versioning policy. The choice was made and pinned by test long
|
|
227
|
+
ago; what did not exist was the sentence a consumer reads before writing an
|
|
228
|
+
exhaustive `match`.
|
|
229
|
+
- Four one-line corrections found by the 2026-09-04 audit: FOCO §1 counted
|
|
230
|
+
"four things" over a list of five; MAP and CONTRIBUTING said ten invariants
|
|
231
|
+
after I10 made it eleven; AGENTS.md claimed both rescoped ADRs "shrank by a
|
|
232
|
+
third" when ADR 0003 grew; and `tilings`' docstring promised an ordering by
|
|
233
|
+
`(patch_size, stride)` that the function does not produce. The docstring now
|
|
234
|
+
states the measured order (patch size ascending, exact tile first within each)
|
|
235
|
+
rather than the function changing, because nothing depends on the order and
|
|
236
|
+
a change would be behaviour.
|
|
237
|
+
- **ADR 0003 is accepted, after being rescoped to what it actually governs.**
|
|
238
|
+
Its first draft decided two things at once: a rule about how an exactness
|
|
239
|
+
claim is written, and a three-letter vocabulary plus naming policy for six
|
|
240
|
+
transforms that were under consideration. A month later the rule was
|
|
241
|
+
governing the code and the vocabulary had been used nowhere: `R1`, `R2` and
|
|
242
|
+
`R3` appear in no docstring, no contract section and no page a reader opens,
|
|
243
|
+
and none of rotation, flips, padding, DCT, quantization or dithering shipped.
|
|
244
|
+
What is now accepted is the rule, that an exactness claim is declared per
|
|
245
|
+
regime with a condition the caller can evaluate before calling, with the four
|
|
246
|
+
measured regime axes in its body including the accumulator amendment from
|
|
247
|
+
ADR 0004. The vocabulary is deferred until the first transform that needs it.
|
|
248
|
+
The document also states plainly what its title had obscured: inexact
|
|
249
|
+
operations are not restricted, since `resize` and windowed `stitch` are
|
|
250
|
+
exactly what users come for, and what is forbidden is a docstring implying an
|
|
251
|
+
exactness the operation does not have.
|
|
252
|
+
|
|
253
|
+
|
|
9
254
|
## [0.5.3] - 2026-09-03
|
|
10
255
|
|
|
11
256
|
### Added
|
|
@@ -771,7 +1016,9 @@ First public release. Public API stable; signatures will only change in 1.x.
|
|
|
771
1016
|
- [`README.md`](README.md) covers installation, the car-vs-track metaphor,
|
|
772
1017
|
validation lab.
|
|
773
1018
|
|
|
774
|
-
[Unreleased]: https://github.com/LeoPR/PatchCraft/compare/v0.5.
|
|
1019
|
+
[Unreleased]: https://github.com/LeoPR/PatchCraft/compare/v0.5.5...HEAD
|
|
1020
|
+
[0.5.5]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.5.5
|
|
1021
|
+
[0.5.4]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.5.4
|
|
775
1022
|
[0.5.3]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.5.3
|
|
776
1023
|
[0.5.2]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.5.2
|
|
777
1024
|
[0.5.1]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.5.1
|
|
@@ -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-09-
|
|
9
|
+
version: "0.5.5"
|
|
10
|
+
date-released: "2026-09-26"
|
|
11
11
|
repository-code: "https://github.com/LeoPR/PatchCraft"
|
|
12
12
|
license: MIT
|
|
13
13
|
abstract: >
|
|
@@ -34,7 +34,7 @@ is silently inert here. Either make that global hook delegate, or set
|
|
|
34
34
|
whatever else the global hooks were doing.
|
|
35
35
|
|
|
36
36
|
`ruff-format` is deliberately not among the hooks. This project has never used
|
|
37
|
-
it, and adopting it would reformat
|
|
37
|
+
it, and adopting it would reformat 37 of 82 files in a commit that changed no
|
|
38
38
|
behaviour.
|
|
39
39
|
|
|
40
40
|
```
|
|
@@ -130,13 +130,15 @@ PatchCraft/
|
|
|
130
130
|
├── setup.py the one build decision: with or without the Rust extension
|
|
131
131
|
├── tools/check_dist.py gate: extension present, versions agree, tag respected
|
|
132
132
|
├── tools/benchmark.py accelerated vs pure, and proves they agree first
|
|
133
|
+
├── tools/compare_exactness_rules.py the power-of-two rule against k_max <= 4, measured
|
|
134
|
+
├── tools/make_outreach_figures.py every outreach figure and generated page, computed
|
|
133
135
|
├── MANIFEST.in what the sdist carries
|
|
134
136
|
├── README.md the call page, canonical, English
|
|
135
137
|
├── README.pt-BR.md the same call page in Portuguese
|
|
136
138
|
├── README.pypi.md the PyPI page (long_description), links absolute
|
|
137
139
|
├── CONTRIBUTING.md this file
|
|
138
140
|
├── MAP.md one-page wayfinding: the tree, and a want-to/go-to table
|
|
139
|
-
├── AGENTS.md the canonical rules;
|
|
141
|
+
├── AGENTS.md the canonical rules; eleven invariants, each earned
|
|
140
142
|
├── CLAUDE.md thin pointer to AGENTS.md plus tool-specific notes
|
|
141
143
|
├── STATUS.md the snapshot; what is in flight and what waits on whom
|
|
142
144
|
├── CHANGELOG.md Keep-a-Changelog format
|
|
@@ -148,7 +150,7 @@ PatchCraft/
|
|
|
148
150
|
├── SECURITY.md how to report, and what the real attack surface is
|
|
149
151
|
├── CODE_OF_CONDUCT.md Contributor Covenant 2.1, verbatim
|
|
150
152
|
├── .github/
|
|
151
|
-
│ ├── ISSUE_TEMPLATE/
|
|
153
|
+
│ ├── ISSUE_TEMPLATE/ four forms and a chooser; the required fields are what makes a report reproducible
|
|
152
154
|
│ ├── PULL_REQUEST_TEMPLATE.md asks whether a returned value moved, which decides the version bump
|
|
153
155
|
│ └── workflows/
|
|
154
156
|
│ ├── test.yml matrix CI on PRs/main, plus the accelerated job
|
|
@@ -185,6 +187,9 @@ PatchCraft/
|
|
|
185
187
|
│ ├── test_accel.py native path, skipped unless the extension was built
|
|
186
188
|
│ ├── test_datasets_helper.py label_subset
|
|
187
189
|
│ ├── test_import.py
|
|
190
|
+
│ ├── test_docs_usage.py runs docs/USAGE.md as a doctest
|
|
191
|
+
│ ├── test_citation.py CITATION.cff agrees with the package
|
|
192
|
+
│ ├── conftest.py shared fixtures
|
|
188
193
|
│ ├── _rng.py audited round-trip helpers (data generation, bit equality)
|
|
189
194
|
│ ├── test_rng.py tests for those helpers
|
|
190
195
|
│ └── _datasets.py dev-only fixtures (MNIST, etc), NOT public API
|
|
@@ -199,14 +204,15 @@ PatchCraft/
|
|
|
199
204
|
│ ├── AUXILIARY.md tests/_datasets, lab/, Z:\ conventions (NOT part of the wheel)
|
|
200
205
|
│ ├── THEORY.md distilled design + §9 condition contract; §0 binding scope
|
|
201
206
|
│ ├── ROADMAP.md milestone plan
|
|
202
|
-
│ ├── FOCO-1.0.md what 1.0 freezes and the blockers
|
|
203
|
-
│ ├── ADR/ one file per decision
|
|
207
|
+
│ ├── FOCO-1.0.md what 1.0 freezes, and the six blockers, all closed
|
|
208
|
+
│ ├── ADR/ one file per decision; the choice only, evidence in STUDIES/
|
|
204
209
|
│ ├── STUDIES/ background reading behind those decisions
|
|
205
210
|
│ └── design/ one spec per work phase: the alternatives measured and the decision
|
|
206
211
|
├── outreach/ material for presenting the project publicly, not shipped
|
|
207
212
|
│ ├── README.md how it is organized and what to check before publishing
|
|
208
|
-
│ ├── 2026-09-
|
|
209
|
-
│
|
|
213
|
+
│ ├── 2026-09-04-lancamento.md the current dated news source the channel texts derive from
|
|
214
|
+
│ ├── 2026-09-03-lancamento.md the previous one, kept as the record of 0.5.1
|
|
215
|
+
│ └── linkedin/ post, artigo, the illustrated page and the figures, PT and EN
|
|
210
216
|
└── archive/ reference-only; gitignored (pruned 2026-05-17, only HISTORY.md kept)
|
|
211
217
|
```
|
|
212
218
|
|
|
@@ -253,7 +259,7 @@ PEP 440's `~=0.5.0` means `>=0.5.0, <0.6.0`. In every one of them a `0.y` bump
|
|
|
253
259
|
is the announcement that something may break, and a `0.y.z` bump is the
|
|
254
260
|
promise that nothing does.
|
|
255
261
|
|
|
256
|
-
That fixes what each digit is for here
|
|
262
|
+
That fixes what each digit is for here.
|
|
257
263
|
|
|
258
264
|
The question that decides it is not "did a returned value change". A bug fix
|
|
259
265
|
changes returned values by definition, and if that forced a `0.Y.0` then a
|
|
@@ -284,6 +290,11 @@ under any rule keyed on whether values changed:
|
|
|
284
290
|
- `0.5.0` made `tilings` return 73 specs where it returned 100. The
|
|
285
291
|
enumeration's output set is the contract. A **y**.
|
|
286
292
|
|
|
293
|
+
One consequence worth stating on its own, because a consumer cannot infer it
|
|
294
|
+
from a `Literal`: **`WeightKind` is an open set.** Adding a member is additive
|
|
295
|
+
and lands in a `0.y` before 1.0 and a minor after it; removing or renaming one
|
|
296
|
+
is a break. Code that matches on it exhaustively should keep a fallback arm.
|
|
297
|
+
|
|
287
298
|
After 1.0 the ordinary reading applies: breaking is major, additive is minor,
|
|
288
299
|
fixed is patch. What 1.0 freezes is written in
|
|
289
300
|
[`docs/FOCO-1.0.md`](docs/FOCO-1.0.md).
|
|
@@ -300,11 +311,6 @@ minting a y announced a break that never happened.
|
|
|
300
311
|
`0.5.1` was briefly numbered `0.6.0` before release. It changes only how the
|
|
301
312
|
package is built and shipped, so it is a z-bump.
|
|
302
313
|
|
|
303
|
-
Two others were right and are worth recording as the shape to copy. `0.3.0`
|
|
304
|
-
moved `stitch` output by ULPs through a different summation order, which is an
|
|
305
|
-
output change and belongs at a y boundary. `0.5.0` made `tilings` return 73
|
|
306
|
-
specs where it returned 100, which is squarely a break.
|
|
307
|
-
|
|
308
314
|
The accelerator in [`accel/`](accel/) has no version of its own to manage. It
|
|
309
315
|
is compiled into this wheel, so it ships when `patchcraft` ships. The version
|
|
310
316
|
in `accel/Cargo.toml` is internal and nothing reads it; what the Python side
|
|
@@ -15,14 +15,14 @@ PatchCraft/
|
|
|
15
15
|
│ README.pt-BR.md ........... the same page in Portuguese
|
|
16
16
|
│ README.pypi.md ............ the PyPI page; links absolute, because relative ones break there
|
|
17
17
|
├── MAP.md .................... this file
|
|
18
|
-
├── AGENTS.md ................. the rules, agent-agnostic and canonical;
|
|
18
|
+
├── AGENTS.md ................. the rules, agent-agnostic and canonical; eleven invariants
|
|
19
19
|
├── CLAUDE.md ................. thin; points at AGENTS.md and holds tool-specific notes
|
|
20
20
|
├── STATUS.md ................. the snapshot: where things are now, and who each item waits on
|
|
21
21
|
├── CHANGELOG.md .............. every release, each change with the measurement behind it
|
|
22
22
|
├── CONTRIBUTING.md ........... layout, the two code paths, the versioning rule, releasing
|
|
23
23
|
├── CITATION.cff .............. citation metadata; GitHub reads it for the cite button
|
|
24
24
|
├── LICENSE ................... MIT
|
|
25
|
-
├── SECURITY.md ............... how to report, and the
|
|
25
|
+
├── SECURITY.md ............... how to report, and the five edges of the attack surface
|
|
26
26
|
├── CODE_OF_CONDUCT.md ........ Contributor Covenant 2.1
|
|
27
27
|
│
|
|
28
28
|
├── src/patchcraft/ ........... THE LIBRARY. 20 public names, frozen by test
|
|
@@ -55,19 +55,21 @@ PatchCraft/
|
|
|
55
55
|
│ ├── USAGE.md .............. the 20 symbols one at a time; every >>> runs in the suite
|
|
56
56
|
│ ├── AUXILIARY.md .......... test fixtures, lab/, the off-tree conventions
|
|
57
57
|
│ ├── ROADMAP.md ............ milestones, historical
|
|
58
|
-
│ ├── FOCO-1.0.md ........... what 1.0 freezes, and the blockers
|
|
59
|
-
│ ├── ADR/ .................. one file per decision
|
|
58
|
+
│ ├── FOCO-1.0.md ........... what 1.0 freezes, and the six blockers, all closed
|
|
59
|
+
│ ├── ADR/ .................. one file per decision; the choice, not the research
|
|
60
60
|
│ ├── design/ ............... one spec per work phase: alternatives measured, then chosen
|
|
61
61
|
│ └── STUDIES/ .............. background reading, and the Strata conformity review
|
|
62
62
|
│
|
|
63
63
|
├── tools/
|
|
64
64
|
│ ├── benchmark.py .......... accelerated vs pure, and proves they agree before timing
|
|
65
|
-
│
|
|
65
|
+
│ ├── check_dist.py ......... release gate: extension present, versions agree, tag respected
|
|
66
|
+
│ ├── compare_exactness_rules.py the power-of-two rule against k_max <= 4, measured
|
|
67
|
+
│ └── make_outreach_figures.py every outreach figure and generated page, computed
|
|
66
68
|
│
|
|
67
69
|
├── outreach/ ................. material for presenting the project; not shipped
|
|
68
70
|
├── lab/ ...................... scratch experiments; only the README is tracked
|
|
69
71
|
└── .github/
|
|
70
|
-
├── ISSUE_TEMPLATE/ .......
|
|
72
|
+
├── ISSUE_TEMPLATE/ ....... four forms, each asking for the geometry and PATCHCRAFT_ACCEL=0
|
|
71
73
|
├── PULL_REQUEST_TEMPLATE.md
|
|
72
74
|
└── workflows/ ............ test.yml (both code paths) and release.yml (one project, six wheels)
|
|
73
75
|
```
|
|
@@ -83,8 +85,8 @@ PatchCraft/
|
|
|
83
85
|
| Know **what each function accepts and rejects**, precisely | [docs/THEORY.md §9](docs/THEORY.md), which is the arbiter where documents disagree |
|
|
84
86
|
| Know **whether this library is the right tool** | [docs/SCOPE.md](docs/SCOPE.md) |
|
|
85
87
|
| Know **what the native accelerator is worth**, and re-measure it | [docs/PERFORMANCE.md](docs/PERFORMANCE.md), then `python tools/benchmark.py` |
|
|
86
|
-
| Know **
|
|
87
|
-
| Understand **why the API looks like this** | [docs/ADR/](docs/ADR/) |
|
|
88
|
+
| Know **where the library applies today** | [docs/GUIDE.md §8](docs/GUIDE.md#8-where-this-applies-today) |
|
|
89
|
+
| Understand **why the API looks like this** | [docs/ADR/](docs/ADR/), and [docs/STUDIES/](docs/STUDIES/) for the measurements behind them |
|
|
88
90
|
| Understand **why a phase was built the way it was** | [docs/design/](docs/design/) |
|
|
89
91
|
| **Contribute**: run the gates, understand the two code paths | [CONTRIBUTING.md](CONTRIBUTING.md) |
|
|
90
92
|
| **Release** it | [CONTRIBUTING.md](CONTRIBUTING.md), the "Releasing" section |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: patchcraft
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.5
|
|
4
4
|
Summary: Image patch extraction, reconstruction, pairing and seam-aware stitching for super-resolution and dataset pipelines.
|
|
5
5
|
Author: Leonardo Marques de Souza
|
|
6
6
|
License-Expression: MIT
|
|
@@ -42,7 +42,7 @@ Dynamic: license-file
|
|
|
42
42
|
|
|
43
43
|
# PatchCraft
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
Cut one image into patches, put it back together, and decide what happens at the seams.
|
|
46
46
|
|
|
47
47
|
[](https://pypi.org/project/patchcraft/)
|
|
48
48
|
[](https://pypi.org/project/patchcraft/)
|
|
@@ -88,7 +88,7 @@ paths, which return the same values.
|
|
|
88
88
|
|
|
89
89
|
`patchcraft.accel_available()` reports at runtime which one you got, and
|
|
90
90
|
`PATCHCRAFT_ACCEL=0` in the environment forces the pure path. On the overlapping
|
|
91
|
-
fold
|
|
91
|
+
fold the accelerator is worth between 5.8x and 16.5x on the machine it was measured on, which
|
|
92
92
|
the [performance page](https://github.com/LeoPR/PatchCraft/blob/main/docs/PERFORMANCE.md)
|
|
93
93
|
reports in full.
|
|
94
94
|
|
|
@@ -144,7 +144,7 @@ assert scrambled.shape == patches.shape # the right shape
|
|
|
144
144
|
assert not torch.equal(scrambled, patches) # and the wrong pixels
|
|
145
145
|
```
|
|
146
146
|
|
|
147
|
-
The saving is real on the other side too. Tiling an image, running a per-patch model and blending the result back with a Hann window took 17 non-blank lines by hand against 3 with `extract` and `stitch`, and the two outputs agreed to about 1.2e-05 on a value in [0, 1].
|
|
147
|
+
The saving is real on the other side too. Tiling an image, running a per-patch model and blending the result back with a Hann window took 17 non-blank lines by hand against 3 with `extract` and `stitch`, and the two outputs agreed to about 1.2e-05 on a value in [0, 1], 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
|
|
|
@@ -170,9 +170,9 @@ The everyday shorthand is that `stride == patch_size` and `stride == patch_size
|
|
|
170
170
|
|
|
171
171
|
## Status
|
|
172
172
|
|
|
173
|
-
This is pre-1.0, so both the output values and the API shape can still move. While the leading digit is zero the middle one is the compatibility boundary, which makes a new `0.y.z` safe to take and a new `0.y` the place where a change is allowed to land, and the [changelog](https://github.com/LeoPR/PatchCraft/blob/main/CHANGELOG.md) records each one with the measurement behind it. The suite collects
|
|
173
|
+
This is pre-1.0, so both the output values and the API shape can still move. While the leading digit is zero the middle one is the compatibility boundary, which makes a new `0.y.z` safe to take and a new `0.y` the place where a change is allowed to land, and the [changelog](https://github.com/LeoPR/PatchCraft/blob/main/CHANGELOG.md) records each one with the measurement behind it. The suite collects 1657 tests and passes on Python 3.12, 3.13 and 3.14, on Ubuntu and on Windows alike.
|
|
174
174
|
|
|
175
|
-
|
|
175
|
+
Where it applies today: on GPU the functions accept CUDA tensors and keep the device, and the Rust kernel is CPU-only, so it does not accelerate there; every figure on this page is a CPU figure, so check exactness on your device before relying on it. No external project consumes the published API yet, which is the gate this project set for calling the shape settled.
|
|
176
176
|
|
|
177
177
|
## Documentation
|
|
178
178
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
[](https://pypi.org/project/patchcraft/)
|
|
9
9
|
[](https://github.com/LeoPR/PatchCraft/blob/main/LICENSE)
|
|
10
10
|
|
|
11
|
-
**
|
|
11
|
+
**Cut one image into patches, and put it back together.** A large image rarely goes into a network whole: it is cut into small tiles, the patches, each one is processed on its own, and the image is put back. PatchCraft owns the `unfold` and `fold` arithmetic, the geometry validation and the seam blending, so that your pipeline can own everything else.
|
|
12
12
|
|
|
13
13
|
**One image at a time, on purpose.** Every call takes one `(C, H, W)` float tensor and returns one tensor, because the patch count depends on the image and a batched API would have to pad or return a list. Your `for` loop, `torch.vmap` or `DataLoader` supplies the batching.
|
|
14
14
|
|
|
@@ -30,7 +30,7 @@ The image goes out as a stack of patches, you do your work on the stack, and it
|
|
|
30
30
|
image. That last arrow has two doors: `reconstruct` when the patches are untouched, and `stitch`
|
|
31
31
|
when a model rewrote them and the seams need to fade.
|
|
32
32
|
|
|
33
|
-
This page is the call page. The manual is [docs/GUIDE.md](docs/GUIDE.md), and it carries the measurements, the tables and the long examples
|
|
33
|
+
This page is the call page. The manual is [docs/GUIDE.md](docs/GUIDE.md), and it carries the measurements, the tables and the long examples.
|
|
34
34
|
|
|
35
35
|
## Install
|
|
36
36
|
|
|
@@ -47,8 +47,8 @@ paths, which return the same values.
|
|
|
47
47
|
|
|
48
48
|
`patchcraft.accel_available()` reports at runtime which one you got, and
|
|
49
49
|
`PATCHCRAFT_ACCEL=0` in the environment forces the pure path. On the overlapping
|
|
50
|
-
fold
|
|
51
|
-
[docs/PERFORMANCE.md](docs/PERFORMANCE.md).
|
|
50
|
+
fold the accelerator is worth between 5.8x and 16.5x on the machine it was
|
|
51
|
+
measured on, which [docs/PERFORMANCE.md](docs/PERFORMANCE.md) reports in full.
|
|
52
52
|
|
|
53
53
|
The distribution name and the import name are both `patchcraft`. The runtime dependencies are `torch>=2.6`, `numpy>=1.26` and `pillow>=10`, and the supported Python versions are in [the guide](docs/GUIDE.md#9-install-details-and-citation) together with the note you need before you install a GPU wheel.
|
|
54
54
|
|
|
@@ -108,7 +108,7 @@ assert not torch.equal(scrambled, patches) # and the wrong pix
|
|
|
108
108
|
|
|
109
109
|
The second one is a stride that does not cover the image. On a 128 by 128 image with `patch=32, stride=20` the grid stops at pixel 112, which leaves 3840 of the 16384 pixels at zero, and a hand-rolled `fold` returns that partly black image without complaining.
|
|
110
110
|
|
|
111
|
-
Writing the tile-and-blend loop by hand costs 17 non-blank lines against 3 here, and the two results agree to about 1.2e-05 on a value in [0, 1], the two routes differing only in the order they sum a separable window. [The guide](docs/GUIDE.md#1-why-not-unfold-and-fold-directly) runs both versions side by side.
|
|
111
|
+
Writing the tile-and-blend loop by hand costs 17 non-blank lines against 3 here, and the two results agree to about 1.2e-05 on a value in [0, 1], more than 300 times smaller than one 8-bit step, the two routes differing only in the order they sum a separable window. [The guide](docs/GUIDE.md#1-why-not-unfold-and-fold-directly) runs both versions side by side.
|
|
112
112
|
|
|
113
113
|
## The geometry has to cover the image
|
|
114
114
|
|
|
@@ -130,7 +130,7 @@ print([s.patch_size for s in tilings(image.shape)]) # 7 exact tilings, from the
|
|
|
130
130
|
|
|
131
131
|
`tilings` is arithmetic on the shape, so it reads nothing and allocates nothing. Pass `allow_overlap=True` when you want the overlapping geometries too.
|
|
132
132
|
|
|
133
|
-
##
|
|
133
|
+
## What you are getting into
|
|
134
134
|
|
|
135
135
|
The surface is one tensor in and one tensor out. There is no batch axis, no dataset, no dataloader and no training, and that boundary is binding rather than provisional, recorded in [docs/THEORY.md](docs/THEORY.md) §0.
|
|
136
136
|
|
|
@@ -146,9 +146,9 @@ The round trip is exact when every value in the count map is a power of two, whi
|
|
|
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
|
+
1657 tests collected and CI is green on {Ubuntu, Windows} x {Python 3.12, 3.13, 3.14}, with `ruff check` and `mypy --strict` in the same run. The package is typed and ships `py.typed`.
|
|
150
150
|
|
|
151
|
-
No external project
|
|
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
|
|
|
153
153
|
## Where to read next
|
|
154
154
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
[](https://pypi.org/project/patchcraft/)
|
|
9
9
|
[](https://github.com/LeoPR/PatchCraft/blob/main/LICENSE)
|
|
10
10
|
|
|
11
|
-
**
|
|
11
|
+
**Recorta uma imagem em patches e monta de volta.** Uma imagem grande raramente entra inteira numa rede: ela é cortada em pedaços pequenos, os patches, cada um é processado sozinho, e a imagem é montada de volta. O PatchCraft cuida da aritmética do `unfold` e do `fold`, da validação da geometria e da mistura nas emendas, para que o seu pipeline possa cuidar de todo o resto.
|
|
12
12
|
|
|
13
13
|
**Uma imagem por vez, de propósito.** Cada chamada recebe um tensor float `(C, H, W)` e devolve um tensor, porque a quantidade de patches depende da imagem, e uma API em lote teria que preencher com padding ou devolver uma lista. Quem fornece o lote é o seu `for`, o `torch.vmap` ou o `DataLoader`.
|
|
14
14
|
|
|
@@ -47,8 +47,8 @@ rodam os caminhos em torch puro, que devolvem os mesmos valores.
|
|
|
47
47
|
|
|
48
48
|
O `patchcraft.accel_available()` informa em runtime qual dos dois você recebeu,
|
|
49
49
|
e `PATCHCRAFT_ACCEL=0` no ambiente força o caminho puro. No fold com
|
|
50
|
-
sobreposição
|
|
51
|
-
[docs/PERFORMANCE.md](docs/PERFORMANCE.md).
|
|
50
|
+
sobreposição o acelerador vale entre 5,8x e 16,5x na máquina em que foi
|
|
51
|
+
medido, que o [docs/PERFORMANCE.md](docs/PERFORMANCE.md) relata por inteiro.
|
|
52
52
|
|
|
53
53
|
O nome da distribuição e o nome de importação são os dois `patchcraft`. As dependências de execução são `torch>=2.6`, `numpy>=1.26` e `pillow>=10`. As versões de Python suportadas estão [no manual](docs/GUIDE.md#9-install-details-and-citation), junto com a observação que você precisa ler antes de instalar uma wheel de GPU.
|
|
54
54
|
|
|
@@ -108,7 +108,7 @@ assert not torch.equal(scrambled, patches) # e os pixels errad
|
|
|
108
108
|
|
|
109
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.
|
|
110
110
|
|
|
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
|
|
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
|
|
|
113
113
|
## A geometria precisa cobrir a imagem
|
|
114
114
|
|
|
@@ -146,9 +146,9 @@ O round-trip é exato quando todo valor do mapa de cobertura é potência de doi
|
|
|
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
|
-
São
|
|
149
|
+
São 1657 testes coletados, com CI verde em {Ubuntu, Windows} x {Python 3.12, 3.13, 3.14}, e com `ruff check` e `mypy --strict` na mesma execução. O pacote é tipado e distribui o `py.typed`.
|
|
150
150
|
|
|
151
|
-
Nenhum projeto externo
|
|
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
|
|
|
153
153
|
## Onde ler em seguida
|
|
154
154
|
|