""" FC Matrix Visualization Module. This module provides functionality for visualizing Functional Connectivity matrices independently from the prediction pipeline. """ import numpy as np # Configure matplotlib for headless environment import matplotlib matplotlib.use('Agg') # Use non-interactive backend import matplotlib.pyplot as plt from pathlib import Path import argparse import os import nibabel as nib try: from nilearn import input_data, connectome from nilearn.image import load_img from nilearn import datasets NILEARN_AVAILABLE = True except ImportError: NILEARN_AVAILABLE = False print("Warning: nilearn not available. Direct fMRI processing disabled.") from config import PREPROCESS_CONFIG # Import shared utility function from visualization import vector_to_matrix class FCVisualizer: """Class for visualizing FC matrices.""" def __init__(self, cmap='RdBu_r', vmin=-1, vmax=1): """ Initialize FCVisualizer with display parameters. Args: cmap: Colormap to use for FC matrices vmin: Minimum value for color scaling vmax: Maximum value for color scaling """ self.cmap = cmap self.vmin = vmin self.vmax = vmax def plot_single_matrix(self, matrix, title="FC Matrix", ax=None, fig=None): """ Plot a single FC matrix. Args: matrix: 2D numpy array containing FC matrix title: Title for the plot ax: Matplotlib axis to plot on (optional) fig: Matplotlib figure (optional) Returns: fig, ax: The figure and axis objects """ if ax is None: fig, ax = plt.subplots(figsize=(8, 6)) im = ax.imshow(matrix, cmap=self.cmap, vmin=self.vmin, vmax=self.vmax) ax.set_title(title) plt.colorbar(im, ax=ax) return fig, ax def plot_matrix_comparison(self, matrices, titles=None, figsize=None): """ Plot multiple FC matrices for comparison. Args: matrices: List of 2D numpy arrays containing FC matrices titles: List of titles for each matrix (optional) figsize: Custom figure size (optional) Returns: fig: The figure object """ n_matrices = len(matrices) if figsize is None: figsize = (5*n_matrices, 5) if titles is None: titles = [f"FC Matrix {i+1}" for i in range(n_matrices)] fig, axes = plt.subplots(1, n_matrices, figsize=figsize) # Handle single matrix case if n_matrices == 1: axes = [axes] for i, (matrix, title) in enumerate(zip(matrices, titles)): im = axes[i].imshow(matrix, cmap=self.cmap, vmin=self.vmin, vmax=self.vmax) axes[i].set_title(title) plt.colorbar(im, ax=axes[i]) plt.tight_layout() return fig def load_and_visualize_npy(self, file_path): """ Load and visualize an FC matrix from a .npy file. Args: file_path: Path to the .npy file Returns: fig: The figure object containing the visualization """ # Load the matrix data = np.load(file_path) # Check if it's an upper triangle or full matrix if len(data.shape) == 1: # Convert upper triangular to full matrix matrix = self._triu_to_matrix(data) else: matrix = data # Plot the matrix filename = os.path.basename(file_path) title = f"FC Matrix: {filename}" fig, _ = self.plot_single_matrix(matrix, title=title) return fig def _triu_to_matrix(self, triu_values, fisher_z=True): """ Convert upper triangular values to a full FC matrix. Args: triu_values: 1D array of upper triangular values fisher_z: Whether values are Fisher z-transformed Returns: full_matrix: 2D symmetric matrix """ # Use shared implementation from visualization.py return vector_to_matrix(triu_values) def process_and_visualize_fmri(self, fmri_file): """ Process an fMRI file and visualize its FC matrix. Args: fmri_file: Path to the fMRI .nii or .nii.gz file Returns: fig: The figure object containing the visualization, or None if processing fails """ if not NILEARN_AVAILABLE: print("Error: nilearn is required for fMRI processing") return None try: # Extract FC matrix (upper triangular values) fc_triu = self._process_single_fmri(fmri_file) # Convert to full matrix fc_matrix = self._triu_to_matrix(fc_triu) # Plot the matrix filename = os.path.basename(fmri_file) title = f"FC Matrix: {filename}" fig, _ = self.plot_single_matrix(fc_matrix, title=title) return fig except Exception as e: print(f"Error processing fMRI file: {e}") return None def _process_single_fmri(self, fmri_file): """ Process a single fMRI file to FC matrix. Args: fmri_file: Path to the fMRI .nii or .nii.gz file Returns: fc_triu: 1D array of upper triangular values (Fisher z-transformed) """ print(f"Processing fMRI file: {fmri_file}") # Use Power 264 atlas power = datasets.fetch_coords_power_2011() coords = np.vstack((power.rois['x'], power.rois['y'], power.rois['z'])).T # Create masker masker = input_data.NiftiSpheresMasker( coords, radius=PREPROCESS_CONFIG['radius'], standardize=True, memory='nilearn_cache', memory_level=1, verbose=0, detrend=True, low_pass=PREPROCESS_CONFIG['low_pass'], high_pass=PREPROCESS_CONFIG['high_pass'], t_r=PREPROCESS_CONFIG['t_r'] ) # Load and process fMRI print(f"Loading NIfTI file...") fmri_img = load_img(fmri_file) print(f"NIfTI file loaded, shape: {fmri_img.shape}") # Transform to time series print(f"Extracting time series...") time_series = masker.fit_transform(fmri_img) print(f"Time series extracted, shape: {time_series.shape}") # Compute FC matrix print(f"Computing FC matrix...") correlation_measure = connectome.ConnectivityMeasure( kind='correlation', vectorize=False, discard_diagonal=False ) fc_matrix = correlation_measure.fit_transform([time_series])[0] print(f"FC matrix computed, shape: {fc_matrix.shape}") # Get upper triangular part triu_indices = np.triu_indices_from(fc_matrix, k=1) fc_triu = fc_matrix[triu_indices] # Fisher z-transform fc_triu = np.arctanh(np.clip(fc_triu, -0.99, 0.99)) # Clip to avoid infinite values print(f"Processing complete. FC features shape: {fc_triu.shape}") return fc_triu def create_synthetic_fc_matrix(seed=None): """ Create a synthetic FC matrix for demonstration purposes. Args: seed: Random seed for reproducibility Returns: matrix: 2D symmetric matrix representing FC """ if seed is not None: np.random.seed(seed) # Number of ROIs (Power atlas has 264) n_rois = 264 # Create random correlation matrix # Method: generate random normal values, create outer product, normalize random_vectors = np.random.randn(n_rois, 50) # 50 random features matrix = np.corrcoef(random_vectors) # Ensure it's in the range [-1, 1] with 1s on diagonal np.fill_diagonal(matrix, 1.0) return matrix def main(): """Command-line interface for FC matrix visualization.""" parser = argparse.ArgumentParser(description='Visualize FC matrices') parser.add_argument('--input', type=str, help='Input file (fMRI .nii/.nii.gz or .npy FC matrix)') parser.add_argument('--output', type=str, help='Output image file (PNG/JPG/PDF)') parser.add_argument('--cmap', type=str, default='RdBu_r', help='Colormap (default: RdBu_r)') parser.add_argument('--vmin', type=float, default=-1, help='Minimum value for colormap') parser.add_argument('--vmax', type=float, default=1, help='Maximum value for colormap') parser.add_argument('--synthetic', action='store_true', help='Generate a synthetic FC matrix') parser.add_argument('--seed', type=int, default=42, help='Random seed for synthetic data') args = parser.parse_args() # Create visualizer visualizer = FCVisualizer(cmap=args.cmap, vmin=args.vmin, vmax=args.vmax) # Determine figure to create fig = None if args.synthetic: # Create synthetic FC matrix matrix = create_synthetic_fc_matrix(seed=args.seed) fig, _ = visualizer.plot_single_matrix(matrix, title="Synthetic FC Matrix") elif args.input: input_path = Path(args.input) if not input_path.exists(): print(f"Error: Input file not found: {args.input}") return # Process based on file type if input_path.suffix == '.npy': # It's a numpy file with FC matrix fig = visualizer.load_and_visualize_npy(input_path) elif input_path.suffix == '.nii' or input_path.suffix == '.gz': # It's an fMRI file if not NILEARN_AVAILABLE: print("Error: nilearn is required for processing fMRI files") return fig = visualizer.process_and_visualize_fmri(input_path) else: print(f"Error: Unsupported file format: {input_path.suffix}") print("Supported formats: .npy (FC matrix), .nii/.nii.gz (fMRI)") return else: # No input or synthetic flag - show demo print("No input file or --synthetic flag provided. Generating a demo matrix.") matrix = create_synthetic_fc_matrix(seed=args.seed) fig, _ = visualizer.plot_single_matrix(matrix, title="Demo FC Matrix") # Save or display the figure if fig is not None: if args.output: fig.savefig(args.output, dpi=300, bbox_inches='tight') print(f"Visualization saved to {args.output}") else: plt.show() print("Visualization displayed. Close the window to exit.") else: print("Error: Failed to create visualization") if __name__ == "__main__": main()