PET / scripts /pet_vlm_dataset.py
DesonDai's picture
Add files using upload-large-folder tool
212e9d7 verified
Raw
History Blame Contribute Delete
3.17 kB
from __future__ import annotations
from pathlib import Path
import nibabel as nib
import numpy as np
import pandas as pd
import torch
import torch.nn.functional as F
from torch.utils.data import Dataset
def normalize_pet(volume: np.ndarray, eps: float = 1e-6) -> np.ndarray:
volume = np.asarray(volume, dtype=np.float32)
finite = np.isfinite(volume)
if not finite.any():
return np.zeros_like(volume, dtype=np.float32)
lo, hi = np.percentile(volume[finite], [0.5, 99.5])
volume = np.clip(volume, lo, hi)
volume = (volume - lo) / max(float(hi - lo), eps)
return volume.astype(np.float32, copy=False)
def resize_volume(volume: np.ndarray, output_size: tuple[int, int, int]) -> torch.Tensor:
tensor = torch.from_numpy(volume)[None, None]
tensor = F.interpolate(tensor, size=output_size, mode="trilinear", align_corners=False)
return tensor[0]
def load_suvr_vector(csv_path: str | Path, include_background: bool = False) -> tuple[list[str], torch.Tensor]:
df = pd.read_csv(csv_path)
if not include_background:
df = df[df["label_name"] != "Background"].copy()
labels = df["label_name"].astype(str).tolist()
values = torch.tensor(df["mean_scalar"].astype(float).to_numpy(), dtype=torch.float32)
return labels, values
def suvr_to_text(labels: list[str], values: torch.Tensor, top_k: int = 8) -> str:
pairs = sorted(zip(labels, values.tolist()), key=lambda x: x[1], reverse=True)
high = ", ".join(f"{name} {value:.3f}" for name, value in pairs[:top_k])
low = ", ".join(f"{name} {value:.3f}" for name, value in pairs[-top_k:])
mean_value = float(values.mean())
return f"FDG-PET regional SUVR summary. Mean SUVR {mean_value:.3f}. Highest regions: {high}. Lowest regions: {low}."
class PETSUVRDataset(Dataset):
def __init__(
self,
manifest_path: str | Path,
output_size: tuple[int, int, int] = (96, 96, 96),
include_background: bool = False,
) -> None:
self.manifest = pd.read_csv(manifest_path)
self.output_size = output_size
self.include_background = include_background
def __len__(self) -> int:
return len(self.manifest)
def __getitem__(self, index: int) -> dict[str, object]:
row = self.manifest.iloc[index]
volume = nib.load(str(row["pet_path"])).get_fdata(dtype=np.float32)
volume = normalize_pet(volume)
image = resize_volume(volume, self.output_size)
labels, suvr = load_suvr_vector(row["suvr_csv_path"], self.include_background)
text = suvr_to_text(labels, suvr)
return {
"sample_id": row["sample_id"],
"image": image,
"suvr": suvr,
"region_labels": labels,
"text": text,
}
def collate_pet_suvr(batch: list[dict[str, object]]) -> dict[str, object]:
return {
"sample_id": [item["sample_id"] for item in batch],
"image": torch.stack([item["image"] for item in batch]),
"suvr": torch.stack([item["suvr"] for item in batch]),
"text": [item["text"] for item in batch],
"region_labels": batch[0]["region_labels"],
}