| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970 |
- """Simple SUV distribution summaries."""
- from __future__ import annotations
- import numpy as np
- import pandas as pd
- import nibabel as nib
- def get_suv_percentiles(
- image: nib.Nifti1Image,
- percentiles: tuple[float, ...] = (25, 50, 75),
- min_suv: float = 0.0,
- ) -> pd.DataFrame:
- """Compute SUV percentile thresholds and voxel counts.
- Parameters
- ----------
- image:
- 3D NIfTI image containing SUV values.
- percentiles:
- Percentiles to compute.
- min_suv:
- Only finite voxels with ``SUV > min_suv`` are used. For processed images
- where background is set to zero, the default ``min_suv=0`` uses only the
- non-background region.
- Returns
- -------
- pandas.DataFrame
- Columns: ``percentile``, ``suv_threshold``,
- ``n_voxels_ge_threshold`` and ``n_voxels_lt_threshold``.
- """
- if not isinstance(image, nib.Nifti1Image):
- raise TypeError("image must be a nib.Nifti1Image.")
- data = image.get_fdata(dtype=np.float64)
- if data.ndim != 3:
- raise ValueError("Input image must be 3D.")
- suv_values = data[np.isfinite(data) & (data > min_suv)]
- if suv_values.size == 0:
- return pd.DataFrame(
- {
- "percentile": percentiles,
- "suv_threshold": [np.nan] * len(percentiles),
- "n_voxels_ge_threshold": [0] * len(percentiles),
- "n_voxels_lt_threshold": [0] * len(percentiles),
- }
- )
- percentile_values = np.percentile(suv_values, percentiles)
- return pd.DataFrame(
- {
- "percentile": percentiles,
- "suv_threshold": percentile_values,
- "n_voxels_ge_threshold": [
- int(np.sum(suv_values >= val)) for val in percentile_values
- ],
- "n_voxels_lt_threshold": [
- int(np.sum(suv_values < val)) for val in percentile_values
- ],
- }
- )
|