ICAInterp / brainEncoding /visualization /flatmap_plotting.py
whuang06's picture
Add ICAInterp visualizer Space
37e13f7 verified
Raw
History Blame Contribute Delete
3.51 kB
"""Shared flatmap plotting helpers."""
from __future__ import annotations
import numpy as np
import matplotlib
from PIL import Image, ImageDraw, ImageFont
matplotlib.use("Agg")
from matplotlib import colormaps # noqa: E402
def load_font(size: int, bold: bool = False):
candidates = [
"DejaVuSans-Bold.ttf" if bold else "DejaVuSans.ttf",
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf" if bold else "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
]
for candidate in candidates:
try:
return ImageFont.truetype(candidate, size)
except OSError:
continue
return ImageFont.load_default()
def text_size(draw: ImageDraw.ImageDraw, text: str, font) -> tuple[int, int]:
box = draw.textbbox((0, 0), text, font=font)
return box[2] - box[0], box[3] - box[1]
def split_rows(mask: np.ndarray) -> tuple[int, int]:
row_counts = mask.sum(axis=1)
height = mask.shape[0]
lo = int(height * 0.35)
hi = int(height * 0.65)
split = lo + int(np.argmin(row_counts[lo:hi]))
return split, int(row_counts[split])
def half_mask(mask: np.ndarray, split: int, half: str) -> np.ndarray:
out = np.zeros_like(mask, dtype=bool)
if half == "left":
out[: split + 1] = mask[: split + 1]
elif half == "right":
out[split + 1 :] = mask[split + 1 :]
else:
raise ValueError(f"Unknown hemisphere {half!r}")
return out
def crop_box_for_half(mask: np.ndarray, split: int, half: str, margin: int) -> tuple[slice, slice]:
hemi = half_mask(mask, split, half)
return crop_box_for_mask(hemi, margin)
def crop_box_for_mask(mask: np.ndarray, margin: int) -> tuple[slice, slice]:
rows, cols = np.where(mask)
if rows.size == 0:
raise ValueError("No pixels found for mask.")
r0 = max(int(rows.min()) - margin, 0)
r1 = min(int(rows.max()) + margin + 1, mask.shape[0])
c0 = max(int(cols.min()) - margin, 0)
c1 = min(int(cols.max()) + margin + 1, mask.shape[1])
return slice(r0, r1), slice(c0, c1)
def colorize_scalar(values: np.ndarray, mask: np.ndarray, limit: float, cmap_name: str = "coolwarm") -> Image.Image:
cmap = colormaps[cmap_name]
normed = np.clip((values / max(limit, 1e-8) + 1.0) / 2.0, 0.0, 1.0)
rgba = (cmap(normed) * 255).astype(np.uint8)
rgba[~mask, 3] = 0
rgba[mask & ~np.isfinite(values), 3] = 0
return Image.fromarray(rgba, mode="RGBA")
def render_colorbar(width: int, height: int, cmap_name: str = "coolwarm") -> Image.Image:
cmap = colormaps[cmap_name]
gradient = np.linspace(-1.0, 1.0, width, dtype=np.float32)
rgba = (cmap((gradient + 1.0) / 2.0) * 255).astype(np.uint8)
bar = np.repeat(rgba[None, :, :], height, axis=0)
return Image.fromarray(bar, mode="RGBA")
def colorize_positive(values: np.ndarray, mask: np.ndarray, limit: float, cmap_name: str = "magma") -> Image.Image:
cmap = colormaps[cmap_name]
normed = np.clip(values / max(limit, 1e-8), 0.0, 1.0)
rgba = (cmap(normed) * 255).astype(np.uint8)
rgba[~mask, 3] = 0
rgba[mask & ~np.isfinite(values), 3] = 0
return Image.fromarray(rgba, mode="RGBA")
def render_positive_colorbar(width: int, height: int, cmap_name: str = "magma") -> Image.Image:
cmap = colormaps[cmap_name]
gradient = np.linspace(0.0, 1.0, width, dtype=np.float32)
rgba = (cmap(gradient) * 255).astype(np.uint8)
bar = np.repeat(rgba[None, :, :], height, axis=0)
return Image.fromarray(bar, mode="RGBA")