Skip to content

Commit 4c0fb10

Browse files
committed
Add demo.
1 parent 86f5a52 commit 4c0fb10

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

96 files changed

+4642
-0
lines changed

.gitignore

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.dropbox
2+
.DS_Store
3+
Icon*
4+
out.*
5+
test.*
6+
test
7+
tmp

README.md

+36

alignbrain.m

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
% Reslice brain such that it's aligned with the anatomical planes, based on
2+
% the coordinates of its centre and those of the eyes (voxel coordinates).
3+
function out = alignbrain(mri, fetaltoras, outfile)
4+
5+
if nargin() < 3
6+
outfile = [];
7+
end
8+
9+
if ischar(mri)
10+
mri = MRIread(mri); % FreeSurfer.
11+
mri.vol = permute(mri.vol, [2 1 3 4]);
12+
end
13+
voxtoras = mri.vox2ras1;
14+
dat = mri.vol(:,:,:,1);
15+
dat = dat ./ gaussblur(dat, 0.33*size(dat));
16+
17+
% Resample in anatomical coordinates.
18+
fov = -70:1:70;
19+
[xf,yf,zf] = ndgrid(fov, fov, fov);
20+
fetaltovox = voxtoras \ fetaltoras;
21+
[xi,yi,zi] = coords(fetaltovox, xf, yf, zf);
22+
res = interpn(dat, xi, yi, zi, 'linear', 0);
23+
24+
% Plot central slices.
25+
reset(clf());
26+
figure(gcf());
27+
colormap gray
28+
29+
hs(1) = subplot(2,2,1);
30+
im = squeeze(res(:,round(end/2),:));
31+
im = flip(im', 1);
32+
imagesc(stretchcon(im));
33+
title('Coronal');
34+
35+
hs(2) = subplot(2,2,2);
36+
im = squeeze(res(round(end/2),:,:));
37+
im = flip(flip(im', 1),2);
38+
imagesc(stretchcon(im));
39+
title('Sagittal');
40+
41+
hs(3) = subplot(2,2,3);
42+
im = squeeze(res(:,:,round(end/2)));
43+
im = flip(im', 1);
44+
imagesc(stretchcon(im));
45+
title('Axial');
46+
47+
hl = [];
48+
for i = 1:3
49+
hl(end+1) = line(hs(i), xlim(), [1 1].*mean(ylim())); %#ok<*AGROW>
50+
hl(end+1) = line(hs(i), [1 1].*mean(xlim()), ylim());
51+
end
52+
axis(hs, 'image', 'off');
53+
set(hl, 'color', 'y', 'linewidth', 1.5);
54+
drawnow();
55+
56+
out = struct();
57+
out.vol = permute(res, [2 1 3 4]);
58+
voxsize = [xf(2,1,1)-xf(1,1,1) yf(1,2,1)-yf(1,1,1) zf(1,1,2)-zf(1,1,1)];
59+
out.volres = voxsize;
60+
scaling = diag([voxsize 1]);
61+
subone = [eye(4,3) [-1 -1 -1 1]'];
62+
voxtofetal = [eye(4,3) [xf(1) yf(1) zf(1) 1]'] * scaling * subone;
63+
out.vox2ras1 = fetaltoras * voxtofetal;
64+
out.vox2ras0 = vox2ras_1to0(out.vox2ras1);
65+
if ~isempty(outfile)
66+
MRIwrite(out, outfile);
67+
end

anatomy.m

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
% Return prior knowledge of fetal anatomy in mm for specific GA in wk.
2+
% Snijders et al. (1994) and Robinson et al. (2008):
3+
% https://www.pedrad.org/Portals/5/Fetal%20MRI/fetal%20eyes.pdf
4+
function [ofd,bpd,odiam,odist] = anatomy(ga)
5+
6+
if ga < 14 || ga > 39
7+
error('ERROR: GA out of range 14-39 weeks.');
8+
end
9+
10+
odiam = 0.47*ga - 0.7;
11+
bod = 1.47*ga + 1.76;
12+
odist = bod - odiam;
13+
xga = 14:39;
14+
bpd = [28 31 34 36 39 42 45 48 51 54 57 60 63 66 69 72 74 77 79 81 83 ...
15+
85 86 87 88 89];
16+
ofd = [35 39 42 46 50 54 57 61 65 69 73 77 81 84 87 91 94 96 99 101 103 ...
17+
105 106 107 107 107];
18+
bpd = interp1(xga, bpd, ga);
19+
ofd = interp1(xga, ofd, ga);

closegaps.m

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
% Iteratively add voxels to each slice of a 3D mask that are included on
2+
% the closest non-zero slices on both sides.
3+
function volmask = closegaps(volmask)
4+
5+
tmp = volmask;
6+
slcind = 1:size(volmask, 3);
7+
change = 1;
8+
while change
9+
for i = slcind
10+
masksize = squeeze(sum(sum(tmp)))';
11+
j = find(slcind<i & masksize>0, 1, 'last');
12+
k = find(slcind>i & masksize>0, 1, 'first');
13+
if not(isempty(j)) && not(isempty(k))
14+
tmp(:,:,i) = tmp(:,:,i) | ( tmp(:,:,j) & tmp(:,:,k) );
15+
end
16+
end
17+
change = not(isequal(tmp, volmask));
18+
volmask = tmp;
19+
end

coords.m

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
% Multiply the 4-by-4 matrix M by the vector [x1, x2, x3, 1].
2+
function [y1,y2,y3,y4] = coords(M, x1, x2, x3)
3+
4+
y1 = M(1,1)*x1 + M(1,2)*x2 + M(1,3)*x3 + M(1,4);
5+
y2 = M(2,1)*x1 + M(2,2)*x2 + M(2,3)*x3 + M(2,4);
6+
y3 = M(3,1)*x1 + M(3,2)*x2 + M(3,3)*x3 + M(3,4);
7+
y4 = M(4,1)*x1 + M(4,2)*x2 + M(4,3)*x3 + M(4,4);

cutprot.m

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
% Remove protruding voxels (once) from each slice of a 3D mask if they are not
2+
% included on the closest slice on at least one side.
3+
function out = cutprot(volmask)
4+
5+
numvox = squeeze(sum(sum(volmask)))';
6+
ind = find(numvox);
7+
out = volmask;
8+
for i = ind
9+
if i == ind(1)
10+
out(:,:,i) = volmask(:,:,i) & volmask(:,:,i+1);
11+
elseif i == ind(end)
12+
out(:,:,i) = volmask(:,:,i) & volmask(:,:,i-1);
13+
else
14+
out(:,:,i) = volmask(:,:,i) & (volmask(:,:,i-1) | volmask(:,:,i+1));
15+
end
16+
end

data/ep2d_01.nii.gz

1.41 MB
Binary file not shown.

data/ep2d_02.nii.gz

1.45 MB
Binary file not shown.

data/ep2d_03.nii.gz

1.47 MB
Binary file not shown.

data/ep2d_04.nii.gz

1.5 MB
Binary file not shown.

data/ep2d_05.nii.gz

1.27 MB
Binary file not shown.

data/ep2d_06.nii.gz

1.28 MB
Binary file not shown.

data/ep2d_07.nii.gz

1.27 MB
Binary file not shown.

data/ep2d_08.nii.gz

1.09 MB
Binary file not shown.

data/ep2d_09.nii.gz

1.09 MB
Binary file not shown.

data/ep2d_10.nii.gz

341 KB
Binary file not shown.

data/ep2d_11.nii.gz

395 KB
Binary file not shown.

data/ep2d_12.nii.gz

420 KB
Binary file not shown.

data/ep2d_13.nii.gz

774 KB
Binary file not shown.

data/ep2d_14.nii.gz

845 KB
Binary file not shown.

data/ep2d_15.nii.gz

855 KB
Binary file not shown.

data/ep2d_16.nii.gz

742 KB
Binary file not shown.

data/ep2d_17.nii.gz

1.49 MB
Binary file not shown.

data/ep2d_18.nii.gz

1.13 MB
Binary file not shown.

data/ep2d_19.nii.gz

1.4 MB
Binary file not shown.

data/ep2d_20.nii.gz

1.34 MB
Binary file not shown.

data/ep2d_21.nii.gz

1.33 MB
Binary file not shown.

data/ep2d_22.nii.gz

1.25 MB
Binary file not shown.

data/ep2d_23.nii.gz

822 KB
Binary file not shown.

data/ep2d_24.nii.gz

863 KB
Binary file not shown.

data/ep2d_25.nii.gz

840 KB
Binary file not shown.

data/ep2d_26.nii.gz

945 KB
Binary file not shown.

data/ep2d_27.nii.gz

874 KB
Binary file not shown.

data/ep2d_28.nii.gz

886 KB
Binary file not shown.

data/ep2d_29.nii.gz

785 KB
Binary file not shown.

data/ep2d_30.nii.gz

1 MB
Binary file not shown.

data/ep2d_31.nii.gz

857 KB
Binary file not shown.

data/ep2d_32.nii.gz

874 KB
Binary file not shown.

data/ep2d_33.nii.gz

1.03 MB
Binary file not shown.

data/ep2d_34.nii.gz

1.08 MB
Binary file not shown.

data/ep2d_35.nii.gz

774 KB
Binary file not shown.

data/ep2d_36.nii.gz

845 KB
Binary file not shown.

data/ep2d_37.nii.gz

1.13 MB
Binary file not shown.

data/ep2d_38.nii.gz

1.35 MB
Binary file not shown.

data/ep2d_39.nii.gz

1.31 MB
Binary file not shown.

data/ep2d_40.nii.gz

1.31 MB
Binary file not shown.

data/ep2d_41.nii.gz

1.34 MB
Binary file not shown.

data/haste_01.nii.gz

3.97 MB
Binary file not shown.

data/haste_02.nii.gz

3.52 MB
Binary file not shown.

demo.m

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
% Demo visualizing fetal-brain localization and orientation estimation.
2+
3+
% Plot stages: 1 brain localization, 2 masking, 3 eye detection (0 is off).
4+
doplot = struct();
5+
doplot.clust1 = 1;
6+
doplot.line1 = 1;
7+
doplot.mser1 = 1;
8+
doplot.mask1 = 1;
9+
doplot.mser2 = 1;
10+
doplot.line2 = 1;
11+
doplot.poly2 = 1;
12+
doplot.mask2 = 1;
13+
doplot.mser3 = 1;
14+
doplot.mask3 = 1;
15+
doplot.geom3 = 1;
16+
17+
% Load data paths and gestational age.
18+
addpath freesurfer
19+
[data,ga] = loaddata();
20+
wait('This demo showcases the different stages of fetal-brain', ...
21+
'localization and orientation estimation. You will be prompted for', ...
22+
'input before each step is run.');
23+
24+
% Import image.
25+
imnum = 1;
26+
mri = MRIread(data{imnum}); % FreeSurfer.
27+
mri.vol = permute(mri.vol, [2 1 3 4]);
28+
wait('Slices of input image', ['"' data{imnum} '"']);
29+
showmask(stretchcon(mri.vol));
30+
31+
% Detect landmarks and reconstruct geometry.
32+
[bcenvox,ecenvox,bmask] = landmarks(mri, ga(imnum), doplot);
33+
fetaltoras = recongeo(mri.vox2ras1, bcenvox, ecenvox, bmask);
34+
35+
% Resample in anatomical space and save for inspection.
36+
outfile = 'out.nii';
37+
wait('Resampling in anatomical space');
38+
alignbrain(mri, fetaltoras, outfile);
39+
fprintf('Done\n');

fetal-align.png

142 KB

fitellipse.m

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
% Fit an ellipse/ellipsoid to 2D/3D image blobs.
2+
function [bcen,semilen,rotmat,ellmask,fill,over] = fitellipse(mask, scalemask)
3+
4+
if nargin() < 2
5+
scalemask = 1;
6+
end
7+
8+
iscellinput = iscell(mask);
9+
if ~iscellinput
10+
mask = {mask};
11+
end
12+
sz = size(mask{1});
13+
numdim = numel(sz);
14+
if numel(sz) == 2
15+
sz(3) = 1;
16+
end
17+
[x,y,z] = ndgrid(1:sz(1), 1:sz(2), 1:sz(3));
18+
19+
nummask = numel(mask);
20+
bcen = zeros(nummask, numdim);
21+
semilen = zeros(nummask, numdim);
22+
rotmat = cell(nummask, 1);
23+
ellmask = cell(nummask, 1);
24+
fill = zeros(nummask, 1);
25+
over = zeros(nummask, 1);
26+
for i = 1:nummask
27+
% Centre of mass and barycentric coordinates.
28+
bw = mask{i};
29+
ind = find(bw);
30+
m000 = numel(ind);
31+
cen = [sum(x(ind)) sum(y(ind)) sum(z(ind))] / m000;
32+
xb = x(ind) - cen(1);
33+
yb = y(ind) - cen(2);
34+
zb = z(ind) - cen(3);
35+
36+
% Second moments - weighted by intensity, which is 1.
37+
m200 = sum(xb.^2);
38+
m020 = sum(yb.^2);
39+
m002 = sum(zb.^2);
40+
m110 = sum(xb.*yb);
41+
m011 = sum(yb.*zb);
42+
m101 = sum(xb.*zb);
43+
mi = [m200 m110 m101; m110 m020 m011; m101 m011 m002] / m000;
44+
45+
% Semi axes lengths and rotation matrix from eigendecomposition.
46+
[evec,eval] = eig(mi, 'vector');
47+
[eval,order] = sort(eval, 'descend');
48+
rot = evec(:,order);
49+
semi = 2 * sqrt(eval);
50+
51+
% Mask of ellipse.
52+
halflen = semi(1);
53+
lower = max(1, floor(cen-halflen));
54+
upper = min(sz, ceil(cen+halflen));
55+
indx = lower(1):upper(1);
56+
indy = lower(2):upper(2);
57+
indz = lower(3):upper(3);
58+
xb = x(indx,indy,indz) - cen(1);
59+
yb = y(indx,indy,indz) - cen(2);
60+
zb = z(indx,indy,indz) - cen(3);
61+
xr = rot(1,1)*xb + rot(2,1)*yb + rot(3,1)*zb;
62+
yr = rot(1,2)*xb + rot(2,2)*yb + rot(3,2)*zb;
63+
zr = rot(1,3)*xb + rot(2,3)*yb + rot(3,3)*zb;
64+
ell = false(sz);
65+
scale = semi .* scalemask;
66+
scale(scale == 0) = 1; % Avoid 2D division by zero.
67+
isell = (xr./scale(1)).^2 + (yr./scale(2)).^2 + (zr./scale(3)).^2 < 1;
68+
ell(indx,indy,indz) = isell;
69+
70+
if sz(3) == 1
71+
cen = cen(1:2);
72+
semi = semi(1:2);
73+
rot = rot(1:2,1:2);
74+
end
75+
rot = sign(det(rot)) * rot; % Proper rotation matrix.
76+
77+
bcen(i,:) = cen;
78+
semilen(i,:) = semi;
79+
rotmat{i} = rot;
80+
ellmask{i} = ell;
81+
fill(i) = sum(bw(ind) & ell(ind)) / nnz(isell);
82+
over(i) = sum(~ell(ind) & bw(ind)) / m000;
83+
end
84+
85+
if ~iscellinput
86+
rotmat = rotmat{1};
87+
ellmask = ellmask{1};
88+
end

fitline.m

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
% Fit line to ND point cloud: return centroid, direction and point distances.
2+
% http://www.ctralie.com/Teaching/COMPSCI290/Lectures/10_PCA/slides.pdf
3+
function [centroid, dir, dist] = fitline(points)
4+
5+
centroid = mean(points, 1);
6+
points = points - centroid;
7+
8+
covmat = points' * points;
9+
[evec,eval] = eig(covmat, 'vector');
10+
11+
[~,order] = sort(abs(eval), 'descend');
12+
evec = evec(:,order);
13+
dir = evec(:,1)';
14+
15+
p = points - centroid;
16+
dist = real(sqrt(sum(p.*p,2)-(sum(p.*dir,2)).^2));

0 commit comments

Comments
 (0)