function test_suite = test_cluster_neighborhood
% tests for cosmo_cluster_neighborhood
%
% # For CoSMoMVPA's copyright information and license terms, #
% # see the COPYING file distributed with CoSMoMVPA. #
try % assignment of 'localfunctions' is necessary in Matlab >= 2016
test_functions = localfunctions();
catch % no problem; early Matlab versions can use initTestSuite fine
end
initTestSuite;
function test_fmri_cluster_neighborhood
ds = cosmo_synthetic_dataset('type', 'fmri', 'size', 'normal');
nf = size(ds.samples, 2);
imsk = find(rand(1, nf) > .8);
rp = randperm(numel(imsk));
ds = cosmo_slice(ds, [imsk(rp) imsk(rp(end:-1:1))], 2);
nh1 = cosmo_cluster_neighborhood(ds, 'progress', false);
nh2 = cosmo_cluster_neighborhood(ds, 'progress', false, 'fmri', 1);
nh_sph = cosmo_spherical_neighborhood(ds, 'progress', false, ...
'radius', 2.5);
nh3 = cosmo_cluster_neighborhood(ds, 'progress', false, 'fmri', nh_sph);
ds.fa.sizes = ones(1, size(ds.samples, 2));
assertEqual(nh1.a, ds.a);
assertEqual(nh1.fa, ds.fa);
nf = size(ds.samples, 2);
rp = randperm(nf);
nfeatures_test = nf;
ijk = [ds.fa.i; ds.fa.j; ds.fa.k];
feature_ids = rp(1:nfeatures_test);
for j = 1:nfeatures_test
feature_id = feature_ids(j);
delta = sqrt(sum(bsxfun(@minus, ijk(:, feature_id), ijk).^2, 1));
msk1 = delta < sqrt(3) + .001;
assertEqual(sort(nh1.neighbors{feature_id}), find(msk1));
msk2 = delta < sqrt(1) + .001;
assertEqual(sort(nh2.neighbors{feature_id}), find(msk2));
msk3 = delta < 2.5;
assertEqual(sort(nh3.neighbors{feature_id}), find(msk3));
end
function test_tiny_fmri_neighborhood
ds = cosmo_synthetic_dataset('type', 'fmri', 'size', 'normal');
ds = cosmo_slice(ds, [22 22], 2);
% should pass
nh = cosmo_cluster_neighborhood(ds, 'progress', false);
function test_meeg_cluster_neighborhood
if cosmo_skip_test_if_no_external('fieldtrip')
return
end
ds = cosmo_synthetic_dataset('type', 'timefreq', 'size', 'normal');
nf = size(ds.samples, 2);
imsk = find(rand(1, nf) > .4);
rp = randperm(numel(imsk));
ds = cosmo_slice(ds, [imsk(rp) imsk(rp(end:-1:1))], 2);
ds = cosmo_dim_prune(ds);
n = numel(ds.a.fdim.values{1});
ds.a.fdim.values{1} = ds.a.fdim.values{1}(randperm(n));
nf = size(ds.samples, 2);
ds.fa.sizes = ones(1, size(ds.samples, 2));
chan_nbrhood = cosmo_meeg_chan_neighborhood(ds, 'delaunay', true, ...
'chantype', 'all', ...
'label', 'dataset');
assertEqual(ds.a.fdim.values(1), chan_nbrhood.a.fdim.values(1));
ncombi = 7; % all 7 possibilities
test_range = ceil(rand(1, ncombi) * 7);
nfeatures_test = 3;
for i = test_range
use_chan = i <= 4;
use_freq = mod(i, 2) == 1;
use_time = mod(ceil(i / 2), 2) == 1;
use_msk = [use_chan, use_freq, use_time];
labels = {'chan', 'freq', 'time'};
ndim = numel(labels);
args = struct();
for k = 1:ndim
if ~use_msk(k)
label = labels{k};
args.(label) = false;
end
end
cl_nbrhood = cosmo_cluster_neighborhood(ds, args, 'progress', false);
assertEqual(cl_nbrhood.fa, ds.fa);
assertEqual(cl_nbrhood.a, ds.a);
rp = randperm(nf);
for k = 1:nfeatures_test
feature_id = rp(k);
counter = zeros(1, nf);
for j = 1:ndim
label = labels{j};
fa = ds.fa.(label);
if use_msk(j)
if j == 1
% channel
ids = chan_nbrhood.neighbors{fa(feature_id)};
else
% anything else
ids = find(abs(fa - fa(feature_id)) <= 1.5);
end
else
ids = find(fa == fa(feature_id));
end
counter(ids) = counter(ids) + 1;
end
nbrs = find(counter == ndim);
assertEqual(nbrs, cl_nbrhood.neighbors{feature_id});
end
end
function test_tiny_meeg_cluster_neighborhood
if cosmo_skip_test_if_no_external('fieldtrip')
return
end
ds = cosmo_synthetic_dataset('type', 'meeg');
nh = cosmo_cluster_neighborhood(ds, 'progress', false);
ds.fa.sizes = ones(1, 6);
assertEqual(ds.fa, nh.fa);
assertEqual(ds.a, nh.a);
half_neighbors = {[1 4]; [2 3 5 6]; [2 3 5 6]};
assertEqual(nh.neighbors, repmat(half_neighbors, 2, 1));
% transpose of fdim elements should be fine
ds2 = ds;
ds2.a.fdim.values = {ds2.a.fdim.values{1}', ds2.a.fdim.values{2}'};
nh2 = cosmo_cluster_neighborhood(ds2, 'progress', false);
cosmo_check_neighborhood(nh2, ds);
cosmo_check_neighborhood(nh, ds);
assertEqual(nh.a, nh2.a);
assertEqual(nh.fa, nh2.fa);
assertEqual(nh.neighbors, nh2.neighbors);
% different fdim elements i not ok
ds3 = ds2;
ds3.a.fdim.values{1} = ds3.a.fdim.values{1}(end:-1:1);
function test_cluster_neighborhood_transpose
opt = struct();
opt.progress = false;
ds = cosmo_synthetic_dataset('type', 'timefreq', 'size', 'normal');
ds = cosmo_dim_remove(ds, 'chan');
nh = cosmo_cluster_neighborhood(ds, opt);
cp = cosmo_cartprod(repmat({[false, true]}, 4, 1));
n = size(cp, 1);
for k = 1:n
t_label = cp{k, 1};
t_value = cp{k, 2};
t_elem1 = cp{k, 3};
t_elem2 = cp{k, 4};
ds2 = ds;
if t_label
ds2.a.fdim.labels = ds2.a.fdim.labels';
end
if t_value
ds2.a.fdim.values = ds2.a.fdim.values';
end
if t_elem1
ds2.a.fdim.values{1} = ds2.a.fdim.values{1}';
end
if t_elem2
ds2.a.fdim.values{2} = ds2.a.fdim.values{2}';
end
nh2 = cosmo_cluster_neighborhood(ds2, opt);
assertEqual(nh2.a, nh.a);
assertEqual(nh2.fa, nh.fa);
assertEqual(nh2.neighbors, nh.neighbors);
end
function test_cluster_neighborhood_surface
if cosmo_skip_test_if_no_external('surfing')
return
end
ds = cosmo_synthetic_dataset('type', 'surface'); % ,'size','normal');
vertices = [0 0 0 1 1 1
1 2 3 1 2 3
0 0 0 0 0 0]';
faces = [3 2 3 2
2 1 5 4
5 4 6 5]';
opt = struct();
opt.progress = false;
opt.vertices = vertices;
opt.faces = faces;
nh1 = cosmo_cluster_neighborhood(ds, opt);
assertEqual(nh1.neighbors, { [1 2 4]
[1 2 3 4 5]
[2 3 5 6]
[1 2 4 5]
[2 3 4 5 6]
[3 5 6] });
assertEqual(ds.a, nh1.a);
ds.fa.sizes = [1 3 2 2 3 1] / 6;
ds.fa.radius = sqrt([1 2 2 2 2 1]);
ds.fa.area = [6 11 9 9 11 6] / 6;
assertEqual(ds.fa, nh1.fa);
opt.direct = false;
nh2 = cosmo_cluster_neighborhood(ds, opt);
assertEqual(nh2.neighbors, num2cell((1:6)'));
assertEqual(ds.a, nh2.a);
ds.fa.radius(:) = 0;
ds.fa.area = [1 3 2 2 3 1] / 6;
assertEqual(ds.fa, nh2.fa);
% take a subset of features
rp = randperm(6);
nsel = 4;
sel = rp(1:nsel);
ds3 = cosmo_slice(ds, sel, 2);
% re-order node ids
rp2 = randperm(6);
ds.a.fdim.values{1} = ds.a.fdim.values{1}(rp2);
opt.direct = true;
nh3 = cosmo_cluster_neighborhood(ds3, opt);
assertEqual(nh3.a, ds3.a);
assertEqual(nh3.fa.node_indices, ds3.fa.node_indices);
node_ids = ds3.a.fdim.values{1}(ds3.fa.node_indices);
for k = 1:nsel
nbs = nh3.neighbors{k}(:);
node_id = node_ids(k);
nb1 = intersect(nh1.neighbors{node_id}, sel);
assertEqual(sort(nb1'), sort(node_ids(nbs))');
end
function test_cluster_neighborhood_source
ds = cosmo_synthetic_dataset('size', 'normal', 'type', 'source');
nf = size(ds.samples, 2);
[unused, idxs] = sort(cosmo_rand(1, nf * 3));
rps = mod(idxs - 1, nf) + 1;
rp = rps(round(nf / 2) + (1:(2 * nf)));
ds = cosmo_slice(ds, rp, 2);
[unused, rp] = sort(cosmo_rand(1, nf));
rp = rp(1:4);
grid_spacing = 10;
ds_pos = ds.a.fdim.values{1}(:, ds.fa.pos) / grid_spacing;
for connectivity = 0:3
if connectivity == 0
radius = sqrt(3) + .001;
args = {};
else
args = {'source', connectivity};
radius = sqrt(connectivity) + .001;
end
nh = cosmo_cluster_neighborhood(ds, 'progress', false, args);
nh_pos = nh.a.fdim.values{1}(:, nh.fa.pos) / grid_spacing;
for r = rp
idxs = nh.neighbors{r};
d = sum(bsxfun(@minus, nh_pos(:, r), ds_pos).^2, 1).^.5;
d_inside = d(idxs);
outside_mask = true(size(d));
outside_mask(d <= radius) = false;
d_outside = d(outside_mask);
assert(all(d_inside <= radius));
assert(all(d_outside > radius));
end
end
function test_cluster_neighborhood_source_mom
ds = cosmo_synthetic_dataset('size', 'normal', 'type', 'source', ...
'data_field', 'mom');
nf = size(ds.samples, 2);
[unused, idxs] = sort(cosmo_rand(1, nf * 3));
rps = mod(idxs - 1, nf) + 1;
rp = rps(round(nf / 2) + (1:(2 * nf)));
ds = cosmo_slice(ds, rp, 2);
grid_spacing = 10;
for connectivity = 0:3
if connectivity == 0
radius = sqrt(3) + .001;
args = {};
else
args = {'source', connectivity};
radius = sqrt(connectivity) + .001;
end
for has_3d_mom = [false true]
ds2 = ds;
if has_3d_mom
assertExceptionThrown(@() ...
cosmo_cluster_neighborhood(ds, ...
'progress', false, args), '');
continue
end
keep_dim = ceil(rand() * 3);
keep_msk = ds2.fa.mom == keep_dim;
ds2 = cosmo_slice(ds, keep_msk, 2);
ds2.fa.mom(:) = 1;
ds2.a.fdim.values{2} = ds2.a.fdim.values{2}(keep_dim);
ds2_pos = ds2.a.fdim.values{1}(:, ds2.fa.pos) / grid_spacing;
nf = size(ds2.samples, 2);
[unused, rp] = sort(cosmo_rand(1, nf));
rp = rp(1:4);
nh = cosmo_cluster_neighborhood(ds2, 'progress', false, args);
nh_pos = nh.a.fdim.values{1}(:, nh.fa.pos) / grid_spacing;
for r = rp
idxs = nh.neighbors{r};
d = sum(bsxfun(@minus, nh_pos(:, r), ...
ds2_pos).^2, 1).^.5;
d_inside = d(idxs);
outside_mask = true(size(d));
outside_mask(d <= radius) = false;
d_outside = d(outside_mask);
assert(all(d_inside <= radius));
assert(all(d_outside > radius));
end
end
end
function test_cluster_neighborhood_fmri_time
for fa_n_rep = [1, 4]
n_time = ceil(rand() * 3 + 4);
TR = 2;
time_values = TR * (1:n_time);
ds_cell = cell(n_time, fa_n_rep);
for fa_t = 1:n_time
ds = cosmo_synthetic_dataset('size', 'normal', 'seed', 0);
n_features = size(ds.samples, 2);
time_idx = ones(1, n_features) * fa_t;
ds = cosmo_dim_insert(ds, 2, 4, {'time'}, {time_values}, {time_idx});
ds_cell(fa_t, :) = repmat({ds}, 1, fa_n_rep);
end
% select subset
ds_full = cosmo_stack(ds_cell, 2);
n_features = size(ds_full.samples, 2);
rp = randperm(n_features);
n_keep = ceil(n_features / 2);
keep_idxs = rp(1:n_keep);
ds = cosmo_slice(ds_full, keep_idxs, 2);
fa_ijk = [ds.fa.i; ds.fa.j; ds.fa.k];
fa_t = ds.fa.time;
for join_time = -1:1
if join_time == -1
args = {};
else
args = {'time', join_time == 1};
end
nh = cosmo_cluster_neighborhood(ds, args, 'progress', false);
assertEqual(nh.origin.fa, ds.fa);
assertEqual(nh.origin.a, ds.a);
assertEqual(nh.fa.sizes, ones(1, n_keep));
assertEqual(nh.a, ds.a);
time_radius = abs(join_time);
for k = 1:numel(nh.neighbors)
idx = nh.neighbors{k};
delta_xyz = sqrt(sum(bsxfun(@minus, fa_ijk(:, k), fa_ijk).^2, 1));
delta_t = abs(fa_t(k) - fa_t);
msk = delta_xyz <= 1.9 & delta_t <= time_radius;
assertEqual(sort(idx), find(msk));
end
end
end
function test_meeg_cluster_neighborhood_unknown_eeg_channels
if cosmo_skip_test_if_no_external('fieldtrip')
return
end
ds_orig = cosmo_synthetic_dataset('type', 'meeg', ...
'sens', 'eeg1005', ...
'size', 'normal');
for with_unknown_channel = [false, true]
ds = ds_orig;
if with_unknown_channel
nchan = max(ds.fa.chan);
idx = ceil(rand() * nchan);
ds.a.fdim.values{1}{idx} = 'foo';
end
nh = cosmo_cluster_neighborhood(ds, 'progress', false);
if with_unknown_channel
empty_msk = ds.fa.chan == idx;
assert(any(empty_msk));
else
empty_msk = false(size(ds.fa.chan));
end
count = cellfun(@numel, nh.neighbors);
assert(all(count(empty_msk) == 0));
assert(all(count(~empty_msk) > 0));
end
function test_cluster_neighborhood_exceptions
ds = cosmo_synthetic_dataset();
aet = @(varargin)assertExceptionThrown( ...
@()cosmo_cluster_neighborhood(varargin{:}, ...
'progress', false), '');
aet(ds, 'foo');
aet(ds, 'fmri', -1);
aet(ds, 'fmri', true);
aet(ds, 'fmri', [1 1]);
ds.a.fdim.labels{2} = 'foo';
ds.fa.foo = ds.fa.j;
aet(ds);
ds2 = cosmo_synthetic_dataset('type', 'meeg');
ds2.a.fdim.labels{1} = 'freq';
ds2.fa.freq = ones(1, 6);
aet(ds2, 'freq');
aet(ds2, 'freq', 2);
aet(ds2, 'freq', NaN);
aet(ds2, 'freq', [true true]);