omnigen_seg / probe_angles.py
zhui711's picture
Upload folder using huggingface_hub
4f08626 verified
Raw
History Blame Contribute Delete
6.25 kB
#!/usr/bin/env python3
"""
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
# ---------------------------------------------------------------------------
# Config
# ---------------------------------------------------------------------------
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)
# Load camera views
with open(CAMERA_JSON) as f:
cam_views = json.load(f)
# Load volume (no labelmap needed for plain DRR)
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)
# ---------------------------------------------------------------------------
# Define permutations: how [c0, c1] map to rot=[rz, rx, ry] for ZXY conv.
# Convention="ZXY" means: rot is applied as Rz(rot[0]) @ Rx(rot[1]) @ Ry(rot[2])
# ---------------------------------------------------------------------------
def make_permutations(c0, c1):
"""Return dict of perm_name -> [rz, rx, ry] for ZXY convention."""
return {
# Basic placements of two angles into three slots
"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],
# Negative sign variants (common in C-arm: pitch inversion)
"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],
# Sign flips on the other placements
"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} ---")
# Copy SV-DRR ground truth for comparison
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) # (1, 1, H, W)
img_np = img.squeeze().cpu().numpy() # (H, W)
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}")
# Also render view 0 with rot=[0,0,0] as sanity check
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")
# Cleanup
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()