From ab00811e1f16c24ce8698ec54760c9c25176fa09 Mon Sep 17 00:00:00 2001 From: Chinmay Chinara Date: Sun, 17 May 2026 18:16:36 -0700 Subject: [PATCH 01/17] Use Docker image for `resection-identification` plugin --- toolbox/core/bst_plugin.m | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/toolbox/core/bst_plugin.m b/toolbox/core/bst_plugin.m index d1d0db3882..264d5077a5 100644 --- a/toolbox/core/bst_plugin.m +++ b/toolbox/core/bst_plugin.m @@ -209,15 +209,9 @@ PlugDesc(end+1) = GetStruct('resection-identification'); PlugDesc(end).Version = 'latest'; PlugDesc(end).Category = 'Anatomy'; - PlugDesc(end).AutoUpdate = 1; - PlugDesc(end).URLzip = ['https://neuroimage.usc.edu/bst/getupdate.php?d=bst_resection_identification_' OsType '.zip']; - PlugDesc(end).TestFile = 'resection_identification'; - if strcmp(OsType, 'win64') - PlugDesc(end).TestFile = [PlugDesc(end).TestFile, '.bat']; - end - PlugDesc(end).URLinfo = 'https://github.com/ajoshiusc/auto_resection_mask/tree/brainstorm-plugin'; + PlugDesc(end).URLinfo = 'https://github.com/ajoshiusc/auto_resection_mask/tree/brainstorm-container'; + PlugDesc(end).ImageSource = 'docker.io/chinmaychinara/auto-resection-mask:latest'; PlugDesc(end).CompiledStatus = 1; - PlugDesc(end).LoadFolders = {'bin'}; % === ANATOMY: ROAST === PlugDesc(end+1) = GetStruct('roast'); From 0a2654e1e34c425041be912b6a541e9175f57a9a Mon Sep 17 00:00:00 2001 From: Chinmay Chinara Date: Sun, 17 May 2026 18:19:06 -0700 Subject: [PATCH 02/17] Add GPU option and container args to `RunContainer` Extend `RunContainer` to accept isGpu and containerArgs parameters (with defaults) so callers can request GPU access and pass additional docker arguments --- toolbox/core/bst_containers.m | 21 +++++++++++++++++---- toolbox/core/bst_plugin.m | 10 ++++++++-- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/toolbox/core/bst_containers.m b/toolbox/core/bst_containers.m index 7c49770960..2cb989c2f3 100644 --- a/toolbox/core/bst_containers.m +++ b/toolbox/core/bst_containers.m @@ -234,9 +234,16 @@ %% ===== RUN CONTAINER AS DAEMON ===== -function errMsg = RunContainer(containerName, imageSha, volumes, isDaemon) -% USAGE: errMsg = bst_containers('RunContainer', containerName, imageSha, volumes, isDaemon) +function errMsg = RunContainer(containerName, imageSha, volumes, isDaemon, isGpu, containerArgs) +% USAGE: errMsg = bst_containers('RunContainer', containerName, imageSha, volumes, isDaemon, isGpu, containerArgs) + % Validate inputs + if nargin < 6 || isempty(containerArgs) + containerArgs = ''; + end + if nargin < 5 || isempty(isGpu) + isGpu = 0; + end if nargin < 4 || isempty(isDaemon) isDaemon = 0; end @@ -261,15 +268,21 @@ volumesStr = strjoin(pairs, ' '); end + % GPU option + gpuStr = ''; + if isGpu + gpuStr = '--gpus all'; + end + % Run container switch engineName case 'docker' if ~isDaemon % Run ENTRYPOINT - cmdStr = sprintf('docker run --rm --name %s %s %s', containerName, volumesStr, imageSha); + cmdStr = sprintf('docker run --rm --name %s %s %s %s %s', containerName, gpuStr, volumesStr, imageSha, containerArgs); else % Replace ENTRYPOINT (if any) with `sleep infinity` - cmdStr = sprintf('docker run -d --name %s %s --entrypoint sleep %s infinity', containerName, volumesStr, imageSha); + cmdStr = sprintf('docker run -d --name %s %s %s --entrypoint sleep %s infinity', containerName, gpuStr, volumesStr, imageSha); end [status, cmdout] = system(cmdStr); end diff --git a/toolbox/core/bst_plugin.m b/toolbox/core/bst_plugin.m index 264d5077a5..5f7d2e6780 100644 --- a/toolbox/core/bst_plugin.m +++ b/toolbox/core/bst_plugin.m @@ -2529,8 +2529,14 @@ function Configure(PlugDesc) % Get tmp dir to bind container TmpDir = bst_get('BrainstormTmpDir', 0, PlugDesc.Name); volumes = {TmpDir, '/data'}; - % Run container as daemon - errMsg = bst_containers('RunContainer', ['bst_' PlugDesc.Name], PlugDesc.ImageSha, volumes, 1); + % Get if NVIDIA GPU is present + isGpu = 1; + [status, cmdout] = system('nvidia-smi'); + if status ~= 0 + isGpu = 0; + end + % Run container as daemon + errMsg = bst_containers('RunContainer', ['bst_' PlugDesc.Name], PlugDesc.ImageSha, volumes, 1, isGpu); if ~isempty(errMsg) return end From c74c7eab7a388ad1a6593343dcb1486a5f82adda Mon Sep 17 00:00:00 2001 From: Chinmay Chinara Date: Sun, 17 May 2026 18:21:42 -0700 Subject: [PATCH 03/17] Keep container alive but kill process on errors/interrupt Useful when the MATLAB function errors or there is some user interrupt (e.g. Ctrl+C) --- toolbox/core/bst_containers.m | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/toolbox/core/bst_containers.m b/toolbox/core/bst_containers.m index 2cb989c2f3..9aac6604ba 100644 --- a/toolbox/core/bst_containers.m +++ b/toolbox/core/bst_containers.m @@ -315,7 +315,13 @@ else commandWrapper = ''''; % Single quote end - [status, cmdout] = system(['docker exec ' containerName ' sh -c ' commandWrapper cmdStr commandWrapper]); + + % If MATLAB function errors, or Ctrl+C is pressed + cleanupObj = onCleanup(@() KeepContainerAlive(containerName)); + + % Execute the running container + commandExec = ['docker exec ' containerName ' sh -c ' commandWrapper cmdStr commandWrapper]; + [status, cmdout] = system(commandExec, '-echo'); if status ~= 0 errMsg = strtrim(cmdout); end @@ -396,6 +402,28 @@ end end +%% ===== KEEP CONTAINER ALIVE ===== +function errMsg = KeepContainerAlive(containerName) +% Keeps the container alive but kills all processes except PID 1 (`sleep infinity` ENTRYPOINT), +% Useful when the MATLAB function errors or there is some user interrupt (e.g. Ctrl+C) + + % Check status of default container engine + [errMsg, engineName] = GetEngine(bst_get('ContainerEngine')); + if ~isempty(errMsg) + return + end + + switch engineName + case 'docker' + cmd = ['docker exec ' containerName ' sh -c "ps -eo pid= | awk ''$1 != 1 {print $1}'' | xargs -r kill -9"']; + [status, cmdout] = system(cmd); + + if status ~= 0 + errMsg = strtrim(cmdout); + end + end + bst_progress('stop'); +end %% ===== REMOVE IMAGE ===== function errMsg = RemoveImage(imageSha, isForce) From c4d6541d5bc2d07d1d8598de40d006df0a6fbd5f Mon Sep 17 00:00:00 2001 From: Chinmay Chinara Date: Sun, 17 May 2026 18:22:03 -0700 Subject: [PATCH 04/17] Run `resection-identification` in container --- .../process_resection_identification.m | 58 ++++++++++--------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/toolbox/process/functions/process_resection_identification.m b/toolbox/process/functions/process_resection_identification.m index 0d4d7099d9..a4701e0594 100644 --- a/toolbox/process/functions/process_resection_identification.m +++ b/toolbox/process/functions/process_resection_identification.m @@ -129,19 +129,24 @@ ~isfield(sMriPreOp.SCS, 'R') || isempty(sMriPreOp.SCS.R) || ~isfield(sMriPreOp.SCS, 'T') || isempty(sMriPreOp.SCS.T) errMsg = 'The fiducials (NAS, LPA, RPA) are missing in the pre-op (default) MRI. Set them first before proceeding.'; return; - end - - % Install/load resection-identification plugin - [isOk, errInstall, PlugDesc] = bst_plugin('Install', 'resection-identification', isInteractive); - if ~isOk - errMsg = [errMsg, errInstall]; - return; - end - + end + + % === CALL RESECTION-IDENTIFICATION PIPELINE === + % Container plugin name + plugName = 'resection-identification'; + bst_progress('text', ['Calling ' plugName]); + tic; + % Ensure container plugin: Installs and/or Loads + % Install container plugin === Import image into container engine + % Load container plugin === Run container (same name as container plugin) + [ensureRes, errMsg] = bst_plugin('Ensure', plugName); + % Retrieve info of container + [errMsg, containerInfo] = bst_containers('GetContainerInfo', ['bst_' plugName]); + % === SAVE BOTH MRI AS NIfTI === bst_progress('text', 'Exporting pre- and post-op MRI...'); - % Create temporary folder - TmpDir = bst_get('BrainstormTmpDir', 0, 'resection_identification'); + % Get temporary folder from container info + TmpDir = containerInfo.volumes{1,1}; % Save pre-op MRI preOpNii = bst_fullfile(TmpDir, 'preop.nii'); out_mri_nii(sMriPreOp, preOpNii); @@ -149,20 +154,17 @@ postOpNii = bst_fullfile(TmpDir, 'postop.nii'); sMriPostOp = in_mri_bst(MriFilePostOp); out_mri_nii(sMriPostOp, postOpNii); - - % === CALL RESECTION-IDENTIFICATION PIPELINE === - bst_progress('text', 'Calling resection-identification...'); - % Get resection-identification executable - ResecExe = bst_fullfile(PlugDesc.Path, PlugDesc.SubFolder, PlugDesc.TestFile); - % Call resection-identification - strCall = ['"' ResecExe '"' ' ' '"' preOpNii '"' ' ' '"' postOpNii '"' ' ' '"' TmpDir '"']; - disp(['RESEC_ID > System call: ' strCall]); - tic; - status = system(strCall); - if (status ~= 0) - errMsg = 'Error during resection-identification, see logs in the command window.'; - bst_progress('stop'); - return; + + % Run command in container + if isempty(errMsg) && containerInfo.isRunning + dataPath = containerInfo.volumes{1,2}; + command = [' python3 auto_resection_mask.py ' dataPath '/preop.nii ' dataPath '/postop.nii']; + errMsg = bst_containers('ExecInContainer', containerInfo.name, command); + else + errMsg = 'Container is not running'; + end + if ~isempty(errMsg) + return end disp(['RESEC_ID > Computation completed in: ' num2str(round(toc)) ' s']); @@ -182,8 +184,10 @@ Post2PreOpNii = bst_fullfile(TmpDir, 'postop.nonlin.post2pre.nii.gz'); MriFilePost2PreOp = import_mri(iSubject, Post2PreOpNii, 'Nifti1', 0, 1, 'postop_coreg_preop'); - % Delete the temporary files - file_delete(TmpDir, 1, 1); + % Unload container plugin === Stop container and Delete bind files + if ensureRes > 0 + bst_plugin('Unload', plugName); + end % Return success isOk = 1; end From 99633bf7b482706564deff4ffdd8a42e6d4e38cc Mon Sep 17 00:00:00 2001 From: Chinmay Chinara Date: Mon, 18 May 2026 15:18:47 -0700 Subject: [PATCH 05/17] Add macOS common PATH entries for Docker detection When GetEngine searches for engine binaries, prepend common macOS Docker locations to PATH (/usr/local/bin, ~/.docker/bin, /Applications/Docker.app/Contents/Resources/bin) so the subsequent 'which' call can locate Docker-related executables. This improves engine detection on macOS systems. --- toolbox/core/bst_containers.m | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/toolbox/core/bst_containers.m b/toolbox/core/bst_containers.m index 9aac6604ba..86dc471272 100644 --- a/toolbox/core/bst_containers.m +++ b/toolbox/core/bst_containers.m @@ -70,6 +70,12 @@ end end else + if ismac + setenv('PATH', ['/usr/local/bin:' ... + bst_fullfile(getenv('HOME'), '.docker/bin') ':' ... + '/Applications/Docker.app/Contents/Resources/bin:' ... + getenv('PATH')]); + end [status, cmdout] = system(['which ' engineNames{iEngine}]); if status == 0 isFound = 1; From 3e3e11d9a2cbc846a09da5a95240b327e65817d1 Mon Sep 17 00:00:00 2001 From: Chinmay Chinara Date: Mon, 18 May 2026 15:20:14 -0700 Subject: [PATCH 06/17] `KeepContainerAlive`: Handle command separately for PC and UNIX --- toolbox/core/bst_containers.m | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/toolbox/core/bst_containers.m b/toolbox/core/bst_containers.m index 86dc471272..13a2c93a23 100644 --- a/toolbox/core/bst_containers.m +++ b/toolbox/core/bst_containers.m @@ -421,9 +421,13 @@ switch engineName case 'docker' - cmd = ['docker exec ' containerName ' sh -c "ps -eo pid= | awk ''$1 != 1 {print $1}'' | xargs -r kill -9"']; - [status, cmdout] = system(cmd); - + if ispc + awkPid = '$1'; % Windows host: do not escape $ + else + awkPid = '\$1'; % macOS/Linux host: prevent host shell expansion + end + cmd = ['docker exec ' containerName ' sh -c "ps -eo pid= | awk ''' awkPid ' != 1 {print ' awkPid '}'' | xargs -r kill -9 2>/dev/null || true"']; + [status, cmdout] = system(cmd); if status ~= 0 errMsg = strtrim(cmdout); end From 8a10b48838b3037d7e26b29fa52087f7c58661a5 Mon Sep 17 00:00:00 2001 From: Chinmay Chinara Date: Mon, 18 May 2026 16:07:29 -0700 Subject: [PATCH 07/17] Clean --- toolbox/process/functions/process_resection_identification.m | 1 - 1 file changed, 1 deletion(-) diff --git a/toolbox/process/functions/process_resection_identification.m b/toolbox/process/functions/process_resection_identification.m index a4701e0594..737650f303 100644 --- a/toolbox/process/functions/process_resection_identification.m +++ b/toolbox/process/functions/process_resection_identification.m @@ -144,7 +144,6 @@ [errMsg, containerInfo] = bst_containers('GetContainerInfo', ['bst_' plugName]); % === SAVE BOTH MRI AS NIfTI === - bst_progress('text', 'Exporting pre- and post-op MRI...'); % Get temporary folder from container info TmpDir = containerInfo.volumes{1,1}; % Save pre-op MRI From a43ae27c42c19f3d583fb54e938c50b1241e9542 Mon Sep 17 00:00:00 2001 From: Chinmay Chinara Date: Mon, 18 May 2026 16:19:15 -0700 Subject: [PATCH 08/17] Set `isGPU=1` if NVIDIA GPU found --- toolbox/core/bst_plugin.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/toolbox/core/bst_plugin.m b/toolbox/core/bst_plugin.m index 5f7d2e6780..ffd72428cb 100644 --- a/toolbox/core/bst_plugin.m +++ b/toolbox/core/bst_plugin.m @@ -2529,11 +2529,11 @@ function Configure(PlugDesc) % Get tmp dir to bind container TmpDir = bst_get('BrainstormTmpDir', 0, PlugDesc.Name); volumes = {TmpDir, '/data'}; - % Get if NVIDIA GPU is present - isGpu = 1; + % Use NVIDIA GPU, if present + isGpu = 0; [status, cmdout] = system('nvidia-smi'); - if status ~= 0 - isGpu = 0; + if status == 0 + isGpu = 1; end % Run container as daemon errMsg = bst_containers('RunContainer', ['bst_' PlugDesc.Name], PlugDesc.ImageSha, volumes, 1, isGpu); From b3005604a948bc869387e7df11917df7ab433aff Mon Sep 17 00:00:00 2001 From: Chinmay Chinara Date: Thu, 28 May 2026 14:09:39 -0700 Subject: [PATCH 09/17] Move to Brainstorm Docker account --- toolbox/core/bst_plugin.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toolbox/core/bst_plugin.m b/toolbox/core/bst_plugin.m index ffd72428cb..afefc7faec 100644 --- a/toolbox/core/bst_plugin.m +++ b/toolbox/core/bst_plugin.m @@ -210,7 +210,7 @@ PlugDesc(end).Version = 'latest'; PlugDesc(end).Category = 'Anatomy'; PlugDesc(end).URLinfo = 'https://github.com/ajoshiusc/auto_resection_mask/tree/brainstorm-container'; - PlugDesc(end).ImageSource = 'docker.io/chinmaychinara/auto-resection-mask:latest'; + PlugDesc(end).ImageSource = ['docker.io/brainstormtools/auto-resection-mask:' PlugDesc(end).Version]; PlugDesc(end).CompiledStatus = 1; % === ANATOMY: ROAST === From 45d9bbf6a0cd61c90d5fd3b57042f9e5a7fa92c2 Mon Sep 17 00:00:00 2001 From: Chinmay Chinara Date: Thu, 28 May 2026 14:19:24 -0700 Subject: [PATCH 10/17] Revert "Add macOS common PATH entries for Docker detection" This reverts commit 99633bf7b482706564deff4ffdd8a42e6d4e38cc. --- toolbox/core/bst_containers.m | 6 ------ 1 file changed, 6 deletions(-) diff --git a/toolbox/core/bst_containers.m b/toolbox/core/bst_containers.m index 615d966fb8..14505111ea 100644 --- a/toolbox/core/bst_containers.m +++ b/toolbox/core/bst_containers.m @@ -70,12 +70,6 @@ end end else - if ismac - setenv('PATH', ['/usr/local/bin:' ... - bst_fullfile(getenv('HOME'), '.docker/bin') ':' ... - '/Applications/Docker.app/Contents/Resources/bin:' ... - getenv('PATH')]); - end [status, cmdout] = system(['which ' engineNames{iEngine}]); if status == 0 isFound = 1; From 6286877afbda67c2ba7f704c8f7b5f0b55f7fd87 Mon Sep 17 00:00:00 2001 From: Chinmay Chinara Date: Thu, 28 May 2026 16:34:53 -0700 Subject: [PATCH 11/17] Process interrupted, safely unload the container --- toolbox/core/bst_containers.m | 34 ++++++---------------------------- 1 file changed, 6 insertions(+), 28 deletions(-) diff --git a/toolbox/core/bst_containers.m b/toolbox/core/bst_containers.m index 14505111ea..fd588aefee 100644 --- a/toolbox/core/bst_containers.m +++ b/toolbox/core/bst_containers.m @@ -326,7 +326,7 @@ end % If MATLAB function errors, or Ctrl+C is pressed - cleanupObj = onCleanup(@() KeepContainerAlive(containerName)); + cleanupObj = onCleanup(@() ProcessInterrupted(containerName)); % Execute the running container commandExec = ['docker exec ' containerName ' sh -c ' commandWrapper cmdStr commandWrapper]; @@ -411,33 +411,6 @@ end end -%% ===== KEEP CONTAINER ALIVE ===== -function errMsg = KeepContainerAlive(containerName) -% Keeps the container alive but kills all processes except PID 1 (`sleep infinity` ENTRYPOINT), -% Useful when the MATLAB function errors or there is some user interrupt (e.g. Ctrl+C) - - % Check status of default container engine - [errMsg, engineName] = GetEngine(bst_get('ContainerEngine')); - if ~isempty(errMsg) - return - end - - switch engineName - case 'docker' - if ispc - awkPid = '$1'; % Windows host: do not escape $ - else - awkPid = '\$1'; % macOS/Linux host: prevent host shell expansion - end - cmd = ['docker exec ' containerName ' sh -c "ps -eo pid= | awk ''' awkPid ' != 1 {print ' awkPid '}'' | xargs -r kill -9 2>/dev/null || true"']; - [status, cmdout] = system(cmd); - if status ~= 0 - errMsg = strtrim(cmdout); - end - end - bst_progress('stop'); -end - %% ===== REMOVE IMAGE ===== function errMsg = RemoveImage(imageSha, isForce) % Validate inputs @@ -467,3 +440,8 @@ end end +%% ===== PROCESS INTERRUPTED ===== +function ProcessInterrupted(containerName) + bst_plugin('Unload', strrep(containerName, 'bst_', '')); + bst_error('The process running in the container was interrupted', 'Container', 0); +end \ No newline at end of file From 17540146883e9798132bcd633fa4082970fb7aaa Mon Sep 17 00:00:00 2001 From: rcassani Date: Mon, 1 Jun 2026 21:58:04 -0400 Subject: [PATCH 12/17] Clean up --- toolbox/core/bst_containers.m | 4 +++- .../process/functions/process_resection_identification.m | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/toolbox/core/bst_containers.m b/toolbox/core/bst_containers.m index 20029a3e30..93262eab9b 100644 --- a/toolbox/core/bst_containers.m +++ b/toolbox/core/bst_containers.m @@ -250,7 +250,6 @@ %% ===== RUN CONTAINER AS DAEMON ===== function errMsg = RunContainer(containerName, imageSha, volumes, isDaemon, isGpu, containerArgs) % USAGE: errMsg = bst_containers('RunContainer', containerName, imageSha, volumes, isDaemon, isGpu, containerArgs) - % Validate inputs if nargin < 6 || isempty(containerArgs) containerArgs = ''; @@ -416,6 +415,7 @@ end end + %% ===== REMOVE IMAGE ===== function errMsg = RemoveImage(imageSha, isForce) % Validate inputs @@ -445,12 +445,14 @@ end end + %% ===== PROCESS INTERRUPTED ===== function ProcessInterrupted(containerName) bst_plugin('Unload', strrep(containerName, 'bst_', '')); bst_error('The process running in the container was interrupted', 'Container', 0); end + %% ===== GET ONLINE MANIFEST DIGEST ===== function [errMsg, manifestSha] = GetOnlineManifest(imageSource) manifestSha = ''; diff --git a/toolbox/process/functions/process_resection_identification.m b/toolbox/process/functions/process_resection_identification.m index 737650f303..fb339f2366 100644 --- a/toolbox/process/functions/process_resection_identification.m +++ b/toolbox/process/functions/process_resection_identification.m @@ -129,7 +129,7 @@ ~isfield(sMriPreOp.SCS, 'R') || isempty(sMriPreOp.SCS.R) || ~isfield(sMriPreOp.SCS, 'T') || isempty(sMriPreOp.SCS.T) errMsg = 'The fiducials (NAS, LPA, RPA) are missing in the pre-op (default) MRI. Set them first before proceeding.'; return; - end + end % === CALL RESECTION-IDENTIFICATION PIPELINE === % Container plugin name @@ -138,8 +138,8 @@ tic; % Ensure container plugin: Installs and/or Loads % Install container plugin === Import image into container engine - % Load container plugin === Run container (same name as container plugin) - [ensureRes, errMsg] = bst_plugin('Ensure', plugName); + % Load container plugin === Run container in standby (name ['bst_' plugName]) + ensureRes = bst_plugin('Ensure', plugName); % Retrieve info of container [errMsg, containerInfo] = bst_containers('GetContainerInfo', ['bst_' plugName]); @@ -154,7 +154,7 @@ sMriPostOp = in_mri_bst(MriFilePostOp); out_mri_nii(sMriPostOp, postOpNii); - % Run command in container + % === RUN COMMAND IN CONTAINER ===== if isempty(errMsg) && containerInfo.isRunning dataPath = containerInfo.volumes{1,2}; command = [' python3 auto_resection_mask.py ' dataPath '/preop.nii ' dataPath '/postop.nii']; From d827c0a9567ae8aae89a82b92b6f3d2bca2fce0b Mon Sep 17 00:00:00 2001 From: rcassani Date: Mon, 1 Jun 2026 21:58:44 -0400 Subject: [PATCH 13/17] Create `temp_dir_resection` from Matlab --- toolbox/process/functions/process_resection_identification.m | 2 ++ 1 file changed, 2 insertions(+) diff --git a/toolbox/process/functions/process_resection_identification.m b/toolbox/process/functions/process_resection_identification.m index fb339f2366..dbf469f0cb 100644 --- a/toolbox/process/functions/process_resection_identification.m +++ b/toolbox/process/functions/process_resection_identification.m @@ -153,6 +153,8 @@ postOpNii = bst_fullfile(TmpDir, 'postop.nii'); sMriPostOp = in_mri_bst(MriFilePostOp); out_mri_nii(sMriPostOp, postOpNii); + % Make dir for processed files + mkdir(bst_fullfile(TmpDir, 'temp_dir_resection')); % === RUN COMMAND IN CONTAINER ===== if isempty(errMsg) && containerInfo.isRunning From 53af9ccabbf77042fc897bf4555fbb8e8c057fd1 Mon Sep 17 00:00:00 2001 From: Chinmay Chinara Date: Tue, 2 Jun 2026 00:33:59 -0700 Subject: [PATCH 14/17] Track `exec` completion to skip cleanup on normal exit --- toolbox/core/bst_containers.m | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/toolbox/core/bst_containers.m b/toolbox/core/bst_containers.m index 93262eab9b..bc9055357a 100644 --- a/toolbox/core/bst_containers.m +++ b/toolbox/core/bst_containers.m @@ -328,13 +328,21 @@ else commandWrapper = ''''; % Single quote end - - % If MATLAB function errors, or Ctrl+C is pressed - cleanupObj = onCleanup(@() ProcessInterrupted(containerName)); - + + % Track whether docker exec completed normally + processState = containers.Map(); + processState('isCompleted') = false; + + % If MATLAB errors or Ctrl+C is pressed, this runs at function exit + cleanupObj = onCleanup(@() ProcessInterrupted(containerName, processState)); + % Execute the running container commandExec = ['docker exec ' containerName ' sh -c ' commandWrapper cmdStr commandWrapper]; [status, cmdout] = system(commandExec, '-echo'); + + % system() returned normally, even if docker returned non-zero status + processState('isCompleted') = true; %#ok + if status ~= 0 errMsg = strtrim(cmdout); end @@ -447,9 +455,11 @@ %% ===== PROCESS INTERRUPTED ===== -function ProcessInterrupted(containerName) - bst_plugin('Unload', strrep(containerName, 'bst_', '')); - bst_error('The process running in the container was interrupted', 'Container', 0); +function ProcessInterrupted(containerName, processState) + if ~processState('isCompleted') + bst_plugin('Unload', regexprep(containerName, '^bst_', '')); + bst_error('The process running in the container was interrupted', 'Container', 0); + end end From b7c2445431438a71d0066ca8170d41dd4d18ba41 Mon Sep 17 00:00:00 2001 From: rcassani Date: Tue, 2 Jun 2026 09:47:21 -0400 Subject: [PATCH 15/17] Track `exec` completion to skip cleanup on normal exit (Part 2) --- toolbox/core/bst_containers.m | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/toolbox/core/bst_containers.m b/toolbox/core/bst_containers.m index bc9055357a..521ff56b17 100644 --- a/toolbox/core/bst_containers.m +++ b/toolbox/core/bst_containers.m @@ -320,6 +320,11 @@ return end + % Flag to track interruption + processState = containers.Map({'isInterruptCleanup'}, {1}); + % Clean up on function end, errors or Ctrl+C is pressed + cleanupObj = onCleanup(@() ProcessInterrupted(containerName, processState)); + % Run command switch engineName case 'docker' @@ -329,24 +334,17 @@ commandWrapper = ''''; % Single quote end - % Track whether docker exec completed normally - processState = containers.Map(); - processState('isCompleted') = false; - - % If MATLAB errors or Ctrl+C is pressed, this runs at function exit - cleanupObj = onCleanup(@() ProcessInterrupted(containerName, processState)); - % Execute the running container commandExec = ['docker exec ' containerName ' sh -c ' commandWrapper cmdStr commandWrapper]; [status, cmdout] = system(commandExec, '-echo'); - - % system() returned normally, even if docker returned non-zero status - processState('isCompleted') = true; %#ok - + if status ~= 0 errMsg = strtrim(cmdout); end end + + % Code in container ended normally + processState('isInterruptCleanup') = 0; end @@ -456,7 +454,7 @@ %% ===== PROCESS INTERRUPTED ===== function ProcessInterrupted(containerName, processState) - if ~processState('isCompleted') + if processState('isInterruptCleanup') bst_plugin('Unload', regexprep(containerName, '^bst_', '')); bst_error('The process running in the container was interrupted', 'Container', 0); end From 4dd7fa8b3b6a797dd7a4e538abd2f35c1346663f Mon Sep 17 00:00:00 2001 From: rcassani Date: Tue, 2 Jun 2026 12:36:42 -0400 Subject: [PATCH 16/17] Make optional the use of GPU for containers --- toolbox/core/bst_containers.m | 13 +++++-------- toolbox/core/bst_plugin.m | 8 +------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/toolbox/core/bst_containers.m b/toolbox/core/bst_containers.m index 521ff56b17..ef5ab78827 100644 --- a/toolbox/core/bst_containers.m +++ b/toolbox/core/bst_containers.m @@ -248,15 +248,12 @@ %% ===== RUN CONTAINER AS DAEMON ===== -function errMsg = RunContainer(containerName, imageSha, volumes, isDaemon, isGpu, containerArgs) -% USAGE: errMsg = bst_containers('RunContainer', containerName, imageSha, volumes, isDaemon, isGpu, containerArgs) +function errMsg = RunContainer(containerName, imageSha, volumes, isDaemon, containerArgs) +% USAGE: errMsg = bst_containers('RunContainer', containerName, imageSha, volumes, isDaemon, containerArgs) % Validate inputs - if nargin < 6 || isempty(containerArgs) + if nargin < 5 || isempty(containerArgs) containerArgs = ''; end - if nargin < 5 || isempty(isGpu) - isGpu = 0; - end if nargin < 4 || isempty(isDaemon) isDaemon = 0; end @@ -281,9 +278,9 @@ volumesStr = strjoin(pairs, ' '); end - % GPU option + % Use GPU with container engine gpuStr = ''; - if isGpu + if bst_get('ContainerUseGpu') && system('which nvidia-smi') == 0 gpuStr = '--gpus all'; end diff --git a/toolbox/core/bst_plugin.m b/toolbox/core/bst_plugin.m index 89add5e8cf..e36e98a0a6 100644 --- a/toolbox/core/bst_plugin.m +++ b/toolbox/core/bst_plugin.m @@ -2540,14 +2540,8 @@ function Configure(PlugDesc) % Get tmp dir to bind container TmpDir = bst_get('BrainstormTmpDir', 0, PlugDesc.Name); volumes = {TmpDir, '/data'}; - % Use NVIDIA GPU, if present - isGpu = 0; - [status, cmdout] = system('nvidia-smi'); - if status == 0 - isGpu = 1; - end % Run container as daemon - errMsg = bst_containers('RunContainer', ['bst_' PlugDesc.Name], PlugDesc.ImageSha, volumes, 1, isGpu); + errMsg = bst_containers('RunContainer', ['bst_' PlugDesc.Name], PlugDesc.ImageSha, volumes, 1); if ~isempty(errMsg) return end From 6e1b482fa6e3bf44a03e32109688d0bf70a26b91 Mon Sep 17 00:00:00 2001 From: rcassani Date: Tue, 2 Jun 2026 15:25:21 -0400 Subject: [PATCH 17/17] Clean up --- toolbox/core/bst_containers.m | 6 +----- toolbox/core/bst_plugin.m | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/toolbox/core/bst_containers.m b/toolbox/core/bst_containers.m index ef5ab78827..a2b1a3d671 100644 --- a/toolbox/core/bst_containers.m +++ b/toolbox/core/bst_containers.m @@ -5,7 +5,7 @@ % [errMsg, engineName] = bst_containers('GetEngine') % [errMsg, imageList] = bst_containers('GetImages') % [errMsg, imageSha] = bst_containers('ImportImage', imageSource, [imageTag]) -% errMsg = bst_containers('RunContainer', containerName, imageSha, [volumes], [isDaemon]) +% errMsg = bst_containers('RunContainer', containerName, imageSha, [volumes], [isDaemon], [containerArgs]) % [errMsg, cmdout] = bst_containers('ExecInContainer', containerName, cmdStr) % [errMsg, containerInfo] = bst_containers('GetContainerInfo', containerName) % errMsg = bst_containers('StopContainer', containerName, [isForced=0]) @@ -321,7 +321,6 @@ processState = containers.Map({'isInterruptCleanup'}, {1}); % Clean up on function end, errors or Ctrl+C is pressed cleanupObj = onCleanup(@() ProcessInterrupted(containerName, processState)); - % Run command switch engineName case 'docker' @@ -330,16 +329,13 @@ else commandWrapper = ''''; % Single quote end - % Execute the running container commandExec = ['docker exec ' containerName ' sh -c ' commandWrapper cmdStr commandWrapper]; [status, cmdout] = system(commandExec, '-echo'); - if status ~= 0 errMsg = strtrim(cmdout); end end - % Code in container ended normally processState('isInterruptCleanup') = 0; end diff --git a/toolbox/core/bst_plugin.m b/toolbox/core/bst_plugin.m index e36e98a0a6..875867855e 100644 --- a/toolbox/core/bst_plugin.m +++ b/toolbox/core/bst_plugin.m @@ -2540,7 +2540,7 @@ function Configure(PlugDesc) % Get tmp dir to bind container TmpDir = bst_get('BrainstormTmpDir', 0, PlugDesc.Name); volumes = {TmpDir, '/data'}; - % Run container as daemon + % Run container as daemon errMsg = bst_containers('RunContainer', ['bst_' PlugDesc.Name], PlugDesc.ImageSha, volumes, 1); if ~isempty(errMsg) return