Source code for laion_fmri.group
"""Multi-subject Group wrapper for cross-subject analyses."""
from laion_fmri.discovery import get_subjects
from laion_fmri.subject import load_subject
[docs]
def load_subjects(subjects):
"""Load multiple subjects into a Group.
Parameters
----------
subjects : list[str | int] or "all"
List of subject identifiers, or ``"all"`` to load every
subject in the dataset.
Returns
-------
Group
"""
if subjects == "all":
subject_ids = get_subjects()
else:
subject_ids = subjects
subjects_dict = {}
for sub in subject_ids:
s = load_subject(sub)
subjects_dict[s.subject_id] = s
return Group(subjects_dict)
[docs]
class Group:
"""Holds several ``Subject`` instances and dispatches to them.
Parameters
----------
subjects_dict : dict[str, laion_fmri.subject.Subject]
Mapping of BIDS ID to loaded subject objects.
"""
def __init__(self, subjects_dict):
self._subjects = dict(subjects_dict)
self._ordered_ids = sorted(self._subjects.keys())
def __len__(self):
return len(self._subjects)
def __getitem__(self, key):
if isinstance(key, int):
sub_id = self._ordered_ids[key]
return self._subjects[sub_id]
if isinstance(key, str):
if key not in self._subjects:
raise KeyError(f"Subject '{key}' not in group.")
return self._subjects[key]
raise TypeError(
f"Key must be int or str, got {type(key).__name__}"
)
def __iter__(self):
for sub_id in self._ordered_ids:
yield sub_id, self._subjects[sub_id]
# ── Cross-subject loaders ───────────────────────────────────
[docs]
def get_shared_betas(
self, session, roi=None, mask=None, nc_threshold=None,
mask_source="anatomical",
):
"""Load shared-stimulus single-trial betas for all subjects.
Parameters
----------
session : str
BIDS session ID. Required -- single-trial betas are
stored per session.
roi : str or None
mask : np.ndarray[bool] or None
nc_threshold : float or None
mask_source : ``"anatomical"`` (default) | ``"rsquare"``
Forwarded to :meth:`Subject.get_betas`; see
:meth:`Subject.get_brain_mask` for the difference.
Returns
-------
dict[str, np.ndarray]
Mapping of subject ID to a betas array of shape
``(n_shared_trials, n_voxels)``.
"""
result = {}
for sub_id, sub in self:
result[sub_id] = sub.get_betas(
session=session,
stimuli="shared",
roi=roi,
mask=mask,
nc_threshold=nc_threshold,
mask_source=mask_source,
)
return result
[docs]
def get_shared_images(self, format="pil"):
"""Load shared stimulus images from disk.
Parameters
----------
format : ``"pil"`` or ``"numpy"``
Output container -- a list of :class:`PIL.Image.Image`
or a stacked ``(N, H, W, 3)`` uint8 array.
"""
from laion_fmri.stimuli import Stimuli
meta = self.get_shared_stimulus_metadata()
with Stimuli(data_dir=self._data_dir()) as stim:
images = [stim.images.get(name) for name in meta["image_name"]]
if format == "pil":
return images
if format == "numpy":
import numpy as np
return np.stack([np.array(img) for img in images]).astype(np.uint8)
raise ValueError(f"Unknown format: {format!r}")
def _data_dir(self):
"""All subjects share the same data dir; return it via the first."""
return self._subjects[self._ordered_ids[0]]._data_dir