Skip to content

Commit 41c27af

Browse files
committed
Add capability for parameter sweep.
1 parent f308b44 commit 41c27af

File tree

3 files changed

+89
-53
lines changed

3 files changed

+89
-53
lines changed

landmarks.m

+70-51
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
% Estimate center of fetal brain and largest slices from 2D-EPI scout.
2-
function [bcenout,ecenout,outmask] = landmarks(mri, ga, doplot)
2+
function [bcenout,ecenout,outmask] = landmarks(mri, ga, doplot, par)
33

44
if nargin() < 3
55
doplot = [];
66
end
7+
if nargin() < 4
8+
par = [];
9+
end
710

811
if ischar(mri)
912
mri = MRIread(mri); % FreeSurfer.
@@ -42,34 +45,41 @@
4245
%% Stage 1.
4346

4447
% MSERs and filtering.
45-
maxarea = pi * bpd/2 * ofd/2 / prod(vsz(1:2));
46-
[bw, numbw, snum] = slcmser(pre, 5, maxarea*0.2, maxarea*1.0);
48+
par = setdefault(par, 'maxratio1', 1.5, 'minarea1', 0.2, 'maxarea1', 1.1, ...
49+
'maxsemi1', 1.1, 'minfill1', 0.5);
50+
refarea = pi * bpd/2 * ofd/2 / prod(vsz(1:2));
51+
[bw, numbw, snum] = slcmser(pre, 5, refarea*par.minarea1, refarea*par.maxarea1);
4752
[cen,semi,rot,~,inside,outside] = fitellipse(bw); %#ok
4853
ratio = semi(:,1) ./ semi(:,2);
4954
area = pi * prod(semi, 2);
5055
keep = true(numbw, 1);
51-
keep = keep & ratio < ofd/bpd * 1.5;
52-
keep = keep & area < maxarea * 1.1;
53-
keep = keep & semi(:,1)*vsz(1) < ofd/2 * 1.1;
54-
keep = keep & inside-outside > 0.5;
56+
keep = keep & ratio < ofd/bpd * par.maxratio1;
57+
keep = keep & area > refarea * par.minarea1;
58+
keep = keep & area < refarea * par.maxarea1;
59+
keep = keep & semi(:,1)*vsz(1) < ofd/2 * par.maxsemi1;
60+
keep = keep & inside-outside > par.minfill1;
5561

5662
% Mean-shift clustering: remove near-duplicate points first.
63+
par = setdefault(par, 'minsep1', 1, 'diam1', 1, 'steptol1', 0.1);
5764
points = [cen(keep,:) snum(keep)] .* vsz;
5865
i = 1;
5966
while i <= size(points,1)
60-
ind = sqrt(sum((points(i,:)-points).^2,2)) < 1; % In mm.
67+
ind = sqrt(sum((points(i,:)-points).^2,2)) < par.minsep1; % In mm.
6168
ind(i) = 0;
6269
points(ind,:) = [];
6370
i = i + 1;
6471
end
65-
[clusters,numclust] = meanshift(points, ofd, 0.1);
72+
[clusters,numclust] = meanshift(points, par.diam1*ofd, par.steptol1, ...
73+
par.minsep1);
6674
if isfield(doplot, 'clust1') && doplot.clust1
6775
wait('Brain localization: mean-shift clustering of MSER centers', ...
6876
'before cluster selection');
69-
showclust(points, clusters, ofd);
77+
showclust(points, clusters, par.diam1*ofd);
7078
end
7179

7280
% Cluster selection.
81+
par = setdefault(par, 'rad1', 1);
82+
len = ofd/2 * par.rad1;
7383
xyz = ndarray([1 1 1], dim) .* vsz;
7484
ind1d = reshape(1:prod(dim), dim);
7585
points = [cen snum] .* vsz;
@@ -78,18 +88,18 @@
7888
for i = 1:numclust
7989
centroid = clusters(i,:);
8090
dist = sqrt(sum((centroid-points).^2, 2));
81-
ind = find(keep & dist<ofd/2);
91+
ind = find(keep & dist<len);
8292
volmask = false(dim);
8393
for j = ind'
8494
volmask(:,:,snum(j)) = volmask(:,:,snum(j)) | bw{j};
8595
end
86-
low = floor((centroid-ofd/2) ./ vsz);
87-
upp = ceil((centroid+ofd/2) ./ vsz);
96+
low = floor((centroid-len) ./ vsz);
97+
upp = ceil((centroid+len) ./ vsz);
8898
low = max(1, low);
8999
upp = min(dim, upp);
90100
subset = ind1d(low(1):upp(1),low(2):upp(2),low(3):upp(3));
91101
sphere = false(dim);
92-
insphere = sqrt(sum((xyz(subset,:)-centroid).^2, 2)) < ofd/2;
102+
insphere = sqrt(sum((xyz(subset,:)-centroid).^2, 2)) < len;
93103
sphere(subset) = insphere;
94104
numvoxin(i) = nnz(sphere & volmask);
95105
numvoxout(i) = nnz(~sphere & volmask);
@@ -104,17 +114,17 @@
104114
linedist = linedist{order(1)};
105115
dist = sqrt(sum((centroid-points(keep,:)).^2, 2));
106116
keeppreclust = keep; %#ok
107-
keep(keep) = dist < ofd/2;
117+
keep(keep) = dist < len;
108118

109119
% Filter distance from line fit.
110120
zscore = (linedist-mean(linedist)) / std(linedist);
111-
zlim = 3;
112121
keeppreline = keep;
113-
keep(keep) = zscore < zlim;
122+
par = setdefault(par, 'zline1', 3);
123+
keep(keep) = zscore < par.zline1;
114124
if isfield(doplot, 'line1') && doplot.line1
115125
wait('Brain localization: rejection of MSERs based on the distance', ...
116126
'to a line fitted through their centers');
117-
showfit(snum(keeppreline), semi(keeppreline,1), zscore, zlim);
127+
showfit(snum(keeppreline), semi(keeppreline,1), zscore, par.zline1);
118128
end
119129
if isfield(doplot, 'mser1') && doplot.mser1
120130
wait('Brain localization: retained MSERs on each slice before', ...
@@ -150,18 +160,22 @@
150160
rawloc = rawloc - boxshift;
151161

152162
% MSER detection and filtering.
153-
maxarea = pi * bpd/2 * ofd/2 / prod(vsz(1:2));
154-
[bw, numbw, snum] = slcmser(boxdat, 5, maxarea*0.05, maxarea*1.1);
163+
par = setdefault(par, 'maxratio2', 1.5, 'minarea2', 0.05, 'maxarea2', 1.1, ...
164+
'maxsemi2', 1.1, 'minfill2', 0.5, 'maxdist2', 1.1);
165+
refarea = pi * bpd/2 * ofd/2 / prod(vsz(1:2));
166+
[bw, numbw, snum] = slcmser(boxdat, 5, refarea*par.minarea2, ...
167+
refarea*par.maxarea2);
155168
[cen,semi,~,~,inside,outside] = fitellipse(bw);
156169
ratio = semi(:,1) ./ semi(:,2);
157170
area = pi * prod(semi, 2);
158171
dist = sqrt(sum((vsz.*([cen snum]-rawloc)).^2, 2));
159172
keep = true([numbw 1]);
160-
keep = keep & ratio < ofd/bpd * 1.5;
161-
keep = keep & area < maxarea * 1.1;
162-
keep = keep & semi(:,1)*vsz(1) < ofd/2 * 1.1;
163-
keep = keep & inside-outside > 0.5;
164-
keep = keep & dist < ofd/2 * 1.1;
173+
keep = keep & ratio < ofd/bpd * par.maxratio2;
174+
keep = keep & area > refarea * par.minarea2;
175+
keep = keep & area < refarea * par.maxarea2;
176+
keep = keep & semi(:,1)*vsz(1) < ofd/2 * par.maxsemi2;
177+
keep = keep & inside-outside > par.minfill2;
178+
keep = keep & dist < ofd/2 * par.maxdist2;
165179
if isfield(doplot, 'mser2') && doplot.mser2
166180
wait('Brain-mask creation: pre-filtered MSERs on each slice before', ...
167181
'addition to the preliminary brain mask');
@@ -186,29 +200,30 @@
186200
% Fit line to average centers.
187201
[~,~,dist] = fitline(scen .* vsz);
188202
zscoreline = (dist-mean(dist)) / std(dist);
189-
zlimline = 2;
203+
par = setdefault(par, 'zline2', 2);
190204
[scenpreline, ssemipreline] = deal(scen, ssemi);
191-
ind = zscoreline < zlimline;
205+
ind = zscoreline < par.zline2;
192206
volmask(:,:,scen(~ind,3)) = 0;
193207
[scen, ssemi] = deal(scen(ind,:), ssemi(ind,:));
194208
if isfield(doplot, 'line2') && doplot.line2
195209
wait('Brain-mask creation: rejection of brain-mask slices based on', ...
196210
'the distance to a line fitted through their centers');
197-
showfit(scenpreline(:,3), ssemipreline(:,1), zscoreline, zlimline);
211+
showfit(scenpreline(:,3), ssemipreline(:,1), zscoreline, par.zline2);
198212
end
199213

200214
% Fit polynomial to average axes.
201-
polywithin = ofd/vsz(3)/2 * 0.5;
215+
par = setdefault(par, 'polywithin2', 0.5);
216+
polywithin = ofd/vsz(3)/2 * par.polywithin2;
202217
ind = abs(scen(:,3)-boxloc(3)) < polywithin;
203218
xdat = scen(ind,3);
204219
ydat = ssemi(ind,1);
205220
coef = polyfit(xdat, ydat, 2);
206221
dist = ssemi(:,1) - polyval(coef, scen(:,3));
207222
zscorepoly = (dist-mean(dist(ind))) / std(dist);
208-
zlimpoly = 1.5;
223+
par = setdefault(par, 'zpoly2', 1.5);
209224
[scenprepoly, ssemiprepoly, boxlocprepoly] = deal(scen, ssemi, boxloc); %#ok
210225
if coef(1) < 0 % Want bad weather.
211-
ind = zscorepoly < zlimpoly;
226+
ind = zscorepoly < par.zpoly2;
212227
volmask(:,:,scen(~ind,3)) = 0;
213228
scen = scen(ind,:); %#ok
214229
ssemi = ssemi(ind,:); %#ok
@@ -217,7 +232,7 @@
217232
wait('Brain-mask creation: rejection of brain-mask slices based on', ...
218233
'the deviation between their major-axis length and a quadratic', ...
219234
'fit across slices');
220-
showfit(scenprepoly(:,3), ssemiprepoly(:,1), zscorepoly, zlimpoly, ...
235+
showfit(scenprepoly(:,3), ssemiprepoly(:,1), zscorepoly, par.zpoly2, ...
221236
coef, boxloc(3)+[-1 1]*polywithin);
222237
end
223238

@@ -244,9 +259,8 @@
244259
%% Stage 3.
245260

246261
% Image cropping and interpolation.
247-
% low = max(1, floor(rawloc-halflen));
248-
% upp = min(dim, floor(rawloc+halflen));
249-
boxvsz = [1.49 1.49 vsz(3)];
262+
par = setdefault(par, 'voxsize3', 1.49);
263+
boxvsz = [par.voxsize3 par.voxsize3 vsz(3)];
250264
lenvox = floor(sqrt(2) * ofd ./ boxvsz); % Side length in new voxels.
251265
scaledown = boxvsz ./ vsz;
252266
shift = bcenvox - (lenvox+1)/2 .* scaledown; % In old voxels.
@@ -266,8 +280,12 @@
266280
boxloc = boxloc(1:3)';
267281

268282
% MSER detection and filtering.
269-
maxarea = pi * (odiam/2)^2 / prod(boxvsz(1:2)); % In voxels.
270-
[bw, numbw, snum] = slcmser(boxdat, 5, maxarea*0.5, maxarea*1.1);
283+
par = setdefault(par, 'maxratio3', 1.5, 'minarea3', 0.5, 'maxarea3', 1.1, ...
284+
'maxsemi3', 1.3, 'minfill3', 0.8, 'mindist3', 0.5, 'maxdist3', 1.5, ...
285+
'mindrop3', 0.5, 'int3', 0.5);
286+
refarea = pi * (odiam/2)^2 / prod(boxvsz(1:2)); % In voxels.
287+
[bw, numbw, snum] = slcmser(boxdat, 5, refarea*par.minarea3, ...
288+
refarea*par.maxarea3);
271289
[cen,semi,rot,ell,inside,outside] = fitellipse(bw); %#ok
272290
ratio = semi(:,1) ./ semi(:,2);
273291
area = pi * prod(semi, 2);
@@ -276,35 +294,35 @@
276294
for i = 1:numbw
277295
im = boxdat(:,:,snum(i));
278296
inribbon = find(ellbig{i} & ~ell{i});
279-
threshold = 0.5 * median(im(ell{i}(:)));
297+
threshold = par.int3 * median(im(ell{i}(:)));
280298
drop(i) = nnz(im(inribbon) < threshold) / numel(inribbon);
281299
end
282300
dist = sqrt(sum((boxvsz .* ([cen snum]-boxloc)).^2, 2));
283-
284-
% Individual filtering.
285301
keep = true(numbw, 1);
286-
keep = keep & ratio < 1.5;
287-
keep = keep & area < maxarea * 1.1;
288-
keep = keep & semi(:,1).*boxvsz(1) < odiam/2 * 1.3;
289-
keep = keep & inside-outside > 0.8;
290-
keep = keep & drop > 0.5;
291-
keep = keep & dist > ofd/2 * 0.5;
292-
keep = keep & dist < ofd/2 * 1.5;
302+
keep = keep & ratio < par.maxratio3;
303+
keep = keep & area > refarea * par.minarea3;
304+
keep = keep & area < refarea * par.maxarea3;
305+
keep = keep & semi(:,1).*boxvsz(1) < odiam/2 * par.maxsemi3;
306+
keep = keep & inside-outside > par.minfill3;
307+
keep = keep & drop > par.mindrop3;
308+
keep = keep & dist > ofd/2 * par.mindist3;
309+
keep = keep & dist < ofd/2 * par.maxdist3;
293310
if isfield(doplot, 'mser3') && doplot.mser3
294311
wait('Eye detection: pre-filtered MSERs on each slice before 3D', ...
295312
'clustering');
296313
showmsers(boxdat, bw, snum, ofd/boxvsz(1), keep);
297314
end
298315

299316
% Clusters of points in 3D.
317+
par = setdefault(par, 'group3', 0.7);
300318
ind = find(keep);
301319
cluster = arrayfun(@(p)p, ind, 'uniformoutput', 0);
302320
numclust = numel(cluster);
303321
for i = 1:numclust
304322
cen1 = [cen(ind(i),:) snum(ind(i))] .* boxvsz;
305323
for j = 1:numclust
306324
cen2 = [cen(ind(j),:) snum(ind(j))] .* boxvsz;
307-
if i ~= j && sqrt(sum((cen1-cen2).^2)) < odiam*0.7
325+
if i ~= j && sqrt(sum((cen1-cen2).^2)) < odiam*par.group3
308326
cluster{i} = [cluster{i} ind(j)];
309327
end
310328
end
@@ -358,6 +376,7 @@
358376
end
359377

360378
% Centroid pair scores.
379+
par = setdefault(par, 'lambda3', 3, 'angle3', 40);
361380
numpair = numclust*(numclust-1)/2;
362381
score = zeros([numpair 1]);
363382
pair = zeros([numpair 2]);
@@ -367,9 +386,9 @@
367386
for j = i+1:numclust
368387
tmp = 0;
369388
tmp = tmp + 1.0 * fun(mean(bdismm([i j]))/(ofd/2) - 1);
370-
tmp = tmp + 3.0 * fun((bdismm(i)-bdismm(j))/mean(bdismm([i j])));
389+
tmp = tmp + fun((bdismm(i)-bdismm(j))/mean(bdismm([i j])))*par.lambda3;
371390
tmp = tmp + 1.0 * fun(edismm(i,j)/odist - 1);
372-
tmp = tmp + 1.0 * fun(ang(i,j)/40 - 1);
391+
tmp = tmp + 1.0 * fun(ang(i,j)/par.angle3 - 1);
373392
tmp = tmp + 1.0 * fun(mean(quality([i j]))/max(quality) - 1);
374393
tmp = tmp + 1.0 * fun(mean(numslc([i j]))/max(numslc) - 1);
375394
score(n) = tmp;
@@ -422,4 +441,4 @@
422441
xsub = 1+cropshift(1) : mri.volsize(1)-cropshift(1);
423442
ysub = 1+cropshift(2) : mri.volsize(2)-cropshift(2);
424443
zsub = 1+cropshift(3) : mri.volsize(3)-cropshift(3);
425-
outmask(xsub,ysub,zsub) = brainmask;
444+
outmask(xsub,ysub,zsub) = brainmask;

meanshift.m

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
function [clusters,numclust] = meanshift(points, diam, steptol)
1+
function [clusters,numclust] = meanshift(points, diam, steptol, minsep)
22

33
if nargin() < 3
44
steptol = 0.1;
55
end
6+
if nargin() < 4
7+
minsep = 1;
8+
end
69
numdim = size(points, 2);
710

811
bounds = [min(points,[],1); max(points,[],1)];
@@ -38,7 +41,7 @@
3841
% Remove near-duplicate clusters.
3942
i = 1;
4043
while i <= numclust
41-
ind = sqrt(sum((clusters(i,:)-clusters).^2,2)) < 10*steptol;
44+
ind = sqrt(sum((clusters(i,:)-clusters).^2,2)) < minsep;
4245
ind(i) = 0;
4346
clusters(ind,:) = [];
4447
numclust = size(clusters, 1);

setdefault.m

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
% Assign field name/value pairs to structure if unset.
2+
function structure = setdefault(structure, varargin)
3+
assert(mod(numel(varargin), 2) == 0)
4+
numfield = numel(varargin) / 2;
5+
for i = 1:numfield
6+
field = varargin{2*i-1};
7+
value = varargin{2*i};
8+
assert(ischar(field) && isscalar(value) && isnumeric(value))
9+
if isfield(structure, field)
10+
continue
11+
end
12+
structure.(field) = value;
13+
end
14+
end

0 commit comments

Comments
 (0)