Skip to content

Commit ba2ba6d

Browse files
authored
[FIX] fix octave create roi (#130)
* test with octave * change name * fix * tweak * fix several tests * reset submod * refactor set up * update tests * fix for matlab * reuse set up
1 parent 4b4f7f2 commit ba2ba6d

16 files changed

+186
-147
lines changed
+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
---
2+
name: tests and coverage with octave
3+
4+
env:
5+
OCTFLAGS: --no-gui --no-window-system --silent
6+
7+
concurrency:
8+
group: ${{ github.workflow }}-${{ github.ref }}
9+
cancel-in-progress: true
10+
11+
on:
12+
push:
13+
branches:
14+
- main
15+
pull_request:
16+
branches: ['*']
17+
schedule:
18+
- cron: 0 0 1 * *
19+
20+
# Allows you to run this workflow manually from the Actions tab
21+
workflow_dispatch:
22+
23+
jobs:
24+
tests_octave:
25+
runs-on: ubuntu-latest
26+
27+
steps:
28+
- name: Install CPP_ROI
29+
uses: actions/checkout@v4
30+
with:
31+
submodules: true
32+
fetch-depth: 0
33+
34+
- name: Install SPM
35+
run: |
36+
git clone https://github.com/spm/spm12.git --depth 1
37+
38+
- name: Install Moxunit and MOcov
39+
run: |
40+
git clone https://github.com/MOxUnit/MOxUnit.git --depth 1
41+
git clone https://github.com/MOcov/MOcov.git --depth 1
42+
43+
- name: Install octave
44+
run: |
45+
sudo apt-get -y -qq update
46+
sudo apt-get -y install \
47+
octave \
48+
liboctave-dev\
49+
octave-common \
50+
octave-io \
51+
octave-image \
52+
octave-signal \
53+
octave-statistics
54+
make -C MOxUnit install
55+
make -C MOcov install
56+
57+
- name: Compile SPM
58+
run: |
59+
make -C spm12/src PLATFORM=octave distclean
60+
make -C spm12/src PLATFORM=octave
61+
make -C spm12/src PLATFORM=octave install
62+
octave $OCTFLAGS --eval "addpath(fullfile(pwd, 'spm12')); savepath();"
63+
64+
- name: Add bids-matlab
65+
run: make install_dev
66+
67+
- name: Run tests
68+
run: |
69+
octave $OCTFLAGS --eval "addpath(fullfile(pwd, 'tests', 'utils')); savepath();"
70+
octave $OCTFLAGS --eval "cd(fullfile(getenv('GITHUB_WORKSPACE'), '.github', 'workflows')); run tests_octave;"
71+
72+
- name: Code coverage
73+
uses: codecov/codecov-action@v4
74+
with:
75+
file: coverage.xml
76+
flags: octave
77+
name: codecov-umbrella
78+
fail_ci_if_error: false

.github/workflows/tests_octave.m

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
function tests_octave()
2+
3+
%
4+
% (C) Copyright 2024 CPP ROI developers
5+
6+
root_dir = getenv('GITHUB_WORKSPACE');
7+
8+
addpath(fullfile(root_dir, 'spm12'));
9+
addpath(fullfile(root_dir, 'lib', 'bids-matlab'));
10+
addpath(fullfile(root_dir, 'MOcov', 'MOcov'));
11+
12+
cd(fullfile(root_dir, 'MOxUnit', 'MOxUnit'));
13+
14+
moxunit_set_path();
15+
16+
cd(fullfile(root_dir));
17+
18+
initCppRoi();
19+
20+
run_tests();
21+
22+
end

src/atlas/copyAtlasToSpmDir.m

-5
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,6 @@ function copyAtlasToSpmDir(varargin)
1717

1818
% (C) Copyright 2022 CPP ROI developers
1919

20-
if bids.internal.is_octave
21-
% the atlas in the spm dir are only useful in matlab with the GUI
22-
return
23-
end
24-
2520
args = inputParser;
2621

2722
addOptional(args, 'atlas', 'AAL', @ischar);

src/atlas/extractRoiFromAtlas.m

+11-2
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@
3939
args = inputParser;
4040

4141
addRequired(args, 'outputDir', isChar);
42-
addRequired(args, 'atlasName', @(x) isAKnownAtlas(x));
42+
addRequired(args, 'atlasName');
4343
addRequired(args, 'roiName', isChar);
44-
addRequired(args, 'hemisphere', @(x) ismember(x, {'L', 'R'}));
44+
addRequired(args, 'hemisphere');
4545

4646
parse(args, varargin{:});
4747

@@ -50,6 +50,15 @@
5050
roiName = args.Results.roiName;
5151
hemisphere = args.Results.hemisphere;
5252

53+
if ~ismember(hemisphere, {'L', 'R'})
54+
msg = sprintf('"hemisphere must be "L" or "R"": %s\nGot: "%s"', ...
55+
hemisphere);
56+
bids.internal.error_handling(mfilename(), ...
57+
'invalidHemisphere', msg, false);
58+
end
59+
60+
isAKnownAtlas(atlasName);
61+
5362
[atlasFile, lut] = getAtlasAndLut(atlasName);
5463

5564
switch lower(atlasName)

src/atlas/unzipAtlas.m

+9-3
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,13 @@ function gunzipAtlasIfNecessary(file)
5959
end
6060

6161
function gunzipWithOctave(file)
62-
copyfile(file, [file '.bak']);
63-
gunzip(file);
64-
copyfile([file '.bak'], file);
62+
if iscellstr(file)
63+
for i = 1:numel(file)
64+
gunzipWithOctave(file{i});
65+
end
66+
else
67+
copyfile(file, [file '.bak']);
68+
gunzip(file);
69+
copyfile([file '.bak'], file);
70+
end
6571
end

src/roi/thresholdToMask.m

+5-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848

4949
% add peakThreshold and clusterSizeInfo to desc
5050
if ~isfield(bf.entities, 'desc')
51-
bf.entities.desc = '';
51+
bf.entities(1).desc = '';
5252
end
5353
descSuffix = sprintf('p%05.2f', peakThreshold);
5454
if clusterSize > 0
@@ -57,6 +57,10 @@
5757
descSuffix = strrep(descSuffix, '.', 'pt');
5858
bf.entities.desc = [bf.entities.desc descSuffix];
5959

60+
if isempty(bf.extension)
61+
bf.extension = '.nii';
62+
end
63+
6064
bf = bf.update();
6165

6266
hdr = spm_vol(inputImage);

tests/test_createRoi.m

+10-27
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99

1010
function test_createRoi_sphere()
1111

12-
volumeDefiningImage = fullfile(demoDir(), 'TStatistic.nii');
12+
inputDir = setUpDemoData();
13+
volumeDefiningImage = fullfile(inputDir, 'inputs', 'TStatistic.nii');
1314

1415
sphere.location = [44 -67 0];
1516
sphere.radius = 5;
@@ -91,38 +92,20 @@ function test_createRoi_intersection_mask_sphere()
9192
value = fileparts(mfilename('fullpath'));
9293
end
9394

94-
function value = demoDir()
95-
96-
value = fullfile(thisDir(), '..', 'demos', 'roi', 'inputs');
97-
98-
if exist(fullfile(value, 'TStatistic.nii'), 'file') == 0 || ...
99-
exist(fullfile(value, 'visual motion_association-test_z_FDR_0.01.nii'), 'file') == 0
100-
gunzip(fullfile(value, '*.gz'));
101-
end
102-
103-
end
104-
10595
function [roiFilename, volumeDefiningImage] = prepareRoiAndVolumeDefiningImage()
10696

107-
volumeDefiningImage = fullfile(demoDir(), 'TStatistic.nii');
108-
109-
roiFilename = fullfile(demoDir(), ...
110-
'space-MNI_atlas-neurosynth_label-visualMotion_desc-p10pt00_mask.nii');
111-
112-
if exist(roiFilename, 'file') == 2
97+
inputDir = setUpDemoData();
11398

114-
else
99+
volumeDefiningImage = fullfile(inputDir, 'inputs', 'TStatistic.nii');
115100

116-
zMap = fullfile(demoDir(), 'visual motion_association-test_z_FDR_0.01.nii');
101+
zMap = fullfile(inputDir, 'inputs', 'visual motion_association-test_z_FDR_0.01.nii');
117102

118-
zMap = renameNeuroSynth(zMap);
119-
zMap = resliceRoiImages(volumeDefiningImage, zMap);
103+
zMap = renameNeuroSynth(zMap);
104+
zMap = resliceRoiImages(volumeDefiningImage, zMap);
120105

121-
zMap = removePrefix(zMap, 'r');
106+
zMap = removePrefix(zMap, 'r');
122107

123-
threshold = 10;
124-
roiFilename = thresholdToMask(zMap, threshold);
125-
126-
end
108+
threshold = 10;
109+
roiFilename = thresholdToMask(zMap, threshold);
127110

128111
end

tests/test_getPeakCoordinates.m

+3-18
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@
1010

1111
function test_getPeakCoordinates_basic()
1212

13-
roiImage = extractRoiFromAtlas(pwd, 'wang', 'V1v', 'L');
13+
inputDir = setUpDemoData();
14+
dataImage = fullfile(inputDir, 'inputs', 'TStatistic.nii');
1415

15-
dataImage = fullfile(demoDir(), 'TStatistic.nii');
16+
roiImage = extractRoiFromAtlas(pwd, 'wang', 'V1v', 'L');
1617

1718
reslicedImages = resliceRoiImages(dataImage, roiImage);
1819

@@ -22,20 +23,4 @@ function test_getPeakCoordinates_basic()
2223
assertEqual(voxelCoord, [28 8 24]);
2324
assertElementsAlmostEqual(maxVal, 1.6212, 'absolute', 1e-3);
2425

25-
delete('*hemi-L_space-MNI_atlas-wang_label-V1v_mask.*');
26-
27-
end
28-
29-
function value = thisDir()
30-
value = fileparts(mfilename('fullpath'));
31-
end
32-
33-
function value = demoDir()
34-
35-
value = fullfile(thisDir(), '..', 'demos', 'roi', 'inputs');
36-
37-
if exist(fullfile(value, 'TStatistic.nii'), 'file') == 0
38-
gunzip(fullfile(value, '*.gz'));
39-
end
40-
4126
end

tests/test_isBinaryMask.m

+6-36
Original file line numberDiff line numberDiff line change
@@ -9,50 +9,20 @@
99

1010
function test_isBinaryMask_true()
1111

12-
roiFilename = prepareRoiAndVolumeDefiningImage();
12+
[roiFilename, zMap] = prepareRoiAndVolumeDefiningImage();
1313
isBinaryMask(roiFilename);
14-
15-
end
16-
17-
function test_isBinaryMask_false()
18-
19-
[~, zMap] = prepareRoiAndVolumeDefiningImage();
2014
assertExceptionThrown(@()isBinaryMask(zMap), 'isBinaryMask:notBinaryImage');
2115

2216
end
2317

24-
function value = thisDir()
25-
value = fileparts(mfilename('fullpath'));
26-
end
27-
28-
function value = demoDir()
29-
30-
value = fullfile(thisDir(), '..', 'demos', 'roi', 'inputs');
31-
32-
if exist(fullfile(value, 'visual motion_association-test_z_FDR_0.01.nii'), 'file') == 0
33-
gunzip(fullfile(value, '*.gz'));
34-
end
35-
36-
end
37-
3818
function [roiFilename, zMap] = prepareRoiAndVolumeDefiningImage()
3919

40-
zMap = fullfile(demoDir(), 'space-MNI_atlas-neurosynth_label-visualMotion_probseg.nii');
41-
42-
roiFilename = fullfile(demoDir(), ...
43-
'space-MNI_atlas-neurosynth_label-visualMotion_desc-p10pt00_mask.nii');
44-
45-
if exist(roiFilename, 'file') == 2
20+
inputDir = setUpDemoData();
4621

47-
else
22+
zMap = fullfile(inputDir, 'inputs', 'visual motion_association-test_z_FDR_0.01.nii');
4823

49-
zMap = fullfile(demoDir(), 'visual motion_association-test_z_FDR_0.01.nii');
50-
51-
zMap = renameNeuroSynth(zMap);
52-
53-
threshold = 10;
54-
roiFilename = thresholdToMask(zMap, threshold);
55-
56-
end
24+
zMap = renameNeuroSynth(zMap);
25+
threshold = 10;
26+
roiFilename = thresholdToMask(zMap, threshold);
5727

5828
end

tests/test_keepHemisphere.m

+1-7
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@
1010

1111
function test_keepHemisphere_basic()
1212

13-
inputDir = fullfile(fileparts(mfilename('fullpath')), '..', 'demos', 'roi');
14-
15-
gunzip(fullfile(inputDir, 'inputs', '*.gz'));
13+
inputDir = setUpDemoData();
1614
zMap = fullfile(inputDir, 'inputs', 'visual motion_association-test_z_FDR_0.01.nii');
1715

1816
zMap = renameNeuroSynth(zMap);
@@ -32,8 +30,4 @@ function test_keepHemisphere_basic()
3230
'file'), ...
3331
2);
3432

35-
% TODO check the data content
36-
37-
delete(fullfile(inputDir, 'inputs', '*.nii'));
38-
3933
end

tests/test_labelClusters.m

+4-17
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99

1010
function test_labelClusters_basic
1111

12-
zMap = fullfile(demoDir(), 'visual motion_association-test_z_FDR_0.01.nii');
12+
inputDir = setUpDemoData();
13+
zMap = fullfile(inputDir, 'inputs', 'visual motion_association-test_z_FDR_0.01.nii');
1314

1415
zMap = renameNeuroSynth(zMap);
1516

@@ -19,28 +20,14 @@
1920
labeledClusters = labelClusters(zMap, peakThreshold, extendThreshold);
2021

2122
expected = 'space-MNI_seg-neurosynth_label-visualMotion_dseg.nii';
22-
assertEqual(exist(fullfile(demoDir(), expected), 'file'), 2);
23+
assertEqual(exist(fullfile(inputDir, 'inputs', expected), 'file'), 2);
2324

2425
labelStruct = struct('ROI', 'ns left MT', ...
2526
'label', 1);
2627

2728
roiName = extractRoiByLabel(labeledClusters, labelStruct);
2829

2930
expected = 'space-MNI_seg-neurosynth_label-nsLeftMT_mask.nii';
30-
assertEqual(exist(fullfile(demoDir(), expected), 'file'), 2);
31-
32-
end
33-
34-
function value = thisDir()
35-
value = fileparts(mfilename('fullpath'));
36-
end
37-
38-
function value = demoDir()
39-
40-
value = fullfile(thisDir(), '..', 'demos', 'roi', 'inputs');
41-
42-
if exist(fullfile(value, 'visual motion_association-test_z_FDR_0.01.nii'), 'file') == 0
43-
gunzip(fullfile(value, '*.gz'));
44-
end
31+
assertEqual(exist(fullfile(inputDir, 'inputs', expected), 'file'), 2);
4532

4633
end

0 commit comments

Comments
 (0)