#!/usr/bin/env python # coding: utf-8 # # Decoding model # # ### Between-subject classification # In[ ]: """ Permutation-based searchlight decoding on hyperaligned/anatomically aligned data This script loads hyperaligned/anatomically aligned training and test data, aggregates runs, and performs manual permutation testing using a Linear SVM decoder within searchlights on the cortical surface. Outputs: - Decoding accuracy per vertex - Permutation distributions - P-values (nonparametric) Author: Theo Marins """ # ============================================================================ # Imports # ============================================================================ import os import gc import time import pickle import warnings import psutil import numpy as np import neuroboros as nb from tqdm import tqdm from sklearn.svm import LinearSVC from sklearn.exceptions import ConvergenceWarning from sklearn.base import clone # Silence convergence warnings warnings.filterwarnings("ignore", category=ConvergenceWarning) # ============================================================================ # Paths # ============================================================================ PERM_DIR = os.path.join(BASE_DIR, "permutations_HA") # or "permutations_AA" for anatomical alignment os.makedirs(OUT_DIR, exist_ok=True) test_data_file = os.path.join(PERM_DIR, "test_data.pkl") train_data_file = os.path.join(PERM_DIR, "train_data.pkl") test_label_file = os.path.join(PERM_DIR, "test_labels.pkl") train_label_file = os.path.join(PERM_DIR, "train_labels.pkl") # ============================================================================ # Load data # ============================================================================ with open(test_data_file, "rb") as f: test_data_dict = pickle.load(f) with open(train_data_file, "rb") as f: train_data_dict = pickle.load(f) with open(test_label_file, "rb") as f: test_label_dict = pickle.load(f) with open(train_label_file, "rb") as f: train_label_dict = pickle.load(f) subjects = list(test_data_dict["l"].keys()) print("Loaded subjects:", subjects) # ============================================================================ # Parameters # ============================================================================ radius = 10 batch_size = 60 n_permutations = 100 n_jobs = -1 decoder = LinearSVC( penalty="l2", dual="auto", max_iter=5000 ) # Memory guard (important for cluster runs) memory_limit_gb = 200 memory_limit_bytes = memory_limit_gb * 1024**3 # ============================================================================ # Utilities # ============================================================================ def check_memory_usage(): """Abort if memory usage exceeds threshold.""" process = psutil.Process(os.getpid()) memory_usage = process.memory_info().rss if memory_usage > memory_limit_bytes: raise MemoryError( f"Memory usage {memory_usage / 1024**3:.2f} GB " f"exceeds limit {memory_limit_gb} GB" ) # ============================================================================ # Permutation test (manual, searchlight-wise) # ============================================================================ def permutation_test_searchlight( batch_indices, sls, train_data, test_data, train_labels, test_labels, decoder, n_permutations ): """ Perform permutation testing for a batch of searchlights. """ rng = np.random.default_rng(42) results = [] # concatenate runs for training train_data = np.concatenate(train_data, axis=0) for idx in batch_indices: sl = sls[idx] X_train = train_data[:, sl] X_test = test_data[:, sl] # True score clf = clone(decoder) clf.fit(X_train, train_labels) score = clf.score(X_test, test_labels) # Permutations perm_scores = [] for _ in range(n_permutations): y_perm = rng.permutation(train_labels) clf = clone(decoder) clf.fit(X_train, y_perm) perm_scores.append(clf.score(X_test, test_labels)) perm_scores = np.array(perm_scores) pval = (np.sum(perm_scores >= score) + 1) / (n_permutations + 1) results.append((idx, score, perm_scores, pval)) return results # ============================================================================ # Main loop # ============================================================================ scores = {} permutation_scores = {} pvalues = {} for hemi in ["l", "r"]: print("\n" + "=" * 60) print(f"Processing hemisphere: {hemi}") print("=" * 60) sls = nb.sls(hemi, radius, space="fsavg-ico32", mask=True) valid_indices = list(range(len(sls))) scores[hemi] = {} permutation_scores[hemi] = {} pvalues[hemi] = {} for subject in tqdm(subjects, desc=f"{hemi} hemisphere"): print(f"Subject {subject}") # Output files (resume-safe) score_file = os.path.join(PERM_DIR, f"score--{hemi}--sub{subject}.pkl") perm_file = os.path.join(PERM_DIR, f"perm_scores--{hemi}--sub{subject}.pkl") pval_file = os.path.join(PERM_DIR, f"pvals_perm--{hemi}--sub{subject}.pkl") if all(os.path.exists(f) for f in [score_file, perm_file, pval_file]): print(" Skipping (already processed)") continue scores[hemi][subject] = np.zeros(len(sls)) permutation_scores[hemi][subject] = np.zeros((len(sls), n_permutations)) pvalues[hemi][subject] = np.zeros(len(sls)) train_data = train_data_dict[hemi][subject] test_data = test_data_dict[hemi][subject] train_labels = train_label_dict[hemi][subject] test_labels = test_label_dict[hemi][subject] # ------------------------------------------------------------ # Process in batches (memory-safe) # ------------------------------------------------------------ for i in range(0, len(valid_indices), batch_size): batch_indices = valid_indices[i:i + batch_size] print(f"Batch {i}–{i + len(batch_indices)}") start = time.time() batch_results = permutation_test_searchlight( batch_indices, sls, train_data, test_data, train_labels, test_labels, decoder, n_permutations ) for idx, score, perm, pval in batch_results: scores[hemi][subject][idx] = score permutation_scores[hemi][subject][idx] = perm pvalues[hemi][subject][idx] = pval print(f" Done in {time.time() - start:.2f}s") check_memory_usage() gc.collect() # ------------------------------------------------------------ # Save subject-level outputs # ------------------------------------------------------------ with open(score_file, "wb") as f: pickle.dump(scores[hemi][subject], f) with open(perm_file, "wb") as f: pickle.dump(permutation_scores[hemi][subject], f) with open(pval_file, "wb") as f: pickle.dump(pvalues[hemi][subject], f) print("\nAll processing completed successfully.")