Skip to content

Commit ced6ab6

Browse files
Merge pull request #117 from OSIPI/matlab_GU
All algorithms wrapped and brain test data
2 parents 3747f4c + 37191e4 commit ced6ab6

21 files changed

Lines changed: 574 additions & 88 deletions

.github/workflows/unit_test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,5 @@ jobs:
3535
run: |
3636
pip install pytest pytest-cov
3737
python -m pytest --doctest-modules --junitxml=junit/test-results.xml --cov=. --cov-report=xml --cov-report=html -r w
38+
python -m pytest --doctest-modules --junitxml=junit/test-results-brain.xml --cov=. --cov-report=xml --cov-report=html --dataFile tests/IVIMmodels/unit_tests/generic_brain.json -r w -k test_ivim_fit_saved
3839

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
__pycache__/
66
*.nii.gz
77
*.nii
8+
*.bval
9+
*.bvec
810
*.npy
911
*.dcm
1012
*.npy
@@ -37,3 +39,4 @@ coverage.xml
3739
phantoms/MR_XCAT_qMRI/*.json
3840
phantoms/MR_XCAT_qMRI/*.txt
3941
tests/IVIMmodels/unit_tests/models
42+
models

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ If you would like to contribute with code, please follow the instructions below:
2222
If you would like to use code from the repository and/or are new to Github or IVIM, please see the jupyter notebook below:
2323
* [Introduction to TF2.4_IVIM-MRI_CodeCollection github and IVIM Analysis using Python](doc/Introduction_to_TF24_IVIM-MRI_CodeCollection_github_and_IVIM_Analysis_using_Python.ipynb)
2424

25+
If you would like to use MATLAB-based algorithms or apply the testing framework to one of these algorithms, please have a look at these instructions:
26+
* [MATLAB-related instructions](doc/matlab_instructions.md)
27+
2528
## Repository Organization
2629

2730
The repository is organized in four main folders along with configuration files for automated testing.

conftest.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -264,8 +264,7 @@ def algorithmlist(algorithms):
264264
for algorithm in algorithms["algorithms"]:
265265
algorithm_dict = algorithms.get(algorithm, {})
266266
requires_matlab = algorithm_dict.get("requires_matlab", False)
267-
if not algorithm_dict.get('deep_learning', False):
268-
yield algorithm, requires_matlab
267+
yield algorithm, requires_matlab, algorithm_dict.get('deep_learning', False)
269268

270269

271270
def bound_input(datafile, algorithms):

doc/matlab_instructions.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
## MATLAB-related instructions
2+
3+
A large portion of IVIM-related code has historically been written in the MATLAB programming language. For this reason, we have included the possibility to call and test MATLAB-based code from the Python-based code developed here. This requires a few additional steps to get going
4+
5+
- In addition to the Python packages listed requirements.txt, one also need to install matlab.engine, for example with pip: `python -m pip install matlabengine`. This requires a corresponding version of MATLAB to be installed.
6+
- To be able to call the MATLAB code in this repository, the src/original folder must be added to the MATLAB path
7+
- Testing of MATLAB-based code only runs offline (not on Github). This is done by running `python -m pytest --withmatlab`
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
0 10 20 30 40 80 110 140 170 200 300 400 500 600 700 800 900

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@ sphinx
1717
sphinx_rtd_theme
1818
pytest-json-report
1919
statsmodels
20-
ivimnet
20+
ivimnet
21+
nlopt

src/original/OJ_GU/IVIM_bayes.m

Lines changed: 66 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@
181181
for i = 1:ceil(M/voxelsPerRun)
182182
% partition voxels
183183
usedVoxels = (i-1)*voxelsPerRun + 1:min(i*voxelsPerRun,M);
184-
184+
185185
fpart = f(usedVoxels);
186186
Dpart = D(usedVoxels);
187187
Dstarpart = Dstar(usedVoxels);
@@ -263,11 +263,11 @@
263263

264264
% Display iteration every 500th iteration
265265
if ~mod(j,500) && j > burns
266-
disp(['Iterations: ' num2str(j-burns)]);
266+
%disp(['Iterations: ' num2str(j-burns)]);
267267
elseif ~mod(j,100) && j < burns
268-
disp(['Burn in-steps: ' num2str(j)]);
268+
%disp(['Burn in-steps: ' num2str(j)]);
269269
elseif j == burns
270-
disp(['Burn in complete: ' num2str(j)]);
270+
%disp(['Burn in complete: ' num2str(j)]);
271271
end
272272
end
273273

@@ -286,28 +286,28 @@
286286
out.S0.std(usedVoxels) = sqrt(theta2sum(:,4)/n-(thetasum(:,4)/n).^2);
287287
else
288288
%mean
289-
out.f.mean(usedVoxels) = mean(squeeze(theta(:,1,burns + 1:n+burns)),2);
290-
out.D.mean(usedVoxels) = mean(squeeze(theta(:,2,burns + 1:n+burns)),2);
291-
out.Dstar.mean(usedVoxels) = mean(squeeze(theta(:,3,burns + 1:n+burns)),2);
292-
out.S0.mean(usedVoxels) = mean(squeeze(theta(:,4,burns + 1:n+burns)),2);
289+
out.f.mean(usedVoxels) = mean(reshape(theta(:,1,burns + 1:n+burns),[size(theta,1),n]),2);
290+
out.D.mean(usedVoxels) = mean(reshape(theta(:,2,burns + 1:n+burns),[size(theta,1),n]),2);
291+
out.Dstar.mean(usedVoxels) = mean(reshape(theta(:,3,burns + 1:n+burns),[size(theta,1),n]),2);
292+
out.S0.mean(usedVoxels) = mean(reshape(theta(:,4,burns + 1:n+burns),[size(theta,1),n]),2);
293293

294294
%median
295-
out.f.median(usedVoxels) = median(squeeze(theta(:,1,burns + 1:n+burns)),2);
296-
out.D.median(usedVoxels) = median(squeeze(theta(:,2,burns + 1:n+burns)),2);
297-
out.Dstar.median(usedVoxels) = median(squeeze(theta(:,3,burns + 1:n+burns)),2);
298-
out.S0.median(usedVoxels) = median(squeeze(theta(:,4,burns + 1:n+burns)),2);
295+
out.f.median(usedVoxels) = median(reshape(theta(:,1,burns + 1:n+burns),[size(theta,1),n]),2);
296+
out.D.median(usedVoxels) = median(reshape(theta(:,2,burns + 1:n+burns),[size(theta,1),n]),2);
297+
out.Dstar.median(usedVoxels) = median(reshape(theta(:,3,burns + 1:n+burns),[size(theta,1),n]),2);
298+
out.S0.median(usedVoxels) = median(reshape(theta(:,4,burns + 1:n+burns),[size(theta,1),n]),2);
299299

300300
% mode
301-
out.f.mode(usedVoxels) = halfSampleMode(squeeze(theta(:,1,burns + 1:n+burns)));
302-
out.D.mode(usedVoxels) = halfSampleMode(squeeze(theta(:,2,burns + 1:n+burns)));
303-
out.Dstar.mode(usedVoxels) = halfSampleMode(squeeze(theta(:,3,burns + 1:n+burns)));
304-
out.S0.mode(usedVoxels) = halfSampleMode(squeeze(theta(:,4,burns + 1:n+burns)));
301+
out.f.mode(usedVoxels) = halfSampleMode(reshape(theta(:,1,burns + 1:n+burns),[size(theta,1),n]));
302+
out.D.mode(usedVoxels) = halfSampleMode(reshape(theta(:,2,burns + 1:n+burns),[size(theta,1),n]));
303+
out.Dstar.mode(usedVoxels) = halfSampleMode(reshape(theta(:,3,burns + 1:n+burns),[size(theta,1),n]));
304+
out.S0.mode(usedVoxels) = halfSampleMode(reshape(theta(:,4,burns + 1:n+burns),[size(theta,1),n]));
305305

306306
% standard deviation
307-
out.f.std(usedVoxels) = std(squeeze(theta(:,1,burns + 1:n+burns)),1,2);
308-
out.D.std(usedVoxels) = std(squeeze(theta(:,2,burns + 1:n+burns)),1,2);
309-
out.Dstar.std(usedVoxels) = std(squeeze(theta(:,3,burns + 1:n+burns)),1,2);
310-
out.S0.std(usedVoxels) = std(squeeze(theta(:,4,burns + 1:n+burns)),1,2);
307+
out.f.std(usedVoxels) = std(reshape(theta(:,1,burns + 1:n+burns),[size(theta,1),n]),1,2);
308+
out.D.std(usedVoxels) = std(reshape(theta(:,2,burns + 1:n+burns),[size(theta,1),n]),1,2);
309+
out.Dstar.std(usedVoxels) = std(reshape(theta(:,3,burns + 1:n+burns),[size(theta,1),n]),1,2);
310+
out.S0.std(usedVoxels) = std(reshape(theta(:,4,burns + 1:n+burns),[size(theta,1),n]),1,2);
311311
end
312312
end
313313

@@ -381,7 +381,52 @@
381381
y(b < 3.75) = log(1.0 + a1.*(3.5156229 + a1.*(3.0899424 + a1.*(1.2067492 + a1.*(0.2659732 + a1.*(0.0360768 + a1.*0.0045813))))));
382382
y(b >= 3.75) = b(b >= 3.75) + log((0.39894228+a2.*(0.01328592+a2.*(0.00225319+a2.*(-0.00157565+a2.*(0.00916281+a2.*(-0.02057706+a2.*(0.02635537+a2.*(-0.01647633+a2.*0.00392377))))))))./sqrt(b(b>=3.75)));
383383

384-
384+
function hsm = halfSampleMode(X)
385+
% calculates the half sample mode for each row in X
386+
if ~ismatrix(X)
387+
error('X must be a matrix or vector');
388+
end
389+
X = sort(X,2);
390+
n = size(X,2);
391+
hsm = HSM_rec(n,X);
392+
function hsm = HSM_rec(n,X)
393+
% special cases
394+
if size(X,2) == 1
395+
hsm = X;
396+
return;
397+
elseif size(X,2) == 2
398+
hsm = sum(X,2)/2;
399+
return;
400+
elseif size(X,2) == 3
401+
hsm = zeros(size(X,1),1);
402+
low = (X(:,2) - X(:,1)) < (X(:,3) - X(:,2)); % use lower pair
403+
eq = (X(:,2) - X(:,1)) == (X(:,3) - X(:,2)); % use mid value
404+
405+
if any(low)
406+
hsm(low) = sum(X(low,1:2),2)/2;
407+
end
408+
if any(eq)
409+
hsm(eq) = X(eq,2);
410+
end
411+
if any(~(low|eq))
412+
hsm(~(low|eq)) = sum(X(~(low|eq),2:3),2)/2; % otherwise
413+
end
414+
return
415+
end
416+
% general case (n > 3)
417+
wmin = X(:,end) - X(:,1);
418+
N = ceil(n/2);
419+
j = ones(size(X,1),1);
420+
for i = 1:(n-N+1)
421+
w = X(:,i+N-1) - X(:,i);
422+
m = w < wmin;
423+
wmin(m) = w(m);
424+
j(m) = i;
425+
end
426+
rowsub = repmat((1:size(X,1))',1,N);
427+
colsub = repmat(j,1,N) + repmat(0:N-1,size(X,1),1);
428+
Xsub = reshape(X(sub2ind(size(X),rowsub,colsub)),size(X,1),N);
429+
hsm = HSM_rec(N,Xsub);
385430

386431

387432

src/original/OJ_GU/IVIM_seg.m

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -140,12 +140,13 @@
140140
A = sum(Yred.*exp(-bred*D))./sum(exp(-2*bred*D));
141141

142142
% Assures that A is within limits
143-
D(A < lim(1,2)) = lim(1,1);
144-
D(A > lim(2,2)) = lim(2,1);
145-
maskD(A < lim(1,2) | A > lim(2,2)) = false;
146-
A(A < lim(1,2)) = lim(1,2);
147-
A(A > lim(2,2)) = lim(2,2);
148-
143+
%D(A < lim(1,2)) = lim(1,1);
144+
%D(A > lim(2,2)) = lim(2,1);
145+
%maskD(A < lim(1,2) | A > lim(2,2)) = false;
146+
A(A < 0) = 0;
147+
%A(A < lim(1,2)) = lim(1,2);
148+
%A(A > lim(2,2)) = lim(2,2);
149+
149150
% Calculate f
150151
if estf && ~step2
151152
if disp_prog
@@ -173,31 +174,36 @@
173174

174175
% calculate possible range of f to remove voxels where f cannot
175176
% result in a fit within limits
176-
f1 = zeros(1,n);
177-
f1(mask_back) = fcalc(lim(1,4)*ones(1,sum(mask_back)),mask_back);
178-
f2 = zeros(1,n);
179-
f2(mask_back) = fcalc(lim(2,4)*ones(1,sum(mask_back)),mask_back);
177+
%f1 = zeros(1,n);
178+
%f1(mask_back) = fcalc(lim(1,4)*ones(1,sum(mask_back)),mask_back);
179+
%f2 = zeros(1,n);
180+
%f2(mask_back) = fcalc(lim(2,4)*ones(1,sum(mask_back)),mask_back);
180181

181-
maskf = maskD & (((f1 > lim(1,3)) & (f1 < lim(2,3))) | ((f2 > lim(1,3)) & (f2 < lim(2,3))));
182-
if disp_prog
183-
fprintf('Discarding %d voxels due to f out of bounds\n',sum(maskD) - sum(maskf));
184-
end
182+
%maskf = maskD & (((f1 > lim(1,3)) & (f1 < lim(2,3))) | ((f2 > lim(1,3)) & (f2 < lim(2,3))));
183+
maskf = mask_back;
184+
%if disp_prog
185+
% fprintf('Discarding %d voxels due to f out of bounds\n',sum(maskD) - sum(maskf));
186+
%end
185187

186188
% Prepares output
187189
Dstar = zeros(1,n);
188-
Dstar(f1 < lim(1,3)) = lim(1,4);
189-
Dstar(f2 > lim(2,3)) = lim(2,4);
190+
%Dstar(f1 < lim(1,3)) = lim(1,4);
191+
%Dstar(f2 > lim(2,3)) = lim(2,4);
190192
maskDstar = maskf;
191193

192194
% Optimization
193-
[Dstar(maskf),maskDstar(maskf)] = optimizeD(Y(:,maskf)-repmat(A(maskf),length(b),1).*exp(-b*D(maskf)),b,optlim,lim(:,4),disp_prog);
194-
195+
if sum(maskf) > 0
196+
[Dstar(maskf),maskDstar(maskf)] = optimizeD(Y(:,maskf)-repmat(A(maskf),length(b),1).*exp(-b*D(maskf)),b,optlim,lim(:,4),disp_prog);
197+
end
198+
195199
% Calculates f
196200
f = lim(1,1)*ones(1,n);
197-
f(f1 < lim(1,3)) = lim(1,3);
198-
f(f2 > lim(2,3)) = lim(2,3);
199-
f(maskDstar) = fcalc(Dstar(maskDstar),maskDstar);
200-
201+
%f(f1 < lim(1,3)) = lim(1,3);
202+
%f(f2 > lim(2,3)) = lim(2,3);
203+
if sum(maskDstar) > 0
204+
f(maskDstar) = fcalc(Dstar(maskDstar),maskDstar);
205+
end
206+
201207
% Checks for f out of bounds
202208
maskf = maskf & (f > lim(1,3)) & (f < lim(2,3));
203209
Dstar(f < lim(1,3)) = lim(1,4);

src/standardized/IAR_LU_biexp.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ def ivim_fit(self, signals, bvalues, **kwargs):
7979

8080
bvec = np.zeros((bvalues.size, 3))
8181
bvec[:,2] = 1
82-
gtab = gradient_table(bvalues, bvec, b0_threshold=0)
82+
gtab = gradient_table(bvalues, bvecs=bvec, b0_threshold=0)
8383

8484
self.IAR_algorithm = IvimModelBiExp(gtab, bounds=self.bounds, initial_guess=self.initial_guess)
8585

@@ -111,7 +111,7 @@ def ivim_fit_full_volume(self, signals, bvalues, **kwargs):
111111

112112
bvec = np.zeros((bvalues.size, 3))
113113
bvec[:,2] = 1
114-
gtab = gradient_table(bvalues, bvec, b0_threshold=0)
114+
gtab = gradient_table(bvalues, bvecs=bvec, b0_threshold=0)
115115

116116
self.IAR_algorithm = IvimModelBiExp(gtab, bounds=self.bounds, initial_guess=self.initial_guess)
117117
b0_index = np.where(bvalues == 0)[0][0]

0 commit comments

Comments
 (0)