Skip to content

Compile and Test CAT Standalone #5

Compile and Test CAT Standalone

Compile and Test CAT Standalone #5

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"