patchcraft 0.2.0__py3-none-any.whl

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/stitch.py ADDED
@@ -0,0 +1,215 @@
1
+ """Stitch patches back into an image with configurable weighting kernels.
2
+
3
+ Where :func:`patchcraft.reconstruct` is a bit-exact inverse of ``extract``,
4
+ ``stitch`` is intended for *modified* patches — patches that have been
5
+ denoised, super-resolved, or otherwise altered — where overlap seams are
6
+ visible if patches are averaged uniformly. Weighting by a window kernel
7
+ (Hann, Gaussian) emphasizes patch centers and reduces those seams.
8
+
9
+ With ``weight="uniform"``, ``stitch`` is mathematically equivalent to
10
+ ``reconstruct`` (down to floating-point ordering).
11
+
12
+ Contract: docs/THEORY.md §2.5 and §9.9.
13
+ """
14
+ from __future__ import annotations
15
+
16
+ import math
17
+ from typing import Literal
18
+
19
+ import torch
20
+ import torch.nn.functional as F # noqa: N812 (torch convention)
21
+
22
+ from patchcraft.extract import _as_pair
23
+
24
+ __all__ = ["stitch"]
25
+
26
+
27
+ WeightKind = Literal["uniform", "hann", "gaussian"]
28
+ _WEIGHT_KINDS: tuple[WeightKind, ...] = ("uniform", "hann", "gaussian")
29
+
30
+
31
+ def _hann_1d(n: int, dtype: torch.dtype, device: torch.device) -> torch.Tensor:
32
+ """Symmetric Hann window in ``[0, 1]``. ``n == 1`` is degenerate → ``[1.0]``."""
33
+ if n == 1:
34
+ return torch.ones(1, dtype=dtype, device=device)
35
+ i = torch.arange(n, dtype=dtype, device=device)
36
+ return 0.5 * (1.0 - torch.cos(2.0 * math.pi * i / (n - 1)))
37
+
38
+
39
+ def _gaussian_1d(n: int, dtype: torch.dtype, device: torch.device) -> torch.Tensor:
40
+ """Gaussian centered at ``(n-1)/2`` with ``sigma = max(1, n/4)``."""
41
+ if n == 1:
42
+ return torch.ones(1, dtype=dtype, device=device)
43
+ sigma = max(1.0, n / 4.0)
44
+ center = (n - 1) / 2.0
45
+ i = torch.arange(n, dtype=dtype, device=device)
46
+ return torch.exp(-((i - center) ** 2) / (2.0 * sigma * sigma))
47
+
48
+
49
+ def _window_kernel(
50
+ kind: WeightKind,
51
+ ph: int,
52
+ pw: int,
53
+ dtype: torch.dtype,
54
+ device: torch.device,
55
+ ) -> torch.Tensor:
56
+ """Build a ``(ph, pw)`` window as the outer product of two 1-D windows."""
57
+ if kind == "uniform":
58
+ return torch.ones(ph, pw, dtype=dtype, device=device)
59
+ if kind == "hann":
60
+ wh = _hann_1d(ph, dtype, device)
61
+ ww = _hann_1d(pw, dtype, device)
62
+ return wh.unsqueeze(1) * ww.unsqueeze(0)
63
+ if kind == "gaussian":
64
+ wh = _gaussian_1d(ph, dtype, device)
65
+ ww = _gaussian_1d(pw, dtype, device)
66
+ return wh.unsqueeze(1) * ww.unsqueeze(0)
67
+ raise ValueError(
68
+ f"weight must be one of {_WEIGHT_KINDS!r}, got {kind!r}"
69
+ )
70
+
71
+
72
+ def stitch(
73
+ patches: torch.Tensor,
74
+ image_shape: tuple[int, int, int],
75
+ stride: int | tuple[int, int],
76
+ *,
77
+ weight: WeightKind = "uniform",
78
+ dilation: int | tuple[int, int] = 1,
79
+ ) -> torch.Tensor:
80
+ """Reassemble a ``(C, H, W)`` image from ``(L, C, ph, pw)`` with blendable weights.
81
+
82
+ Use ``stitch`` when patches have been modified (model output, denoised,
83
+ super-resolved). Use :func:`patchcraft.reconstruct` when patches came
84
+ straight from ``extract`` and you want a bit-exact inverse with no
85
+ extra arithmetic.
86
+
87
+ ``weight`` controls how overlapping patches are blended:
88
+
89
+ - ``"uniform"`` — each covering patch contributes equally. Mathematically
90
+ equivalent to ``reconstruct`` (no seam attenuation).
91
+ - ``"hann"`` — Hann window: full weight at patch center, zero at patch
92
+ edges. Strong seam suppression. **Caveat:** image-corner pixels that
93
+ are covered only by patches whose edge-weight at that location is zero
94
+ will be zero in the output. Document this for callers.
95
+ - ``"gaussian"`` — Gaussian centered on the patch with
96
+ ``sigma = max(1.0, min(ph, pw) / 4)``. Smooth seam suppression without
97
+ the strict zero at the edge (no corner-zero artifact).
98
+
99
+ Internally: each patch is multiplied by the 2-D weight kernel, the
100
+ weighted patches are folded into the numerator, the weight kernel itself
101
+ is folded over the same geometry into the denominator, and
102
+ ``numerator / denominator.clamp(min=1e-6)`` gives the output. The clamp
103
+ absorbs float noise on covered pixels; geometry validation guarantees
104
+ no uncovered pixels.
105
+
106
+ Rejects (per §9.9): ``dilation != 1``; ``stride > patch_size`` in any
107
+ axis; ``patches.ndim != 4``; non-floating-point patches (kernel
108
+ multiplication breaks integer semantics for non-uniform weights —
109
+ callers convert to ``float`` first); ``image_shape`` inconsistent with
110
+ the patch grid; unknown ``weight``.
111
+
112
+ Dtype and device of ``patches`` are preserved.
113
+ """
114
+ if not isinstance(patches, torch.Tensor):
115
+ raise TypeError(
116
+ f"patches must be torch.Tensor, got {type(patches).__name__}"
117
+ )
118
+ if patches.ndim != 4:
119
+ raise ValueError(
120
+ f"patches must have ndim==4 (L, C, ph, pw), got ndim={patches.ndim}"
121
+ )
122
+ if not patches.is_floating_point():
123
+ raise ValueError(
124
+ f"stitch requires floating-point patches, got dtype={patches.dtype}. "
125
+ "Convert with patches.float() — weight kernels are float-valued and "
126
+ "integer semantics would silently quantize the result."
127
+ )
128
+
129
+ if weight not in _WEIGHT_KINDS:
130
+ raise ValueError(
131
+ f"weight must be one of {_WEIGHT_KINDS!r}, got {weight!r}"
132
+ )
133
+
134
+ n_patches, c, ph, pw = patches.shape
135
+
136
+ if not (isinstance(image_shape, tuple) and len(image_shape) == 3):
137
+ raise ValueError(
138
+ f"image_shape must be a 3-tuple (C, H, W), got {image_shape!r}"
139
+ )
140
+ for axis_name, val in zip(("C", "H", "W"), image_shape, strict=True):
141
+ if not isinstance(val, int) or isinstance(val, bool) or val <= 0:
142
+ raise ValueError(
143
+ f"image_shape[{axis_name}] must be a positive int, got {val!r}"
144
+ )
145
+ target_c, h, w = image_shape
146
+ if target_c != c:
147
+ raise ValueError(
148
+ f"image_shape channels={target_c} does not match patches channel count {c}"
149
+ )
150
+
151
+ sh, sw = _as_pair(stride, "stride")
152
+ dh, dw = _as_pair(dilation, "dilation")
153
+
154
+ if dh != 1 or dw != 1:
155
+ raise ValueError(
156
+ f"stitch requires dilation==1, got dilation=({dh}, {dw}). "
157
+ "Patches extracted with dilation > 1 cannot round-trip — consume them as features."
158
+ )
159
+ if sh > ph or sw > pw:
160
+ raise ValueError(
161
+ f"stitch forbids stride > patch_size (partial coverage forbidden), "
162
+ f"got stride=({sh}, {sw}) and patch_size=({ph}, {pw})."
163
+ )
164
+
165
+ num_h = (h - ph) // sh + 1
166
+ num_w = (w - pw) // sw + 1
167
+ if num_h <= 0 or num_w <= 0:
168
+ raise ValueError(
169
+ f"image_shape={image_shape} too small for patch_size=({ph}, {pw}) "
170
+ f"and stride=({sh}, {sw})"
171
+ )
172
+ expected_n_patches = num_h * num_w
173
+ if n_patches != expected_n_patches:
174
+ raise ValueError(
175
+ f"patches.shape[0]={n_patches} inconsistent with grid implied by "
176
+ f"image_shape={image_shape}, patch_size=({ph}, {pw}), "
177
+ f"stride=({sh}, {sw}): expected L={expected_n_patches} "
178
+ f"(num_h={num_h}, num_w={num_w})."
179
+ )
180
+
181
+ kernel = _window_kernel(weight, ph, pw, patches.dtype, patches.device)
182
+
183
+ # Weighted patches: broadcast kernel (ph, pw) across (L, C, ph, pw).
184
+ weighted = patches * kernel
185
+
186
+ # Numerator fold: (L, C, ph, pw) -> (1, C*ph*pw, L) for F.fold.
187
+ num_flat = (
188
+ weighted.permute(1, 2, 3, 0)
189
+ .reshape(c * ph * pw, n_patches)
190
+ .unsqueeze(0)
191
+ )
192
+ folded_num = F.fold(
193
+ num_flat,
194
+ output_size=(h, w),
195
+ kernel_size=(ph, pw),
196
+ stride=(sh, sw),
197
+ )
198
+
199
+ # Denominator fold: replicate kernel across L patches; one "channel"
200
+ # broadcasts across image C in the division.
201
+ kernel_flat = (
202
+ kernel.flatten().unsqueeze(1).repeat(1, n_patches).unsqueeze(0)
203
+ )
204
+ folded_den = F.fold(
205
+ kernel_flat,
206
+ output_size=(h, w),
207
+ kernel_size=(ph, pw),
208
+ stride=(sh, sw),
209
+ )
210
+
211
+ # clamp(min=1e-6): for "uniform" this matches reconstruct's count-map
212
+ # clamp. For "hann", corner pixels covered only by edge-weight-zero
213
+ # positions have ~0 numerator AND ~0 denominator — output is dominated
214
+ # by the clamp (i.e., zero). Documented artifact (§9.9).
215
+ return (folded_num / folded_den.clamp(min=1e-6))[0]
@@ -0,0 +1,266 @@
1
+ Metadata-Version: 2.4
2
+ Name: patchcraft
3
+ Version: 0.2.0
4
+ Summary: Image patch extraction, reconstruction, pairing and seam-aware stitching for super-resolution and dataset pipelines.
5
+ Project-URL: Homepage, https://github.com/LeoPR/PatchCraft
6
+ Project-URL: Repository, https://github.com/LeoPR/PatchCraft
7
+ Project-URL: Issues, https://github.com/LeoPR/PatchCraft/issues
8
+ Project-URL: Changelog, https://github.com/LeoPR/PatchCraft/blob/main/CHANGELOG.md
9
+ Project-URL: Documentation, https://github.com/LeoPR/PatchCraft/blob/main/docs/USAGE.md
10
+ Author: Leonardo Marques de Souza
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: dataset,image,patches,super-resolution,torch
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Intended Audience :: Science/Research
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3 :: Only
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
24
+ Classifier: Topic :: Scientific/Engineering :: Image Processing
25
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
26
+ Classifier: Typing :: Typed
27
+ Requires-Python: >=3.12
28
+ Requires-Dist: numpy>=1.26
29
+ Requires-Dist: pillow>=10
30
+ Requires-Dist: torch>=2.6
31
+ Provides-Extra: cache
32
+ Requires-Dist: zstandard>=0.22; extra == 'cache'
33
+ Provides-Extra: dev
34
+ Requires-Dist: mypy>=1.10; extra == 'dev'
35
+ Requires-Dist: pytest-cov; extra == 'dev'
36
+ Requires-Dist: pytest>=8; extra == 'dev'
37
+ Requires-Dist: ruff>=0.5; extra == 'dev'
38
+ Requires-Dist: torchvision>=0.20; extra == 'dev'
39
+ Description-Content-Type: text/markdown
40
+
41
+ # PatchCraft
42
+
43
+ A small library for **encoding an image into patches and decoding it back**. Built to slot into other people's `torch` pipelines as one transform among many — like a `GaussianBlur` step in a `Compose([...])`.
44
+
45
+ > **Status (2026-05-17):** v0.1.0 released; v0.2.0-track is on `main` (not yet tagged). Public API: `extract`, `Patchify`, `reconstruct`, `stitch`, `pair`, `resize`, `Cache`, plus geometry helpers (`num_patches`, `tilings`, `TilingSpec`, `scale_factor`, `paired_tilings`, `PairedTilingSpec`), pixel metrics (`patch_metrics`, `per_patch_mse`, `per_patch_psnr`), and `PatchPair`/`PatchMeta`.
46
+
47
+ ## The lib vs. this repo
48
+
49
+ Think of the lib as a **car** and this repo as the **car plus its test track**.
50
+
51
+ - **The car** — [`src/patchcraft/`](src/patchcraft/) — is what gets installed by `pip install patchcraft`. It is a single library with one job: take one image (`Tensor[C, H, W]`), encode it into patches, decode patches back into the image, optionally pair LR/HR, resize, cache. **One image at a time, every time.** No datasets, no training, no orchestration, no batching across images. Multi-image is the caller's `for` loop, or `torch.vmap`, or a `DataLoader`.
52
+ - **The track** — [`tests/`](tests/), [`lab/`](lab/), [`tests/_datasets.py`](tests/_datasets.py), and the dev extras (`torchvision`, etc.) — is the pit crew, telemetry, driver and stopwatch that **prove the car works** on real images (MNIST today; more later). It downloads datasets, drives the lib through varied geometries, measures correctness. It never ships in the wheel.
53
+
54
+ The car is also **acoplável** — designed to drop into someone else's pipeline:
55
+
56
+ ```python
57
+ from patchcraft import Patchify
58
+ from torchvision import transforms
59
+
60
+ transform = transforms.Compose([
61
+ transforms.ToTensor(),
62
+ transforms.GaussianBlur(kernel_size=3),
63
+ Patchify(patch_size=4, stride=2), # ← PatchCraft as one step
64
+ ])
65
+ ```
66
+
67
+ `Patchify` is a callable; chain it inside a `Compose`, let `DataLoader` parallelize over workers. PatchCraft gives you the primitive; the surrounding pipeline stays your code.
68
+
69
+ ## Visual cheat sheet
70
+
71
+ The five core operations, one diagram each. Letters mark which patch each cell came from / goes to.
72
+
73
+ ### `extract` — image → patch stack
74
+
75
+ `patch_size=4`, `stride=4` (no overlap) on an 8×8 image:
76
+
77
+ ```
78
+ image (1, 8, 8) patches (4, 1, 4, 4)
79
+ +-----------------+ +-----+ +-----+
80
+ | . . . . | . . . . | | A | | B |
81
+ | . A . . | . B . . | extract +-----+ +-----+
82
+ | . . . . | . . . . | --------> patch0 patch1
83
+ | . . . . | . . . . |
84
+ |---------+---------| +-----+ +-----+
85
+ | . . . . | . . . . | | C | | D |
86
+ | . C . . | . D . . | +-----+ +-----+
87
+ | . . . . | . . . . | patch2 patch3
88
+ | . . . . | . . . . | (row-major order)
89
+ +-----------------+
90
+ ```
91
+
92
+ ### `reconstruct` — patch stack → image (bit-exact when `stride == patch_size`)
93
+
94
+ Each output pixel = sum of patch contributions / `count` map (= how many patches covered it). When `stride == patch_size`, `count` is all-ones and the divide is a no-op.
95
+
96
+ ```
97
+ stride == patch --> count map all 1 --> trivial copy
98
+ stride < patch --> count map > 1 --> weighted average
99
+
100
+ patch=4, stride=2, image cols 0..7:
101
+ col: 0 1 2 3 4 5 6 7
102
+ patch0: x x x x
103
+ patch1: x x x x
104
+ patch2: x x x x
105
+ count: 1 1 2 2 2 2 1 1 <- divide sum by this
106
+ ```
107
+
108
+ ### `pair` — LR <-> HR, same image region, different resolution
109
+
110
+ `scale_factor=2`: every k-th LR patch corresponds to the k-th HR patch; HR coords are LR coords times the integer scale.
111
+
112
+ ```
113
+ LR (1, 4, 4) HR (1, 8, 8)
114
+ +---------+ +-------------+
115
+ | . . . . | | . . . . . . . . |
116
+ | .[A]. . | k = 1 --> | . .[A A]. . . . |
117
+ | . . . . | | . .[A A]. . . . |
118
+ | . . . . | | . . . . . . . . |
119
+ +---------+ | . . . . . . . . |
120
+ | . . . . . . . . |
121
+ | . . . . . . . . |
122
+ | . . . . . . . . |
123
+ +-------------+
124
+
125
+ LR patch at (row=1, col=1) <--> HR patch at (row=2, col=2)
126
+ ```
127
+
128
+ ### `stitch` — same fold geometry as `reconstruct`, but each patch weighted by a window kernel
129
+
130
+ Use when patches were modified by a model and uniform averaging shows boundary seams. Window kernels for `patch_size=4`:
131
+
132
+ ```
133
+ weight="uniform" weight="hann" weight="gaussian"
134
+ (== reconstruct) centers > edges centers >> edges (never 0)
135
+
136
+ + + + + . . . . . o o .
137
+ + + + + . X X . o X X o
138
+ + + + + . X X . o X X o
139
+ + + + + . . . . . o o .
140
+
141
+ no seam attenuation strong attenuation, smooth attenuation,
142
+ image corners -> 0 corners preserved
143
+ ```
144
+
145
+ ### Everything stays one-image-at-a-time
146
+
147
+ ```
148
+ for image in images:
149
+ patches = extract(image, ...) # PatchCraft primitive
150
+ result = model(patches) # caller's work
151
+ out = stitch(result, ...) # PatchCraft primitive
152
+ ```
153
+
154
+ Multi-image parallelism is the caller's pipeline (`torch.vmap`, `DataLoader` workers, etc.) — see [`SCOPE.md`](docs/SCOPE.md) §2.
155
+
156
+ ## Scope (what the car does)
157
+
158
+ - **Extract** patches from a single image with configurable size, stride and dilation (`extract`, `Patchify`).
159
+ - **Reconstruct** an image from its patches — exact and weighted-overlap (`reconstruct`).
160
+ - **Stitch** *modified* patches (model output, denoised, super-resolved) back into one image with a window kernel that attenuates boundary seams (`stitch`, with `weight="uniform"|"hann"|"gaussian"`).
161
+ - **Plan** the geometry ahead of time: `num_patches((H, W), ...)` for the count, `tilings((H, W), allow_overlap=...)` for every full-coverage `(patch_size, stride)` combo (no image, no allocation — just arithmetic). For LR↔HR setups: `scale_factor(...)` and `paired_tilings(...)`.
162
+ - **Pair** LR and HR patches with metadata sufficient to reconstruct either (`pair`, `PatchPair`, `PatchMeta`).
163
+ - **Measure** pixel-level error between two patch stacks: `patch_metrics`, `per_patch_mse`, `per_patch_psnr`.
164
+ - **Resize** with pluggable backends — PIL or torch (`resize`).
165
+ - **Cache** results on disk with content-addressed keys, OneDrive-race retry, optional zstd (`Cache`).
166
+
167
+ ## Scope (what the car does NOT do)
168
+
169
+ - **Not a dataset manager.** PatchCraft does not load, download, batch, shuffle, or stream datasets. That's the track's job — `tests/_datasets.py` has `mnist_subset(...)` for dev fixtures, and `torchvision` is in the `[dev]` extra (never a runtime dep of the car).
170
+ - **Not a multi-image API.** Every primitive takes one image. Use `vmap` or a Python loop if you need to apply it to many.
171
+ - No SVMs, no kernels, no quantum circuits — those belong to other projects.
172
+ - No neural network training — PatchCraft is infrastructure, not a model.
173
+
174
+ ## Install
175
+
176
+ ### From PyPI
177
+
178
+ ```
179
+ pip install patchcraft # core only
180
+ pip install patchcraft[cache] # adds zstandard for compressed Cache entries
181
+ ```
182
+
183
+ ### From source (development)
184
+
185
+ ```
186
+ git clone https://github.com/LeoPR/PatchCraft.git
187
+ cd patchcraft
188
+ pip install -e ".[dev,cache]"
189
+ ```
190
+
191
+ For GPU support, install a matching torch wheel before PatchCraft
192
+ (e.g. `pip install torch --index-url https://download.pytorch.org/whl/cu124`).
193
+
194
+ ## Run tests
195
+
196
+ ```
197
+ pytest
198
+ pytest -m "not gpu" # skip GPU-requiring tests
199
+ ```
200
+
201
+ ## Layout
202
+
203
+ ```
204
+ PatchCraft/
205
+ ├── pyproject.toml package metadata, build backend (hatchling)
206
+ ├── README.md this file
207
+ ├── LICENSE MIT
208
+ ├── .python-version 3.13
209
+ ├── .gitignore ignores archive/, venvs, caches, outputs
210
+ ├── src/patchcraft/ library core — one-image-at-a-time primitives
211
+ │ ├── __init__.py re-exports the full public API
212
+ │ ├── extract.py patches via F.unfold; Patchify wrapper (ADR 0002)
213
+ │ ├── reconstruct.py inverse via F.fold + count map
214
+ │ ├── geometry.py pre-flight: num_patches, tilings, TilingSpec
215
+ │ ├── pair.py LR↔HR pairing; PatchPair, PatchMeta
216
+ │ ├── resize.py resize with PIL or torch backends
217
+ │ └── cache.py content-addressed disk cache
218
+ ├── tests/ pytest suite (contract tests for src/)
219
+ │ ├── test_extract.py extract + Patchify
220
+ │ ├── test_reconstruct.py
221
+ │ ├── test_geometry.py num_patches + tilings
222
+ │ ├── test_pair.py
223
+ │ ├── test_resize.py
224
+ │ ├── test_cache.py
225
+ │ ├── test_datasets_helper.py label_subset
226
+ │ ├── test_import.py
227
+ │ └── _datasets.py dev-only fixtures (MNIST, etc) — NOT public API
228
+ ├── lab/ ephemeral experiments; see lab/README.md
229
+ │ ├── README.md bench rules (tracked)
230
+ │ └── .gitignore ignores everything else (tracked)
231
+ ├── docs/
232
+ │ ├── USAGE.md live REPL walkthrough of every public API
233
+ │ ├── SCOPE.md responsibilities matrix + parallelization analysis
234
+ │ ├── AUXILIARY.md tests/_datasets, lab/, Z:\ conventions (NOT part of the wheel)
235
+ │ ├── THEORY.md distilled design + §9 condition contract; §0 binding scope
236
+ │ ├── ROADMAP.md milestone plan
237
+ │ └── ADR/
238
+ │ ├── 0001-patch-extraction-api.md pure function `extract`
239
+ │ └── 0002-patchify-transform.md callable wrapper for Compose pipelines
240
+ └── archive/ reference-only; gitignored (pruned 2026-05-17 — only HISTORY.md kept)
241
+ ```
242
+
243
+ ## Validation lab
244
+
245
+ The library is "one image in, one tensor out" by design — but you only know it works once you run it end-to-end on real images. That happens in two places, neither of which is part of the shipped package:
246
+
247
+ - [`tests/`](tests/) — formal pytest suite that defines the contract from [`docs/THEORY.md`](docs/THEORY.md) §9.
248
+ - [`lab/`](lab/) — ephemeral scripts and notebooks for fast hypothesis-checking. See [`lab/README.md`](lab/README.md) for the bench rules; outputs go to `Z:\outputs\patchcraft\` (off-tree).
249
+
250
+ Datasets used by tests/lab are downloaded lazily into `Z:\caches\datasets\<name>\` on first use; they do not ship with the package and are never bundled into the wheel.
251
+
252
+ ## Where to read next
253
+
254
+ | If you want… | Open |
255
+ |---|---|
256
+ | A hands-on tour with real REPL outputs for every public API | [`docs/USAGE.md`](docs/USAGE.md) |
257
+ | The line between "PatchCraft's job" and "your pipeline's job", plus the parallelization story | [`docs/SCOPE.md`](docs/SCOPE.md) |
258
+ | The auxiliary test fixtures and lab conventions (not shipped) | [`docs/AUXILIARY.md`](docs/AUXILIARY.md) |
259
+ | Design decisions, math, the per-API contract | [`docs/THEORY.md`](docs/THEORY.md) |
260
+ | Architecture Decision Records | [`docs/ADR/`](docs/ADR/) |
261
+ | Milestone plan | [`docs/ROADMAP.md`](docs/ROADMAP.md) |
262
+ | Per-release changes | [`CHANGELOG.md`](CHANGELOG.md) |
263
+
264
+ ## Author
265
+
266
+ Leonardo Marques de Souza
@@ -0,0 +1,14 @@
1
+ patchcraft/__init__.py,sha256=mvvN6gwkDi6ZFhqjoImxxajfHzk7nnsmZmDADZKrQMU,957
2
+ patchcraft/cache.py,sha256=Rs5_HS7nIVEYPvaDnvbgFcHTEx4xnB2Iqx2QCaeVLr4,8596
3
+ patchcraft/extract.py,sha256=yFSd-ytukeVnieuu45OTpvULDFNEminhA97goMR04xs,4123
4
+ patchcraft/geometry.py,sha256=AZZPV0JMKhpy5DVP-6XMJC-JhNipVZYhPvi8yYHd-7c,10501
5
+ patchcraft/metrics.py,sha256=tJ8hPV4Emaw6jBzWTep3_ad7M-DbbRXdPRvlOuh3xZs,5164
6
+ patchcraft/pair.py,sha256=fXzC03RVtlRh62lRdreknI7tAVz9eMPpLfYpj9VHaZ8,5779
7
+ patchcraft/reconstruct.py,sha256=NGYGPbnQJ8o4aYiOV3m_dNc4X4WHekfidRPa1lelspc,4497
8
+ patchcraft/resize.py,sha256=FB43uJcMTW5FPhthBVcluRTH749X6Cu6PDpzqyyws_U,7009
9
+ patchcraft/stitch.py,sha256=CR-TD1LiaORmKoMarQ_pN20zvlC__CirB1z7778SvmE,8461
10
+ patchcraft/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ patchcraft-0.2.0.dist-info/METADATA,sha256=JjLZOEneH7B5cDYAo7qHUbeC5G78wuHpG73jf0nNwT0,13375
12
+ patchcraft-0.2.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
13
+ patchcraft-0.2.0.dist-info/licenses/LICENSE,sha256=mFbXuSyc-xMcSV55A6NVsfgvW7ldtJb6Eky-Rv5UHN8,1082
14
+ patchcraft-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Leonardo Marques de Souza
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.