brain_deer.plugins.laminosaurus.analysis

Core layer-fMRI analysis helpers for Laminosaurus.

Classes

LaminarVoxelPreferenceResult

Whole-volume Fisher z(r_vs_sup) − z(r_vs_deep) using ROI superficial/deep layer-mean references.

InterRoiLayerConnectivityResult

Pearson r between each layer mean TS in ROI A and each layer mean TS in ROI B (4-D data).

InterRoiTaskComparisonResult

Two condition-specific inter-ROI laminar FC maps plus aligned delta_r (e.g. left vs right tapping blocks).

OrthogonalSupDeepCrossRoiResult

Huber-style orthogonal superficial/deep composites per ROI, then cross-ROI correlations and depth profiles.

InterRoiDepthBinSummary

2×2 block means over laminar pairs: rows = ROI A bin, columns = ROI B bin.

LaminarSeedTargetResult

Correlations of ROI superficial vs deep laminar reference time series with each column mean TS.

ColumnColumnConnectivityResult

Pairwise Pearson correlation between column-based time series with multiple-testing control.

Functions

depth_bin_index(→ int | None)

Map layer label to 0 = L1–split (upper), 1 = split+1–10 (lower). Labels outside 1–10 are excluded.

aggregate_inter_roi_depth_bins(...)

Mean value in each quadrant: A-upper×B-upper, A-upper×B-lower, … using only finite matrix cells.

slice_mask(→ numpy.ndarray)

Keep only one slice from a 3-D ROI mask.

mask_from_column_labels(→ numpy.ndarray)

Union mask of all voxels whose column label is in labels.

build_column_mask(→ tuple[numpy.ndarray, int | None])

Create a mask from the column label under the current voxel.

compute_layer_profile(→ LayerProfileResult)

Compute a layer profile inside an ROI.

compute_column_summary(→ dict[str, Any])

Summarize per-column layer measurements inside the ROI.

compute_column_profiles(→ ColumnExplorerResult | None)

Build a layer-by-column matrix inside an ROI.

compute_column_column_connectivity(...)

Pairwise Pearson correlations between column (or sup/deep) mean time series with MC correction.

compute_laminar_voxel_preference_map(...)

Voxelwise Fisher z(r(voxel, T_sup)) − z(r(voxel, T_deep)) over the 4-D volume.

volume_indices_from_bids_events(→ tuple[numpy.ndarray, ...)

Map BIDS *_events.tsv rows to volume indices for two trial_type values.

volume_block_lists_from_bids_events(...)

One numpy array of volume indices per events row (block), in onset order.

inter_roi_fc_split_half_block_reliability(→ dict[str, Any])

Split-half reliability: odd vs even event blocks (same condition), correlate upper triangles of r.

inter_roi_task_block_permutation_mean_abs_delta_r(...)

Randomly reassign whole event blocks to pseudo conditions; null for mean(abs(delta_r)) laminar matrix.

mutual_orthogonalize_timeseries(→ tuple[numpy.ndarray, ...)

Dual regression residuals (Huber-style unique laminar components).

layerwise_mean_timeseries(→ tuple[numpy.ndarray, ...)

Mean BOLD time course per cortical layer label inside roi_mask.

layerwise_mean_timeseries_two_rois(...)

Like two layerwise_mean_timeseries() calls, but one 3-D crop and one TR loop.

within_roi_layer_correlation_matrix(...)

Full within-ROI layer × layer correlation matrix (Huber et al. 2017 Fig. 5 style).

layer_correlation_matrix_from_timeseries(→ numpy.ndarray)

Symmetric Pearson r matrix between rows (each row = one layer time series).

split_layer_labels_superficial_deep(→ tuple[list[int], ...)

Split sorted layer IDs into (superficial, deep) groups.

huber_style_orthogonalized_sup_deep_composites(...)

ROI-mean superficial vs deep composite TS, then mutual orthogonalization.

huber_style_orthogonalized_sup_deep_from_ts_matrix(...)

Same as huber_style_orthogonalized_sup_deep_composites() but from a precomputed TS matrix.

compute_inter_roi_layer_connectivity(...)

Pearson r between layer-mean time series in ROI A vs ROI B (4-D data).

inter_roi_layer_connectivity_from_timeseries(...)

Inter-ROI laminar FC from precomputed layer-mean time series (rows = layers, cols = TRs).

compute_orthogonal_sup_deep_cross_roi(...)

Orthogonal sup/deep composites in each ROI, then 4×4 cross-correlations and laminar profiles.

compute_inter_roi_task_comparison(...)

Two inter-ROI laminar FC maps on disjoint TR subsets plus aligned delta_r matrix.

compute_laminar_seed_target_preference(...)

Two ROI reference time series (superficial vs deep layer thirds) vs each column mean TS.

compute_layer_connectivity(→ ConnectivityResult | None)

Compute layer-by-layer timeseries correlations inside the ROI.

compute_layer_dynamics(→ DynamicsResult | None)

Compute time-resolved laminar dynamics inside the ROI.

compute_seed_connectivity_map(...)

Compute a whole-volume seed connectivity map and an ROI-restricted laminar summary.

compute_profile_comparison(→ ProfileComparisonResult)

Compare two functional volumes over the same layered ROI.

compute_qc_dashboard(→ dict[str, Any])

Generate QC metrics for an ROI-based laminar analysis.

compute_control_metrics(→ dict[str, Any])

Subject-level control / QC metrics from layer + functional data (no external motion file).

Module Contents

class brain_deer.plugins.laminosaurus.analysis.LaminarVoxelPreferenceResult

Whole-volume Fisher z(r_vs_sup) − z(r_vs_deep) using ROI superficial/deep layer-mean references.

class brain_deer.plugins.laminosaurus.analysis.InterRoiLayerConnectivityResult

Pearson r between each layer mean TS in ROI A and each layer mean TS in ROI B (4-D data).

class brain_deer.plugins.laminosaurus.analysis.InterRoiTaskComparisonResult

Two condition-specific inter-ROI laminar FC maps plus aligned delta_r (e.g. left vs right tapping blocks).

class brain_deer.plugins.laminosaurus.analysis.OrthogonalSupDeepCrossRoiResult

Huber-style orthogonal superficial/deep composites per ROI, then cross-ROI correlations and depth profiles.

Composites are ROI-mean BOLD over superficial vs deep layer bins, then mutual_orthogonalize_timeseries() within each ROI. Cross-correlations use the four orthogonalized time series. Depth profiles correlate each layer-mean time series in the target ROI with the partner’s orthogonalized superficial and deep composites (Huber et al. 2017-style laminar FC readout).

class brain_deer.plugins.laminosaurus.analysis.InterRoiDepthBinSummary

2×2 block means over laminar pairs: rows = ROI A bin, columns = ROI B bin.

brain_deer.plugins.laminosaurus.analysis.depth_bin_index(layer_label: int, *, split_layer: int = INTER_ROI_DEPTH_SPLIT_LAYER) → int | None

Map layer label to 0 = L1–split (upper), 1 = split+1–10 (lower). Labels outside 1–10 are excluded.

brain_deer.plugins.laminosaurus.analysis.aggregate_inter_roi_depth_bins(matrix: numpy.ndarray, layer_labels_a: list[int], layer_labels_b: list[int], *, split_layer: int = INTER_ROI_DEPTH_SPLIT_LAYER, value_name: str = 'r') → InterRoiDepthBinSummary | None

Mean value in each quadrant: A-upper×B-upper, A-upper×B-lower, … using only finite matrix cells.

class brain_deer.plugins.laminosaurus.analysis.LaminarSeedTargetResult

Correlations of ROI superficial vs deep laminar reference time series with each column mean TS.

class brain_deer.plugins.laminosaurus.analysis.ColumnColumnConnectivityResult

Pairwise Pearson correlation between column-based time series with multiple-testing control.

brain_deer.plugins.laminosaurus.analysis.slice_mask(mask: numpy.ndarray, axis: int, index: int) → numpy.ndarray

Keep only one slice from a 3-D ROI mask.

brain_deer.plugins.laminosaurus.analysis.mask_from_column_labels(column_volume: numpy.ndarray, labels: Iterable[int], *, restrict_to_layer_positive: numpy.ndarray | None = None) → numpy.ndarray

Union mask of all voxels whose column label is in labels.

If restrict_to_layer_positive is set (same 3-D shape as column_volume), only voxels with layer value > 0 are included (typical cortical mask).

brain_deer.plugins.laminosaurus.analysis.build_column_mask(column_data: numpy.ndarray, voxel: tuple[int, int, int]) → tuple[numpy.ndarray, int | None]

Create a mask from the column label under the current voxel.

brain_deer.plugins.laminosaurus.analysis.compute_layer_profile(functional_volume: numpy.ndarray, layer_volume: numpy.ndarray, roi_mask: numpy.ndarray, *, reduction: str = 'mean', normalize: str = 'none', positive_only: bool = False, reverse_order: bool = False, column_volume: numpy.ndarray | None = None, upsample_factor: int = 1, smoothing_radius: int = 0, anchor_zero_endpoints: bool = True) → LayerProfileResult

Compute a layer profile inside an ROI.

brain_deer.plugins.laminosaurus.analysis.compute_column_summary(functional_volume: numpy.ndarray, layer_volume: numpy.ndarray, roi_mask: numpy.ndarray, *, column_volume: numpy.ndarray | None, reduction: str, positive_only: bool, reverse_order: bool, anchor_zero_endpoints: bool = True) → dict[str, Any]

Summarize per-column layer measurements inside the ROI.

brain_deer.plugins.laminosaurus.analysis.compute_column_profiles(functional_volume: numpy.ndarray, layer_volume: numpy.ndarray, roi_mask: numpy.ndarray, *, column_volume: numpy.ndarray | None, reduction: str = 'mean', normalize: str = 'minmax', positive_only: bool = False, reverse_order: bool = False, max_columns: int = 96, anchor_zero_endpoints: bool = True) → ColumnExplorerResult | None

Build a layer-by-column matrix inside an ROI.

brain_deer.plugins.laminosaurus.analysis.compute_column_column_connectivity(functional_4d: numpy.ndarray, layer_volume: numpy.ndarray, column_volume: numpy.ndarray, roi_mask: numpy.ndarray, *, node_mode: str = 'sup_deep', reverse_order: bool = False, correction: str = 'fdr_bh', alpha: float = 0.05) → ColumnColumnConnectivityResult | None

Pairwise Pearson correlations between column (or sup/deep) mean time series with MC correction.

node_mode is either column_mean (one node per column) or sup_deep (two nodes per column: superficial third vs deep third of layer labels).

Off-diagonal p-values are adjusted with correction (fdr_bh, bonferroni, holm, or none). Significance uses alpha on adjusted p-values (two-sided Pearson tests, df = T-2).

brain_deer.plugins.laminosaurus.analysis.compute_laminar_voxel_preference_map(functional_4d: numpy.ndarray, layer_volume: numpy.ndarray, roi_mask: numpy.ndarray, *, reverse_order: bool = False, chunk_size: int = 50000, target_mask: numpy.ndarray | None = None) → LaminarVoxelPreferenceResult | None

Voxelwise Fisher z(r(voxel, T_sup)) − z(r(voxel, T_deep)) over the 4-D volume.

T_sup and T_deep are ROI mean time series over the upper / lower third of layer labels in roi_mask. Each voxel’s time series is correlated with both references (Pearson on centered data). If target_mask is set (same 3-D shape), only those voxels are computed and written; elsewhere stays NaN — the UI passes layer_volume > 0 so work scales with cortical coverage, not full grid size. Use None to fill the entire grid (slow on large volumes).

brain_deer.plugins.laminosaurus.analysis.volume_indices_from_bids_events(events_path: str | pathlib.Path, *, tr: float, n_volumes: int, trial_type_label_1: str, trial_type_label_2: str) → tuple[numpy.ndarray, numpy.ndarray] | None

Map BIDS *_events.tsv rows to volume indices for two trial_type values.

Uses TR-sliced volumes [k·TR, (k+1)·TR) overlapping each event [onset, onset+duration). Matching is case-insensitive on trial_type / stimulus_type / trial_type.

brain_deer.plugins.laminosaurus.analysis.volume_block_lists_from_bids_events(events_path: str | pathlib.Path, *, tr: float, n_volumes: int, trial_type_label_1: str, trial_type_label_2: str) → tuple[list[numpy.ndarray], list[numpy.ndarray]] | None

One numpy array of volume indices per events row (block), in onset order.

Pausen zwischen Blöcken entstehen automatisch, weil nur Volumina gezählt werden, die mit der jeweiligen Event-Zeile überlappen — nicht aufeinanderfolgende TRs werden nicht in denselben Block gezwungen.

brain_deer.plugins.laminosaurus.analysis.inter_roi_fc_split_half_block_reliability(functional_4d: numpy.ndarray, layer_volume: numpy.ndarray, roi_mask_a: numpy.ndarray, roi_mask_b: numpy.ndarray, blocks: list[numpy.ndarray], *, name_a: str = 'ROI A', name_b: str = 'ROI B', reverse_order: bool = False, exclude_overlap: bool = True, partial_correlation: bool = False, extra_nuisance: numpy.ndarray | None = None) → dict[str, Any]

Split-half reliability: odd vs even event blocks (same condition), correlate upper triangles of r.

brain_deer.plugins.laminosaurus.analysis.inter_roi_task_block_permutation_mean_abs_delta_r(functional_4d: numpy.ndarray, layer_volume: numpy.ndarray, roi_mask_a: numpy.ndarray, roi_mask_b: numpy.ndarray, blocks_1: list[numpy.ndarray], blocks_2: list[numpy.ndarray], n_perm: int, *, label_1: str, label_2: str, name_a: str = 'ROI A', name_b: str = 'ROI B', reverse_order: bool = False, exclude_overlap: bool = True, partial_correlation: bool = False, extra_nuisance: numpy.ndarray | None = None, seed: int | None = None) → dict[str, Any]

Randomly reassign whole event blocks to pseudo conditions; null for mean(abs(delta_r)) laminar matrix.

brain_deer.plugins.laminosaurus.analysis.mutual_orthogonalize_timeseries(ts_a: numpy.ndarray, ts_b: numpy.ndarray) → tuple[numpy.ndarray, numpy.ndarray]

Dual regression residuals (Huber-style unique laminar components).

ts_a_orth is ts_a after removing linear fit to ts_b (+ intercept); ts_b_orth symmetrically. Matches the common laminar-fMRI practice cited in Huber et al. (2017) Neuron (superficial vs deep seeds sharing variance).

brain_deer.plugins.laminosaurus.analysis.layerwise_mean_timeseries(functional_4d: numpy.ndarray, layer_volume: numpy.ndarray, roi_mask: numpy.ndarray, layer_labels: Iterable[int], *, time_indices: numpy.ndarray | None = None) → tuple[numpy.ndarray, list[int]]

Mean BOLD time course per cortical layer label inside roi_mask.

Returns (matrix, labels) with matrix shape (len(labels), n_time) — each row is one layer’s ROI-mean over voxels with that label (only voxels in roi_mask).

brain_deer.plugins.laminosaurus.analysis.layerwise_mean_timeseries_two_rois(functional_4d: numpy.ndarray, layer_volume: numpy.ndarray, roi_mask_a: numpy.ndarray, layer_labels_a: Iterable[int], roi_mask_b: numpy.ndarray, layer_labels_b: Iterable[int], *, time_indices: numpy.ndarray | None = None) → tuple[numpy.ndarray, list[int], numpy.ndarray, list[int]]

Like two layerwise_mean_timeseries() calls, but one 3-D crop and one TR loop.

brain_deer.plugins.laminosaurus.analysis.within_roi_layer_correlation_matrix(functional_4d: numpy.ndarray, layer_volume: numpy.ndarray, roi_mask: numpy.ndarray, *, time_indices: numpy.ndarray | None = None, layer_labels: Iterable[int] | None = None) → tuple[numpy.ndarray, list[int]]

Full within-ROI layer × layer correlation matrix (Huber et al. 2017 Fig. 5 style).

Each matrix entry is Pearson r between ROI-mean layer time series. Rows/columns follow increasing layer_labels (defaults to positive integers present under roi_mask).

brain_deer.plugins.laminosaurus.analysis.layer_correlation_matrix_from_timeseries(ts_matrix: numpy.ndarray) → numpy.ndarray

Symmetric Pearson r matrix between rows (each row = one layer time series).

brain_deer.plugins.laminosaurus.analysis.split_layer_labels_superficial_deep(layer_labels: Iterable[int], *, superficial: str = 'upper_numeric') → tuple[list[int], list[int]]

Split sorted layer IDs into (superficial, deep) groups.

superficial='upper_numeric' (default): higher label IDs → superficial (pial), lower IDs → deep (white-matter–proximate), for equidistant laminar bins 1…N. lower_numeric inverts that mapping (use only if your atlas encodes pial with lower numbers). The choice swaps composite time courses and the entire Huber-style 4×4.

brain_deer.plugins.laminosaurus.analysis.huber_style_orthogonalized_sup_deep_composites(functional_4d: numpy.ndarray, layer_volume: numpy.ndarray, roi_mask: numpy.ndarray, *, superficial_labels: Iterable[int], deep_labels: Iterable[int], time_indices: numpy.ndarray | None = None) → tuple[numpy.ndarray, numpy.ndarray, dict[str, Any]]

ROI-mean superficial vs deep composite TS, then mutual orthogonalization.

Returns (ts_sup_orth, ts_deep_orth, metrics) with scalar r_raw / r_orth_between_composites.

brain_deer.plugins.laminosaurus.analysis.huber_style_orthogonalized_sup_deep_from_ts_matrix(ts_matrix: numpy.ndarray, layer_labels_order: list[int], *, superficial_labels: Iterable[int], deep_labels: Iterable[int]) → tuple[numpy.ndarray, numpy.ndarray, dict[str, Any]]

Same as huber_style_orthogonalized_sup_deep_composites() but from a precomputed TS matrix.

ts_matrix rows align with layer_labels_order (one row per layer mean time course).

brain_deer.plugins.laminosaurus.analysis.compute_inter_roi_layer_connectivity(functional_4d: numpy.ndarray, layer_volume: numpy.ndarray, roi_mask_a: numpy.ndarray, roi_mask_b: numpy.ndarray, *, name_a: str = 'ROI A', name_b: str = 'ROI B', reverse_order: bool = False, exclude_overlap: bool = True, time_indices: numpy.ndarray | None = None, partial_correlation: bool = False, extra_nuisance: numpy.ndarray | None = None) → InterRoiLayerConnectivityResult | None

Pearson r between layer-mean time series in ROI A vs ROI B (4-D data).

For each distinct layer label in A (and B), the mean 4-D time course over voxels with that label in the ROI is correlated with every layer mean in the other ROI. If exclude_overlap is True, voxels belonging to both masks are removed from both sides so the same tissue does not drive both time series.

If time_indices is set (1-D int, subset of 0..T-1), correlations use only those TRs (e.g. task blocks from a BIDS events file).

If partial_correlation is True, each pair is computed on residuals after regressing both time series on [intercept, cortical mean BOLD] (mean over layer_volume > 0 per TR inside a tight 3-D crop of cortex ∪ ROI A ∪ ROI B; plus extra_nuisance columns if given). This reduces shared variance from widespread fluctuations without reading the entire FOV each TR. extra_nuisance must have shape (n_TR_full, K) along the full functional run; it is subset with time_indices when those are used.

brain_deer.plugins.laminosaurus.analysis.inter_roi_layer_connectivity_from_timeseries(ts_rows_a: numpy.ndarray, ts_rows_b: numpy.ndarray, layer_labels_a: list[int], layer_labels_b: list[int], *, name_a: str = 'ROI A', name_b: str = 'ROI B') → InterRoiLayerConnectivityResult | None

Inter-ROI laminar FC from precomputed layer-mean time series (rows = layers, cols = TRs).

brain_deer.plugins.laminosaurus.analysis.compute_orthogonal_sup_deep_cross_roi(functional_4d: numpy.ndarray, layer_volume: numpy.ndarray, roi_mask_a: numpy.ndarray, roi_mask_b: numpy.ndarray, *, name_a: str = 'ROI A', name_b: str = 'ROI B', view_label: str = 'full', exclude_overlap: bool = True, time_indices: numpy.ndarray | None = None, partial_correlation: bool = False, superficial_mode: str = 'upper_numeric') → OrthogonalSupDeepCrossRoiResult | None

Orthogonal sup/deep composites in each ROI, then 4×4 cross-correlations and laminar profiles.

superficial_mode is passed to split_layer_labels_superficial_deep() (upper_numeric vs lower_numeric). If partial_correlation is True, each layer-mean time series is residualized w.r.t. [intercept, cortical mean BOLD] (layer_volume > 0, tight crop per TR) before composites, matching the fast partial regressor used for inter-ROI FC.

brain_deer.plugins.laminosaurus.analysis.compute_inter_roi_task_comparison(functional_4d: numpy.ndarray, layer_volume: numpy.ndarray, roi_mask_a: numpy.ndarray, roi_mask_b: numpy.ndarray, indices_1: numpy.ndarray, indices_2: numpy.ndarray, *, label_1: str, label_2: str, name_a: str = 'ROI A', name_b: str = 'ROI B', reverse_order: bool = False, exclude_overlap: bool = True, partial_correlation: bool = False, extra_nuisance: numpy.ndarray | None = None) → InterRoiTaskComparisonResult | None

Two inter-ROI laminar FC maps on disjoint TR subsets plus aligned delta_r matrix.

brain_deer.plugins.laminosaurus.analysis.compute_laminar_seed_target_preference(functional_4d: numpy.ndarray, layer_volume: numpy.ndarray, column_volume: numpy.ndarray, roi_mask: numpy.ndarray, *, reverse_order: bool = False, max_columns: int = 96) → LaminarSeedTargetResult | None

Two ROI reference time series (superficial vs deep layer thirds) vs each column mean TS.

For each cortical column overlapping the ROI, the target time series is the mean 4-D signal over voxels with that column label and positive layer (same convention as column-mean nodes). Reference T_sup / T_deep are ROI-wide means over the upper / lower third of layer labels (matching compute_column_column_connectivity sup_deep split). z_diff is Fisher z(r_sup) − Fisher z(r_deep); positive values favour coupling to the superficial laminar reference.

brain_deer.plugins.laminosaurus.analysis.compute_layer_connectivity(functional_4d: numpy.ndarray, layer_volume: numpy.ndarray, roi_mask: numpy.ndarray, *, reverse_order: bool = False) → ConnectivityResult | None

Compute layer-by-layer timeseries correlations inside the ROI.

brain_deer.plugins.laminosaurus.analysis.compute_layer_dynamics(functional_4d: numpy.ndarray, layer_volume: numpy.ndarray, roi_mask: numpy.ndarray, *, reverse_order: bool = False, positive_only: bool = False) → DynamicsResult | None

Compute time-resolved laminar dynamics inside the ROI.

brain_deer.plugins.laminosaurus.analysis.compute_seed_connectivity_map(functional_4d: numpy.ndarray, seed_mask: numpy.ndarray, layer_volume: numpy.ndarray, *, column_volume: numpy.ndarray | None = None, reverse_order: bool = False, chunk_size: int = 50000, anchor_zero_endpoints: bool = True) → SeedConnectivityResult | None

Compute a whole-volume seed connectivity map and an ROI-restricted laminar summary.

brain_deer.plugins.laminosaurus.analysis.compute_profile_comparison(primary_volume: numpy.ndarray, secondary_volume: numpy.ndarray, layer_volume: numpy.ndarray, roi_mask: numpy.ndarray, *, primary_name: str, secondary_name: str, reduction: str = 'mean', positive_only: bool = False, reverse_order: bool = False, anchor_zero_endpoints: bool = True) → ProfileComparisonResult

Compare two functional volumes over the same layered ROI.

brain_deer.plugins.laminosaurus.analysis.compute_qc_dashboard(functional_volume: numpy.ndarray, layer_volume: numpy.ndarray, roi_mask: numpy.ndarray, *, column_volume: numpy.ndarray | None = None, profile_result: LayerProfileResult | None = None) → dict[str, Any]

Generate QC metrics for an ROI-based laminar analysis.

brain_deer.plugins.laminosaurus.analysis.compute_control_metrics(layer_volume: numpy.ndarray, *, functional_3d: numpy.ndarray | None = None, functional_4d: numpy.ndarray | None = None, roi_mask: numpy.ndarray | None = None, column_volume: numpy.ndarray | None = None, max_voxels_tsnr_sample: int = 25000) → dict[str, Any]

Subject-level control / QC metrics from layer + functional data (no external motion file).

Intended for PoC documentation per participant: cortical coverage, global temporal stability, optional ROI overlap, optional column map coverage. Motion (FD, DVARS from confounds.tsv) should still be taken from the preprocessing pipeline when available.