From e8666d027160e052f452927bcaec13c246e38432 Mon Sep 17 00:00:00 2001 From: ila Date: Wed, 24 Jun 2026 09:29:17 +0800 Subject: [PATCH] chore: add stamp match report script --- match_stamp.py | 156 +++++++++++++++++++++++++++++++++++++++++++++++++ tasks.md | 14 +++++ 2 files changed, 170 insertions(+) create mode 100644 match_stamp.py diff --git a/match_stamp.py b/match_stamp.py new file mode 100644 index 0000000..51d8c5e --- /dev/null +++ b/match_stamp.py @@ -0,0 +1,156 @@ +"""Generate a CSV report matching merged images to source stamp images. + +This script is intentionally standalone and does not import cmbot project code. +Edit the three paths below, then run: + + python match_stamp.py + +It does not copy, move, rename, or delete any image files. +""" +from __future__ import print_function + +import csv +from datetime import datetime +from pathlib import Path + + +OUTPUT_DIR = r"D:\chengma\cmbot\output\20260623_094529" +STAMP_DIR = r"D:\chengma\印花和底图\已处理印花\卡通71(66大码200斤 KEKE已上)\横1" +REPORT_PATH = r"D:\chengma\cmbot\match_stamp_report.csv" + +IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".webp"} +FIELDNAMES = [ + "status", + "code", + "merged_image", + "stamp_image", + "merged_folder", + "message", +] + + +def is_image(path): + return path.is_file() and path.suffix.lower() in IMAGE_EXTENSIONS + + +def extract_code_from_merged_name(path): + """Extract stamp code from the final underscore suffix. + + Example: 1_TY037.png -> TY037. + """ + stem = path.stem + if "_" not in stem: + return "" + code = stem.rsplit("_", 1)[1].strip() + return code + + +def iter_images(root): + root = Path(root) + if not root.exists(): + return + for path in sorted(root.rglob("*")): + if is_image(path): + yield path + + +def build_stamp_index(stamp_dir): + index = {} + for path in iter_images(stamp_dir): + code = path.stem.strip() + if not code: + continue + index.setdefault(code, []).append(path.resolve()) + return index + + +def match_merged_image(path, stamp_index): + code = extract_code_from_merged_name(path) + if not code: + return { + "status": "bad_name", + "code": "", + "merged_image": str(path.resolve()), + "stamp_image": "", + "merged_folder": str(path.parent.resolve()), + "message": "File name has no underscore suffix code", + } + + candidates = stamp_index.get(code, []) + if not candidates: + return { + "status": "missing", + "code": code, + "merged_image": str(path.resolve()), + "stamp_image": "", + "merged_folder": str(path.parent.resolve()), + "message": "No stamp image found for code", + } + + stamp_paths = " | ".join(str(p) for p in candidates) + if len(candidates) > 1: + return { + "status": "duplicate", + "code": code, + "merged_image": str(path.resolve()), + "stamp_image": stamp_paths, + "merged_folder": str(path.parent.resolve()), + "message": "Multiple stamp images found for code", + } + + return { + "status": "matched", + "code": code, + "merged_image": str(path.resolve()), + "stamp_image": stamp_paths, + "merged_folder": str(path.parent.resolve()), + "message": "", + } + + +def write_csv(rows, report_path): + report_path = Path(report_path) + report_path.parent.mkdir(parents=True, exist_ok=True) + with report_path.open("w", encoding="utf-8-sig", newline="") as f: + writer = csv.DictWriter(f, fieldnames=FIELDNAMES) + writer.writeheader() + for row in rows: + writer.writerow(row) + + +def generate_report(output_dir, stamp_dir, report_path): + output_dir = Path(output_dir) + stamp_dir = Path(stamp_dir) + + stamp_index = build_stamp_index(stamp_dir) + rows = [] + for path in iter_images(output_dir): + rows.append(match_merged_image(path, stamp_index)) + + write_csv(rows, report_path) + return rows + + +def summarize(rows): + counts = {} + for row in rows: + status = row.get("status", "") + counts[status] = counts.get(status, 0) + 1 + return counts + + +def main(): + started = datetime.now() + rows = generate_report(OUTPUT_DIR, STAMP_DIR, REPORT_PATH) + counts = summarize(rows) + print("Generated report: {}".format(Path(REPORT_PATH).resolve())) + print("Merged images: {}".format(len(rows))) + print("Matched: {}".format(counts.get("matched", 0))) + print("Missing: {}".format(counts.get("missing", 0))) + print("Duplicate: {}".format(counts.get("duplicate", 0))) + print("Bad name: {}".format(counts.get("bad_name", 0))) + print("Elapsed: {}".format(datetime.now() - started)) + + +if __name__ == "__main__": + main() diff --git a/tasks.md b/tasks.md index 10f007b..c033ce3 100644 --- a/tasks.md +++ b/tasks.md @@ -1541,3 +1541,17 @@ - [x] 行为:如果过滤后没有可选图片模型,显示「未配置可用图片模型」类占位并保持开始生成前校验提示 - [x] 测试:标题模型不出现在图片 AI 模型下拉;图片模型仍显示;标题生成仍能按 `title_model` 找到同名模型;只有标题模型时下拉为空/占位 - [x] 验证:`py_compile`、`test_ai_outfit_panel.py`、全套 `python -m unittest discover -s tests`、离屏启动 AI 穿搭页通过 + +### 19.30 独立脚本:合并图匹配印花清单 — `match_stamp.py` + +背景: + +需要一个独立脚本(不引用现有项目代码)按“方案2”生成匹配清单:例如合并图 `output\20260623_094529\TY037\1_TY037.png`,从文件名后缀提取 `TY037`,在印花文件夹 `D:\chengma\印花和底图\已处理印花\卡通71(66大码200斤 KEKE已上)\横1` 中匹配 `TY037.png`,输出 CSV 清单。 + +任务: + +- [x] 新增 `match_stamp.py`,只使用 Python 标准库,不导入 `src/` 或项目服务代码 +- [x] 脚本顶部提供 `OUTPUT_DIR` / `STAMP_DIR` / `REPORT_PATH`,默认生成 `match_stamp_report.csv` +- [x] 递归扫描合并图和印花图,按合并图文件名最后一个 `_` 后的编码匹配印花文件 stem +- [x] CSV 字段:`status` / `code` / `merged_image` / `stamp_image` / `merged_folder` / `message`,支持 `matched` / `missing` / `duplicate` / `bad_name` +- [x] 验证:`python -m py_compile match_stamp.py`;用示例目录生成系统临时 CSV,匹配 96 张、缺失 0、重复 0、坏名 0