vlm3d-beyond-normals / code /mr /preprocess.py
odurugol's picture
VLM3D 2026 models and code of team Beyond Normals
b360028
Raw History Blame Contribute Delete
5.87 kB
"""MR-RATE atlas study -> 4-channel uint8 volume + 4 presence flags.
Every series of a study is registered to the MNI152 1 mm grid (193x229x193), so
the series are voxel-aligned. Each MR type becomes one channel: the mean of up
to four of its series, each scaled to [0, 1] first. The same code builds the
training cache and runs at inference.
"""
from __future__ import annotations
import gzip
import io
import re
import zipfile
import nibabel as nib
import numpy as np
from scipy.ndimage import zoom
MODALITIES = ("t1w", "t2w", "flair", "swi") # channel order is part of the model
SRC_SHAPE = (193, 229, 193)
MAX_PER_MODALITY = 4
# d15: head box cropped from the atlas, linear zoom to 1.5 mm
# d2: near-full field of view, exact 2x2x2 block average to 2 mm
GEOMETRIES = {
"d15": {"offset": (12, 16, 6), "crop": (168, 192, 156), "out": (112, 128, 104), "mode": "zoom"},
"d2": {"offset": (0, 2, 0), "crop": (192, 224, 192), "out": (96, 112, 96), "mode": "block"},
}
_MOD_RE = re.compile(r"_atlas_([a-z0-9]+)-", re.IGNORECASE)
_MOD_ALT = {m: re.compile(rf"(?:^|[^a-z]){m}(?:[^a-z]|$)", re.IGNORECASE) for m in MODALITIES}
def modality_of(name: str) -> str | None:
stem = name.rsplit("/", 1)[-1]
m = _MOD_RE.search(stem)
tok = m.group(1).lower() if m else None
if tok in MODALITIES:
return tok
return next((mod for mod, rx in _MOD_ALT.items() if rx.search(stem)), None)
def normalize(vol: np.ndarray) -> np.ndarray:
"""Min-max to [0, 1] between the 1st and 99.5th percentile of non-zero voxels.
Registration leaves exact zeros outside the head, so non-zero voxels stand in for a head mask.
"""
fg = vol[vol > 0]
if fg.size < 1000:
return np.zeros_like(vol)
lo, hi = np.percentile(fg, (1.0, 99.5))
if not np.isfinite(hi) or hi <= lo:
lo, hi = float(fg.min()), float(fg.max())
if hi <= lo:
return np.zeros_like(vol)
return np.clip((vol - lo) / (hi - lo), 0.0, 1.0)
def _crop(vol: np.ndarray, geometry: str) -> np.ndarray:
g = GEOMETRIES[geometry]
offset, shape = g["offset"], g["crop"]
if vol.shape == SRC_SHAPE:
return vol[tuple(slice(o, o + c) for o, c in zip(offset, shape))]
if vol.shape == shape:
return vol
out = np.zeros(shape, dtype=vol.dtype) # unexpected grid: centre crop or pad
src, dst = [], []
for s, t in zip(vol.shape, shape):
o = abs(s - t) // 2
src.append(slice(o, o + t) if s >= t else slice(0, s))
dst.append(slice(0, t) if s >= t else slice(o, o + s))
out[tuple(dst)] = vol[tuple(src)]
return out
def resample(vol: np.ndarray, geometry: str) -> np.ndarray:
g = GEOMETRIES[geometry]
v = _crop(vol, geometry)
d, h, w = g["out"]
if g["mode"] == "block":
return v.reshape(d, 2, h, 2, w, 2).mean(axis=(1, 3, 5))
out = zoom(v, tuple(o / c for o, c in zip(g["out"], v.shape)), order=1, prefilter=False)
if out.shape != g["out"]: # zoom can be off by one
pad = np.zeros(g["out"], dtype=out.dtype)
sl = tuple(slice(0, min(a, b)) for a, b in zip(out.shape, g["out"]))
pad[sl] = out[sl]
out = pad
return out
def _read_normalized(read, name):
try:
vol = read(name)
except Exception:
return None
if vol is None:
return None
if vol.ndim == 4:
vol = vol[..., 0]
return normalize(vol) if vol.ndim == 3 else None
def process_study(names, read, geometries=("d15",)):
"""Series names + reader -> ({geometry: (volume uint8 [4, *out], presence uint8 [4])}, n_series_used)."""
cache = {}
def load(n):
if n not in cache:
cache[n] = _read_normalized(read, n)
return cache[n]
per_mod, unmatched, n_used = {}, [], 0
for name in sorted(names):
mod = modality_of(name)
if mod is None:
unmatched.append(name)
continue
bucket = per_mod.setdefault(mod, [])
if len(bucket) < MAX_PER_MODALITY and load(name) is not None:
bucket.append(name)
n_used += 1
if n_used == 0 and unmatched: # unrecognised names: use them as T1w rather than nothing
for name in unmatched[:MAX_PER_MODALITY]:
if load(name) is not None:
per_mod.setdefault(MODALITIES[0], []).append(name)
n_used += 1
out = {}
for geo in geometries:
vol = np.zeros((len(MODALITIES), *GEOMETRIES[geo]["out"]), dtype=np.uint8)
presence = np.zeros(len(MODALITIES), dtype=np.uint8)
for i, mod in enumerate(MODALITIES):
series = [resample(cache[n], geo) for n in per_mod.get(mod, [])]
if series:
acc = series[0] if len(series) == 1 else np.mean(series, axis=0)
vol[i] = np.clip(np.rint(acc * 255.0), 0, 255).astype(np.uint8)
presence[i] = 1
out[geo] = (vol, presence)
return out, n_used
def _load_nii_bytes(raw: bytes) -> np.ndarray:
fh = nib.FileHolder(fileobj=gzip.GzipFile(fileobj=io.BytesIO(raw)))
img = nib.Nifti1Image.from_file_map({"header": fh, "image": fh})
return np.asanyarray(img.dataobj, dtype=np.float32)
def process_zip(path, geometries=("d15",)):
"""A study zip (training layout <id>/atlas_img/, challenge layout <id>/atlas/)."""
with zipfile.ZipFile(path) as zf:
names = [n for n in zf.namelist() if n.endswith((".nii.gz", ".nii")) and "/atlas" in n
and "/atlas_seg/" not in n and "mask" not in n]
return process_study(names, lambda n: _load_nii_bytes(zf.read(n)), geometries)
def process_files(paths, geometries=("d15",)):
return process_study([str(p) for p in paths],
lambda p: np.asanyarray(nib.load(p).dataobj, dtype=np.float32), geometries)