Spaces:
Paused
Paused
| # -*- coding: utf-8 -*- | |
| # ============================================================ | |
| # patch_detected.py — แก้ repo 2DImage2BMI ให้รันได้บน Hugging Face Space | |
| # | |
| # ===== ต้องการอะไร ===== | |
| # repo ต้นฉบับรันบนเครื่องเราไม่ได้ ด้วย 2 เหตุผล: | |
| # (1) import ของ 4 ชิ้นที่ build ไม่ผ่าน (SCHP/PSP/CPM/CRFRNN) | |
| # (2) ฮาร์ดโค้ด .cuda() ไว้ -> ถ้าเครื่องไม่มี GPU จะพังทันที | |
| # Kaggle มี T4 เลยเจอแค่ปัญหา (1) ส่วน (2) ไม่เคยโผล่ | |
| # พอย้ายมา Space ฟรีที่รันบน CPU -> เจอปัญหา (2) เต็มๆ | |
| # | |
| # ===== ไฟล์นี้ทำอะไร ===== | |
| # แก้ 2 ไฟล์ในrepo ให้ใช้งานได้: | |
| # Detected.py -> ตัด import ที่ไม่มี + เปลี่ยน .cuda() เป็น auto-detect | |
| # modeling/affine_align.py -> เปลี่ยน .cuda() เป็น auto-detect | |
| # รันซ้ำได้ ไม่พัง (เช็ค marker ก่อนแก้ทุกครั้ง) | |
| # | |
| # ===== วิธีคิด ===== | |
| # *** ไม่ฮาร์ดโค้ดเป็น 'cpu' *** แต่ทำเป็น auto-detect: | |
| # _DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu' | |
| # -> มี GPU ก็ใช้ GPU / ไม่มีก็ใช้ CPU = ใช้ได้ทุก hardware ไม่ต้องแก้อีก | |
| # | |
| # *** ห้ามเอา SCHP/PSP/CPM/CRFRNN กลับมา แม้จะลงได้ *** | |
| # ของพวกนั้นมีไว้ "ลบแขนออกจาก mask" ก่อนวัดสัดส่วน | |
| # แต่โมเดล SVR ของเราเทรนบน mask ที่ "มีแขน" อยู่แล้ว | |
| # ถ้าตอนใช้งานไปตัดแขน -> WSR/Area/H2W เปลี่ยนหมด -> SVR เจอเลขที่ไม่เคยเห็น -> BMI มั่ว | |
| # กฎเหล็ก: ตอนใช้ต้องเหมือนตอนเทรนเป๊ะ รวมถึงข้อบกพร่องด้วย | |
| # ============================================================ | |
| import sys | |
| import os | |
| MARK = '# [cut] ' # เครื่องหมายของบรรทัดที่ถูกคอมเมนต์ทิ้ง | |
| TAG = '# [patched]' # เครื่องหมายของบรรทัดที่ถูกแก้ (ใช้เช็คว่า patch ไปแล้วหรือยัง) | |
| # ---------- โค้ดตรวจหา device ที่จะแทรกเข้าไป ---------- | |
| DEVICE_SNIPPET = ( | |
| f"import torch as _torch {TAG}\n" | |
| f"_DEVICE = 'cuda' if _torch.cuda.is_available() else 'cpu' {TAG} auto-detect: มี GPU ใช้ GPU / ไม่มีใช้ CPU" | |
| ) | |
| # ============================================================ | |
| # รายการแก้ของแต่ละไฟล์ | |
| # ============================================================ | |
| PATCHES = { | |
| # ---------------- ไฟล์หลัก ---------------- | |
| 'Detected.py': { | |
| # (1) คอมเมนต์ทิ้ง: import ของที่เราไม่มี + โค้ดที่เรียกใช้มัน | |
| 'cut': [ | |
| 'from Human_Parse import HumanParser', | |
| 'from PSP import HumanParser_PSP', | |
| 'from CPM import CPM_Keypoint', | |
| 'from CRFRNN import CRFRNN_Contour', | |
| 'self._HumanParser = HumanParser()', | |
| 'Arms_mask = self._HumanParser.Arms_detect(img)', | |
| 'ContourOutput = ContourOutput ^ Arms_mask', | |
| ], | |
| # (2) แทรกโค้ดตรวจ device ต่อท้าย import สุดท้าย | |
| 'insert_after': 'import time', | |
| # (3) เปลี่ยน .cuda() เป็น .to(_DEVICE) | |
| # ⚠️ ห้ามเติมคอมเมนต์ต่อท้ายข้อความที่แทน ถ้าจุดนั้นอยู่ "กลางนิพจน์" | |
| # เพราะคอมเมนต์จะกลืนส่วนที่เหลือของบรรทัด -> วงเล็บไม่ปิด -> SyntaxError | |
| # (เคยพลาดมาแล้วตอนเทส) ใส่ได้เฉพาะบรรทัดที่จบในตัวเอง | |
| 'replace': [ | |
| ('Model = Pose2Seg().cuda()', | |
| 'Model = Pose2Seg().to(_DEVICE)'), | |
| # detectron2 default เป็น cuda -> ต้องสั่งให้ตรง device ด้วย | |
| # (บรรทัดนี้เติม tag ได้ เพราะเป็นบรรทัดเต็มที่จบในตัวเอง) | |
| ('cfg.MODEL.WEIGHTS = model_zoo.get_checkpoint_url(key_file)', | |
| 'cfg.MODEL.WEIGHTS = model_zoo.get_checkpoint_url(key_file)\n' | |
| f' cfg.MODEL.DEVICE = _DEVICE {TAG}'), | |
| ], | |
| }, | |
| # ---------------- ไส้ของ Pose2Seg: ตัวโมเดลหลัก ---------------- | |
| # เจอทีหลัง (7 ส.ค. รอบ 2) — ตอนแรก patch แค่ 2 ไฟล์ แล้วยังพัง | |
| # "Found no NVIDIA driver" เพราะไฟล์นี้ยังเรียก .cuda(0) และ torch.load แบบไม่บอก device | |
| 'modeling/build_model.py': { | |
| 'cut': [], | |
| 'insert_after': 'import torch.nn.functional as F', | |
| 'replace': [ | |
| # .cuda(0) มี 5 จุด (บรรทัด 47, 50, 178, 191, 218) — replace ทีเดียวได้หมด | |
| ('.cuda(0)', '.to(_DEVICE)'), | |
| # ===== หัวใจ: ตัวที่ทำให้ error จริง ===== | |
| # pose2seg_release.pkl ถูกเซฟจากเครื่องที่มี GPU | |
| # torch.load แบบไม่ใส่ map_location จะพยายามคืนค่าลง GPU เดิม | |
| # -> เครื่องไม่มี GPU = "Found no NVIDIA driver" | |
| ('pretrained_dict = torch.load(path)', | |
| 'pretrained_dict = torch.load(path, map_location=_DEVICE)'), | |
| ], | |
| }, | |
| # ---------------- ไส้ของ Pose2Seg: การจัดตำแหน่งภาพ ---------------- | |
| 'modeling/affine_align.py': { | |
| 'cut': [], | |
| 'insert_after': 'import torch.nn.functional as F', | |
| # ทั้งสองจุดอยู่กลางนิพจน์ -> ห้ามเติมคอมเมนต์ต่อท้าย | |
| 'replace': [ | |
| ('torch.from_numpy(Hs_new).cuda()', | |
| 'torch.from_numpy(Hs_new).to(_DEVICE)'), | |
| ('align_corners=True).float().cuda()', | |
| 'align_corners=True).float().to(_DEVICE)'), | |
| ], | |
| }, | |
| } | |
| def patch_file(path, spec): | |
| """ | |
| แก้ไฟล์ 1 ไฟล์ตาม spec | |
| คืน (จำนวนที่แก้, list ของสิ่งที่ยังแก้ไม่ได้) | |
| """ | |
| src = open(path, encoding='utf-8').read() | |
| n = 0 | |
| # ---------- (1) คอมเมนต์ทิ้งบรรทัดที่ไม่ต้องการ ---------- | |
| for bad in spec['cut']: | |
| if bad in src and MARK + bad not in src: # เงื่อนไขที่ 2 = กัน patch ซ้ำ | |
| src = src.replace(bad, MARK + bad) | |
| n += 1 | |
| print(f' ตัด : {bad[:58]}') | |
| # ---------- (2) แทรกโค้ดตรวจ device ---------- | |
| anchor = spec['insert_after'] | |
| if TAG not in src: # ยังไม่เคยแทรก | |
| if anchor not in src: | |
| return n, [f'หา anchor "{anchor}" ไม่เจอ — แทรก _DEVICE ไม่ได้'] | |
| src = src.replace(anchor, anchor + '\n' + DEVICE_SNIPPET, 1) # แทรกครั้งเดียวพอ | |
| n += 1 | |
| print(f' แทรก : _DEVICE (auto-detect) ต่อจาก "{anchor}"') | |
| # ---------- (3) เปลี่ยน .cuda() -> .to(_DEVICE) ---------- | |
| for old, new in spec['replace']: | |
| if old in src and new not in src: | |
| src = src.replace(old, new) | |
| n += 1 | |
| print(f' เปลี่ยน: {old[:50]}') | |
| open(path, 'w', encoding='utf-8').write(src) | |
| # ---------- ตรวจซ้ำว่าไม่เหลืออะไรที่จะทำให้พัง ---------- | |
| left = [] | |
| for bad in spec['cut']: | |
| if bad in src and MARK + bad not in src: | |
| left.append(f'ยังไม่ถูกตัด: {bad}') | |
| # ตรวจจาก "ข้อความใหม่ต้องมีอยู่จริง" ไม่ใช่ "ข้อความเก่าต้องหายไป" | |
| # เพราะบางเคสข้อความเก่าเป็นส่วนหนึ่งของข้อความใหม่ (เช่น cfg.MODEL.WEIGHTS) | |
| # ถ้าเช็คว่าเก่าหายไป จะฟ้องผิดทั้งที่ patch สำเร็จแล้ว | |
| for _, new in spec['replace']: | |
| if new not in src: | |
| left.append(f'ยังไม่ถูกเปลี่ยนเป็น: {new.splitlines()[0][:50]}') | |
| # ตรวจขั้นสุดท้าย: ต้องไม่เหลือ .cuda() ในไฟล์ที่อยู่ในเส้นทางรันจริง | |
| for i, line in enumerate(src.splitlines(), 1): | |
| if '.cuda()' in line and not line.strip().startswith('#'): | |
| left.append(f'ยังเหลือ .cuda() บรรทัด {i}: {line.strip()[:50]}') | |
| # ตรวจว่าไฟล์ยัง compile ผ่าน (กันพลาดแบบเติมคอมเมนต์กลางนิพจน์) | |
| try: | |
| compile(src, path, 'exec') | |
| except SyntaxError as e: | |
| left.append(f'SyntaxError บรรทัด {e.lineno}: {e.msg}') | |
| return n, left | |
| def main(repo): | |
| # รับได้ทั้ง path ของ repo และ path ของ Detected.py (เผื่อเรียกแบบเดิม) | |
| if repo.endswith('.py'): | |
| repo = os.path.dirname(repo) | |
| if not os.path.isdir(repo): | |
| print(f'❌ ไม่เจอโฟลเดอร์ repo: {repo}') | |
| return 1 | |
| print(f'patch repo: {repo}\n') | |
| total, problems = 0, [] | |
| for rel, spec in PATCHES.items(): | |
| p = os.path.join(repo, rel) | |
| print(f' [{rel}]') | |
| if not os.path.exists(p): | |
| print(f' ❌ ไม่เจอไฟล์') | |
| problems.append(f'ไม่เจอ {rel}') | |
| continue | |
| n, left = patch_file(p, spec) | |
| total += n | |
| problems += [f'{rel}: {x}' for x in left] | |
| print(f' -> แก้ไป {n} จุด' if n else ' -> ไม่มีอะไรต้องแก้ (patch ไปแล้ว)') | |
| print() | |
| if problems: | |
| print('❌ มีปัญหา:') | |
| for x in problems: | |
| print(f' - {x}') | |
| return 1 | |
| print(f'✅ patch สำเร็จ รวม {total} จุด') | |
| # ============================================================ | |
| # ตรวจรอบสุดท้าย: สแกน "ทั้ง repo" หา .cuda ที่ยังเหลือ | |
| # | |
| # ทำไมต้องมี: ตอนแรกผม patch แค่ Detected.py + affine_align.py | |
| # แล้วมั่นใจว่าครบ -> ที่จริง modeling/build_model.py ยังมี .cuda(0) อีก 5 จุด | |
| # -> deploy ไปแล้วเจอ "Found no NVIDIA driver" เสียเวลาอีกรอบ | |
| # การสแกนทั้ง repo ทำให้เห็นทุกจุดตั้งแต่ตอน patch ไม่ต้องรอไปพังบนเซิร์ฟเวอร์ | |
| # | |
| # ข้ามโค้ดใต้ if __name__ == '__main__' เพราะไม่ถูกรันตอน import | |
| # ============================================================ | |
| print('\n --- สแกนทั้ง repo หา .cuda ที่ยังเหลือ ---') | |
| leftover = [] | |
| for dirpath, dirnames, files in os.walk(repo): | |
| dirnames[:] = [d for d in dirnames if d not in ('__pycache__', '.git')] | |
| for fn in files: | |
| if not fn.endswith('.py'): | |
| continue | |
| p = os.path.join(dirpath, fn) | |
| rel = os.path.relpath(p, repo).replace('\\', '/') | |
| try: | |
| lines = open(p, encoding='utf-8').read().splitlines() | |
| except Exception: | |
| continue | |
| in_main = False | |
| for i, line in enumerate(lines, 1): | |
| if line.startswith("if __name__"): | |
| in_main = True # ตั้งแต่บรรทัดนี้ลงไปไม่ถูกรันตอน import | |
| # ข้าม: โค้ดใน __main__ / คอมเมนต์ / บรรทัดที่เรา patch เข้าไปเอง | |
| if in_main or line.strip().startswith('#') or TAG in line: | |
| continue | |
| if '.cuda' in line: | |
| leftover.append(f'{rel}:{i} {line.strip()[:60]}') | |
| if leftover: | |
| print(' ⚠️ ยังเหลือ .cuda (เช็คว่าไฟล์นี้ถูก import ตอนรันจริงไหม):') | |
| for x in leftover: | |
| print(f' {x}') | |
| print(' หมายเหตุ: ถ้าไฟล์นั้นไม่มีใคร import ก็ปล่อยได้ ไม่ต้องแก้') | |
| else: | |
| print(' ✅ ไม่เหลือ .cuda ที่ไหนเลย') | |
| print('\n✅ ตรวจแล้ว: ไม่เหลือ import ที่จะพัง และ .cuda ในไฟล์ที่ patch ถูกแก้ครบ') | |
| return 0 | |
| if __name__ == '__main__': | |
| target = sys.argv[1] if len(sys.argv) > 1 else '/home/user/app/2DImage2BMI-main' | |
| sys.exit(main(target)) | |