Source code for snake.core.handlers.activations.activations

"""Activation Handler."""

import numpy as np
from numpy.typing import NDArray
import pandas as pd

from ...._meta import LogMixin
from ...phantom import Phantom, DynamicData
from ...simulation import SimConfig
from ..base import AbstractHandler
from ..utils import apply_weights
from .roi import BRAINWEB_OCCIPITAL_ROI, get_indices_inside_ellipsoid
from .bold import get_bold, block_design, get_event_ts
from ...transform import apply_affine


[docs] class ActivationMixin(LogMixin): """Add activation inside the region of interest. for a single type of event. Parameters ---------- event_condition: array-like of shape (3, n_events) yields description of events for this condition as a (onsets, durations, amplitudes) triplet hrf_model: Choice for the HRF, FIR is not oversampling: Oversampling factor to perform the convolution. Default=50. min_onset: Minimal onset relative to frame_times[0] (in seconds) events that start before frame_times[0] + min_onset are not considered. Default=-24. base_tissue_name: Name of the tissue to intersect with the ROI. atlas: str, default=None Name of the atlas to use for the ROI. atlas_label: int | str, default=-1 Label of the ROI in the atlas. Notes ----- If no atlases is provided, the ROI is computed by intersecting the base tissue with an ellipsoid in the occipital region. If a probabilistic atlas is provided, the effective BOLD signal will be the product of the voxel base_tissue_name (e.g. gray-matter) and of the atlas mask. See Also -------- nilearn.compute_regressors """ event_condition: pd.DataFrame | np.ndarray duration: float offset: float = 0 event_name: str roi_tissue_name: str = "ROI" delta_r2s: float = 1000.0 # mHz hrf_model: str = "glover" oversampling: int = 10 min_onset: float = -24.0 base_tissue_name: str = "gm" # The ROI intersected with the gray matter mask. # Use nilearn for downloading the atlas. atlas: str | None = "hardvard-oxford__cort-maxprob-thr50-1mm" atlas_label: int | str = ""
[docs] def get_static(self, phantom: Phantom, sim_config: SimConfig) -> Phantom: """Get the static ROI.""" # shape: tuple[int, ...] | None = phantom.tissues_mask.shape[1:] tissue_index = phantom.labels == self.base_tissue_name if self.atlas is None: roi = self._get_roi_base(phantom) else: roi = self._get_roi_atlas(phantom) # update the phantom new_phantom = phantom.add_tissue( self.roi_tissue_name, roi, phantom.props[tissue_index, :], phantom.name + "-roi", ) return new_phantom
[docs] def _get_roi_base(self, phantom: Phantom) -> NDArray: tissue_index = phantom.labels == self.base_tissue_name if tissue_index.sum() == 0: raise ValueError( f"Tissue {self.base_tissue_name} not found in the phantom." ) roi_base = phantom.masks[tissue_index].squeeze().copy() occ_roi = BRAINWEB_OCCIPITAL_ROI.copy() roi_zoom = np.array(roi_base.shape) / np.array(occ_roi["shape"]) self.log.debug( "ROI parameters (orig, target, zoom) %s, %s, %s", occ_roi["shape"], roi_base.shape, roi_zoom, ) ellipsoid = get_indices_inside_ellipsoid( roi_base.shape, center=np.array(occ_roi["center"]) * roi_zoom, semi_axes_lengths=np.array(occ_roi["semi_axes_lengths"]) * roi_zoom, euler_angles=occ_roi["euler_angles"], ) roi_base[~ellipsoid] = 0 return roi_base
[docs] def _get_roi_atlas(self, phantom: Phantom) -> NDArray: """Get the ROI from the atlas. Currently, only the Harvard-Oxford atlas is supported. """ atlas_base, atlas_name = self.atlas.split("__") from nilearn.datasets.atlas import fetch_atlas_harvard_oxford if atlas_base == "hardvard-oxford": atlas = fetch_atlas_harvard_oxford(atlas_name=atlas_name) else: raise ValueError(f"Atlas {atlas_base} not supported.") maps = atlas.maps if isinstance(self.atlas_label, str): idx = atlas.labels.index(self.atlas_label) else: idx = self.atlas_label if maps.dataobj.ndim == 4: # probabilistic atlas atlas_mask = np.array(maps.dataobj[..., idx]).astype(np.float32) else: atlas_mask = np.array(maps.dataobj == idx).astype(np.float32) # Resample the atlas to the phantom affine atlas_mask = apply_affine( atlas_mask, old_affine=maps.affine, new_affine=phantom.affine, new_shape=phantom.anat_shape, use_gpu=True, ) roi = atlas_mask * phantom.masks[phantom.labels_idx[self.base_tissue_name]] return roi
[docs] def get_dynamic(self, phantom: Phantom, sim_conf: SimConfig) -> DynamicData: """Get dynamic time series for adding Activations.""" bold_strength = sim_conf.seq.TE / self.delta_r2s self.log.info("Computed BOLD Strength: %s", bold_strength) bold = get_bold( sim_conf.sim_tr_ms, sim_conf.max_sim_time, self.event_condition, self.hrf_model, self.oversampling, self.min_onset, bold_strength, ).squeeze() events = get_event_ts( self.event_condition, sim_conf.max_sim_time, sim_conf.sim_tr_ms, self.min_onset, ).squeeze() return DynamicData( name="-".join(["activation", self.event_name]), data=np.concatenate([bold[None, :], events[None, :]]), func=self.apply_weights, )
[docs] @staticmethod def apply_weights(phantom: Phantom, data: NDArray, time_idx: int) -> Phantom: """Apply weights to the ROI.""" return apply_weights(phantom, "ROI", data[0], time_idx)
[docs] class BlockActivationHandler(ActivationMixin, AbstractHandler): """Activation Handler with block design. Parameters ---------- block_on: float time the block activation is on. block_off: float time the block activation is off duration: float Total duration of the pattern in seconds offset: float, default 0 Start time of the pattern in seconds roi_tissue_name: str, default "ROI" Name of the ROI tissue event_name: str, default "block_on" Name of the event delta_r2s: float, default 1000.0 Delta R2s value hrf_model: str, default "glover" HRF model oversampling: int, default 50 Oversampling factor min_onset: float, default -24.0 Minimal onset roi_threshold: float, default 0.0 ROI threshold Notes ----- See Also the GLM module of Nilearn. """ __handler_name__ = "activation-block" block_on: float block_off: float duration: float offset: float = 0 roi_tissue_name: str = "ROI" event_name: str = "block_on" delta_r2s: float = 1000.0 hrf_model: str = "glover" oversampling: int = 50 min_onset: float = -24.0 roi_threshold: float = 0.0 base_tissue_name: str = "gm" atlas: str | None = "hardvard-oxford__cort-maxprob-thr50-1mm" atlas_label: int | str = 48
[docs] def __post_init__(self): self.event_condition = block_design( self.block_on, self.block_off, self.duration, self.offset, self.event_name, )