Skip to content

Commit 362ff95

Browse files
authored
[REF] Adapt Stat Volume to SPM25 (aramis-lab#1626)
* todos * Adapt to spm standalone * No GUI question * Fix unit tests * Fix unit tests 2 * Fix unit test spm
1 parent 4bd9f4d commit 362ff95

File tree

7 files changed

+30
-186
lines changed

7 files changed

+30
-186
lines changed

.github/workflows/test_non_regression_fast.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ jobs:
4949
source /builds/miniconda/etc/profile.d/conda.sh
5050
conda activate "${{ github.workspace }}"/env
5151
source /usr/local/Modules/init/profile.sh
52-
module load clinica.all
52+
module load clinica_standalone.all
5353
make install
5454
cd test
5555
poetry run pytest --verbose \

clinica/pipelines/statistics_volume/info.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
{
99
"type": "software",
1010
"name": "spm",
11-
"version": ">=12"
11+
"version": ">=25"
1212
}
1313
]
14-
}
14+
}

clinica/pipelines/statistics_volume/statistics_volume_utils.py

Lines changed: 6 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -406,9 +406,7 @@ def run_m_script(m_file: str) -> str:
406406
"""
407407
from pathlib import Path
408408

409-
from clinica.pipelines.statistics_volume.statistics_volume_utils import ( # noqa
410-
_delete_last_line,
411-
_run_matlab_script_with_matlab,
409+
from clinica.pipelines.statistics_volume.statistics_volume_utils import (
412410
_run_matlab_script_with_spm_standalone,
413411
)
414412
from clinica.utils.spm import use_spm_standalone_if_available
@@ -420,53 +418,14 @@ def run_m_script(m_file: str) -> str:
420418
raise ValueError(
421419
f"[Error] {m_file} is not a Matlab file (extension must be .m)"
422420
)
423-
if use_spm_standalone_if_available():
424-
_delete_last_line(m_file)
425-
_run_matlab_script_with_spm_standalone(m_file)
426-
else:
427-
_run_matlab_script_with_matlab(str(m_file))
421+
use_spm_standalone_if_available()
422+
_run_matlab_script_with_spm_standalone(m_file)
428423
output_mat_file = (m_file.parent.parent / "2_sample_t_test" / "SPM.mat").resolve()
429424
if not output_mat_file.is_file():
430425
raise RuntimeError(f"Output matrix {output_mat_file} was not produced")
431426
return str(output_mat_file)
432427

433428

434-
def _delete_last_line(filename: Path) -> None:
435-
"""Removes the last line of the provided file.
436-
437-
Used to remove the call to spm jobman if the MATLAB file
438-
is used with SPM standalone.
439-
440-
Parameters
441-
----------
442-
filename: Path
443-
Path to filename
444-
"""
445-
import os
446-
447-
with open(filename, "r+", encoding="utf-8") as file:
448-
# Move the pointer (similar to a cursor in a text editor) to the end of the file
449-
file.seek(0, os.SEEK_END)
450-
451-
# This code means the following code skips the very last character in the file -
452-
# i.e. in the case the last line is null we delete the last line
453-
# and the penultimate one
454-
pos = file.tell() - 1
455-
456-
# Read each character in the file one at a time from the penultimate
457-
# character going backwards, searching for a newline character
458-
# If we find a new line, exit the search
459-
while pos > 0 and file.read(1) != "\n":
460-
pos -= 1
461-
file.seek(pos, os.SEEK_SET)
462-
463-
# So long as we're not at the start of the file, delete all the characters ahead
464-
# of this position
465-
if pos > 0:
466-
file.seek(pos, os.SEEK_SET)
467-
file.truncate()
468-
469-
470429
def _run_matlab_script_with_spm_standalone(m_file: Path) -> None:
471430
"""Runs a matlab script spm_standalone
472431
@@ -492,56 +451,10 @@ def _get_matlab_standalone_command(m_file: Path) -> str:
492451
SystemError
493452
If this is run on unsupported platforms.
494453
"""
495-
if _is_running_on_mac():
496-
return f"cd $SPMSTANDALONE_HOME && ./run_spm12.sh $MCR_HOME batch {m_file}"
497-
if _is_running_on_linux():
498-
return f"$SPMSTANDALONE_HOME/run_spm12.sh $MCR_HOME batch {m_file}"
499-
raise SystemError("Clinica only support Mac OS and Linux")
500-
501-
502-
def _is_running_on_os(os: str) -> bool:
503-
import platform
504-
505-
return platform.system().lower().startswith(os)
506-
507-
508-
_is_running_on_mac = functools.partial(_is_running_on_os, os="darwin")
509-
_is_running_on_linux = functools.partial(_is_running_on_os, os="linux")
510-
511-
512-
def _run_matlab_script_with_matlab(m_file: str) -> None:
513-
"""Runs a matlab script using matlab
514-
515-
Parameters
516-
----------
517-
m_file: str
518-
Path to the script
519-
"""
520-
import platform
521-
from os.path import abspath
522-
523-
from nipype.interfaces.matlab import MatlabCommand, get_matlab_command
524-
525-
_add_spm_path_to_matlab_script(m_file)
526-
MatlabCommand.set_default_matlab_cmd(get_matlab_command())
527-
matlab = MatlabCommand()
528-
if platform.system().lower().startswith("linux"):
529-
matlab.inputs.args = "-nosoftwareopengl"
530-
matlab.inputs.paths = Path(m_file).parent
531-
matlab.inputs.script = Path(m_file).stem
532-
matlab.inputs.single_comp_thread = False
533-
matlab.inputs.logfile = abspath("./matlab_output.log")
534-
matlab.run()
535-
536-
537-
def _add_spm_path_to_matlab_script(m_file: str) -> None:
538-
from clinica.utils.check_dependency import get_spm_home
454+
from clinica.utils.check_dependency import get_spm_standalone_home
455+
from clinica.utils.spm import _get_real_spm_standalone_file
539456

540-
with open(m_file, "r") as fp:
541-
lines = fp.readlines()
542-
lines.insert(0, f"addpath('{get_spm_home()}');")
543-
with open(m_file, "w") as fp:
544-
fp.writelines(lines)
457+
return f"$SPMSTANDALONE_HOME/{_get_real_spm_standalone_file(get_spm_standalone_home())} $MCR_HOME batch {m_file}"
545458

546459

547460
def clean_template_file(mat_file: str, template_file: str) -> str:

clinica/pipelines/statistics_volume/template_model_contrast.m

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@
1818
matlabbatch{1}.spm.stats.con.consess{2}.tcon.weights = weights2;
1919
matlabbatch{1}.spm.stats.con.consess{2}.tcon.sessrep = 'none';
2020

21+
% run batch
2122
spm_jobman('run', matlabbatch)

clinica/pipelines/statistics_volume/template_model_creation.m

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
% Then View > Show m-code
44

55
spm('defaults','pet');
6+
spm_get_defaults('cmdline',true);
67
spm_jobman('initcfg');
78

89
matlabbatch{1}.spm.stats.factorial_design.dir = {@OUTPUTDIR};
@@ -19,3 +20,6 @@
1920
matlabbatch{1}.spm.stats.factorial_design.globalc.g_omit = 1;
2021
matlabbatch{1}.spm.stats.factorial_design.globalm.gmsca.gmsca_no = 1;
2122
matlabbatch{1}.spm.stats.factorial_design.globalm.glonorm = 1;
23+
24+
25+
% no need run batch bc things will be added here by spm

clinica/pipelines/statistics_volume/template_model_estimation.m

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,6 @@
88
matlabbatch{1}.spm.stats.fmri_est.spmmat = {@SPMMAT};
99
matlabbatch{1}.spm.stats.fmri_est.write_residuals = 0;
1010
matlabbatch{1}.spm.stats.fmri_est.method.Classical = 1;
11-
spm_jobman('run', matlabbatch)
11+
12+
% run batch
13+
spm_jobman('run', matlabbatch)

test/unittests/pipelines/statistics_volume/test_statistics_volume_utils.py

Lines changed: 13 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -199,52 +199,6 @@ def test_is_number(input_number, expected):
199199
assert _is_number(input_number) == expected
200200

201201

202-
@pytest.mark.parametrize(
203-
"input_data,expected",
204-
[
205-
("", ""),
206-
("foo bar baz", "foo bar baz"),
207-
("foo bar baz\n", "foo bar baz\n"),
208-
("foo bar\nbaz", "foo bar"),
209-
("foo bar\nbaz\n", "foo bar"),
210-
(
211-
_get_tsv_data_two_classes(),
212-
"\n".join(_get_tsv_data_two_classes().split("\n")[:-1]),
213-
),
214-
],
215-
ids=(
216-
"empty content",
217-
"one line",
218-
"one line with new line",
219-
"two lines",
220-
"two lines with new line",
221-
"multiple lines",
222-
),
223-
)
224-
def test_delete_last_line(tmp_path, input_data, expected):
225-
from clinica.pipelines.statistics_volume.statistics_volume_utils import (
226-
_delete_last_line,
227-
)
228-
229-
(tmp_path / "foo.tsv").write_text(input_data)
230-
231-
_delete_last_line(tmp_path / "foo.tsv")
232-
233-
assert (tmp_path / "foo.tsv").read_text() == expected
234-
235-
236-
def test_delete_last_line_empty(tmp_path):
237-
from clinica.pipelines.statistics_volume.statistics_volume_utils import (
238-
_delete_last_line,
239-
)
240-
241-
(tmp_path / "foo.tsv").touch()
242-
243-
_delete_last_line(tmp_path / "foo.tsv")
244-
245-
assert (tmp_path / "foo.tsv").read_text() == ""
246-
247-
248202
def test_write_covariate_lines_error(tmp_path):
249203
from clinica.pipelines.statistics_volume.statistics_volume_utils import (
250204
_write_covariate_lines,
@@ -303,7 +257,7 @@ def test_run_m_script_no_output_file_error(tmp_path, mocker):
303257
m_file = tmp_path / "script.m"
304258
m_file.touch()
305259
mocker.patch(
306-
"clinica.pipelines.statistics_volume.statistics_volume_utils._run_matlab_script_with_matlab",
260+
"clinica.pipelines.statistics_volume.statistics_volume_utils._run_matlab_script_with_spm_standalone",
307261
return_value=None,
308262
)
309263
(tmp_path / "spm_home_folder").mkdir()
@@ -330,7 +284,7 @@ def test_run_m_script(tmp_path, mocker):
330284
expected_output_file = expected_output_folder / "SPM.mat"
331285
expected_output_file.touch()
332286
mocker.patch(
333-
"clinica.pipelines.statistics_volume.statistics_volume_utils._run_matlab_script_with_matlab",
287+
"clinica.pipelines.statistics_volume.statistics_volume_utils._run_matlab_script_with_spm_standalone",
334288
return_value=None,
335289
)
336290
(tmp_path / "spm_home_folder").mkdir()
@@ -339,51 +293,21 @@ def test_run_m_script(tmp_path, mocker):
339293
assert run_m_script(m_file) == str(expected_output_file)
340294

341295

342-
@pytest.mark.parametrize(
343-
"mocked_values,expected",
344-
[
345-
(
346-
[True, False],
347-
"cd $SPMSTANDALONE_HOME && ./run_spm12.sh $MCR_HOME batch script.m",
348-
),
349-
([False, True], "$SPMSTANDALONE_HOME/run_spm12.sh $MCR_HOME batch script.m"),
350-
],
351-
ids=("running on MAC OS", "running on Linux"),
352-
)
353-
def test_get_matlab_standalone_command(mocker, mocked_values, expected):
354-
from clinica.pipelines.statistics_volume.statistics_volume_utils import (
355-
_get_matlab_standalone_command,
356-
)
357-
358-
for func, return_value in zip(
359-
["_is_running_on_mac", "_is_running_on_linux"], mocked_values
360-
):
361-
mocker.patch(
362-
f"clinica.pipelines.statistics_volume.statistics_volume_utils.{func}",
363-
return_value=return_value,
364-
)
365-
366-
assert _get_matlab_standalone_command(Path("script.m")) == expected
367-
368-
369-
def test_get_matlab_standalone_command_system_error(mocker):
296+
def test_get_matlab_standalone_command():
297+
import clinica.utils.check_dependency as check_dep
298+
import clinica.utils.spm as spm_utils
370299
from clinica.pipelines.statistics_volume.statistics_volume_utils import (
371300
_get_matlab_standalone_command,
372301
)
373302

374-
for func, return_value in zip(
375-
["_is_running_on_mac", "_is_running_on_linux"], [False, False]
376-
):
377-
mocker.patch(
378-
f"clinica.pipelines.statistics_volume.statistics_volume_utils.{func}",
379-
return_value=return_value,
380-
)
381-
382-
with pytest.raises(
383-
SystemError,
384-
match="Clinica only support Mac OS and Linux",
385-
):
386-
_get_matlab_standalone_command(Path("script.m"))
303+
with mock.patch.object(spm_utils, "_get_real_spm_standalone_file") as mock_get_real:
304+
with mock.patch.object(check_dep, "get_spm_standalone_home") as mock_get_spm:
305+
mock_get_real.return_value = "run_spm12.sh"
306+
assert (
307+
_get_matlab_standalone_command(Path("script.m"))
308+
== f"$SPMSTANDALONE_HOME/run_spm12.sh $MCR_HOME batch script.m"
309+
)
310+
mock_get_spm.assert_called_once()
387311

388312

389313
@pytest.mark.parametrize(

0 commit comments

Comments
 (0)