| |
| """ |
| Probe script: find the exact Euler angle mapping SV-DRR uses. |
| |
| For each test view (0, 404, 1239), we try multiple permutations of how the |
| two coordinate values [c0, c1] from camera_views.json map to DiffDRR's |
| 3-component Euler angles rot=[rz, rx, ry] (convention="ZXY"), with xyz=[0, 1800, 0]. |
| |
| NO look_at_rotation is used — raw Euler angles are injected directly. |
| |
| Output: probe_output/{view_id}_{perm_name}.png (+ SV-DRR ground truth copy) |
| """ |
|
|
| import json |
| import math |
| import sys |
| from pathlib import Path |
|
|
| import numpy as np |
| from PIL import Image |
|
|
| SCRIPT_DIR = Path(__file__).resolve().parent |
| DIFFDRR_DIR = SCRIPT_DIR / "DiffDRR" |
| if str(DIFFDRR_DIR) not in sys.path: |
| sys.path.insert(0, str(DIFFDRR_DIR)) |
|
|
| import torch |
|
|
| |
| |
| |
| CT_CLEAN = SCRIPT_DIR / "data" / "lidc_TotalSeg_test" / "LIDC-IDRI-0001" / "01_body" / "CT_clean.nii.gz" |
| MASK_NII = SCRIPT_DIR / "data" / "lidc_TotalSeg_test" / "LIDC-IDRI-0001" / "02_totalseg" / "mask.nii.gz" |
| CAMERA_JSON = SCRIPT_DIR.parent / "wt_dataset" / "LIDC_IDRI" / "img_complex_fb_256" / "camera_views.json" |
| SVDRR_DIR = SCRIPT_DIR.parent / "wt_dataset" / "LIDC_IDRI" / "img_complex_fb_256" / "LIDC-IDRI-0001" |
| OUT_DIR = SCRIPT_DIR / "probe_output" |
|
|
| SDD = 2000.0 |
| HEIGHT = 256 |
| DELX = 2.0 |
| RADIUS = 1800.0 |
| EULER_CONV = "ZXY" |
|
|
| VIEW_INDICES = [0, 404, 1239] |
|
|
|
|
| def save_uint8_png(arr_2d: np.ndarray, path: Path, do_flip: bool = True): |
| """Percentile-clip, optional horizontal flip, save as 8-bit PNG.""" |
| if do_flip: |
| arr_2d = np.ascontiguousarray(np.flip(arr_2d, axis=-1)) |
| lo = float(np.percentile(arr_2d, 0.5)) |
| hi = float(np.percentile(arr_2d, 99.5)) |
| if hi - lo > 0: |
| normed = np.clip(arr_2d, lo, hi) |
| normed = (normed - lo) / (hi - lo) |
| else: |
| normed = np.zeros_like(arr_2d) |
| uint8 = (normed * 255).astype(np.uint8) |
| Image.fromarray(uint8, mode="L").save(str(path)) |
|
|
|
|
| def main(): |
| from diffdrr.data import read |
| from diffdrr.drr import DRR |
| from diffdrr.pose import convert |
|
|
| OUT_DIR.mkdir(parents=True, exist_ok=True) |
|
|
| |
| with open(CAMERA_JSON) as f: |
| cam_views = json.load(f) |
|
|
| |
| print("Loading CT volume...") |
| subject = read( |
| volume=str(CT_CLEAN), |
| labelmap=str(MASK_NII), |
| orientation="AP", |
| center_volume=True, |
| ) |
|
|
| device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") |
| print(f"Device: {device}") |
|
|
| drr_module = DRR( |
| subject, |
| sdd=SDD, |
| height=HEIGHT, |
| delx=DELX, |
| ).to(device) |
|
|
| |
| |
| |
| |
| def make_permutations(c0, c1): |
| """Return dict of perm_name -> [rz, rx, ry] for ZXY convention.""" |
| return { |
| |
| "A_c0_0_c1": [c0, 0.0, c1], |
| "B_c0_c1_0": [c0, c1, 0.0], |
| "C_c1_c0_0": [c1, c0, 0.0], |
| "D_0_c0_c1": [0.0, c0, c1], |
| "E_0_c1_c0": [0.0, c1, c0], |
| "F_c1_0_c0": [c1, 0.0, c0], |
| |
| "G_nc0_0_c1": [-c0, 0.0, c1], |
| "H_c0_0_nc1": [c0, 0.0, -c1], |
| "I_nc0_0_nc1": [-c0, 0.0, -c1], |
| "J_0_nc0_c1": [0.0, -c0, c1], |
| "K_0_c0_nc1": [0.0, c0, -c1], |
| "L_0_nc0_nc1": [0.0, -c0, -c1], |
| |
| "M_nc1_c0_0": [-c1, c0, 0.0], |
| "N_c1_nc0_0": [c1, -c0, 0.0], |
| "O_nc1_nc0_0": [-c1, -c0, 0.0], |
| "P_nc0_c1_0": [-c0, c1, 0.0], |
| "Q_c0_nc1_0": [c0, -c1, 0.0], |
| "R_nc0_nc1_0": [-c0, -c1, 0.0], |
| } |
|
|
| xyz_fixed = torch.tensor([[0.0, RADIUS, 0.0]], dtype=torch.float32, device=device) |
|
|
| for view_idx in VIEW_INDICES: |
| cv = cam_views[view_idx] |
| c0, c1 = cv["coordinate"] |
| svdrr_id = cv["id"] |
| print(f"\n--- View {view_idx} (svdrr_id={svdrr_id}): c0={c0:.6f}, c1={c1:.6f} ---") |
|
|
| |
| gt_path = SVDRR_DIR / f"{svdrr_id}.png" |
| if gt_path.exists(): |
| gt_img = Image.open(gt_path) |
| gt_img.save(str(OUT_DIR / f"{view_idx:04d}_GT.png")) |
| print(f" Saved GT: {view_idx:04d}_GT.png") |
|
|
| perms = make_permutations(c0, c1) |
| for pname, rot_vec in perms.items(): |
| rot_t = torch.tensor([rot_vec], dtype=torch.float32, device=device) |
|
|
| pose = convert( |
| rot_t, xyz_fixed, |
| parameterization="euler_angles", |
| convention=EULER_CONV, |
| ) |
|
|
| with torch.no_grad(): |
| img = drr_module(pose) |
|
|
| img_np = img.squeeze().cpu().numpy() |
|
|
| out_name = f"{view_idx:04d}_{pname}.png" |
| save_uint8_png(img_np, OUT_DIR / out_name, do_flip=True) |
|
|
| print(f" Saved {len(perms)} permutation images for view {view_idx}") |
|
|
| |
| print("\n--- View 0 sanity check: rot=[0, 0, 0] ---") |
| rot_zero = torch.tensor([[0.0, 0.0, 0.0]], dtype=torch.float32, device=device) |
| pose_zero = convert(rot_zero, xyz_fixed, parameterization="euler_angles", convention=EULER_CONV) |
| with torch.no_grad(): |
| img_zero = drr_module(pose_zero) |
| save_uint8_png(img_zero.squeeze().cpu().numpy(), OUT_DIR / "0000_rot000.png", do_flip=True) |
| print(" Saved 0000_rot000.png") |
|
|
| |
| del drr_module, subject |
| torch.cuda.empty_cache() |
|
|
| print(f"\nAll probe images saved to: {OUT_DIR}/") |
| print("Compare *_GT.png (ground truth) with permutation PNGs to find the match.") |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|