Source code for PyNutil.io.volume_nifti
from __future__ import annotations
from typing import Optional
import numpy as np
from .nifti_writer import write_nifti
def scale_to_uint8(data: np.ndarray) -> np.ndarray:
arr = np.asarray(data, dtype=np.float32)
finite = np.isfinite(arr)
if not np.any(finite):
return np.zeros(arr.shape, dtype=np.uint8)
vmin = float(np.min(arr[finite]))
vmax = float(np.max(arr[finite]))
if not np.isfinite(vmin) or not np.isfinite(vmax) or vmax <= vmin:
return np.zeros(arr.shape, dtype=np.uint8)
scaled = (arr - vmin) * (255.0 / (vmax - vmin))
scaled = np.clip(scaled, 0.0, 255.0)
out = np.zeros(arr.shape, dtype=np.uint8)
out[finite] = scaled[finite].round().astype(np.uint8)
return out
def isotropic_resolution_um_for_volume(
*,
atlas_volume: Optional[np.ndarray],
volume: np.ndarray,
base_voxel_um: float,
logger=None,
) -> float:
if atlas_volume is None:
return float(base_voxel_um)
atlas_shape = np.array(atlas_volume.shape, dtype=np.float32)
vol_shape = np.array(volume.shape, dtype=np.float32)
if atlas_shape.shape != (3,) or vol_shape.shape != (3,) or np.any(vol_shape <= 0):
return float(base_voxel_um)
implied = atlas_shape / vol_shape
iso_scale = float(np.median(implied))
if logger is not None and (np.max(implied) - np.min(implied) > 1e-3):
logger.warning(
"Non-uniform volume scaling detected (atlas_shape=%s, volume_shape=%s). "
"Writing isotropic voxel spacing using median scale %.6f.",
tuple(int(x) for x in atlas_shape),
tuple(int(x) for x in vol_shape),
iso_scale,
)
return float(base_voxel_um * iso_scale)
[docs]
def save_volume_niftis(
*,
output_folder: str,
interpolated_volume: Optional[np.ndarray],
frequency_volume: Optional[np.ndarray],
damage_volume: Optional[np.ndarray] = None,
atlas_volume: Optional[np.ndarray],
voxel_size_um: Optional[float],
logger=None,
) -> None:
if interpolated_volume is None and frequency_volume is None:
return
base_voxel_um = float(voxel_size_um) if voxel_size_um is not None else 1.0
def _save_one(volume: np.ndarray, *, name: str) -> None:
vol_u8 = scale_to_uint8(volume)
res = isotropic_resolution_um_for_volume(
atlas_volume=atlas_volume,
volume=vol_u8,
base_voxel_um=base_voxel_um,
logger=logger,
)
write_nifti(vol_u8, res, f"{output_folder}/interpolated_volume/{name}")
if interpolated_volume is not None:
_save_one(interpolated_volume, name="interpolated_volume")
if frequency_volume is not None:
_save_one(frequency_volume, name="frequency_volume")
if damage_volume is not None:
_save_one(damage_volume, name="damage_volume")