scrollweave 0.9.0__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.
- scrollweave-0.9.0/.editorconfig +19 -0
- scrollweave-0.9.0/.gitattributes +20 -0
- scrollweave-0.9.0/.github/workflows/python-rust.yml +42 -0
- scrollweave-0.9.0/.github/workflows/rust.yml +33 -0
- scrollweave-0.9.0/.gitignore +19 -0
- scrollweave-0.9.0/Cargo.lock +21 -0
- scrollweave-0.9.0/Cargo.toml +33 -0
- scrollweave-0.9.0/LICENSE +17 -0
- scrollweave-0.9.0/PKG-INFO +194 -0
- scrollweave-0.9.0/README.md +174 -0
- scrollweave-0.9.0/build_backend.py +195 -0
- scrollweave-0.9.0/crates/scrollweave-cli/Cargo.toml +15 -0
- scrollweave-0.9.0/crates/scrollweave-cli/src/main.rs +185 -0
- scrollweave-0.9.0/crates/scrollweave-ffi/Cargo.toml +15 -0
- scrollweave-0.9.0/crates/scrollweave-ffi/src/lib.rs +834 -0
- scrollweave-0.9.0/docs/ARCHITECTURE.md +120 -0
- scrollweave-0.9.0/docs/PORT_STATUS.md +55 -0
- scrollweave-0.9.0/docs/RELEASE_STATUS.md +7 -0
- scrollweave-0.9.0/examples/generate_ui.rs +77 -0
- scrollweave-0.9.0/examples/python_usage.py +32 -0
- scrollweave-0.9.0/fixtures/compressed_rgb.png +0 -0
- scrollweave-0.9.0/fixtures/expected_long.png +0 -0
- scrollweave-0.9.0/fixtures/shot_01.png +0 -0
- scrollweave-0.9.0/fixtures/shot_02.png +0 -0
- scrollweave-0.9.0/fixtures/shot_03.png +0 -0
- scrollweave-0.9.0/pyproject.toml +56 -0
- scrollweave-0.9.0/research/__init__.py +0 -0
- scrollweave-0.9.0/research/reference/README.md +1 -0
- scrollweave-0.9.0/research/reference/__init__.py +1 -0
- scrollweave-0.9.0/research/reference/active.py +108 -0
- scrollweave-0.9.0/research/reference/alignment.py +96 -0
- scrollweave-0.9.0/research/reference/appearance.py +68 -0
- scrollweave-0.9.0/research/reference/baseline_core.py +160 -0
- scrollweave-0.9.0/research/reference/confidence.py +34 -0
- scrollweave-0.9.0/research/reference/core.py +214 -0
- scrollweave-0.9.0/research/reference/evidence.py +71 -0
- scrollweave-0.9.0/research/reference/fast_morph.py +54 -0
- scrollweave-0.9.0/research/reference/geometry.py +114 -0
- scrollweave-0.9.0/research/reference/graphcut.py +55 -0
- scrollweave-0.9.0/research/reference/layered.py +134 -0
- scrollweave-0.9.0/research/reference/layered_audited.py +18 -0
- scrollweave-0.9.0/research/reference/layered_calibrated.py +21 -0
- scrollweave-0.9.0/research/reference/layered_planned.py +30 -0
- scrollweave-0.9.0/research/reference/layered_repair.py +30 -0
- scrollweave-0.9.0/research/reference/prior_calibration.py +40 -0
- scrollweave-0.9.0/research/reference/repair.py +141 -0
- scrollweave-0.9.0/research/reference/spatial.py +54 -0
- scrollweave-0.9.0/scrollweave/__init__.py +52 -0
- scrollweave-0.9.0/scrollweave/__main__.py +128 -0
- scrollweave-0.9.0/scrollweave/_binding.py +593 -0
- scrollweave-0.9.0/scrollweave/_native/.gitkeep +0 -0
- scrollweave-0.9.0/scrollweave/api.py +441 -0
- scrollweave-0.9.0/scrollweave/py.typed +0 -0
- scrollweave-0.9.0/src/align.rs +253 -0
- scrollweave-0.9.0/src/codec.rs +602 -0
- scrollweave-0.9.0/src/compose.rs +86 -0
- scrollweave-0.9.0/src/evidence.rs +119 -0
- scrollweave-0.9.0/src/feature.rs +92 -0
- scrollweave-0.9.0/src/geometry.rs +190 -0
- scrollweave-0.9.0/src/graph.rs +210 -0
- scrollweave-0.9.0/src/graphcut.rs +178 -0
- scrollweave-0.9.0/src/image.rs +132 -0
- scrollweave-0.9.0/src/layered.rs +1020 -0
- scrollweave-0.9.0/src/lib.rs +28 -0
- scrollweave-0.9.0/src/planner.rs +292 -0
- scrollweave-0.9.0/src/stitch.rs +540 -0
- scrollweave-0.9.0/src/visibility.rs +104 -0
- scrollweave-0.9.0/tests/edge_cases.rs +269 -0
- scrollweave-0.9.0/tests/integration.rs +102 -0
- scrollweave-0.9.0/tests/layered.rs +152 -0
- scrollweave-0.9.0/tests/python/test_api.py +117 -0
- scrollweave-0.9.0/tests/python/test_layered_api.py +94 -0
- scrollweave-0.9.0/tests/python/test_native_edge.py +301 -0
- scrollweave-0.9.0/tests/python/test_packaging.py +84 -0
- scrollweave-0.9.0/tools/benchmark_native.py +116 -0
- scrollweave-0.9.0/tools/smoke_test.py +179 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
root = true
|
|
2
|
+
|
|
3
|
+
[*]
|
|
4
|
+
charset = utf-8
|
|
5
|
+
end_of_line = lf
|
|
6
|
+
insert_final_newline = true
|
|
7
|
+
trim_trailing_whitespace = true
|
|
8
|
+
|
|
9
|
+
[*.rs]
|
|
10
|
+
indent_style = space
|
|
11
|
+
indent_size = 4
|
|
12
|
+
|
|
13
|
+
[*.py]
|
|
14
|
+
indent_style = space
|
|
15
|
+
indent_size = 4
|
|
16
|
+
|
|
17
|
+
[*.{md,yml,yaml,toml,json}]
|
|
18
|
+
indent_style = space
|
|
19
|
+
indent_size = 2
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
* text=auto eol=lf
|
|
2
|
+
|
|
3
|
+
*.rs text eol=lf
|
|
4
|
+
*.py text eol=lf
|
|
5
|
+
*.md text eol=lf
|
|
6
|
+
*.toml text eol=lf
|
|
7
|
+
*.yml text eol=lf
|
|
8
|
+
*.yaml text eol=lf
|
|
9
|
+
*.json text eol=lf
|
|
10
|
+
*.txt text eol=lf
|
|
11
|
+
|
|
12
|
+
*.png binary
|
|
13
|
+
*.jpg binary
|
|
14
|
+
*.jpeg binary
|
|
15
|
+
*.bin binary
|
|
16
|
+
*.dll binary
|
|
17
|
+
*.dylib binary
|
|
18
|
+
*.so binary
|
|
19
|
+
*.exe binary
|
|
20
|
+
*.whl binary
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
name: Python / Rust core
|
|
2
|
+
on: [push, pull_request]
|
|
3
|
+
jobs:
|
|
4
|
+
build:
|
|
5
|
+
strategy:
|
|
6
|
+
fail-fast: false
|
|
7
|
+
matrix:
|
|
8
|
+
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
9
|
+
python: ['3.10', '3.13']
|
|
10
|
+
runs-on: ${{ matrix.os }}
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
- uses: dtolnay/rust-toolchain@stable
|
|
14
|
+
- uses: actions/setup-python@v5
|
|
15
|
+
with:
|
|
16
|
+
python-version: ${{ matrix.python }}
|
|
17
|
+
- run: python -m pip install ruff
|
|
18
|
+
- run: python -m ruff format --check .
|
|
19
|
+
- run: python -m ruff check .
|
|
20
|
+
- run: cargo test --workspace --locked
|
|
21
|
+
- run: python -m pip install .
|
|
22
|
+
- run: python -m pip check
|
|
23
|
+
- run: python tools/smoke_test.py
|
|
24
|
+
- run: python -m unittest discover -s tests/python -v
|
|
25
|
+
- run: python -m scrollweave --help
|
|
26
|
+
- run: python -m scrollweave --version
|
|
27
|
+
- run: python -m pip wheel . --no-deps --no-build-isolation -w dist
|
|
28
|
+
wheels:
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
steps:
|
|
31
|
+
- uses: actions/checkout@v4
|
|
32
|
+
- uses: dtolnay/rust-toolchain@stable
|
|
33
|
+
- uses: actions/setup-python@v5
|
|
34
|
+
with:
|
|
35
|
+
python-version: '3.12'
|
|
36
|
+
- run: python -m pip install auditwheel
|
|
37
|
+
- run: python -m pip wheel . --no-deps --no-build-isolation -w dist
|
|
38
|
+
- run: auditwheel repair dist/*.whl -w wheelhouse
|
|
39
|
+
- uses: actions/upload-artifact@v4
|
|
40
|
+
with:
|
|
41
|
+
name: scrollweave-manylinux-wheel
|
|
42
|
+
path: wheelhouse/*.whl
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: rust-core
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
pull_request:
|
|
5
|
+
jobs:
|
|
6
|
+
test:
|
|
7
|
+
strategy:
|
|
8
|
+
matrix:
|
|
9
|
+
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
10
|
+
runs-on: ${{ matrix.os }}
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
- name: Show toolchain
|
|
14
|
+
run: rustc --version
|
|
15
|
+
- name: Format check
|
|
16
|
+
run: cargo fmt --all -- --check
|
|
17
|
+
- name: Clippy
|
|
18
|
+
run: cargo clippy --workspace --all-targets -- -D warnings
|
|
19
|
+
- name: Check
|
|
20
|
+
run: cargo check --workspace --all-targets
|
|
21
|
+
- name: Unit and integration tests
|
|
22
|
+
run: cargo test --workspace --all-targets
|
|
23
|
+
- name: Documentation
|
|
24
|
+
run: cargo doc --workspace --no-deps
|
|
25
|
+
- name: Build CLI and FFI (release)
|
|
26
|
+
run: cargo build --release --workspace
|
|
27
|
+
msrv:
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
steps:
|
|
30
|
+
- uses: actions/checkout@v4
|
|
31
|
+
- uses: dtolnay/rust-toolchain@1.73.0
|
|
32
|
+
- name: Check declared MSRV
|
|
33
|
+
run: cargo check --workspace --all-targets --locked
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# This file is automatically @generated by Cargo.
|
|
2
|
+
# It is not intended for manual editing.
|
|
3
|
+
version = 3
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "scrollweave"
|
|
7
|
+
version = "0.9.0"
|
|
8
|
+
|
|
9
|
+
[[package]]
|
|
10
|
+
name = "scrollweave-cli"
|
|
11
|
+
version = "0.9.0"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"scrollweave",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[[package]]
|
|
17
|
+
name = "scrollweave-ffi"
|
|
18
|
+
version = "0.9.0"
|
|
19
|
+
dependencies = [
|
|
20
|
+
"scrollweave",
|
|
21
|
+
]
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "scrollweave"
|
|
3
|
+
version = "0.9.0"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
rust-version = "1.73"
|
|
6
|
+
description = "Dependency-free Rust engine for scroll screenshot reconstruction and basic image composition"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
documentation = "https://docs.rs/scrollweave"
|
|
10
|
+
include = [
|
|
11
|
+
"/Cargo.toml",
|
|
12
|
+
"/Cargo.lock",
|
|
13
|
+
"/LICENSE",
|
|
14
|
+
"/README.md",
|
|
15
|
+
"/docs/ARCHITECTURE.md",
|
|
16
|
+
"/docs/PORT_STATUS.md",
|
|
17
|
+
"/docs/RELEASE_STATUS.md",
|
|
18
|
+
"src/**/*.rs",
|
|
19
|
+
"tests/**/*.rs",
|
|
20
|
+
"examples/*.rs",
|
|
21
|
+
"fixtures/*.png",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[lib]
|
|
25
|
+
name = "scrollweave"
|
|
26
|
+
path = "src/lib.rs"
|
|
27
|
+
crate-type = ["rlib"]
|
|
28
|
+
|
|
29
|
+
[workspace]
|
|
30
|
+
members = ["crates/scrollweave-ffi", "crates/scrollweave-cli"]
|
|
31
|
+
resolver = "2"
|
|
32
|
+
|
|
33
|
+
[dependencies]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ScrollWeave contributors
|
|
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.
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: scrollweave
|
|
3
|
+
Version: 0.9.0
|
|
4
|
+
Summary: Rust-core, zero third-party dependency Python screenshot stitching library
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/xtr-hub/scrollweave
|
|
8
|
+
Project-URL: Repository, https://github.com/xtr-hub/scrollweave
|
|
9
|
+
Project-URL: Documentation, https://github.com/xtr-hub/scrollweave/blob/main/docs/ARCHITECTURE.md
|
|
10
|
+
Keywords: screenshot,stitching,scrolling,rust,computer-vision
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Rust
|
|
17
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# ScrollWeave — Python library backed by Rust (zero third-party runtime dependencies)
|
|
22
|
+
|
|
23
|
+
A **Python library** with a **Rust-only algorithmic core**. Import with `import scrollweave`. Both Cargo dependencies and Python runtime dependencies are empty. Native build requires `rustc`/`cargo` and a working linker; Python 3.10+.
|
|
24
|
+
|
|
25
|
+
> **Verification status (2026-10-30, Windows, Rust 1.93.1, Python 3.12.7)**: `cargo test --workspace --all-targets` passes (31 tests), `python -m unittest discover -s tests/python` passes (25 tests), `pip install .` builds and loads the native wheel, `tools/smoke_test.py` passes against both the source tree and the installed wheel, and `fixtures/shot_01..03.png` reconstruct `fixtures/expected_long.png` byte-exactly with positions `0,90,180` (MAE 0, height difference 0). The 15-frame regression suite also runs: truth 512x3306, output 512x3307, missing 0; MAE 0.1267 / exact 99.819% with masks and MAE 0.1822 / exact 99.728% without masks. A separate six-scenario headless-browser regression (fixed headers/sidebars, 2-3 scrolling frames) auto-detected the content region and produced clean content with MAE 0.01-0.03 after page padding. Byte-exact parity with the research Python prototype, 30/UI/Japanese suites and other platforms remain unverified. Treat this as a tested release for the shipped and 15-frame fixtures, not a production-certified binary.
|
|
26
|
+
|
|
27
|
+
## Quick start
|
|
28
|
+
|
|
29
|
+
```shell
|
|
30
|
+
python -m pip install scrollweave
|
|
31
|
+
python -c "import scrollweave; print(scrollweave.__version__)"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The PyPI wheel bundles the platform-specific native dynamic library, so there is no
|
|
35
|
+
`pip install numpy pillow scipy ...` step. When no wheel exists for your platform,
|
|
36
|
+
`pip` builds the source distribution locally and therefore needs Rust/Cargo and a
|
|
37
|
+
working linker.
|
|
38
|
+
|
|
39
|
+
From a checkout, install and validate the native algorithms with:
|
|
40
|
+
|
|
41
|
+
```shell
|
|
42
|
+
python -m pip install .
|
|
43
|
+
cargo test --workspace
|
|
44
|
+
python -m unittest discover -s tests/python -v
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Main Python API
|
|
48
|
+
|
|
49
|
+
The public API has one function per domain: reconstruction, planning and layout.
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from scrollweave import (
|
|
53
|
+
Goal, Image, LayeredOptions, PlanOptions, PriorMasks, Viewport,
|
|
54
|
+
compose, plan_views, stitch_basic, stitch_layered,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Layered reconstruction requires vertical scroll positions.
|
|
58
|
+
r = stitch_layered(
|
|
59
|
+
['001.png', '002.png', '003.png'],
|
|
60
|
+
scroll=[0, 195, 390],
|
|
61
|
+
viewport=Viewport(87, 681),
|
|
62
|
+
options=LayeredOptions(segmentation='temporal'),
|
|
63
|
+
diagnostics=True,
|
|
64
|
+
)
|
|
65
|
+
r.save('merged.png')
|
|
66
|
+
r.save_debug('diagnostics')
|
|
67
|
+
print(r.scroll, r.viewport, r.geometry_mode)
|
|
68
|
+
print(r.support_fraction, r.unverified_fraction)
|
|
69
|
+
|
|
70
|
+
# Omit viewport when the screenshots contain a fixed web header/sidebar.
|
|
71
|
+
# The native planner detects persistent full-width/full-height chrome and crops
|
|
72
|
+
# it before reconstruction:
|
|
73
|
+
auto = stitch_layered(['001.png', '002.png', '003.png'], scroll=[0, 195, 390])
|
|
74
|
+
auto.save('merged_content.png')
|
|
75
|
+
|
|
76
|
+
# Planning is a separate domain operation.
|
|
77
|
+
repair = plan_views(r, PlanOptions(count=3, goal=Goal.REPAIR))
|
|
78
|
+
verification = plan_views(r, PlanOptions(count=2, goal=Goal.VERIFICATION))
|
|
79
|
+
|
|
80
|
+
# Basic 2D alignment/compositing remains available as a separate entry point.
|
|
81
|
+
basic = stitch_basic(
|
|
82
|
+
['001.png', '002.png', '003.png'],
|
|
83
|
+
positions=[0, 195, 390],
|
|
84
|
+
viewport=Viewport(0, 720),
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Prior masks are validated domain objects, not bare N*H*W bytes.
|
|
88
|
+
masks = PriorMasks(
|
|
89
|
+
width=760, height=512,
|
|
90
|
+
frames=(bytes(760 * 512),) * 3,
|
|
91
|
+
)
|
|
92
|
+
layered = stitch_layered(
|
|
93
|
+
['001.png', '002.png', '003.png'],
|
|
94
|
+
scroll=[0, 195, 390],
|
|
95
|
+
viewport=Viewport(87, 681),
|
|
96
|
+
options=LayeredOptions(recurrent_mask=masks, flat_mask=masks),
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# Compose is independent of stitching.
|
|
100
|
+
compose(['001.png', '002.png'], layout='vertical').save('vertical.png')
|
|
101
|
+
|
|
102
|
+
# In-memory RGB frames (only Python built-ins, no Pillow / NumPy)
|
|
103
|
+
frame = Image(width=32, height=32, rgb=b'\xff\x00\x00' * (32 * 32))
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
`Viewport(top=..., bottom=..., left=..., right=...)` uses source-frame coordinates;
|
|
107
|
+
`bottom` and `right` are exclusive. Passing a viewport disables automatic fixed-chrome
|
|
108
|
+
detection and uses exactly the supplied band/region. Omitting it lets the native
|
|
109
|
+
layer detect a persistent header/sidebar and infer the scroll viewport automatically.
|
|
110
|
+
|
|
111
|
+
## Result model
|
|
112
|
+
|
|
113
|
+
`BasicResult` contains `image`, `positions`, `viewport`, `graph`,
|
|
114
|
+
`support_fraction`, `unverified_fraction` and optional `diagnostics`.
|
|
115
|
+
|
|
116
|
+
`LayeredResult` contains `image`, `scroll`, `viewport`, `geometry_mode`,
|
|
117
|
+
`support_fraction`, `unverified_fraction`, and nested objects:
|
|
118
|
+
|
|
119
|
+
- `evidence`: `source_ids`, `support`, `views`, `conflict`, `unverified`,
|
|
120
|
+
`invalid_source`, `estimated_ui`, `ui_probability`, `rejected_prior_rows`
|
|
121
|
+
- `diagnostics` (opt-in): `coverage`, `confidence`, `provenance`, `screen_mask`
|
|
122
|
+
|
|
123
|
+
Planner output is returned by `plan_views()` as `ViewSuggestion` values; it is not
|
|
124
|
+
embedded in the reconstruction result. Evidence flags do **not** mean pixels are
|
|
125
|
+
correct or incorrect; missing content is never invented, and all output pixels at
|
|
126
|
+
observed locations are copied from original input RGB buffers.
|
|
127
|
+
|
|
128
|
+
## CLI
|
|
129
|
+
|
|
130
|
+
```shell
|
|
131
|
+
python -m scrollweave stitch 001.png 002.png 003.png -o merged.png \
|
|
132
|
+
--positions 0,195,390 --viewport 87:681 --debug-dir debug \
|
|
133
|
+
--recommend-views 2 --recommend-repair-views 2
|
|
134
|
+
python -m scrollweave stitch 001.png 002.png 003.png -o merged.png \
|
|
135
|
+
--algorithm basic --positions 0,195,390
|
|
136
|
+
python -m scrollweave compose a.png b.png -o combined.png --layout horizontal
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The Rust package `scrollweave-cli` builds a `scrollweave` binary; the native FFI
|
|
140
|
+
cdylib is named `scrollweave_ffi`. The Python console command is also `scrollweave`.
|
|
141
|
+
|
|
142
|
+
## Source layout / modules
|
|
143
|
+
|
|
144
|
+
| Rust module | Research capability it addresses |
|
|
145
|
+
|---|---|
|
|
146
|
+
| `align.rs`, `graph.rs` | Image-derived integer scroll offsets, overlap graph, robust pose optimization |
|
|
147
|
+
| `geometry.rs` | Persistent screen-border detection + activity-envelope fallback |
|
|
148
|
+
| `evidence.rs` | Independent position support, conflict and unverified maps; duplicate-view disambiguation |
|
|
149
|
+
| `layered.rs` | Automatic fixed-header/sidebar content-region detection, rectangular frame cropping, document/screen evidence, transient candidates, morphology, appearance histogram, side rails, prior calibration, original pixel source selection |
|
|
150
|
+
| `graphcut.rs` | Optional binary grid Potts mincut via pure Rust Dinic (experimental) |
|
|
151
|
+
| `planner.rs` | Verification suggestions and expected repair suggestions |
|
|
152
|
+
| `crates/scrollweave-ffi/src/lib.rs` | C ABI v5 to Python; buffers allocated and freed in Rust |
|
|
153
|
+
| `codec.rs` | PNG (8-bit, non-interlaced), BMP24, PPM P6; raw RGB via C ABI |
|
|
154
|
+
|
|
155
|
+
### Important scope and mathematical limits
|
|
156
|
+
|
|
157
|
+
- This is a **Rust reimplementation of the Python research algorithms**, *not* the Python research functions executed behind a thin Rust shell. Core operations are fully Rust; Python manages paths, arguments and copying result buffers.
|
|
158
|
+
- It follows the layered **default behaviors / major research branches**, not line-for-line numerical reproduction. The Gaussian appearance filter, morphology, graphcut solver, edge statistics and source scoring have approximations/differences. Exact pixel parity is not yet verified. Refer to `docs/PORT_STATUS.md`.
|
|
159
|
+
- The default layered algorithm is a layered RGB reconstruction method, **not** verified high-accuracy removal of all dynamic UI. Motion-tracked arbitrary moving object segmentation and OCR are not present in the original *working* default path, and are not asserted here.
|
|
160
|
+
- The layered compositor currently handles vertically scrolling same-width/same-height frames. The basic matcher supports simpler horizontal alignment; the layered path explicitly rejects horizontal offsets.
|
|
161
|
+
- When scroll positions are not supplied, the older Rust feature/graph matcher estimates them. It is not guaranteed on heavy occlusion or near-blank screenshots and may decline input.
|
|
162
|
+
- JPEG, HEIC and MP4 decoders are **not** included. Convert externally to PNG or supply raw RGB `Image` objects. No OCR or trained AI model.
|
|
163
|
+
- Native behavior is verified by the tests and shipped fixtures above. No improvement over the research Python prototype is claimed, and automatic matcher results on periodic content can be ambiguous.
|
|
164
|
+
|
|
165
|
+
## Test and independent evidence
|
|
166
|
+
|
|
167
|
+
```shell
|
|
168
|
+
cargo test --workspace # Must run on a Rust-equipped machine
|
|
169
|
+
cargo fmt --all -- --check
|
|
170
|
+
cargo clippy --workspace --all-targets -- -D warnings
|
|
171
|
+
python -m ruff format --check .
|
|
172
|
+
python -m ruff check .
|
|
173
|
+
python -m unittest discover -s tests/python -v
|
|
174
|
+
python tools/smoke_test.py # installed-package end-to-end smoke test
|
|
175
|
+
python tools/benchmark_native.py --truth fixtures/expected_long.png \
|
|
176
|
+
--positions 0,90,180 fixtures/shot_01.png fixtures/shot_02.png fixtures/shot_03.png
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
`research/reference/` stores a **development-only frozen Python research reference** for future parity work; it is not imported or installed by ScrollWeave, and its scientific packages are neither runtime nor build dependencies of this Rust-backed library.
|
|
180
|
+
|
|
181
|
+
MIT license.
|
|
182
|
+
|
|
183
|
+
### Reproduce the original 15-frame UI pressure test (full regression ZIP)
|
|
184
|
+
|
|
185
|
+
After installing the package and extracting the full regression ZIP:
|
|
186
|
+
|
|
187
|
+
```shell
|
|
188
|
+
python tools/benchmark_native.py --suite benchmark_data/starry \
|
|
189
|
+
--recurrent-mask benchmark_data/starry/recurrent_mask.bin \
|
|
190
|
+
--flat-mask benchmark_data/starry/flat_mask.bin \
|
|
191
|
+
--viewport 90:678 --output reconstructed.png --metrics measured.json
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
The mask files are development fixtures derived from the *original observed test screenshots*, **not automatically learned from input on this call**. For a mask-free experiment omit both flags. The benchmark checks **the entire ground-truth page** and reports height error explicitly; it does not silently crop to the overlap. The shipped fixture was run on Windows on 2026-10-29: positions `0,90,180` produced an exact 120x330 page (MAE 0).
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# ScrollWeave — Python library backed by Rust (zero third-party runtime dependencies)
|
|
2
|
+
|
|
3
|
+
A **Python library** with a **Rust-only algorithmic core**. Import with `import scrollweave`. Both Cargo dependencies and Python runtime dependencies are empty. Native build requires `rustc`/`cargo` and a working linker; Python 3.10+.
|
|
4
|
+
|
|
5
|
+
> **Verification status (2026-10-30, Windows, Rust 1.93.1, Python 3.12.7)**: `cargo test --workspace --all-targets` passes (31 tests), `python -m unittest discover -s tests/python` passes (25 tests), `pip install .` builds and loads the native wheel, `tools/smoke_test.py` passes against both the source tree and the installed wheel, and `fixtures/shot_01..03.png` reconstruct `fixtures/expected_long.png` byte-exactly with positions `0,90,180` (MAE 0, height difference 0). The 15-frame regression suite also runs: truth 512x3306, output 512x3307, missing 0; MAE 0.1267 / exact 99.819% with masks and MAE 0.1822 / exact 99.728% without masks. A separate six-scenario headless-browser regression (fixed headers/sidebars, 2-3 scrolling frames) auto-detected the content region and produced clean content with MAE 0.01-0.03 after page padding. Byte-exact parity with the research Python prototype, 30/UI/Japanese suites and other platforms remain unverified. Treat this as a tested release for the shipped and 15-frame fixtures, not a production-certified binary.
|
|
6
|
+
|
|
7
|
+
## Quick start
|
|
8
|
+
|
|
9
|
+
```shell
|
|
10
|
+
python -m pip install scrollweave
|
|
11
|
+
python -c "import scrollweave; print(scrollweave.__version__)"
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The PyPI wheel bundles the platform-specific native dynamic library, so there is no
|
|
15
|
+
`pip install numpy pillow scipy ...` step. When no wheel exists for your platform,
|
|
16
|
+
`pip` builds the source distribution locally and therefore needs Rust/Cargo and a
|
|
17
|
+
working linker.
|
|
18
|
+
|
|
19
|
+
From a checkout, install and validate the native algorithms with:
|
|
20
|
+
|
|
21
|
+
```shell
|
|
22
|
+
python -m pip install .
|
|
23
|
+
cargo test --workspace
|
|
24
|
+
python -m unittest discover -s tests/python -v
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Main Python API
|
|
28
|
+
|
|
29
|
+
The public API has one function per domain: reconstruction, planning and layout.
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from scrollweave import (
|
|
33
|
+
Goal, Image, LayeredOptions, PlanOptions, PriorMasks, Viewport,
|
|
34
|
+
compose, plan_views, stitch_basic, stitch_layered,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
# Layered reconstruction requires vertical scroll positions.
|
|
38
|
+
r = stitch_layered(
|
|
39
|
+
['001.png', '002.png', '003.png'],
|
|
40
|
+
scroll=[0, 195, 390],
|
|
41
|
+
viewport=Viewport(87, 681),
|
|
42
|
+
options=LayeredOptions(segmentation='temporal'),
|
|
43
|
+
diagnostics=True,
|
|
44
|
+
)
|
|
45
|
+
r.save('merged.png')
|
|
46
|
+
r.save_debug('diagnostics')
|
|
47
|
+
print(r.scroll, r.viewport, r.geometry_mode)
|
|
48
|
+
print(r.support_fraction, r.unverified_fraction)
|
|
49
|
+
|
|
50
|
+
# Omit viewport when the screenshots contain a fixed web header/sidebar.
|
|
51
|
+
# The native planner detects persistent full-width/full-height chrome and crops
|
|
52
|
+
# it before reconstruction:
|
|
53
|
+
auto = stitch_layered(['001.png', '002.png', '003.png'], scroll=[0, 195, 390])
|
|
54
|
+
auto.save('merged_content.png')
|
|
55
|
+
|
|
56
|
+
# Planning is a separate domain operation.
|
|
57
|
+
repair = plan_views(r, PlanOptions(count=3, goal=Goal.REPAIR))
|
|
58
|
+
verification = plan_views(r, PlanOptions(count=2, goal=Goal.VERIFICATION))
|
|
59
|
+
|
|
60
|
+
# Basic 2D alignment/compositing remains available as a separate entry point.
|
|
61
|
+
basic = stitch_basic(
|
|
62
|
+
['001.png', '002.png', '003.png'],
|
|
63
|
+
positions=[0, 195, 390],
|
|
64
|
+
viewport=Viewport(0, 720),
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Prior masks are validated domain objects, not bare N*H*W bytes.
|
|
68
|
+
masks = PriorMasks(
|
|
69
|
+
width=760, height=512,
|
|
70
|
+
frames=(bytes(760 * 512),) * 3,
|
|
71
|
+
)
|
|
72
|
+
layered = stitch_layered(
|
|
73
|
+
['001.png', '002.png', '003.png'],
|
|
74
|
+
scroll=[0, 195, 390],
|
|
75
|
+
viewport=Viewport(87, 681),
|
|
76
|
+
options=LayeredOptions(recurrent_mask=masks, flat_mask=masks),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# Compose is independent of stitching.
|
|
80
|
+
compose(['001.png', '002.png'], layout='vertical').save('vertical.png')
|
|
81
|
+
|
|
82
|
+
# In-memory RGB frames (only Python built-ins, no Pillow / NumPy)
|
|
83
|
+
frame = Image(width=32, height=32, rgb=b'\xff\x00\x00' * (32 * 32))
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`Viewport(top=..., bottom=..., left=..., right=...)` uses source-frame coordinates;
|
|
87
|
+
`bottom` and `right` are exclusive. Passing a viewport disables automatic fixed-chrome
|
|
88
|
+
detection and uses exactly the supplied band/region. Omitting it lets the native
|
|
89
|
+
layer detect a persistent header/sidebar and infer the scroll viewport automatically.
|
|
90
|
+
|
|
91
|
+
## Result model
|
|
92
|
+
|
|
93
|
+
`BasicResult` contains `image`, `positions`, `viewport`, `graph`,
|
|
94
|
+
`support_fraction`, `unverified_fraction` and optional `diagnostics`.
|
|
95
|
+
|
|
96
|
+
`LayeredResult` contains `image`, `scroll`, `viewport`, `geometry_mode`,
|
|
97
|
+
`support_fraction`, `unverified_fraction`, and nested objects:
|
|
98
|
+
|
|
99
|
+
- `evidence`: `source_ids`, `support`, `views`, `conflict`, `unverified`,
|
|
100
|
+
`invalid_source`, `estimated_ui`, `ui_probability`, `rejected_prior_rows`
|
|
101
|
+
- `diagnostics` (opt-in): `coverage`, `confidence`, `provenance`, `screen_mask`
|
|
102
|
+
|
|
103
|
+
Planner output is returned by `plan_views()` as `ViewSuggestion` values; it is not
|
|
104
|
+
embedded in the reconstruction result. Evidence flags do **not** mean pixels are
|
|
105
|
+
correct or incorrect; missing content is never invented, and all output pixels at
|
|
106
|
+
observed locations are copied from original input RGB buffers.
|
|
107
|
+
|
|
108
|
+
## CLI
|
|
109
|
+
|
|
110
|
+
```shell
|
|
111
|
+
python -m scrollweave stitch 001.png 002.png 003.png -o merged.png \
|
|
112
|
+
--positions 0,195,390 --viewport 87:681 --debug-dir debug \
|
|
113
|
+
--recommend-views 2 --recommend-repair-views 2
|
|
114
|
+
python -m scrollweave stitch 001.png 002.png 003.png -o merged.png \
|
|
115
|
+
--algorithm basic --positions 0,195,390
|
|
116
|
+
python -m scrollweave compose a.png b.png -o combined.png --layout horizontal
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The Rust package `scrollweave-cli` builds a `scrollweave` binary; the native FFI
|
|
120
|
+
cdylib is named `scrollweave_ffi`. The Python console command is also `scrollweave`.
|
|
121
|
+
|
|
122
|
+
## Source layout / modules
|
|
123
|
+
|
|
124
|
+
| Rust module | Research capability it addresses |
|
|
125
|
+
|---|---|
|
|
126
|
+
| `align.rs`, `graph.rs` | Image-derived integer scroll offsets, overlap graph, robust pose optimization |
|
|
127
|
+
| `geometry.rs` | Persistent screen-border detection + activity-envelope fallback |
|
|
128
|
+
| `evidence.rs` | Independent position support, conflict and unverified maps; duplicate-view disambiguation |
|
|
129
|
+
| `layered.rs` | Automatic fixed-header/sidebar content-region detection, rectangular frame cropping, document/screen evidence, transient candidates, morphology, appearance histogram, side rails, prior calibration, original pixel source selection |
|
|
130
|
+
| `graphcut.rs` | Optional binary grid Potts mincut via pure Rust Dinic (experimental) |
|
|
131
|
+
| `planner.rs` | Verification suggestions and expected repair suggestions |
|
|
132
|
+
| `crates/scrollweave-ffi/src/lib.rs` | C ABI v5 to Python; buffers allocated and freed in Rust |
|
|
133
|
+
| `codec.rs` | PNG (8-bit, non-interlaced), BMP24, PPM P6; raw RGB via C ABI |
|
|
134
|
+
|
|
135
|
+
### Important scope and mathematical limits
|
|
136
|
+
|
|
137
|
+
- This is a **Rust reimplementation of the Python research algorithms**, *not* the Python research functions executed behind a thin Rust shell. Core operations are fully Rust; Python manages paths, arguments and copying result buffers.
|
|
138
|
+
- It follows the layered **default behaviors / major research branches**, not line-for-line numerical reproduction. The Gaussian appearance filter, morphology, graphcut solver, edge statistics and source scoring have approximations/differences. Exact pixel parity is not yet verified. Refer to `docs/PORT_STATUS.md`.
|
|
139
|
+
- The default layered algorithm is a layered RGB reconstruction method, **not** verified high-accuracy removal of all dynamic UI. Motion-tracked arbitrary moving object segmentation and OCR are not present in the original *working* default path, and are not asserted here.
|
|
140
|
+
- The layered compositor currently handles vertically scrolling same-width/same-height frames. The basic matcher supports simpler horizontal alignment; the layered path explicitly rejects horizontal offsets.
|
|
141
|
+
- When scroll positions are not supplied, the older Rust feature/graph matcher estimates them. It is not guaranteed on heavy occlusion or near-blank screenshots and may decline input.
|
|
142
|
+
- JPEG, HEIC and MP4 decoders are **not** included. Convert externally to PNG or supply raw RGB `Image` objects. No OCR or trained AI model.
|
|
143
|
+
- Native behavior is verified by the tests and shipped fixtures above. No improvement over the research Python prototype is claimed, and automatic matcher results on periodic content can be ambiguous.
|
|
144
|
+
|
|
145
|
+
## Test and independent evidence
|
|
146
|
+
|
|
147
|
+
```shell
|
|
148
|
+
cargo test --workspace # Must run on a Rust-equipped machine
|
|
149
|
+
cargo fmt --all -- --check
|
|
150
|
+
cargo clippy --workspace --all-targets -- -D warnings
|
|
151
|
+
python -m ruff format --check .
|
|
152
|
+
python -m ruff check .
|
|
153
|
+
python -m unittest discover -s tests/python -v
|
|
154
|
+
python tools/smoke_test.py # installed-package end-to-end smoke test
|
|
155
|
+
python tools/benchmark_native.py --truth fixtures/expected_long.png \
|
|
156
|
+
--positions 0,90,180 fixtures/shot_01.png fixtures/shot_02.png fixtures/shot_03.png
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
`research/reference/` stores a **development-only frozen Python research reference** for future parity work; it is not imported or installed by ScrollWeave, and its scientific packages are neither runtime nor build dependencies of this Rust-backed library.
|
|
160
|
+
|
|
161
|
+
MIT license.
|
|
162
|
+
|
|
163
|
+
### Reproduce the original 15-frame UI pressure test (full regression ZIP)
|
|
164
|
+
|
|
165
|
+
After installing the package and extracting the full regression ZIP:
|
|
166
|
+
|
|
167
|
+
```shell
|
|
168
|
+
python tools/benchmark_native.py --suite benchmark_data/starry \
|
|
169
|
+
--recurrent-mask benchmark_data/starry/recurrent_mask.bin \
|
|
170
|
+
--flat-mask benchmark_data/starry/flat_mask.bin \
|
|
171
|
+
--viewport 90:678 --output reconstructed.png --metrics measured.json
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
The mask files are development fixtures derived from the *original observed test screenshots*, **not automatically learned from input on this call**. For a mask-free experiment omit both flags. The benchmark checks **the entire ground-truth page** and reports height error explicitly; it does not silently crop to the overlap. The shipped fixture was run on Windows on 2026-10-29: positions `0,90,180` produced an exact 120x330 page (MAE 0).
|