screenshot-vision-algorithm 0.3.5__tar.gz → 0.3.10__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.
Files changed (48) hide show
  1. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/PKG-INFO +1 -1
  2. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/pyproject.toml +1 -1
  3. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm/android/wechat/algorithms/speaker_band.py +12 -4
  4. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm/android/wechat/ocr/avatar_guard.py +1 -1
  5. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm/android/wechat/ocr/nickname_binding.py +78 -15
  6. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm.egg-info/PKG-INFO +1 -1
  7. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/README.md +0 -0
  8. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/setup.cfg +0 -0
  9. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm/__init__.py +0 -0
  10. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm/_config.py +0 -0
  11. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm/android/__init__.py +0 -0
  12. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm/android/wechat/__init__.py +0 -0
  13. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm/android/wechat/algorithms/__init__.py +0 -0
  14. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm/android/wechat/algorithms/avatar_column.py +0 -0
  15. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm/android/wechat/algorithms/badge_detection.py +0 -0
  16. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm/android/wechat/algorithms/card_bbox.py +0 -0
  17. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm/android/wechat/algorithms/phash_utils.py +0 -0
  18. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm/android/wechat/algorithms/template_matching.py +0 -0
  19. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm/android/wechat/algorithms/title_ocr.py +0 -0
  20. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm/android/wechat/merge/__init__.py +0 -0
  21. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm/android/wechat/merge/multipage.py +0 -0
  22. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm/android/wechat/ocr/__init__.py +0 -0
  23. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm/android/wechat/ocr/badge_ocr.py +0 -0
  24. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm/android/wechat/ocr/text_ocr_adapter.py +0 -0
  25. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm/android/wechat/profiles/__init__.py +0 -0
  26. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm/android/wechat/profiles/android.py +0 -0
  27. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm/android/wechat/profiles/harmony.py +0 -0
  28. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm/android/wechat/profiles/ios.py +0 -0
  29. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm/android/wechat/templates/android/8.0.69/chat_back_chevron.png +0 -0
  30. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm/android/wechat/templates/android/8.0.69/chat_input_emoji_smile.png +0 -0
  31. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm/android/wechat/templates/android/8.0.69/chat_input_plus.png +0 -0
  32. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm/android/wechat/templates/android/8.0.69/chat_input_voice.png +0 -0
  33. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm/android/wechat/templates/android/8.0.69/chat_title_more_dots.png +0 -0
  34. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm/android/wechat/templates/android/8.0.69/favorite_label.png +0 -0
  35. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm/android/wechat/templates/android/8.0.69/new_messages_hint_suffix.png +0 -0
  36. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm/android/wechat/templates/android/8.0.69/unread_divider_hint.png +0 -0
  37. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm/android/wechat/templates/android/8.0.69/unread_divider_hint_v2_textonly.png +0 -0
  38. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm/android/wechat/templates/android/8.0.69/wechat_note_header.png +0 -0
  39. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm/android/xhs/__init__.py +0 -0
  40. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm/android/zhihu/__init__.py +0 -0
  41. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm/logging.py +0 -0
  42. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm/png_utils.py +0 -0
  43. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm.egg-info/SOURCES.txt +0 -0
  44. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm.egg-info/dependency_links.txt +0 -0
  45. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm.egg-info/requires.txt +0 -0
  46. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/src/screenshot_vision_algorithm.egg-info/top_level.txt +0 -0
  47. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/tests/test_card_bbox.py +0 -0
  48. {screenshot_vision_algorithm-0.3.5 → screenshot_vision_algorithm-0.3.10}/tests/test_package.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: screenshot_vision_algorithm
3
- Version: 0.3.5
3
+ Version: 0.3.10
4
4
  Summary: Multi-platform screenshot vision algorithms: WeChat (card bbox, speaker bands, avatar detection, OCR helpers), XHS & Zhihu (planned)
5
5
  License: MIT
6
6
  Requires-Python: >=3.10
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "screenshot_vision_algorithm"
7
- version = "0.3.5"
7
+ version = "0.3.10"
8
8
  description = "Multi-platform screenshot vision algorithms: WeChat (card bbox, speaker bands, avatar detection, OCR helpers), XHS & Zhihu (planned)"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -26,7 +26,7 @@ from screenshot_vision_algorithm.android.wechat.algorithms.template_matching imp
26
26
  CHAT_BAND_FIRST_AVATAR_TOP_GAP_REJECT_BASELINE = 30
27
27
 
28
28
  #: 首个发言人筒上移量(一个头像高度,防止几何误差),见 PRD §5 补充2。
29
- CHAT_FIRST_BAND_TOP_EXTEND_BASELINE = 100
29
+ CHAT_FIRST_BAND_TOP_EXTEND_BASELINE = 20
30
30
 
31
31
  #: 底部输入区预留(与主列表 scanner 一脉,baseline)。
32
32
  CHAT_COMPOSER_RESERVE_BOTTOM_BASELINE = 160
@@ -173,7 +173,7 @@ def detect_chat_side_avatar_ytops(
173
173
  y1 = max(0, bounds.y_top)
174
174
  y2 = min(h, bounds.y_bottom_excl)
175
175
  try:
176
- from processor.left_avatar_column import (
176
+ from screenshot_vision_algorithm.android.wechat.algorithms.avatar_column import (
177
177
  detect_left_avatar_column_layout,
178
178
  left_avatar_ytops,
179
179
  )
@@ -221,6 +221,7 @@ def build_prd_speaker_vertical_bands(
221
221
  scale_w: float,
222
222
  first_avatar_gap_reject_baseline: int = CHAT_BAND_FIRST_AVATAR_TOP_GAP_REJECT_BASELINE,
223
223
  first_band_top_extend_baseline: int = CHAT_FIRST_BAND_TOP_EXTEND_BASELINE,
224
+ avatar_merge_min_sep_baseline: int = 24,
224
225
  ) -> PrdChatVerticalLayout:
225
226
  """由时间序头像顶构造 PRD §5 顶隙 orphan + 左闭右开发言人筒。
226
227
 
@@ -248,13 +249,13 @@ def build_prd_speaker_vertical_bands(
248
249
 
249
250
  # 时间轴序去重:过近视为同一人头像重复检出
250
251
  merged_starts: list[int] = []
251
- min_sep = int(round(24 * sw))
252
+ min_sep = int(round(int(avatar_merge_min_sep_baseline) * sw))
252
253
  for e in timeline:
253
254
  y = int(e.y_top)
254
255
  if not merged_starts or y - merged_starts[-1] >= min_sep:
255
256
  merged_starts.append(y)
256
257
 
257
- # 首个筒上界上移(PRD §5 补充2):上移 100px @1080,防几何误差与系统变化。
258
+ # 首个筒上界上移(PRD §5 补充2):上移 20px @1080,防几何误差与系统变化。
258
259
  first_band_extend = int(round(int(first_band_top_extend_baseline) * sw))
259
260
 
260
261
  bands: list[tuple[int, int]] = []
@@ -275,6 +276,13 @@ def build_prd_speaker_vertical_bands(
275
276
  elif first_band_start < orphan_top[1]:
276
277
  orphan_top = (orphan_top[0], first_band_start)
277
278
 
279
+ # 无 orphan 时,首个 band 上界对齐 y_ct,保证纵向连续全覆盖
280
+ # (extend 较小且头像紧贴 y_ct 时可能产生小间隙)
281
+ if orphan_top is None and bands:
282
+ first_y0, first_y1 = bands[0]
283
+ if first_y0 > y_ct:
284
+ bands[0] = (y_ct, first_y1)
285
+
278
286
  return PrdChatVerticalLayout(
279
287
  content_bounds=bounds,
280
288
  orphan_top_band=orphan_top,
@@ -19,7 +19,7 @@ except ImportError:
19
19
  cv2 = None # type: ignore[assignment]
20
20
 
21
21
  try:
22
- from processor.left_avatar_column import AvatarCentroid
22
+ from screenshot_vision_algorithm.android.wechat.algorithms.avatar_column import AvatarCentroid
23
23
  except ImportError:
24
24
  AvatarCentroid = None # type: ignore[misc, assignment]
25
25
 
@@ -230,6 +230,32 @@ class NicknameOcrConfig:
230
230
  """Prefix / substring tags that exclude a block from nickname
231
231
  consideration even if all geometric criteria pass."""
232
232
 
233
+ # ── 以下字段可由 rules_config.yaml 覆盖(housekeeping 胶水层读 yaml → 灌参)──
234
+
235
+ skip_block_patterns: tuple[str, ...] = ()
236
+ """附加正则列表,逐条编译后传递给 ``_should_skip_block``。
237
+ 内置正则(时间戳/纯数字/系统提示等)**始终并行生效**,
238
+ config 提供的是追加规则,不替代内置逻辑。"""
239
+
240
+ inside_y_tolerance_px: float = 4.0
241
+ """``_completely_inside`` 的 Y 方向容差(px @ 1080 基准 × scale_w)。
242
+ 默认 4.0 与 ``card_binding.inside_y_tolerance_px`` 对齐。"""
243
+
244
+ cross_band_distance_threshold_px: float = 50.0
245
+ """Step C 跨 band 距离门控基线(px @ 1080 × scale_w)。
246
+ 文字底部到卡片所属 speaker_band 顶部超过此值则不作为昵称。"""
247
+
248
+ # ── speaker_band 参数(同名传给 build_prd_speaker_vertical_bands)──
249
+
250
+ first_avatar_gap_reject_baseline: int = 30
251
+ """orphan 间隙阈值(px @ 1080)。"""
252
+
253
+ first_band_top_extend_baseline: int = 20
254
+ """首个 band 上移量(px @ 1080)。"""
255
+
256
+ avatar_merge_min_sep_px: int = 24
257
+ """头像合并最小间距(px @ 1080 × scale_w)。"""
258
+
233
259
 
234
260
  _ATTRIBUTION_PREFIX_DROPS: tuple[str, ...] = (
235
261
  "撤回了一条消息",
@@ -351,6 +377,9 @@ _PURE_TIME_RE = re.compile(r"^\d{1,2}:\d{2}(:\d{2})?$")
351
377
  _WEEKDAY_TIME_RE = re.compile(
352
378
  r"^周[一二三四五六日天].*?(?:上午|下午|晚上|凌晨)?\s*\d{0,2}[::]?\d{0,2}"
353
379
  )
380
+ _YESTERDAY_TODAY_TIME_RE = re.compile(
381
+ r"^(?:昨天|今天)\s*\d{0,2}:?\d{0,2}$"
382
+ )
354
383
  _MOBILE_PHONE_SUFFIX_RE = re.compile(r"1\d{10}\s*$")
355
384
 
356
385
 
@@ -388,6 +417,8 @@ def _is_chat_timestamp_or_divider(text: str) -> bool:
388
417
  return True
389
418
  if _WEEKDAY_TIME_RE.match(t):
390
419
  return True
420
+ if _YESTERDAY_TODAY_TIME_RE.match(t):
421
+ return True
391
422
  return False
392
423
 
393
424
 
@@ -411,7 +442,7 @@ def _is_avatar_column_nickname_row(
411
442
  avatar_layout: Any = None,
412
443
  ) -> bool:
413
444
  try:
414
- from processor.left_avatar_column import nickname_bbox_in_avatar_column
445
+ from screenshot_vision_algorithm.android.wechat.algorithms.avatar_column import nickname_bbox_in_avatar_column
415
446
  except ImportError:
416
447
  nickname_bbox_in_avatar_column = None # type: ignore[assignment,misc]
417
448
  if avatar_layout is not None and nickname_bbox_in_avatar_column is not None:
@@ -446,7 +477,7 @@ def _avatar_anchor_for_block(
446
477
  if avatar_layout is None or getattr(avatar_layout, "empty", True):
447
478
  return None
448
479
  try:
449
- from processor.left_avatar_column import find_avatar_anchor_for_nickname_bbox
480
+ from screenshot_vision_algorithm.android.wechat.algorithms.avatar_column import find_avatar_anchor_for_nickname_bbox
450
481
  except ImportError:
451
482
  return None
452
483
  return find_avatar_anchor_for_nickname_bbox(
@@ -463,9 +494,9 @@ def _load_left_avatar_layout(
463
494
  if png_path is None:
464
495
  return None
465
496
  try:
466
- from processor.chat_bubble_geometry_prd import compute_chat_content_vertical_bounds
467
- from processor.left_avatar_column import detect_left_avatar_column_layout
468
- from processor.nickname_avatar_guard import load_png_bgr
497
+ from screenshot_vision_algorithm.android.wechat.algorithms.speaker_band import compute_chat_content_vertical_bounds
498
+ from screenshot_vision_algorithm.android.wechat.algorithms.avatar_column import detect_left_avatar_column_layout
499
+ from screenshot_vision_algorithm.android.wechat.ocr.avatar_guard import load_png_bgr
469
500
  except ImportError:
470
501
  return None
471
502
  arr = load_png_bgr(str(png_path))
@@ -595,7 +626,7 @@ def _nickname_passes_avatar_guard(
595
626
  if bgr_guard is None:
596
627
  return not config.require_avatar_guard_for_nickname
597
628
  try:
598
- from processor.nickname_avatar_guard import avatar_roi_pass
629
+ from screenshot_vision_algorithm.android.wechat.ocr.avatar_guard import avatar_roi_pass
599
630
  except ImportError:
600
631
  return not config.require_avatar_guard_for_nickname
601
632
 
@@ -687,13 +718,13 @@ def _load_prd_layout_optional(
687
718
  if png_path is None or not config.use_prd_chat_vertical_bands:
688
719
  return None
689
720
  try:
690
- from processor.chat_bubble_geometry_prd import (
721
+ from screenshot_vision_algorithm.android.wechat.algorithms.speaker_band import (
691
722
  build_prd_speaker_vertical_bands,
692
723
  compute_chat_content_vertical_bounds,
693
724
  detect_chat_side_avatar_ytops,
694
725
  merge_avatar_ytops_time_order,
695
726
  )
696
- from processor.nickname_avatar_guard import load_png_bgr
727
+ from screenshot_vision_algorithm.android.wechat.ocr.avatar_guard import load_png_bgr
697
728
  except ImportError:
698
729
  return None
699
730
  arr = load_png_bgr(str(png_path))
@@ -705,7 +736,12 @@ def _load_prd_layout_optional(
705
736
  bounds = compute_chat_content_vertical_bounds(arr, sw, screen_h=oh)
706
737
  ly, ry = detect_chat_side_avatar_ytops(arr, sw, bounds)
707
738
  timeline = merge_avatar_ytops_time_order(ly, ry)
708
- return build_prd_speaker_vertical_bands(timeline, bounds, scale_w=sw)
739
+ return build_prd_speaker_vertical_bands(
740
+ timeline, bounds, scale_w=sw,
741
+ first_avatar_gap_reject_baseline=config.first_avatar_gap_reject_baseline,
742
+ first_band_top_extend_baseline=config.first_band_top_extend_baseline,
743
+ avatar_merge_min_sep_baseline=config.avatar_merge_min_sep_px,
744
+ )
709
745
 
710
746
 
711
747
  def _finalize_prd_attribution(
@@ -882,7 +918,7 @@ def _resume_thumb_bindings_and_orphans(
882
918
  # OCR bbox 与卡片 bbox 各有 ±2~3px 抖动:卡内标题行可能比卡顶高出 1~3px,
883
919
  # 若按严格包含判定会被误认为"卡外文字"而误绑为昵称(2026-06-11 真实案例)。
884
920
  # 真昵称行整体位于卡片上方(y2 < 卡顶),不受此容差影响。
885
- _inside_tol = 4.0 * max(scale_w, 1.0)
921
+ _inside_tol = float(config.inside_y_tolerance_px) * max(scale_w, 1.0)
886
922
 
887
923
  def _completely_inside(block_bbox: tuple, card_bbox: tuple) -> bool:
888
924
  bx1, by1, bx2, by2 = block_bbox
@@ -912,8 +948,25 @@ def _resume_thumb_bindings_and_orphans(
912
948
  return True
913
949
  if _is_favorite_card_label(t):
914
950
  return True
951
+ # ── 附加正则(来自 rules_config.yaml skip_patterns,编译后缓存)──
952
+ if _compiled_skip_patterns:
953
+ for _pat in _compiled_skip_patterns:
954
+ try:
955
+ if _pat.search(t):
956
+ return True
957
+ except Exception:
958
+ continue
915
959
  return False
916
960
 
961
+ # ── 编译 config 追加的正则(函数内缓存,避免每次 block 重新编译)──
962
+ _compiled_skip_patterns: list[re.Pattern] = []
963
+ if config.skip_block_patterns:
964
+ for p in config.skip_block_patterns:
965
+ try:
966
+ _compiled_skip_patterns.append(re.compile(p))
967
+ except re.error:
968
+ pass
969
+
917
970
  bindings: list[ResumeThumbBinding] = []
918
971
  orphan = 0
919
972
  excluded_indices: set[int] = set()
@@ -962,7 +1015,17 @@ def _resume_thumb_bindings_and_orphans(
962
1015
  _cy1, _cy2 = float(tbb[1]), float(tbb[3])
963
1016
  for _bi, (_b0, _b1) in enumerate(_sb_list):
964
1017
  if _cy2 > float(_b0) and _cy1 < float(_b1):
965
- band_idx = _bi # 相交 → 候选;持续覆盖 → 最后一个(下方)胜出
1018
+ band_idx = _bi # 相交 → 候选;持续覆盖 → 最后一个(下方)胜出
1019
+
1020
+ # ── 如果卡片与 orphan_top_band 相交 → 直接记为找不到归属 ──
1021
+ if layout_opt is not None:
1022
+ ot = getattr(layout_opt, "orphan_top_band", None)
1023
+ if ot is not None:
1024
+ oy0, oy1 = float(ot[0]), float(ot[1])
1025
+ _cy1, _cy2 = float(tbb[1]), float(tbb[3])
1026
+ if _cy2 > oy0 and _cy1 < oy1:
1027
+ orphan += 1
1028
+ continue
966
1029
 
967
1030
  matched_nickname: Optional[str] = None
968
1031
 
@@ -1053,14 +1116,14 @@ def _resume_thumb_bindings_and_orphans(
1053
1116
 
1054
1117
  if best_i is not None:
1055
1118
  # PRD §B.1 C:距离检查——文字底部到卡片所属 speaker_band 顶部
1056
- # 超过阈值(50px baseline × scale_w)则不作为昵称
1119
+ # 超过阈值(config.cross_band_distance_threshold_px × scale_w)则不作为昵称
1057
1120
  _use_as_nickname = True
1058
1121
  if band_idx is not None and band_idx >= 0:
1059
1122
  _sb_list = getattr(layout_opt, "speaker_bands", ())
1060
1123
  if band_idx < len(_sb_list):
1061
1124
  _band_y0 = float(_sb_list[band_idx][0])
1062
1125
  _gap = _band_y0 - best_bottom # positive → block above band
1063
- _threshold = 50.0 * float(scale_w)
1126
+ _threshold = float(config.cross_band_distance_threshold_px) * float(scale_w)
1064
1127
  if _gap > _threshold:
1065
1128
  _use_as_nickname = False
1066
1129
  if _use_as_nickname:
@@ -1177,7 +1240,7 @@ def first_attrib_verbatim_display_line_and_bbox(
1177
1240
  scale_w = float(original_width) / 1080.0
1178
1241
  if png_path is not None:
1179
1242
  try:
1180
- from processor.nickname_avatar_guard import load_png_bgr
1243
+ from screenshot_vision_algorithm.android.wechat.ocr.avatar_guard import load_png_bgr
1181
1244
 
1182
1245
  bgr_guard = load_png_bgr(str(png_path))
1183
1246
  except ImportError:
@@ -1748,7 +1811,7 @@ def extract_nicknames(
1748
1811
  bgr_guard = None
1749
1812
  if png_path is not None:
1750
1813
  try:
1751
- from processor.nickname_avatar_guard import load_png_bgr
1814
+ from screenshot_vision_algorithm.android.wechat.ocr.avatar_guard import load_png_bgr
1752
1815
 
1753
1816
  bgr_guard = load_png_bgr(str(png_path))
1754
1817
  except ImportError:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: screenshot_vision_algorithm
3
- Version: 0.3.5
3
+ Version: 0.3.10
4
4
  Summary: Multi-platform screenshot vision algorithms: WeChat (card bbox, speaker bands, avatar detection, OCR helpers), XHS & Zhihu (planned)
5
5
  License: MIT
6
6
  Requires-Python: >=3.10