CBT_project/xfr_plot_level.py
Xiao Furen 332226f843 Add pixi.toml configuration for project dependencies and tasks
- Created a new pixi.toml file to manage project dependencies and tasks.
- Defined workspace metadata including authors, channels, name, platforms, and version.
- Specified tasks for preprocessing, PSO, DE, and other scripts.
- Listed dependencies for Python and various libraries with specific versions.
- Configured additional PyPI options for PyTorch installation from a custom index.
2026-10-19 20:34:28 +08:00

99 lines
No EOL
3.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python
"""
為每個 (volume, level) 的骨頭遮罩({level}_binary_sdf.nii.gz,缺則 _binary)繪製
X-ray 四視角圖(不畫螺絲),存到 Output/{date}/{volume}/。
輸出檔名:{volume} {level}_CBT.png
(例:1.3.6.1.4.1.9328.50.4.0005 L1_CBT.png)
Usage:
python xfr_plot_level.py # 全部 volume 的 L1~L5
python xfr_plot_level.py 0005 # 該 volume 的 L1~L5
python xfr_plot_level.py 0005 L1 # 單一 (volume, level)
python xfr_plot_level.py --dir <standardized_dir> --output <output_base>
"""
import argparse
import os
import sys
from imaging.transforms import level_file_path
from visualization.res_bone_figure import render_bone_figure
standardized_dir = '/mnt/1248/open2/cyrou/CBT/Seg/Resample/standardized-xfr/'
output_base = '/mnt/1248/open2/cyrou/Output'
LEVELS = ('L1', 'L2', 'L3', 'L4', 'L5')
USAGE = 'Usage: python xfr_plot_level.py [volume_id] [level] [--dir D] [--output O]'
def parse_args(argv):
parser = argparse.ArgumentParser(description='繪製各 lumbar level 的骨頭 X-ray 圖(不畫螺絲)')
parser.add_argument('volume_id', nargs='?', default=None,
help='volume ID(完整 UID 或末段,如 0005);省略=全部')
parser.add_argument('level', nargs='?', default=None,
help=f'level({" / ".join(LEVELS)});省略=全部')
parser.add_argument('--dir', default=standardized_dir,
help=f'standardized 資料夾(預設 {standardized_dir})')
parser.add_argument('--output', default=output_base,
help=f'輸出根目錄(預設 {output_base})')
return parser.parse_args(argv)
def main():
args = parse_args(sys.argv[1:])
if args.level is not None:
level_key = args.level.upper()
if level_key not in LEVELS:
print(f'Invalid level: {args.level} (choose from {"/".join(LEVELS)})')
sys.exit(1)
if args.level is not None and args.volume_id is None:
print(f'{USAGE}\nlevel requires volume_id')
sys.exit(1)
volumes = [d for d in sorted(os.listdir(args.dir))
if os.path.isdir(os.path.join(args.dir, d))]
if args.volume_id is not None:
key = args.volume_id.lower()
vols = [v for v in volumes
if v.lower() == key or v.rsplit('.', 1)[-1] == key]
if not vols:
print(f'Volume not found: {args.volume_id}')
sys.exit(1)
volumes = vols
levels = (args.level.upper(),) if args.level else LEVELS
tasks = [(vid, lvl) for vid in volumes for lvl in levels]
print(f'{len(volumes)} volume(s) x {len(levels)} level(s) = {len(tasks)} figure(s)',
flush=True)
ok, skip = [], []
for i, (vid, lvl) in enumerate(tasks, 1):
vol_dir = os.path.join(args.dir, vid)
# _binary.nii.gz 現為原解析度;0.5mm 用 _binary_sdf(SDF 平滑遮罩)
# 未旋轉檔:新世代在 crop/ 子資料夾、舊世代在頂層
sdf_path = level_file_path(vol_dir, lvl, 'binary_sdf')
binary_path = sdf_path if os.path.exists(sdf_path) \
else level_file_path(vol_dir, lvl, 'binary')
cortical_path = level_file_path(vol_dir, lvl, 'cortical')
path = render_bone_figure(vid, lvl, binary_path, cortical_path,
base_folder=args.output)
if path is None:
skip.append((vid, lvl))
print(f'[{i}/{len(tasks)}] {vid} {lvl}: skipped', flush=True)
else:
ok.append((vid, lvl))
print(f'[{i}/{len(tasks)}] {vid} {lvl}: saved {path}', flush=True)
print('=' * 60)
print(f'Done. saved={len(ok)} skipped={len(skip)}')
if skip:
print('Skipped (missing/empty mask):')
for vid, lvl in skip:
print(f' {vid} {lvl}')
if __name__ == '__main__':
main()