diff --git a/examples/freesurfer/freesurfer-args.json b/examples/freesurfer/freesurfer-args.json index 0e37565..53a4733 100644 --- a/examples/freesurfer/freesurfer-args.json +++ b/examples/freesurfer/freesurfer-args.json @@ -1,6 +1,6 @@ { - "image": "/vip/Home/API/reconall-example/INPUTS/ds001600/sub-1/anat/sub-1_T1w.nii.gz", - "results-directory": "/vip/Home/API/reconall-example/OUTPUTS/", - "options": "-all", - "license key": "/vip/Home/API/reconall-example/INPUTS/LICENSE.txt" -} \ No newline at end of file + "nifti": "/vip/Home/API/reconall-example/INPUTS/ds001600/sub-1/anat/sub-1_T1w.nii.gz", + "subjid": "sub-1_T1w", + "directives": "-all", + "license": "/vip/Home/API/reconall-example/INPUTS/LICENSE.txt" +} diff --git a/examples/freesurfer/freesurfer-recon-all.sh b/examples/freesurfer/freesurfer-recon-all.sh index c0d5f19..c8df2c4 100755 --- a/examples/freesurfer/freesurfer-recon-all.sh +++ b/examples/freesurfer/freesurfer-recon-all.sh @@ -3,6 +3,6 @@ python3 ./vip-cli.py \ --session reconall-example \ --input $(realpath ./inputs) \ - --pipeline "Freesurfer (recon-all)/0.3.8" \ + --pipeline "Freesurfer-Recon-all/7.3.1" \ --arguments freesurfer-args.json \ --api-key VIP_API_TOKEN diff --git a/examples/freesurfer/freesurfer_longitudinal_processing/1-girder_download_input_data.py b/examples/freesurfer/freesurfer_longitudinal_processing/1-girder_download_input_data.py new file mode 100644 index 0000000..603e69f --- /dev/null +++ b/examples/freesurfer/freesurfer_longitudinal_processing/1-girder_download_input_data.py @@ -0,0 +1,46 @@ +# Description: +# Author: Frederic Cervenansky < frederic.cervenansky@creatis.insa-lyon.fr> +# +# Licence Cecill-B +# Copyright (C) Creatis 2017-2024 + + +# import +import girder_client +import types +import sys +import click +from pathlib import Path + +# api Rest url of the warehouse +#Windows users: paths use backslashes (\) while Linux/macOS use forward slashes (/) +url='https://insert/your-server-here/api/v1' +#example URLs would be: +# https://srmnopt.creatis.insa-lyon.fr/warehouse/api/v1 --> for NMR and Optics +# https://myriad.creatis.insa-lyon.fr/api/v1 --> for MYRIAD + +# apiKey is a "mechanism" to share authentication and rights on folder. +# User should defined through the web interface (inside user information) an apiKey ith the corresponding privileges +apiKey = 'GIRDER_API_KEY' + +# Generate the warehouse client +gc = girder_client.GirderClient(apiUrl=url) + +# Authentication to the warehouse +gc.authenticate(apiKey=apiKey) + +# Use the ID of the folder you chose put your input MRIs +folderId ='FOLDER_ID' # 3D Flow phantom+Bubbles +#an example of a folder ID: 698de7fe82d062f2aea9619d + +# Local download directory +download_dir = Path('/insert/your/download/path') + +# Download data from Girder +gc.downloadFolderRecursive(folderId, str(download_dir)) + +# Add derivatives/freesurfer folder +derivatives_fs = download_dir / 'derivatives' / 'freesurfer' +derivatives_fs.mkdir(parents=True, exist_ok=True) # will create if not exists, do nothing if exists + +print(f"'derivatives/freesurfer' folder ensured at: {derivatives_fs}") diff --git a/examples/freesurfer/freesurfer_longitudinal_processing/2-FS_CROSS_parallel_jobs.py b/examples/freesurfer/freesurfer_longitudinal_processing/2-FS_CROSS_parallel_jobs.py new file mode 100644 index 0000000..2db5e39 --- /dev/null +++ b/examples/freesurfer/freesurfer_longitudinal_processing/2-FS_CROSS_parallel_jobs.py @@ -0,0 +1,58 @@ +#!/usr/bin/env -S uv run --script +# /// script +# requires-python = ">=3.11" +# dependencies = [ +# "vip-client", +# ] + +from vip_client import VipSession +from pathlib import Path +import os + +# This directory will be uploaded to VIP — ensure no sensitive data +# (e.g., DICOMs, scripts containing API keys) is included. +input_dir = Path("/insert/your/input/path") # make sure license file is copied in this directory +output_dir = Path("/insert/your/output/path/derivatives/freesurfer") + +# Save current working directory and change to /tmp (temporary workaround for VIP bug) +orig_cwd = os.getcwd() +os.chdir('/tmp') + +# Get T1w NIfTIs only from sub-* folders, excluding run-02, run-03, ... +nifti_files = [ + str(f) + for sub in input_dir.iterdir() + if sub.is_dir() and sub.name.startswith("sub-") + for f in sub.rglob("*.nii.gz") + if f.name.endswith("_T1w.nii.gz") + and ("_run-" not in f.name or "_run-01_" in f.name) +] + +# Create subjid from filenames +subjid = [Path(f).name.replace(".nii.gz", "") for f in nifti_files] + +input_settings = { + "nifti": nifti_files, + "license": str(input_dir / "license.txt"), + "subjid": subjid +} + +session = VipSession.init( + api_key="VIP_API_KEY", + input_dir=str(input_dir), + output_dir= str(output_dir), + pipeline_id="FreeSurfer-Recon-all/7.3.1", + input_settings=input_settings +) + +session.run_session() +session.display() + +# Restore original working directory +os.chdir(orig_cwd) + +# Download outputs to the output_dir +session.download_outputs(get_status=['Finished', 'Killed']) + +# Optional cleanup of outputs on VIP +# session.finish() diff --git a/examples/freesurfer/freesurfer_longitudinal_processing/3-tar_sub_TPs.py b/examples/freesurfer/freesurfer_longitudinal_processing/3-tar_sub_TPs.py new file mode 100644 index 0000000..5703735 --- /dev/null +++ b/examples/freesurfer/freesurfer_longitudinal_processing/3-tar_sub_TPs.py @@ -0,0 +1,53 @@ +from pathlib import Path +import tarfile +import shutil + +fs_dir = Path("/insert/your/input/path/derivatives/freesurfer") +tmp_dir = fs_dir / "tmp" + +# create a tmp directory if it doesn't exist +tmp_dir.mkdir(exist_ok=True) + +# Collect eligible tarballs (.tar.gz or .tgz) +tarballs = [ + t for t in fs_dir.iterdir() + if t.is_file() + and t.suffixes in ([".tar", ".gz"], [".tgz"]) + and "ses-" in t.name + and ".long." not in t.name +] + +subjects = {} + +for tar_path in tarballs: + subj = tar_path.name.split("_")[0] # sub-XXXX + + # Remove full archive suffix safely + if tar_path.suffixes == [".tar", ".gz"]: + base_name = tar_path.name[:-7] # remove ".tar.gz" + else: # .tgz + base_name = tar_path.stem # removes ".tgz" + + extract_dir = tmp_dir / "extracted" / base_name + extract_dir.mkdir(parents=True, exist_ok=True) + + # Untar (auto-detect compression) + with tarfile.open(tar_path, "r:*") as tar: + tar.extractall(path=extract_dir) + + subjects.setdefault(subj, []).append(extract_dir) + +# Group per subject +for subj, tp_dirs in subjects.items(): + out_tar = tmp_dir / f"{subj}_TPs.tgz" + + with tarfile.open(out_tar, "w:gz") as tar: + for tp_dir in tp_dirs: + for item in tp_dir.iterdir(): + tar.add(item, arcname=item.name) + +# Cleanup extracted files +shutil.rmtree(tmp_dir / "extracted", ignore_errors=True) + +print("✅ Done: grouped longitudinal timepoints per subject.") + diff --git a/examples/freesurfer/freesurfer_longitudinal_processing/4-FS_BASE_parallel_jobs.py b/examples/freesurfer/freesurfer_longitudinal_processing/4-FS_BASE_parallel_jobs.py new file mode 100644 index 0000000..cbf5101 --- /dev/null +++ b/examples/freesurfer/freesurfer_longitudinal_processing/4-FS_BASE_parallel_jobs.py @@ -0,0 +1,61 @@ +#!/usr/bin/env -S uv run --script +# /// script +# requires-python = ">=3.11" +# dependencies = [ +# "vip-client", +# ] + +from vip_client import VipSession +from pathlib import Path +import os + +# This directory will be uploaded to VIP — ensure no sensitive data +# (e.g., DICOMs, scripts containing API keys) is included. +input_dir = Path("/insert/your/input/path/derivatives/freesurfer/tmp") #make sure license file is copied in this directory +output_dir = Path("/insert/your/output/path/derivatives/freesurfer") + +# Save current working directory and change to /tmp (temporary workaround for VIP bug) +orig_cwd = os.getcwd() +os.chdir('/tmp') + +# Collect tarballs and BASE_IDs +tp_tarballs = [f for f in input_dir.iterdir() if f.suffix in (".tgz", ".tar.gz") and "_TPs" in f.name] +tp_tarballs.sort() # optional + +base_ids = [f.name.split("_")[0] for f in tp_tarballs] + +# Build batch input settings +input_settings = { + "LICENSE_FILE": str(input_dir / "license.txt"), + "TP_TARBALL": [str(f) for f in tp_tarballs], # list of tarballs + "BASE_ID": base_ids, # list of base IDs +} + +session = VipSession.init( + api_key="VIP_API_KEY", + input_dir=str(input_dir), + output_dir= str(output_dir), + pipeline_id="FreeSurfer-Recon-all-BASE/7.3.1", + input_settings=input_settings, +) + +session.run_session() # VIP will process tarballs in parallel +session.display() + +# Restore original working directory +os.chdir(orig_cwd) + +# Download outputs to the output_dir +session.download_outputs(get_status=['Finished', 'Killed']) + +# Optional cleanup of outputs on VIP +# session.finish() + +# Delete tmp folder locally +# WARNING: This removes the entire directory. +# Make sure that the license file is stored in another safe location. +shutil.rmtree(input_dir) +print(f"Deleted temporary folder: {input_dir}") + + + diff --git a/examples/freesurfer/freesurfer_longitudinal_processing/5-FS_LONG_parallel_jobs.py b/examples/freesurfer/freesurfer_longitudinal_processing/5-FS_LONG_parallel_jobs.py new file mode 100644 index 0000000..7b43e00 --- /dev/null +++ b/examples/freesurfer/freesurfer_longitudinal_processing/5-FS_LONG_parallel_jobs.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +# /// script +# requires-python = ">=3.11" +# dependencies = [ +# "vip-client", +# ] +# /// + +from vip_client import VipSession +from vip_client.utils import vip + +# VIP API Key +API_KEY = "VIP_API_KEY" + +# Initialize VIP connection +VipSession.init(api_key=API_KEY) + +# VIP paths +BASE_DIR = "/vip/Home/API/VipSession-xxxxxx-xxxxxx-bxx/OUTPUTS/YYYY-MM-DD_HHMMSS/" # from the output of BASE pipeline +TP_DIR = "/vip/Home/API/VipSession-xxxxxx-xxxxxx-bxx/INPUTS/" # from the input of BASE pipeline +LICENSE_FILE = "/vip/Home/API/VipSession-xxxxxx-xxxxxx-bxx/INPUTS/license.txt" # from the input of BASE pipeline + +# Local output directory +output_dir = Path("/home/zakaria/VIP/guillaume_data_test/derivatives/freesurfer") + +# List BASE and TP files from VIP +base_files = [ + item['path'] + for item in vip.list_elements(BASE_DIR) + if not item['isDirectory'] and item['path'].split('/')[-1].startswith("sub-") and "_TPs" not in item['path'] +] + +tp_files = [ + item['path'] + for item in vip.list_elements(TP_DIR) + if not item['isDirectory'] and item['path'].split('/')[-1].startswith("sub-") and "_TPs" in item['path'] +] + +# Match BASE → TP tarball +base_dict = {f.split("/")[-1].split(".")[0]: f for f in base_files} +tp_dict = {f.split("/")[-1].split("_TPs")[0]: f for f in tp_files} +subjects = set(base_dict.keys()) & set(tp_dict.keys()) + +if not subjects: + raise RuntimeError("No matching BASE and TP_TARBALL found!") + +print(f"Submitting {len(subjects)} LONG jobs for subjects: {', '.join(subjects)}") + +# Gather VIP input files per subject +vip_input_files = [] +for sub in subjects: + vip_input_files.append(base_dict[sub]) + vip_input_files.append(tp_dict[sub]) + +# Input settings for the LONG pipeline +input_settings = { + "LICENSE_FILE": LICENSE_FILE, + "BASE_ID": [base_dict[sub] for sub in subjects], + "TP_TARBALL": [tp_dict[sub] for sub in subjects], + "directives": "-all", +} + +# Create VIP session +session_name = "VIP_FS_LONG_parallel" +session = VipSession(session_name) + +# Launch pipeline directly on VIP +session.launch_pipeline( + pipeline_id="FreeSurfer-Recon-all-LONG/7.3.1", + input_settings=input_settings, + output_dir= str(output_dir) +) + +# Monitor progress +session.monitor_workflows() + +# Download outputs to the output_dir +session.download_outputs(get_status=['Finished', 'Killed']) + +# Optional cleanup of outputs on VIP +# session.finish() + diff --git a/examples/freesurfer/freesurfer_longitudinal_processing/6-girder_upload_freesurfer_folder.py b/examples/freesurfer/freesurfer_longitudinal_processing/6-girder_upload_freesurfer_folder.py new file mode 100644 index 0000000..c9dee45 --- /dev/null +++ b/examples/freesurfer/freesurfer_longitudinal_processing/6-girder_upload_freesurfer_folder.py @@ -0,0 +1,39 @@ +# Description: +# Author: Frederic Cervenansky < frederic.cervenansky@creatis.insa-lyon.fr> +# +# Licence Cecill-B +# Copyright (C) Creatis 2017-2024 + + +# import +import girder_client +import types +import sys +import click + +# api Rest url of the warehouse +#Windows users: paths use backslashes (\) while Linux/macOS use forward slashes (/) +url='https://insert/your-server-here/api/v1' +# https://srmnopt.creatis.insa-lyon.fr/warehouse/api/v1 --> for NMR and Optics +# https://myriad.creatis.insa-lyon.fr/api/v1 --> for MYRIAD + +# apiKey is a "mechanism" to share authentication and rights on folder. +# User should defined through the web interface (inside user information) an apiKey with the corresponding privileges +apiKey = 'GIRDER_API_KEY' + +# Generate the warehouse client +gc = girder_client.GirderClient(apiUrl=url) + +# Authentication to the warehouse +gc.authenticate(apiKey=apiKey) + +# upload local freesurfer folder in the original input folder on Girder +folderId ='FOLDER_ID' # 3D Flow phantom+Bubbles +#an example of a folder ID: 698de7fe82d062f2aea9619d + +# If 'derivatives' folder exists but 'freeSurfer' folder is absent: +# gc.upload('/insert/your/upload/path/derivatives/freesurfer', folderId) + +# Default upload (if 'derivatives' folder is absent on the warehouse): +gc.upload('/insert/your/upload/path/derivatives/', folderId) + diff --git a/examples/lcmodel/launch_lcmodel.ipynb b/examples/lcmodel/launch_lcmodel.ipynb index 3f4155e..66d130a 100644 --- a/examples/lcmodel/launch_lcmodel.ipynb +++ b/examples/lcmodel/launch_lcmodel.ipynb @@ -125,7 +125,7 @@ "outputs": [], "source": [ "# launch the execution and wait for it to finish\n", - "my_session.launch_pipeline(pipeline_id = \"LCModel/0.2-egi\", input_settings = input_settings)\n", + "my_session.launch_pipeline(pipeline_id = \"LCModel/0.6\", input_settings = input_settings)\n", "my_session.monitor_workflows()" ] },