| import argparse, yaml | |
| import numpy as np | |
| from skimage import measure | |
| from utils import normalize_volume | |
| from io_utils import load_slices_from_folder, load_dicom_folder, load_nifti, save_mesh_ply | |
| def build_volume(source, source_type='folder', glob_pattern="*.png"): | |
| if source_type == 'folder': | |
| vol = load_slices_from_folder(source, glob_pattern=glob_pattern) | |
| elif source_type == 'dicom': | |
| vol = load_dicom_folder(source) | |
| elif source_type == 'nifti': | |
| vol = load_nifti(source) | |
| else: | |
| raise ValueError("Unsupported source_type") | |
| return vol | |
| def mesh_from_volume(vol, iso=0.5, spacing=(1.0,1.0,1.0)): | |
| vol_n = normalize_volume(vol) | |
| verts, faces, normals, values = measure.marching_cubes(vol_n, level=iso, spacing=spacing) | |
| return verts, faces, normals | |
| def main(): | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument("--source", required=True) | |
| parser.add_argument("--source_type", choices=['folder','dicom','nifti'], default='folder') | |
| parser.add_argument("--glob", default="*.png") | |
| parser.add_argument("--config", default="config.yaml") | |
| parser.add_argument("--out", default="mesh.ply") | |
| parser.add_argument("--iso", type=float, default=None) | |
| args = parser.parse_args() | |
| try: | |
| cfg = yaml.safe_load(open(args.config)) | |
| except Exception: | |
| cfg = {} | |
| iso = args.iso or (cfg.get('reconstruct',{}).get('iso_value', 0.5)) | |
| spacing = cfg.get('reconstruct',{}).get('spacing', [1.0,1.0,1.0]) | |
| vol = build_volume(args.source, source_type=args.source_type, glob_pattern=args.glob) | |
| verts, faces, normals = mesh_from_volume(vol, iso=iso, spacing=tuple(spacing)) | |
| save_mesh_ply(verts, faces, args.out, normals=normals) | |
| print(f"Saved mesh to {args.out} (verts={len(verts)}, faces={len(faces)})") | |
| if __name__ == '__main__': | |
| main() | |