flowocr 0.1.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.
Files changed (91) hide show
  1. flowocr/__init__.py +15 -0
  2. flowocr/analyze/__init__.py +8 -0
  3. flowocr/analyze/align.py +151 -0
  4. flowocr/analyze/build_tracks.py +2886 -0
  5. flowocr/analyze/cluster_layers.py +155 -0
  6. flowocr/analyze/game_align.py +652 -0
  7. flowocr/analyze/gamescript.py +1220 -0
  8. flowocr/analyze/gtdbundle.py +306 -0
  9. flowocr/analyze/match.py +67 -0
  10. flowocr/analyze/matchers/__init__.py +5 -0
  11. flowocr/analyze/matchers/gametext.py +70 -0
  12. flowocr/analyze/merge_nameplate.py +178 -0
  13. flowocr/analyze/models/slot_pair.json +148 -0
  14. flowocr/analyze/nameplate.py +148 -0
  15. flowocr/analyze/pair_features.py +168 -0
  16. flowocr/analyze/pair_model.py +127 -0
  17. flowocr/analyze/refine_boundaries.py +409 -0
  18. flowocr/analyze/script_align.py +641 -0
  19. flowocr/analyze/scriptmatch.py +1018 -0
  20. flowocr/analyze/slot_learned.py +306 -0
  21. flowocr/analyze/slot_lines.py +251 -0
  22. flowocr/analyze/slot_modes.py +143 -0
  23. flowocr/analyze/slot_pairs.py +562 -0
  24. flowocr/analyze/slot_veto.py +104 -0
  25. flowocr/analyze/uigate.py +351 -0
  26. flowocr/artifacts/__init__.py +5 -0
  27. flowocr/artifacts/evalkit.py +123 -0
  28. flowocr/artifacts/matchedio.py +75 -0
  29. flowocr/artifacts/srtio.py +166 -0
  30. flowocr/artifacts/tracksio.py +345 -0
  31. flowocr/extensions.py +77 -0
  32. flowocr/extract/__init__.py +8 -0
  33. flowocr/extract/childproc.py +118 -0
  34. flowocr/extract/decode_proc.py +511 -0
  35. flowocr/extract/decode_shards.py +574 -0
  36. flowocr/extract/detpost.py +215 -0
  37. flowocr/extract/edge_proc.py +314 -0
  38. flowocr/extract/edge_refine.py +598 -0
  39. flowocr/extract/fast_det.py +214 -0
  40. flowocr/extract/ffcheck.py +87 -0
  41. flowocr/extract/framegrid.py +265 -0
  42. flowocr/extract/framesource.py +1264 -0
  43. flowocr/extract/ocr_args.py +754 -0
  44. flowocr/extract/ocr_complete.py +231 -0
  45. flowocr/extract/ocr_parallel.py +208 -0
  46. flowocr/extract/ort_server.py +632 -0
  47. flowocr/extract/ortclient.py +363 -0
  48. flowocr/extract/ptsclock.py +150 -0
  49. flowocr/extract/recdecode.py +78 -0
  50. flowocr/extract/recort.py +162 -0
  51. flowocr/extract/recpack.py +94 -0
  52. flowocr/extract/recpool.py +206 -0
  53. flowocr/extract/recprep.py +71 -0
  54. flowocr/extract/refine_video.py +271 -0
  55. flowocr/extract/regions.py +387 -0
  56. flowocr/extract/reuse_v2.py +593 -0
  57. flowocr/extract/run_groups.py +247 -0
  58. flowocr/extract/run_ocr2.py +1605 -0
  59. flowocr/extract/supervisor.py +261 -0
  60. flowocr/extract/timeline.py +84 -0
  61. flowocr/extract/typewriter_fuse.py +394 -0
  62. flowocr/models.py +263 -0
  63. flowocr/output/__init__.py +3 -0
  64. flowocr/output/export.py +205 -0
  65. flowocr/output/layout.py +210 -0
  66. flowocr/output/presets/__init__.py +6 -0
  67. flowocr/output/presets/_overlay.py +40 -0
  68. flowocr/output/presets/default.py +21 -0
  69. flowocr/output/presets/default_all.py +20 -0
  70. flowocr/output/presets/dev.py +21 -0
  71. flowocr/output/presets/matched_srt.py +32 -0
  72. flowocr/output/presets/script.py +20 -0
  73. flowocr/output/presets/srt_main.py +33 -0
  74. flowocr/output/render.py +82 -0
  75. flowocr/output/run_srt.py +83 -0
  76. flowocr/output/script.py +541 -0
  77. flowocr/paths.py +168 -0
  78. flowocr/provenance.py +119 -0
  79. flowocr/typeset/__init__.py +9 -0
  80. flowocr/typeset/__main__.py +38 -0
  81. flowocr/typeset/assfile.py +216 -0
  82. flowocr/typeset/core.py +821 -0
  83. flowocr/typeset/fx/__init__.py +11 -0
  84. flowocr-0.1.0.dist-info/METADATA +109 -0
  85. flowocr-0.1.0.dist-info/RECORD +91 -0
  86. flowocr-0.1.0.dist-info/WHEEL +5 -0
  87. flowocr-0.1.0.dist-info/entry_points.txt +10 -0
  88. flowocr-0.1.0.dist-info/licenses/LICENSE +674 -0
  89. flowocr-0.1.0.dist-info/licenses/LICENSES/Apache-2.0.txt +201 -0
  90. flowocr-0.1.0.dist-info/licenses/LICENSES/PP-OCRv6-NOTICE.md +18 -0
  91. flowocr-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,143 @@
1
+ """**原型**:跨窗缝合换成"锚点模态"——不依赖顺序、签名不会漂(next-steps 的方案 A 往下再走一步)。
2
+
3
+ 现行 `stitch_slots` 有三处叠在一起的病(读代码看到的,不是猜的):
4
+
5
+ 1. 共边判据取 `min(|Δy0|, |Δy1|)`——**两条边谁近认谁**,同一个槽位可以先靠上边收一个、再靠下边收另一个;
6
+ 2. 签名是成员的**池化并集**,收一个漂一点(`--slot-geom median` 想治它,五部上方向不一致);
7
+ 3. 贪心按 run 数降序挑种子,**结果依赖顺序**(任何改签名的动作都会把分配重洗一遍)。
8
+
9
+ 这里三处一起换掉:
10
+
11
+ * **锚点先于分配定死**:全片所有窗区域的上边 / 下边 / 竖直中心、左边 / 右边 / 水平中心、字高(对数),
12
+ 各自做一维密度估计取**峰**。一个窗区域 = 一票(于是一个时间窗对一个峰至多贡献它的区域数,
13
+ 密集 OCR 的几百条 run 主导不了——方案 B 的"每窗权重设上限"在这里是结构性的)。峰定了就不再动。
14
+ * **对齐方式由整个槽位共同决定**:候选槽位 = 联合格子 `(y 锚类型, y 峰, x 锚类型, x 峰, 字高峰)`,
15
+ 3 × 3 = 9 种对齐组合。同一条带 1 行和 3 行的实例共享"不动的那条边"的峰,落在同一个格子里;
16
+ **高度变化本身不构成驱逐理由**。
17
+ * **按支持度剥离**:每轮取**覆盖不同时间窗最多**的格子,把它里面还没分配的窗区域整体立为一个槽位,
18
+ 再重数。先立住的是"最典型的",不是"最大的";和输入顺序无关(平手按格子键排序)。
19
+
20
+ ⚠ 已知管不了的:**不同时刻占同一位置的不同元素**(纯几何分不开)、**随时间迁移的带**(峰是全片的)。
21
+ 先拿尺量,再决定要不要加时间。
22
+
23
+ 用法:
24
+ from flowocr.analyze import slot_modes; slots = slot_modes.stitch(L) # L 来自 cluster_layers.load
25
+ python -m flowocr.analyze.slot_modes f5 # 打印槽位统计
26
+ """
27
+ from __future__ import annotations
28
+
29
+ import bisect
30
+ import math
31
+ import sys
32
+ from collections import defaultdict
33
+
34
+
35
+ BW_Y, BW_X, BW_H = 0.012, 0.015, math.log(1.25)
36
+ """核宽:y / x 是画面高 / 宽的比例(1080p 上约 13 px / 29 px),字高是对数比。"""
37
+ H_GAP = math.log(1.3)
38
+ """同一个槽位里字高排序后相邻两个的比超过它就断开(现行 `slot_fits` 的字高比门是 1.7,那个是对签名比的)。"""
39
+ MIN_SUPPORT = 2
40
+ """格子至少覆盖这么多个时间窗才立槽;再小的各自成单例槽(不硬凑)。"""
41
+
42
+
43
+ def peaks(vals: list[float], bw: float) -> list[float]:
44
+ """一维三角核密度的局部极大(每个样本一票)。返回峰的位置,升序。"""
45
+ xs = sorted(vals)
46
+ if not xs:
47
+ return []
48
+
49
+ def dens(v: float) -> float:
50
+ lo, hi = bisect.bisect_left(xs, v - bw), bisect.bisect_right(xs, v + bw)
51
+ return sum(1 - abs(xs[k] - v) / bw for k in range(lo, hi))
52
+ uniq = sorted(set(xs))
53
+ d = [dens(v) for v in uniq]
54
+ out = []
55
+ for i, v in enumerate(uniq):
56
+ lo, hi = bisect.bisect_left(uniq, v - bw), bisect.bisect_right(uniq, v + bw)
57
+ if d[i] >= max(d[lo:hi]) and (not out or v - out[-1] > bw):
58
+ out.append(v)
59
+ return out
60
+
61
+
62
+ def snap(v: float, pk: list[float], bw: float) -> int:
63
+ """最近的峰的下标;超出 1.5 个核宽就不属于任何峰(-1)。"""
64
+ i = bisect.bisect_left(pk, v)
65
+ best = min((j for j in (i - 1, i) if 0 <= j < len(pk)), key=lambda j: abs(pk[j] - v), default=-1)
66
+ return best if best >= 0 and abs(pk[best] - v) <= 1.5 * bw else -1
67
+
68
+
69
+ def stitch(L, bw_y: float = BW_Y, bw_x: float = BW_X, min_support: int = MIN_SUPPORT) -> list[list[int]]:
70
+ wr = L.win_regions
71
+ from flowocr.analyze import build_tracks as bt # 在这里 import:`slot_lines` 只借本模块的 peaks / snap,别把管线拖进来
72
+ g = [bt.region_geom(L.runs, w["runs"]) for w in wr] # (cx, x0, x1, y0, y1, h)
73
+ feats = {
74
+ "yt": [q[3] / L.H for q in g], "yb": [q[4] / L.H for q in g], "ym": [(q[3] + q[4]) / 2 / L.H for q in g],
75
+ "xl": [q[1] / L.W for q in g], "xr": [q[2] / L.W for q in g], "xc": [q[0] / L.W for q in g],
76
+ "h": [math.log(max(1.0, q[5])) for q in g],
77
+ }
78
+ bws = {"yt": bw_y, "yb": bw_y, "ym": bw_y, "xl": bw_x, "xr": bw_x, "xc": bw_x, "h": BW_H}
79
+ pid = {}
80
+ for name, vals in feats.items():
81
+ pk = peaks(vals, bws[name])
82
+ pid[name] = [snap(v, pk, bws[name]) for v in vals]
83
+
84
+ # 字高**不当锚点**(第一版当了):它是个软量,量化成峰之后 36 和 38 会落在两个峰的两侧,
85
+ # 同一条对话带(cx 相同、上边差 5 px)就这么被拆开(gi2-s1-023 / 038,标注是 same)。
86
+ # 改成剥离时的相容性检查:格子里按 log 字高排序、相邻差 > H_GAP 处断开,取最大的那一段。
87
+ keys_of: list[list[tuple]] = []
88
+ for i in range(len(wr)):
89
+ ks = []
90
+ for ty in ("yt", "yb", "ym"):
91
+ for tx in ("xl", "xc", "xr"):
92
+ if pid[ty][i] >= 0 and pid[tx][i] >= 0:
93
+ ks.append((ty, pid[ty][i], tx, pid[tx][i]))
94
+ keys_of.append(ks)
95
+
96
+ def h_major(mem: set[int]) -> list[int]:
97
+ order = sorted(mem, key=lambda i: feats["h"][i])
98
+ runs_, cur = [], [order[0]]
99
+ for a_, b_ in zip(order, order[1:]):
100
+ if feats["h"][b_] - feats["h"][a_] > H_GAP:
101
+ runs_.append(cur)
102
+ cur = []
103
+ cur.append(b_)
104
+ runs_.append(cur)
105
+ return max(runs_, key=lambda r: (len({wr[i]["win"] for i in r}), -r[0]))
106
+
107
+ cell: dict[tuple, set[int]] = defaultdict(set)
108
+ for i, ks in enumerate(keys_of):
109
+ for k in ks:
110
+ cell[k].add(i)
111
+ free = set(range(len(wr)))
112
+ slots: list[list[int]] = []
113
+ while True:
114
+ best, best_s = None, 0
115
+ for k, mem in cell.items():
116
+ if len(mem) < max(best_s, min_support):
117
+ continue # 成员数是窗数的上界,先剪
118
+ s = len({wr[i]["win"] for i in h_major(mem)})
119
+ if s > best_s or (s == best_s and best is not None and k < best):
120
+ best, best_s = k, s
121
+ if best is None or best_s < min_support:
122
+ break
123
+ mem = sorted(h_major(cell[best]))
124
+ slots.append(mem)
125
+ for i in mem:
126
+ free.discard(i)
127
+ for k in keys_of[i]:
128
+ cell[k].discard(i)
129
+ cell = {k: v for k, v in cell.items() if v}
130
+ slots.extend([i] for i in sorted(free))
131
+ return slots
132
+
133
+
134
+ if __name__ == "__main__":
135
+ from flowocr.analyze import cluster_layers as CL
136
+ for tag in sys.argv[1:]:
137
+ L = CL.load(tag)
138
+ sl = stitch(L)
139
+ multi = [s for s in sl if len(s) > 1]
140
+ nrun = sorted((sum(len(L.win_regions[w]["runs"]) for w in s) for s in sl), reverse=True)
141
+ print(f"{tag}: 窗区域 {len(L.win_regions)} -> 槽位 {len(sl)}(多成员 {len(multi)}、单例 {len(sl) - len(multi)});"
142
+ f"最大的五个槽位 run 数 {nrun[:5]},单例里的 run 占 "
143
+ f"{sum(len(L.win_regions[s[0]]['runs']) for s in sl if len(s) == 1) / len(L.runs):.1%}")