{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "# QSIParc Basics: Parcellation and Regional Analysis\n\nThis notebook demonstrates how to use the QSIParc runner for diffusion-weighted parcellation.\n\n## Overview\n\nQSIParc performs parcellation using the `parcellate` Python package (not Docker):\n- Regional quantification of diffusion metrics from QSIRecon outputs\n- Atlas-based parcellation with configurable masking\n- Generation of region-wise statistics (TSV files)\n\n## Prerequisites\n\n- QSIRecon reconstructed data\n- The `parcellate` package installed (`pip install parcellate`)\n- Atlas definitions (auto-discovered from QSIRecon BIDS dseg files)"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Setup"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pathlib import Path\n",
    "\n",
    "import pandas as pd\n",
    "\n",
    "from voxelops import (\n",
    "    QSIParcDefaults,\n",
    "    QSIParcInputs,\n",
    "    run_qsiparc,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Define Paths"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Input paths -- update these to match your data\n",
    "qsirecon_dir = Path(\"/data/derivatives/qsirecon/\")\n",
    "participant = \"01\"\n",
    "\n",
    "# Output paths (optional -- defaults to qsirecon_dir parent)\n",
    "output_dir = Path(\"/data/derivatives/qsiparc/\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Basic Usage\n",
    "\n",
    "### Option 1: Use Default Configuration"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create inputs\n",
    "inputs = QSIParcInputs(\n",
    "    qsirecon_dir=qsirecon_dir,\n",
    "    participant=participant,\n",
    "    output_dir=output_dir,\n",
    ")\n",
    "\n",
    "# Run with defaults (mask=\"gm\", force=False)\n",
    "result = run_qsiparc(inputs)\n",
    "\n",
    "print(f\"Success: {result['success']}\")\n",
    "print(f\"Duration: {result['duration_human']}\")\n",
    "print(f\"Output files: {len(result.get('output_files', []))}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Option 2: Override Resources"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Run with specific parallelism settings\n",
    "result = run_qsiparc(\n",
    "    inputs,\n",
    "    n_jobs=4,     # Parallel jobs\n",
    "    n_procs=2,    # Processors per job\n",
    ")\n",
    "\n",
    "print(f\"Success: {result['success']}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Option 3: Custom Configuration"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create custom configuration\n",
    "config = QSIParcDefaults(\n",
    "    mask=\"gm\",                   # Mask type: \"gm\", \"wm\", \"brain\", or path\n",
    "    force=True,                  # Overwrite existing outputs\n",
    "    background_label=0,          # Label for background voxels\n",
    "    resampling_target=\"data\",    # Resampling strategy\n",
    "    log_level=\"DEBUG\",           # Verbose logging\n",
    "    n_jobs=4,\n",
    "    n_procs=2,\n",
    ")\n",
    "\n",
    "result = run_qsiparc(inputs, config)\n",
    "\n",
    "print(f\"Success: {result['success']}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Inspect Execution Record"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"Execution Details:\")\n",
    "print(f\"  Tool: {result['tool']}\")\n",
    "print(f\"  Participant: {result['participant']}\")\n",
    "print(f\"  Duration: {result['duration_human']}\")\n",
    "print(f\"  Success: {result['success']}\")\n",
    "\n",
    "print(\"\\nConfiguration Used:\")\n",
    "config_used = result[\"config\"]\n",
    "print(f\"  Mask: {config_used.mask}\")\n",
    "print(f\"  Force: {config_used.force}\")\n",
    "print(f\"  Resampling target: {config_used.resampling_target}\")\n",
    "print(f\"  Log level: {config_used.log_level}\")\n",
    "print(f\"  N jobs: {config_used.n_jobs}\")\n",
    "print(f\"  N procs: {config_used.n_procs}\")\n",
    "\n",
    "print(f\"\\nOutput files: {len(result.get('output_files', []))}\")\n",
    "for f in result.get(\"output_files\", [])[:5]:\n",
    "    print(f\"  {f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Check Expected Outputs"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "outputs = result[\"expected_outputs\"]\n",
    "\n",
    "print(\"Expected Output Locations:\")\n",
    "print(f\"  Output directory: {outputs.output_dir}\")\n",
    "\n",
    "# Verify outputs\n",
    "print(\"\\nOutput Validation:\")\n",
    "print(f\"  Output dir exists: {outputs.output_dir.exists()}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Explore Parcellation Outputs"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if outputs.output_dir.exists():\n",
    "    print(f\"Parcellation outputs for {participant}:\\n\")\n",
    "\n",
    "    # List all files\n",
    "    parcellation_maps = []\n",
    "    statistics = []\n",
    "    other = []\n",
    "\n",
    "    for f in outputs.output_dir.rglob(f\"*sub-{participant}*\"):\n",
    "        if f.is_file():\n",
    "            if f.suffix == \".gz\":\n",
    "                parcellation_maps.append(f)\n",
    "            elif f.suffix in [\".csv\", \".tsv\"]:\n",
    "                statistics.append(f)\n",
    "            else:\n",
    "                other.append(f)\n",
    "\n",
    "    print(f\"Parcellation Maps ({len(parcellation_maps)}):\")\n",
    "    for f in sorted(parcellation_maps):\n",
    "        print(f\"  {f.name}\")\n",
    "\n",
    "    print(f\"\\nStatistics Files ({len(statistics)}):\")\n",
    "    for f in sorted(statistics):\n",
    "        print(f\"  {f.name}\")\n",
    "\n",
    "    if other:\n",
    "        print(f\"\\nOther Files ({len(other)}):\")\n",
    "        for f in sorted(other)[:5]:\n",
    "            print(f\"  {f.name}\")\n",
    "else:\n",
    "    print(\"Output directory not found\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Load and Analyze Regional Statistics"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Find statistics files\n",
    "stats_files = list(outputs.participant_dir.rglob(\"*.csv\"))\n",
    "\n",
    "if stats_files:\n",
    "    # Load first statistics file\n",
    "    stats_file = stats_files[0]\n",
    "    print(f\"Loading: {stats_file.name}\\n\")\n",
    "\n",
    "    # Load data\n",
    "    stats_df = pd.read_csv(stats_file)\n",
    "\n",
    "    print(f\"Shape: {stats_df.shape}\")\n",
    "    print(f\"Columns: {list(stats_df.columns)}\")\n",
    "    print(\"\\nFirst few rows:\")\n",
    "    display(stats_df.head())\n",
    "\n",
    "    # Summary statistics\n",
    "    print(\"\\nSummary Statistics:\")\n",
    "    display(stats_df.describe())\n",
    "else:\n",
    "    print(\"No statistics files found\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Visualize Regional Metrics"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "import seaborn as sns\n",
    "\n",
    "if stats_files and \"stats_df\" in locals():\n",
    "    # Assume there's a 'region' and some metric columns\n",
    "    # Adjust based on actual column names\n",
    "\n",
    "    # Example: Plot distribution of FA values across regions\n",
    "    if \"FA\" in stats_df.columns:\n",
    "        plt.figure(figsize=(12, 6))\n",
    "\n",
    "        # Histogram\n",
    "        plt.subplot(1, 2, 1)\n",
    "        plt.hist(stats_df[\"FA\"].dropna(), bins=30, edgecolor=\"black\")\n",
    "        plt.xlabel(\"Fractional Anisotropy (FA)\")\n",
    "        plt.ylabel(\"Number of Regions\")\n",
    "        plt.title(\"Distribution of FA Across Regions\")\n",
    "        plt.grid(alpha=0.3)\n",
    "\n",
    "        # Box plot\n",
    "        plt.subplot(1, 2, 2)\n",
    "        plt.boxplot(stats_df[\"FA\"].dropna())\n",
    "        plt.ylabel(\"Fractional Anisotropy (FA)\")\n",
    "        plt.title(\"FA Distribution Summary\")\n",
    "        plt.grid(alpha=0.3)\n",
    "\n",
    "        plt.tight_layout()\n",
    "        plt.show()\n",
    "\n",
    "        print(\"\\nFA Statistics:\")\n",
    "        print(f\"  Mean: {stats_df['FA'].mean():.3f}\")\n",
    "        print(f\"  Std: {stats_df['FA'].std():.3f}\")\n",
    "        print(f\"  Min: {stats_df['FA'].min():.3f}\")\n",
    "        print(f\"  Max: {stats_df['FA'].max():.3f}\")\n",
    "\n",
    "    # If there are multiple metrics, create correlation matrix\n",
    "    numeric_cols = stats_df.select_dtypes(include=\"number\").columns\n",
    "    if len(numeric_cols) > 1:\n",
    "        plt.figure(figsize=(10, 8))\n",
    "        correlation = stats_df[numeric_cols].corr()\n",
    "        sns.heatmap(correlation, annot=True, fmt=\".2f\", cmap=\"coolwarm\", center=0)\n",
    "        plt.title(\"Correlation Matrix of Diffusion Metrics\")\n",
    "        plt.tight_layout()\n",
    "        plt.show()\n",
    "else:\n",
    "    print(\"No data available for visualization\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Batch Processing"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Get list of participants from QSIRecon directory\n",
    "participant_dirs = sorted(qsirecon_dir.glob(\"sub-*\"))\n",
    "participants = [d.name.replace(\"sub-\", \"\") for d in participant_dirs if d.is_dir()]\n",
    "\n",
    "print(f\"Found {len(participants)} participants: {participants}\\n\")\n",
    "\n",
    "config = QSIParcDefaults(\n",
    "    n_jobs=4,\n",
    "    n_procs=2,\n",
    ")\n",
    "\n",
    "results = []\n",
    "\n",
    "for participant in participants:\n",
    "    print(f\"Processing participant {participant}...\")\n",
    "\n",
    "    inputs = QSIParcInputs(\n",
    "        qsirecon_dir=qsirecon_dir,\n",
    "        participant=participant,\n",
    "    )\n",
    "\n",
    "    try:\n",
    "        result = run_qsiparc(inputs, config)\n",
    "        results.append(result)\n",
    "        print(f\"  Success in {result['duration_human']}\\n\")\n",
    "    except Exception as e:\n",
    "        print(f\"  Failed: {e}\\n\")\n",
    "        results.append(\n",
    "            {\n",
    "                \"participant\": participant,\n",
    "                \"success\": False,\n",
    "                \"error\": str(e),\n",
    "            }\n",
    "        )\n",
    "\n",
    "# Summary\n",
    "successful = sum(1 for r in results if r.get(\"success\"))\n",
    "print(f\"\\n{'='*60}\")\n",
    "print(f\"Processed {len(results)} participants:\")\n",
    "print(f\"  Successful: {successful}\")\n",
    "print(f\"  Failed: {len(results) - successful}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Combine Statistics Across Participants"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Collect statistics from all successful runs\n",
    "all_stats = []\n",
    "\n",
    "for result in results:\n",
    "    if result.get(\"success\"):\n",
    "        participant = result[\"participant\"]\n",
    "        outputs = result[\"expected_outputs\"]\n",
    "\n",
    "        # Find statistics files\n",
    "        stats_files = list(outputs.participant_dir.rglob(\"*.csv\"))\n",
    "\n",
    "        for stats_file in stats_files:\n",
    "            df = pd.read_csv(stats_file)\n",
    "            df[\"participant\"] = participant\n",
    "            all_stats.append(df)\n",
    "\n",
    "if all_stats:\n",
    "    # Combine all dataframes\n",
    "    combined_stats = pd.concat(all_stats, ignore_index=True)\n",
    "\n",
    "    print(f\"Combined statistics: {combined_stats.shape}\")\n",
    "    print(\"\\nFirst few rows:\")\n",
    "    display(combined_stats.head())\n",
    "\n",
    "    # Save to file\n",
    "    output_file = output_dir / \"combined_regional_statistics.csv\"\n",
    "    output_file.parent.mkdir(parents=True, exist_ok=True)\n",
    "    combined_stats.to_csv(output_file, index=False)\n",
    "    print(f\"\\nSaved combined statistics to: {output_file}\")\n",
    "else:\n",
    "    print(\"No statistics to combine\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Error Handling"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from voxelops.exceptions import (\n",
    "    InputValidationError,\n",
    "    ProcedureExecutionError,\n",
    ")\n",
    "\n",
    "try:\n",
    "    result = run_qsiparc(\n",
    "        inputs,\n",
    "        fs_license=fs_license,\n",
    "    )\n",
    "    print(f\"Success: {result['success']}\")\n",
    "\n",
    "except InputValidationError as e:\n",
    "    print(f\"Input validation failed: {e}\")\n",
    "    print(\"Common issues:\")\n",
    "    print(\"  - QSIRecon directory doesn't exist\")\n",
    "    print(\"  - Participant not found in QSIRecon output\")\n",
    "\n",
    "except ProcedureExecutionError as e:\n",
    "    print(f\"Execution failed: {e}\")\n",
    "    print(f\"Check logs: {result.get('log_file')}\")\n",
    "\n",
    "except Exception as e:\n",
    "    print(f\"Unexpected error: {e}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Next Steps\n",
    "\n",
    "After parcellation:\n",
    "\n",
    "1. Analyze regional statistics across subjects\n",
    "2. Compare metrics between groups (e.g., patients vs controls)\n",
    "3. Correlate with behavioral or clinical measures\n",
    "4. Visualize parcellation overlays on structural images\n",
    "5. Export to statistical analysis software (R, SPSS, etc.)\n",
    "\n",
    "## Tips\n",
    "\n",
    "- **Atlas choice**: Different atlases provide different regional granularity\n",
    "- **Quality control**: Check parcellation overlays visually\n",
    "- **Statistics**: Export to CSV for easy analysis in R, Python, or SPSS\n",
    "- **Batch processing**: Process all subjects with the same configuration for consistency\n",
    "- **Version control**: Pin Docker image versions for reproducibility\n",
    "- **Data organization**: Maintain a consistent directory structure across subjects"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "VoxelOps",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}