CBT_project/xfr_migrate_crop.py

113 lines
4.4 KiB
Python
Raw Normal View History

#!/home/xfr/.conda/envs/cbt/bin/python
"""一次性的 layout 遷移:把已產出 volume 的「未旋轉 <level> 檔」移入 crop/ 子資料夾。
layout2026-09-08 xfr_preprocess 直接產出無須再跑本腳本
<vol>/crop/<L>_binary|_smd|_smd_resampled|_binary_sdf|_binary_nn|_roi.nii.gz
<vol>/crop/<L>_planes.png <vol>/lumbar/
<vol>/rotated/<L>_*.nii.gz 不變
<vol>/transform.json<name>_labels.txt頂層不變
本腳本供改動前已產出尚未重新處理的 volume standardized-xfr-3
1) <vol>/ 頂層的 <L>_*.nii.gzL = LABEL_MAP level 移到 <vol>/crop/
2) <vol>/lumbar/*.png 移到 <vol>/crop/lumbar/ 清空後刪除
Idempotent已遷就的 volume 重跑無作用crop/ 已有同名檔時不覆蓋跳過該檔
頂層檔保留人工核對rotated/ 與其他頂層檔不動
讀端xfr_orig_labels / xfr_inverse_transform / xfr_cbt_native
imaging.transforms.level_file_path 同時認 crop/ 與頂層舊世代
standardized-xfr-2 不遷移也能讀如需一致化可對該 root 跑本腳本
Usage:
python xfr_migrate_crop.py [ROOT] [--dry-run]
"""
import argparse
import os
import shutil
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from config.constant import LABEL_MAP
DEFAULT_ROOT = '/mnt/1248/open2/cyrou/CBT/Seg/Resample/standardized-xfr-3'
LEVEL_SET = set(LABEL_MAP.values())
def migrate_volume(vol_dir, dry_run=False):
"""回傳 (moved, kept) 兩個清單(人讀的說明文字)。"""
crop_dir = os.path.join(vol_dir, 'crop')
moved, kept = [], []
for f in sorted(os.listdir(vol_dir)):
if not f.endswith('.nii.gz'):
continue
if f.split('_', 1)[0] not in LEVEL_SET:
kept.append(f)
continue
dst = os.path.join(crop_dir, f)
if os.path.exists(dst):
kept.append(f'{f}crop/ 已有同名檔,未動)')
continue
src = os.path.join(vol_dir, f)
if dry_run:
moved.append(f'{f} (dry-run)')
else:
os.makedirs(crop_dir, exist_ok=True)
shutil.move(src, dst)
moved.append(f)
lumbar_dir = os.path.join(vol_dir, 'lumbar')
if os.path.isdir(lumbar_dir):
for f in sorted(os.listdir(lumbar_dir)):
dst = os.path.join(crop_dir, f)
if os.path.exists(dst):
kept.append(f'lumbar/{f}crop/ 已有同名檔,未動)')
continue
if dry_run:
moved.append(f'lumbar/{f} (dry-run)')
else:
os.makedirs(crop_dir, exist_ok=True)
shutil.move(os.path.join(lumbar_dir, f), dst)
moved.append(f'lumbar/{f}')
if not dry_run:
try:
os.rmdir(lumbar_dir)
except OSError:
kept.append('lumbar/(仍有其他檔,未刪)')
return moved, kept
def main():
parser = argparse.ArgumentParser(
description='Migrate per-level pre-rotated files from <vol>/ top level '
'to <vol>/crop/ (plus lumbar/*.png -> crop/).')
parser.add_argument('root', nargs='?', default=DEFAULT_ROOT,
help=f'standardized generation dir (default: {DEFAULT_ROOT})')
parser.add_argument('--dry-run', action='store_true',
help='Only report what would be moved.')
args = parser.parse_args()
if not os.path.isdir(args.root):
sys.exit(f'not a directory: {args.root}')
vols = sorted(d for d in os.listdir(args.root)
if os.path.isdir(os.path.join(args.root, d)))
tag = '[dry-run] ' if args.dry_run else ''
n_done = n_empty = 0
for i, vol in enumerate(vols, 1):
vol_dir = os.path.join(args.root, vol)
moved, kept = migrate_volume(vol_dir, dry_run=args.dry_run)
if not moved:
n_empty += 1
print(f'{tag}[{i}/{len(vols)}] - {vol} (no file to move)')
continue
n_done += 1
print(f'{tag}[{i}/{len(vols)}] ok {vol} moved {len(moved)} file(s)')
for m in moved:
print(f' -> {m}')
for k in kept:
print(f' !! kept: {k}')
print(f'\n{tag}done: {n_done} volume(s) migrated, {n_empty} untouched '
f'out of {len(vols)}')
if __name__ == '__main__':
main()