Compile and Test CAT Standalone #5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Compile and Test CAT Standalone | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| source: | |
| description: "Source of standalone binaries" | |
| required: false | |
| default: "build" | |
| type: choice | |
| options: | |
| - build | |
| - release | |
| release_tag: | |
| description: "Release tag (only for release source)" | |
| required: false | |
| type: string | |
| env: | |
| MLM_LICENSE_TOKEN: ${{ secrets.MATLAB_BATCH_TOKEN }} | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true | |
| jobs: | |
| # ──────────────────────────────────────────────────── | |
| # Job 1: Download release standalone (if source == release) | |
| # ──────────────────────────────────────────────────── | |
| download_release_standalone: | |
| name: Download Release Standalone | |
| if: inputs.source == 'release' | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: | |
| standalone_os: [Linux, Mac, Mac_arm64, Win] | |
| steps: | |
| - name: Download release asset | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| TAG="${{ inputs.release_tag }}" | |
| OS="${{ matrix.standalone_os }}" | |
| ASSET_NAME="CAT_R2023b_MCR_${OS}.zip" | |
| echo "Downloading $ASSET_NAME from release $TAG" | |
| gh release download "$TAG" --repo ${{ github.repository }} --pattern "$ASSET_NAME" | |
| - name: Upload as artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: cat-standalone-${{ matrix.standalone_os }} | |
| path: CAT_R2023b_MCR_${{ matrix.standalone_os }}.zip | |
| retention-days: 1 | |
| # ──────────────────────────────────────────────────── | |
| # Job 2: Build standalone (if source == build or default) | |
| # ──────────────────────────────────────────────────── | |
| build_standalone: | |
| name: Build Standalone on ${{ matrix.os_name }} | |
| if: inputs.source == 'build' || inputs.source == '' | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| version: ["R2023b"] | |
| os: ["ubuntu-22.04", "macos-13", "macos-latest", "windows-latest"] | |
| include: | |
| - os: ubuntu-22.04 | |
| os_name: Linux | |
| standalone_os: Linux | |
| mex_ext: mexa64 | |
| - os: macos-13 | |
| os_name: macOS_Intel | |
| standalone_os: Mac | |
| mex_ext: mexmaci64 | |
| - os: macos-latest | |
| os_name: macOS_ARM64 | |
| standalone_os: Mac_arm64 | |
| mex_ext: mexmaca64 | |
| - os: windows-latest | |
| os_name: Windows | |
| standalone_os: Win | |
| mex_ext: mexw64 | |
| steps: | |
| - name: Checkout CAT | |
| uses: actions/checkout@v4 | |
| - name: Update | |
| run: make update | |
| - name: Install rsync | |
| if: runner.os == 'Windows' | |
| run: choco install -y rsync | |
| - name: Checkout SPM | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: spm/spm | |
| path: spm | |
| - name: Install CAT in SPM toolbox | |
| shell: bash | |
| run: | | |
| mkdir -p spm/toolbox/CAT | |
| rsync -a --exclude='.git' \ | |
| --exclude='spm/toolbox/CAT/' \ | |
| --exclude='batches/' \ | |
| --exclude='catQC/' \ | |
| --exclude='check_pipeline/' \ | |
| --exclude='development/' \ | |
| --exclude='html/' \ | |
| --exclude='internal/' \ | |
| --exclude='mexmaca/' \ | |
| --exclude='mexmaci/' \ | |
| --exclude='spm/' \ | |
| --exclude='Makefile' \ | |
| ./ spm/toolbox/CAT/ | |
| - name: Cleanup (Linux/Mac) | |
| if: runner.os != 'Windows' | |
| run: rm -rf standalone | |
| - name: Cleanup (Windows) | |
| if: runner.os == 'Windows' | |
| run: Remove-Item -Recurse -Force standalone | |
| - name: Set up MATLAB | |
| uses: matlab-actions/setup-matlab@v2 | |
| with: | |
| release: ${{ matrix.version }} | |
| products: MATLAB_Compiler | |
| - name: Set environment variable with MATLAB version | |
| shell: bash | |
| run: | | |
| echo "MATLAB_VERSION=R2023b" >> $GITHUB_ENV | |
| - name: Compile CAT mex-files | |
| uses: matlab-actions/run-command@v2 | |
| with: | |
| command: | | |
| addpath('spm'); | |
| cd('spm/toolbox/CAT') | |
| compile | |
| - name: Install zip on Windows | |
| if: runner.os == 'Windows' | |
| run: choco install zip -y | |
| shell: bash | |
| # Decompress .nii.gz templates so they are available as .nii in the compiled standalone. | |
| # Without this, atlas config (cat_conf_ROI.m) fails because it checks exist(...'.nii','file'). | |
| - name: Decompress NII.GZ templates | |
| shell: bash | |
| run: | | |
| echo "Decompressing .nii.gz files in CAT templates..." | |
| find spm/toolbox/CAT/templates_MNI152NLin2009cAsym -name '*.nii.gz' -exec gunzip -f {} \; | |
| find spm/toolbox/CAT/templates_surfaces -name '*.nii.gz' -exec gunzip -f {} \; | |
| find spm/toolbox/CAT/templates_surfaces_32k -name '*.nii.gz' -exec gunzip -f {} \; | |
| echo "Decompression complete" | |
| # Build SPM standalone (which includes CAT as toolbox) | |
| - name: Build MATLAB standalone | |
| uses: matlab-actions/run-command@v2 | |
| with: | |
| command: | | |
| cd('spm'); | |
| addpath(genpath('.')); | |
| savepath; | |
| cd('config'); | |
| spm_make_standalone | |
| - name: Compress standalone to ZIP | |
| shell: bash | |
| run: | | |
| mv spm/toolbox/CAT/standalone standalone/ | |
| mv standalone cat_standalone | |
| zip -r CAT_${{ env.MATLAB_VERSION }}_MCR_${{ matrix.standalone_os }}.zip cat_standalone/* | |
| - name: Upload standalone artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: cat-standalone-${{ matrix.standalone_os }} | |
| path: CAT_*.zip | |
| retention-days: 7 | |
| # ──────────────────────────────────────────────────── | |
| # Job 3: Test standalone on each OS | |
| # ──────────────────────────────────────────────────── | |
| test_standalone: | |
| name: Test Standalone on ${{ matrix.os_name }} | |
| needs: [build_standalone, download_release_standalone] | |
| if: | | |
| always() && | |
| (needs.build_standalone.result == 'success' || needs.download_release_standalone.result == 'success') | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: ["ubuntu-22.04", "macos-13", "macos-latest", "windows-latest"] | |
| include: | |
| - os: ubuntu-22.04 | |
| os_name: Linux | |
| standalone_os: Linux | |
| - os: macos-13 | |
| os_name: macOS_Intel | |
| standalone_os: Mac | |
| - os: macos-latest | |
| os_name: macOS_ARM64 | |
| standalone_os: Mac_arm64 | |
| - os: windows-latest | |
| os_name: Windows | |
| standalone_os: Win | |
| steps: | |
| - name: Download standalone artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: cat-standalone-${{ matrix.standalone_os }} | |
| path: . | |
| - name: Extract standalone | |
| shell: bash | |
| run: | | |
| unzip -q CAT_*_MCR_${{ matrix.standalone_os }}.zip | |
| echo "Contents of cat_standalone:" | |
| ls -la cat_standalone | |
| # ── Install MATLAB Runtime ── | |
| - name: Install MATLAB Runtime (Linux) | |
| if: runner.os == 'Linux' | |
| shell: bash | |
| run: | | |
| # Download MCR R2023b | |
| echo "Downloading MATLAB Runtime R2023b for Linux..." | |
| wget -q https://ssd.mathworks.com/supportfiles/downloads/R2023b/Release/10/deployment_files/installer/complete/glnxa64/MATLAB_Runtime_R2023b_Update_10_glnxa64.zip -O mcr.zip | |
| unzip -q mcr.zip -d mcr_installer | |
| echo "Installing MATLAB Runtime..." | |
| sudo ./mcr_installer/install -agreeToLicense yes -destinationFolder /usr/local/MATLAB/MATLAB_Runtime 2>&1 | |
| echo "MCRROOT=/usr/local/MATLAB/MATLAB_Runtime/R2023b" >> $GITHUB_ENV | |
| echo "LD_LIBRARY_PATH=/usr/local/MATLAB/MATLAB_Runtime/R2023b/runtime/glnxa64:/usr/local/MATLAB/MATLAB_Runtime/R2023b/bin/glnxa64:/usr/local/MATLAB/MATLAB_Runtime/R2023b/sys/os/glnxa64:/usr/local/MATLAB/MATLAB_Runtime/R2023b/sys/opengl/lib/glnxa64:/usr/local/MATLAB/MATLAB_Runtime/R2023b/extern/bin/glnxa64" >> $GITHUB_ENV | |
| echo "MCR_INHIBIT_CTF_LOCK=1" >> $GITHUB_ENV | |
| echo "SPM_HTML_BROWSER=0" >> $GITHUB_ENV | |
| rm -rf mcr.zip mcr_installer | |
| echo "MATLAB Runtime installed at /usr/local/MATLAB/MATLAB_Runtime/R2023b" | |
| - name: Install MATLAB Runtime (macOS) | |
| if: runner.os == 'macOS' | |
| shell: bash | |
| run: | | |
| ARCH=$(uname -m) | |
| if [ "$ARCH" = "arm64" ]; then | |
| echo "Downloading MATLAB Runtime R2023b for macOS ARM64..." | |
| wget -q https://ssd.mathworks.com/supportfiles/downloads/R2023b/Release/10/deployment_files/installer/complete/maca64/MATLAB_Runtime_R2023b_Update_10_maca64.dmg -O mcr.dmg | |
| else | |
| echo "Downloading MATLAB Runtime R2023b for macOS Intel..." | |
| wget -q https://ssd.mathworks.com/supportfiles/downloads/R2023b/Release/10/deployment_files/installer/complete/maci64/MATLAB_Runtime_R2023b_Update_10_maci64.dmg.zip -O mcr.dmg.zip | |
| unzip -q mcr.dmg.zip | |
| MCR_DMG=$(ls *.dmg | head -n 1) | |
| mv "$MCR_DMG" mcr.dmg | |
| fi | |
| echo "Mounting DMG..." | |
| hdiutil attach mcr.dmg -mountpoint /Volumes/MCR | |
| echo "Installing MATLAB Runtime..." | |
| # Find the installer app and its actual executable (name varies by architecture) | |
| INSTALLER_APP=$(find /Volumes/MCR -name "*.app" -maxdepth 1 | head -n 1) | |
| if [ -z "$INSTALLER_APP" ]; then | |
| echo "ERROR: No installer .app found in DMG" | |
| ls -la /Volumes/MCR/ | |
| exit 1 | |
| fi | |
| # The executable name matches the app name (e.g., InstallForMacOSAppleSilicon) | |
| APP_NAME=$(basename "$INSTALLER_APP" .app) | |
| INSTALLER_BIN="$INSTALLER_APP/Contents/MacOS/$APP_NAME" | |
| if [ ! -f "$INSTALLER_BIN" ]; then | |
| # Fallback: find the actual executable inside the app bundle | |
| INSTALLER_BIN=$(find "$INSTALLER_APP/Contents/MacOS" -type f -perm +111 | head -n 1) | |
| fi | |
| echo "Using installer: $INSTALLER_BIN" | |
| sudo "$INSTALLER_BIN" -agreeToLicense yes -destinationFolder "$HOME/MATLAB_Runtime" 2>&1 | tee install.log | |
| sleep 2 | |
| sudo chown -R $USER "$HOME/MATLAB_Runtime" | |
| sudo chown -R $USER "$HOME/.matlab" 2>/dev/null || true | |
| hdiutil detach /Volumes/MCR 2>/dev/null || true | |
| echo "MCRROOT=$HOME/MATLAB_Runtime/R2023b" >> $GITHUB_ENV | |
| echo "MCR_INHIBIT_CTF_LOCK=1" >> $GITHUB_ENV | |
| echo "SPM_HTML_BROWSER=0" >> $GITHUB_ENV | |
| rm -f mcr.dmg mcr.dmg.zip | |
| echo "MATLAB Runtime installed at $HOME/MATLAB_Runtime/R2023b" | |
| - name: Install MATLAB Runtime (Windows) | |
| if: runner.os == 'Windows' | |
| shell: bash | |
| run: | | |
| echo "Downloading MATLAB Runtime R2023b for Windows..." | |
| curl -sL -o mcr.zip https://ssd.mathworks.com/supportfiles/downloads/R2023b/Release/10/deployment_files/installer/complete/win64/MATLAB_Runtime_R2023b_Update_10_win64.zip | |
| unzip -q mcr.zip -d mcr_installer | |
| echo "Installing MATLAB Runtime..." | |
| INSTALLER=$(find mcr_installer -name "setup.exe" | head -n 1) | |
| "$INSTALLER" -agreeToLicense yes -destinationFolder "C:\\MATLAB_Runtime" | |
| echo "Installation command completed" | |
| echo "C:\\MATLAB_Runtime\\R2023b\\runtime\\win64" >> $GITHUB_PATH | |
| echo "MCRROOT=C:\\MATLAB_Runtime\\R2023b" >> $GITHUB_ENV | |
| echo "MCR_INHIBIT_CTF_LOCK=1" >> $GITHUB_ENV | |
| echo "SPM_HTML_BROWSER=0" >> $GITHUB_ENV | |
| rm -rf mcr.zip mcr_installer | |
| echo "MATLAB Runtime installed at C:\\MATLAB_Runtime\\R2023b" | |
| # ── Setup and extract CTF ── | |
| - name: Setup CAT standalone (Linux) | |
| if: runner.os == 'Linux' | |
| shell: bash | |
| run: | | |
| chmod +x cat_standalone/spm25 | |
| echo "Extracting CTF archive..." | |
| cat_standalone/spm25 function exit || true | |
| echo "CAT standalone setup complete" | |
| - name: Setup CAT standalone (macOS) | |
| if: runner.os == 'macOS' | |
| shell: bash | |
| run: | | |
| # Remove quarantine attributes to avoid Gatekeeper blocking binaries | |
| xattr -dr com.apple.quarantine cat_standalone 2>/dev/null || true | |
| chmod -R +x cat_standalone/ | |
| echo "Extracting CTF archive..." | |
| cd cat_standalone | |
| ./run_spm25.sh "$MCRROOT" function exit || true | |
| cd .. | |
| echo "CAT standalone setup complete" | |
| - name: Setup CAT standalone (Windows) | |
| if: runner.os == 'Windows' | |
| shell: bash | |
| run: | | |
| echo "Extracting CTF archive..." | |
| cd cat_standalone | |
| # On Windows, spm25.exe should find MCR via PATH/registry | |
| ./spm25.exe function exit 2>&1 || true | |
| cd .. | |
| echo "CAT standalone setup complete" | |
| echo "Standalone contents after setup:" | |
| find cat_standalone -maxdepth 3 -type d | head -20 | |
| # ── Find test data (single_subj_T1.nii from SPM canonical directory) ── | |
| - name: Locate test data | |
| shell: bash | |
| run: | | |
| echo "Searching for single_subj_T1.nii inside standalone..." | |
| T1=$(find cat_standalone -name "single_subj_T1.nii" -type f 2>/dev/null | head -n 1) | |
| # If not found as .nii, try .nii.gz and decompress | |
| if [ -z "$T1" ]; then | |
| T1GZ=$(find cat_standalone -name "single_subj_T1.nii.gz" -type f 2>/dev/null | head -n 1) | |
| if [ -n "$T1GZ" ]; then | |
| echo "Found compressed: $T1GZ — decompressing..." | |
| gunzip -k "$T1GZ" | |
| T1="${T1GZ%.gz}" | |
| fi | |
| fi | |
| if [ -z "$T1" ]; then | |
| echo "ERROR: single_subj_T1.nii not found in standalone" | |
| echo "Listing all directories:" | |
| find cat_standalone -maxdepth 4 -type d 2>/dev/null | |
| echo "Listing all .nii and .nii.gz files:" | |
| find cat_standalone -name "*.nii" -o -name "*.nii.gz" 2>/dev/null | head -30 | |
| exit 1 | |
| fi | |
| echo "Found test data: $T1" | |
| # Copy to working directory so output goes there | |
| cp "$T1" ./single_subj_T1.nii | |
| echo "Test data ready: ./single_subj_T1.nii" | |
| # ── Run CAT segmentation test ── | |
| - name: Test CAT segmentation (Linux) | |
| if: runner.os == 'Linux' | |
| timeout-minutes: 60 | |
| shell: bash | |
| run: | | |
| SPMROOT=$(pwd)/cat_standalone | |
| BATCH="${SPMROOT}/spm25_mcr/spm25/toolbox/CAT/standalone/cat_standalone_segment.m" | |
| # If batch not at expected location, search for it | |
| if [ ! -f "$BATCH" ]; then | |
| BATCH=$(find cat_standalone -name "cat_standalone_segment.m" -type f | head -n 1) | |
| fi | |
| echo "Using batch file: $BATCH" | |
| echo "Running CAT segmentation on single_subj_T1.nii..." | |
| "${SPMROOT}/run_spm25.sh" "$MCRROOT" batch "$BATCH" "$(pwd)/single_subj_T1.nii" | |
| - name: Test CAT segmentation (macOS) | |
| if: runner.os == 'macOS' | |
| timeout-minutes: 60 | |
| shell: bash | |
| run: | | |
| SPMROOT=$(pwd)/cat_standalone | |
| BATCH=$(find cat_standalone -name "cat_standalone_segment.m" -type f | head -n 1) | |
| echo "Using batch file: $BATCH" | |
| echo "Running CAT segmentation on single_subj_T1.nii..." | |
| "${SPMROOT}/run_spm25.sh" "$MCRROOT" batch "$BATCH" "$(pwd)/single_subj_T1.nii" | |
| - name: Test CAT segmentation (Windows) | |
| if: runner.os == 'Windows' | |
| timeout-minutes: 60 | |
| shell: bash | |
| run: | | |
| SPMROOT=$(pwd)/cat_standalone | |
| BATCH=$(find cat_standalone -name "cat_standalone_segment.m" -type f | head -n 1) | |
| echo "Using batch file: $BATCH" | |
| echo "Running CAT segmentation on single_subj_T1.nii..." | |
| "${SPMROOT}/spm25.exe" batch "$BATCH" "$(pwd)/single_subj_T1.nii" | |
| # ── Verify outputs ── | |
| - name: Verify segmentation outputs | |
| shell: bash | |
| run: | | |
| echo "Checking for segmentation output files..." | |
| # Look for typical CAT output files | |
| found=0 | |
| for pattern in "mri/mwp1*" "mri/p0*" "report/cat_*" "surf/lh.thickness*"; do | |
| matches=$(find . -path "./$pattern" 2>/dev/null) | |
| if [ -n "$matches" ]; then | |
| echo "Found: $matches" | |
| found=1 | |
| fi | |
| done | |
| if [ "$found" -eq 0 ]; then | |
| echo "WARNING: No standard CAT output files found in current directory" | |
| echo "Checking all nii files created:" | |
| find . -name "*.nii" -newer single_subj_T1.nii 2>/dev/null || echo "No new .nii files" | |
| find . -name "*.gii" 2>/dev/null || echo "No .gii files" | |
| find . -name "*.xml" -path "*/report/*" 2>/dev/null || echo "No report XMLs" | |
| fi | |
| echo "Test completed successfully" |