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


[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. roi_threshold: float, default 0.0 If greater than 0, the roi becomes a binary mask, with roi_threshold as separation. 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 hrf_model: str = "glover" oversampling: int = 10 min_onset: float = -24.0 roi_threshold: float = 0.0 base_tissue_name = "gm"
[docs] def get_static(self, phantom: Phantom, sim_config: SimConfig) -> Phantom: """Get the static ROI.""" tissue_index = phantom.labels == self.base_tissue_name # shape: tuple[int, ...] | None = phantom.tissues_mask.shape[1:] if tissue_index.sum() == 0: raise ValueError( f"Tissue {self.base_tissue_name} not found in the phantom." ) roi = phantom.masks[tissue_index].squeeze() occ_roi = BRAINWEB_OCCIPITAL_ROI.copy() # if self.bbox: # self.log.debug("ROI shape was ", occ_roi) # scaled_bbox = [0] * 6 # for i in range(3): # scaled_bbox[2 * i] = ( # int(self.bbox[2 * i] * occ_roi["shape"][i]) # if self.bbox[2 * i] # else 0 # ) # scaled_bbox[2 * i + 1] = ( # int(self.bbox[2 * i + 1] * occ_roi["shape"][i]) # if self.bbox[2 * i + 1] # else occ_roi["shape"][i] # ) # if scaled_bbox[2 * i + 1] < 0: # scaled_bbox[2 * i + 1] += occ_roi["shape"][i] # # replace None value by boundaries (0 or value) # # and shift the box # occ_roi["shape"] = ( # scaled_bbox[1] - scaled_bbox[0], # scaled_bbox[3] - scaled_bbox[2], # scaled_bbox[5] - scaled_bbox[4], # ) # occ_roi["center"] = ( # occ_roi["center"][0] - scaled_bbox[0], # occ_roi["center"][1] - scaled_bbox[2], # occ_roi["center"][2] - scaled_bbox[4], # ) roi_zoom = np.array(roi.shape) / np.array(occ_roi["shape"]) self.log.debug( "ROI parameters (orig, target, zoom) %s, %s, %s", occ_roi["shape"], roi.shape, roi_zoom, ) ellipsoid = get_indices_inside_ellipsoid( roi.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[~ellipsoid] = 0 # update the phantom new_phantom = Phantom( phantom.name + "-roi", masks=np.concatenate((phantom.masks, roi[None, ...]), axis=0), labels=np.concatenate((phantom.labels, np.array([self.roi_tissue_name]))), props=np.concatenate( (phantom.props, phantom.props[tissue_index, :]), axis=0, ), ) new_phantom = phantom.add_tissue( self.roi_tissue_name, roi, phantom.props[tissue_index, :], phantom.name + "-roi", ) return new_phantom
[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.""" __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 def __post_init__(self): self.event_condition = block_design( self.block_on, self.block_off, self.duration, self.offset, self.event_name, )