scrollweave 0.9.0__py3-none-win_amd64.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.
@@ -0,0 +1,52 @@
1
+ """ScrollWeave: zero third-party dependencies Python API + Rust native core."""
2
+
3
+ from ._binding import NativeUnavailable
4
+ from .api import (
5
+ AlignOptions,
6
+ BasicResult,
7
+ Diagnostics,
8
+ Evidence,
9
+ GeometryMode,
10
+ Goal,
11
+ GraphStats,
12
+ Image,
13
+ LayeredOptions,
14
+ LayeredResult,
15
+ PlanOptions,
16
+ PriorMasks,
17
+ Segmentation,
18
+ Viewport,
19
+ ViewSuggestion,
20
+ compose,
21
+ plan_views,
22
+ read_image,
23
+ stitch_basic,
24
+ stitch_layered,
25
+ write_image,
26
+ )
27
+
28
+ __version__ = '0.9.0'
29
+ __all__ = [
30
+ 'AlignOptions',
31
+ 'BasicResult',
32
+ 'Diagnostics',
33
+ 'Evidence',
34
+ 'GeometryMode',
35
+ 'Goal',
36
+ 'GraphStats',
37
+ 'Image',
38
+ 'LayeredOptions',
39
+ 'LayeredResult',
40
+ 'PlanOptions',
41
+ 'PriorMasks',
42
+ 'Segmentation',
43
+ 'ViewSuggestion',
44
+ 'Viewport',
45
+ 'compose',
46
+ 'plan_views',
47
+ 'read_image',
48
+ 'stitch_basic',
49
+ 'stitch_layered',
50
+ 'write_image',
51
+ 'NativeUnavailable',
52
+ ]
@@ -0,0 +1,128 @@
1
+ """python -m scrollweave stitch/compose ..."""
2
+
3
+ import argparse
4
+
5
+ from . import (
6
+ AlignOptions,
7
+ Image,
8
+ LayeredOptions,
9
+ PlanOptions,
10
+ Segmentation,
11
+ Viewport,
12
+ __version__,
13
+ compose,
14
+ plan_views,
15
+ stitch_basic,
16
+ stitch_layered,
17
+ )
18
+
19
+
20
+ def main(argv=None):
21
+ parser = argparse.ArgumentParser(prog='scrollweave')
22
+ parser.add_argument('--version', action='version', version=f'%(prog)s {__version__}')
23
+ sub = parser.add_subparsers(dest='operation', required=True)
24
+
25
+ st = sub.add_parser('stitch', help='reconstruct scrolling screenshots')
26
+ st.add_argument('images', nargs='+')
27
+ st.add_argument('-o', '--output', required=True)
28
+ st.add_argument('--algorithm', choices=('layered', 'basic'), default='layered')
29
+ st.add_argument('--positions', help='comma-separated vertical scroll positions')
30
+ st.add_argument('--viewport', help='top:bottom content band')
31
+ st.add_argument('--no-trim', action='store_true')
32
+ st.add_argument('--no-appearance', action='store_true')
33
+ st.add_argument('--segmentation', choices=('temporal', 'graphcut'), default='temporal')
34
+ st.add_argument('--debug-dir')
35
+ st.add_argument('--recommend-views', type=int, default=0)
36
+ st.add_argument('--recommend-repair-views', type=int, default=0)
37
+ st.add_argument('--candidate-positions', help='comma-separated candidate scroll offsets')
38
+ st.add_argument('--minimum-separation', type=int, default=48)
39
+
40
+ cp = sub.add_parser('compose', help='compose without alignment')
41
+ cp.add_argument('images', nargs='+')
42
+ cp.add_argument('-o', '--output', required=True)
43
+ cp.add_argument(
44
+ '--layout', choices=['vertical', 'horizontal', 'grid', 'overlay'], default='vertical'
45
+ )
46
+ cp.add_argument('--columns', type=int, default=2)
47
+ cp.add_argument('--gap', type=int, default=0)
48
+
49
+ args = parser.parse_args(argv)
50
+ if args.operation == 'compose':
51
+ output = compose(args.images, layout=args.layout, columns=args.columns, gap=args.gap)
52
+ output.save(args.output)
53
+ print(f'Saved {args.output}: {output.width}x{output.height}')
54
+ return
55
+
56
+ frames = [Image.open(path) for path in args.images]
57
+ viewport = None
58
+ if args.viewport is not None:
59
+ top, bottom = (int(value) for value in args.viewport.split(':'))
60
+ viewport = Viewport(top, bottom)
61
+ positions = [int(value) for value in args.positions.split(',')] if args.positions else None
62
+
63
+ if args.algorithm == 'basic':
64
+ if positions is None:
65
+ raise SystemExit('stitch --algorithm basic requires --positions')
66
+ result = stitch_basic(
67
+ frames,
68
+ positions=positions,
69
+ viewport=viewport,
70
+ options=AlignOptions(auto_trim=not args.no_trim),
71
+ diagnostics=True,
72
+ )
73
+ placed = len([position for position in result.positions if position is not None])
74
+ else:
75
+ if positions is None:
76
+ raise SystemExit('stitch requires --positions for layered reconstruction')
77
+ if viewport is None and args.no_trim:
78
+ viewport = Viewport(0, frames[0].height)
79
+ options = LayeredOptions(
80
+ appearance=not args.no_appearance,
81
+ segmentation=Segmentation(args.segmentation),
82
+ )
83
+ result = stitch_layered(
84
+ frames,
85
+ scroll=positions,
86
+ viewport=viewport,
87
+ options=options,
88
+ diagnostics=True,
89
+ )
90
+ placed = len(result.scroll)
91
+
92
+ result.save(args.output)
93
+ if args.debug_dir:
94
+ result.save_debug(args.debug_dir)
95
+ for count, goal in (
96
+ (args.recommend_views, 'verification'),
97
+ (args.recommend_repair_views, 'repair'),
98
+ ):
99
+ if count == 0:
100
+ continue
101
+ if not hasattr(result, 'scroll'):
102
+ raise SystemExit('planning requires the layered pipeline')
103
+ planners = plan_views(
104
+ result,
105
+ PlanOptions(
106
+ count=count,
107
+ goal=goal,
108
+ candidates=(
109
+ [int(value) for value in args.candidate_positions.split(',')]
110
+ if args.candidate_positions
111
+ else None
112
+ ),
113
+ minimum_separation=args.minimum_separation,
114
+ ),
115
+ )
116
+ for view in planners:
117
+ print(
118
+ f'Recommend {view.goal.value}: scroll={view.scroll_position}, '
119
+ f'estimated_gain={view.estimated_gain:.1f}'
120
+ )
121
+ print(
122
+ f'Saved {args.output}: {result.image.width}x{result.image.height}, '
123
+ f'{placed}/{len(frames)} frames placed'
124
+ )
125
+
126
+
127
+ if __name__ == '__main__':
128
+ main()