function test_suite = test_spherical_neighborhood
% tests for cosmo_spherical_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_simple_neighborhood
ds = cosmo_synthetic_dataset();
nh1 = cosmo_spherical_neighborhood(ds, 'radius', 0, 'progress', false);
mp = cosmo_align({ds.fa.i, ds.fa.j, ds.fa.k}, ...
{nh1.fa.i, nh1.fa.j, nh1.fa.k});
ds_al = cosmo_slice(ds, mp, 2);
assertEqual(nh1.a, ds.a);
assertEqual(nh1.fa.i, ds_al.fa.i);
assertEqual(nh1.fa.j, ds_al.fa.j);
assertEqual(nh1.fa.k, ds_al.fa.k);
assertEqual(nh1.fa.nvoxels, ones(1, 6));
assertEqual(nh1.fa.radius, zeros(1, 6));
assertEqual(nh1.fa.center_ids, mp);
assertEqual(nh1.neighbors, num2cell(mp'));
assertEqual(nh1.origin.fa, ds_al.fa);
assertEqual(nh1.origin.a.fdim, ds_al.a.fdim);
nh2 = cosmo_spherical_neighborhood(ds, 'radius', 1.5, 'progress', false);
mp = cosmo_align({ds.fa.i, ds.fa.j, ds.fa.k}, ...
{nh1.fa.i, nh1.fa.j, nh1.fa.k});
ds_al = cosmo_slice(ds, mp, 2);
assertEqual(nh2.a, ds.a);
assertEqual(nh2.fa.i, ds_al.fa.i);
assertEqual(nh2.fa.j, ds_al.fa.j);
assertEqual(nh2.fa.k, ds_al.fa.k);
nvoxels = [4 6 4 4 6 4];
assertEqual(nh2.fa.nvoxels, nvoxels(mp));
assertEqual(nh2.fa.radius, ones(1, 6) * 1.5);
assertEqual(nh2.fa.center_ids, mp);
assertEqual(numel(nh2.neighbors), 6);
nh = { [1 4 2 5]; ...
[2 1 5 3 4 6]; ...
[3 2 6 5]; ...
[4 1 5 2]; ...
[5 4 2 6 1 3]; ...
[6 5 3 2] };
for k = 1:6
assertEqual(nh2.neighbors{k}, nh{mp(k)});
end
nh3 = cosmo_spherical_neighborhood(ds, 'count', 4, 'progress', false);
mp = cosmo_align({ds.fa.i, ds.fa.j, ds.fa.k}, ...
{nh1.fa.i, nh1.fa.j, nh1.fa.k});
ds_al = cosmo_slice(ds, mp, 2);
assertEqual(nh3.a, ds.a);
assertEqual(nh3.fa.i, ds_al.fa.i);
assertEqual(nh3.fa.j, ds_al.fa.j);
assertEqual(nh3.fa.k, ds_al.fa.k);
assertEqual(nh3.fa.nvoxels, [4 4 4 4 4 4]);
radii = [sqrt(2) 1 sqrt(2) sqrt(2) 1 sqrt(2)];
assertElementsAlmostEqual(nh3.fa.radius, ...
radii(mp), ...
'relative', 1e-3);
assertEqual(nh3.fa.center_ids, mp);
assertEqual(numel(nh3.neighbors), 6);
nh = { [1 4 2 5]; ...
[2 1 5 3]; ...
[3 2 6 5]; ...
[4 1 5 2]; ...
[5 4 2 6]; ...
[6 5 3 2] };
for k = 1:6
assertEqual(nh3.neighbors{k}, nh{mp(k)});
end
function test_exceptions
ds = cosmo_synthetic_dataset();
aet = @(x)assertExceptionThrown(@() ...
cosmo_spherical_neighborhood(x{:}), '');
aet({ds});
aet({ds, 'foo'});
aet({ds, 'foo', 1});
aet({ds, 'radius', [1 2]});
aet({ds, 'count', [1 2]});
aet({ds, 'radius', -1});
aet({ds, 'count', -1});
aet({ds, 'radius', 1, 'count', 1});
aet({ds, 'count', 7});
aet({'foo', 'count', 7});
function test_sparse_dataset
nfeatures_test = 3;
ds = cosmo_synthetic_dataset('size', 'big');
nf = size(ds.samples, 2);
rp = randperm(nf);
nf_full = round(nf * .4);
ids = rp(1:nf_full);
ds_sel = cosmo_slice(ds, ids, 2);
ids_full = repmat(ids, 1, 2);
ds_full = cosmo_slice(ds, ids_full, 2);
radius = 2 + rand();
nh4 = cosmo_spherical_neighborhood(ds_full, 'radius', radius, ...
'progress', false);
assertEqual(numel(nh4.neighbors), numel(ids));
mp = cosmo_align({ds_sel.fa.i, ds_sel.fa.j, ds_sel.fa.k}, ...
{nh4.fa.i, nh4.fa.j, nh4.fa.k});
ds_al = cosmo_slice(ds_sel, mp, 2);
assertEqual(nh4.a, ds.a);
assertEqual(nh4.fa.i, ds_al.fa.i);
assertEqual(nh4.fa.j, ds_al.fa.j);
assertEqual(nh4.fa.k, ds_al.fa.k);
assertEqual(nh4.fa.center_ids, mp);
rp = randperm(size(ds_al.samples, 2));
center_ids = rp(1:nfeatures_test);
ijk = [ds_al.fa.i; ds_al.fa.j; ds_al.fa.k];
ijk_full = [ds_full.fa.i; ds_full.fa.j; ds_full.fa.k];
for center_id = center_ids
ijk_center = ijk(:, center_id);
delta = sum(bsxfun(@minus, ijk_center, ijk_full).^2, 1).^.5;
nbr_ids = find(delta <= radius);
assertEqual(nbr_ids, sort(nh4.neighbors{center_id}));
end
function test_with_freq_dimension_dataset
ds = cosmo_synthetic_dataset('size', 'big');
nfeatures = size(ds.samples, 2);
freqs = [2 4 6];
nfreqs = numel(freqs);
ds_cell = cell(nfreqs, 1);
for k = 1:nfreqs
ds_freq = ds;
ds_freq.a.fdim.labels = [{'freq'}; ds_freq.a.fdim.labels];
ds_freq.a.fdim.values = [{freqs}; ds_freq.a.fdim.values];
ds_freq.fa.freq = ones(1, nfeatures) * k;
ds_cell{k} = ds_freq;
end
ds_full = cosmo_stack(ds_cell, 2);
rp = randperm(nfeatures * nfreqs);
id_full = rp(1:((nfreqs - 1) * nfeatures));
ds_full = cosmo_slice(ds_full, id_full, 2);
ijk_full = [ds_full.fa.i; ds_full.fa.j; ds_full.fa.k];
[ijk_idxs, ijk_unq] = cosmo_index_unique(ijk_full');
ijk_idx = cellfun(@(x)x(1), ijk_idxs);
ds_al = cosmo_slice(ds_full, ijk_idx, 2);
radius = 5 + rand() * 3;
nh = cosmo_spherical_neighborhood(ds_full, 'radius', radius, ...
'progress', false);
assertEqual(nh.fa.i, ds_al.fa.i);
assertEqual(nh.fa.j, ds_al.fa.j);
assertEqual(nh.fa.k, ds_al.fa.k);
assertEqual(nh.a.fdim.labels, ds_full.a.fdim.labels(2:4));
assertEqual(nh.a.fdim.values, ds_full.a.fdim.values(2:4));
assertEqual(numel(nh.neighbors), numel(ijk_idxs));
ijk_full = [ds_full.fa.i; ds_full.fa.j; ds_full.fa.k];
ijk_al = [ds_al.fa.i; ds_al.fa.j; ds_al.fa.k];
% test match for euclidean distance
rp = randperm(numel(ijk_idxs));
rp = rp(1:10);
for r = rp
nbrs = nh.neighbors{r};
ijk_center = ijk_al(:, r);
delta = sum(bsxfun(@minus, ijk_center, ijk_full).^2, 1).^.5;
assertEqual(find(delta <= radius), sort(nbrs));
end
% test with transposed dimension values
ds2_full = ds_full;
ds2_full.a.fdim.values = cellfun(@transpose, ...
ds2_full.a.fdim.values, ...
'UniformOutput', false)';
nh2 = cosmo_spherical_neighborhood(ds2_full, 'radius', radius, ...
'progress', false);
nh2.origin.a.fdim.values = cellfun(@transpose, ...
nh2.origin.a.fdim.values, ...
'UniformOutput', false)';
assertEqual(nh, nh2);
assertFalse(isfield(nh.fa, 'inside'));
function test_meeg_source_dataset
ds = cosmo_synthetic_dataset('type', 'source', 'size', 'normal');
nf = size(ds.samples, 2);
[unused, idxs] = sort(cosmo_rand(1, nf * 4, 'seed', 1));
rps = mod(idxs - 1, nf) + 1;
id_full = rps(round(nf / 2) + (1:(3 * nf)));
ds_full = cosmo_slice(ds, id_full, 2);
pos_full = ds_full.fa.pos;
pos_idxs = cosmo_index_unique(pos_full');
pos_idx = cellfun(@(x)x(1), pos_idxs);
ds_unq = cosmo_slice(ds_full, pos_idx, 2);
radius = 1.2 + .2 * rand();
nh = cosmo_spherical_neighborhood(ds_full, 'radius', radius, ...
'progress', false);
mp = cosmo_align(ds_unq.fa.pos', nh.fa.pos');
ds_al = cosmo_slice(ds_unq, mp, 2);
assertEqual(nh.fa.pos, ds_al.fa.pos);
assertEqual(nh.a.fdim.labels{1}, ds.a.fdim.labels{1});
assertEqual(nh.a.fdim.values{1}, ds.a.fdim.values{1});
assertEqual(numel(nh.neighbors), numel(pos_idx));
count = ceil(4 / 3 * pi * (radius)^3 * .5);
nh2 = cosmo_spherical_neighborhood(ds_full, 'count', count, ...
'progress', false);
assertEqual(nh2.fa.pos, ds_al.fa.pos);
assertEqual(nh2.a.fdim.labels{1}, ds.a.fdim.labels{1});
assertEqual(nh2.a.fdim.values{1}, ds.a.fdim.values{1});
assertEqual(numel(nh2.neighbors), numel(pos_idx));
pos_al = ds_al.a.fdim.values{1}(:, ds_al.fa.pos);
pos_full = nh.a.fdim.values{1}(:, ds_full.fa.pos);
voxel_size = 10;
for r = 1:numel(pos_idx)
d = sum(bsxfun(@minus, pos_full, pos_al(:, r)).^2, 1).^.5;
idxs = find(d <= (radius * voxel_size));
assertEqual(sort(nh.neighbors{r}), sort(idxs));
end
[p, q] = cosmo_overlap(nh.neighbors, nh2.neighbors);
dp = diag(p);
dq = diag(q);
assertTrue(mean(dp) > .8);
assertTrue(mean(dq) > .3);
function test_small_meeg_source_dataset_without_inside_field
ds = cosmo_synthetic_dataset('type', 'source', 'size', 'small');
voxel_size = 10;
idx = cosmo_index_unique(ds.fa.pos');
n_pos = numel(idx);
ds_pos = ds.a.fdim.values{1}(:, ds.fa.pos);
for radius = 0:.4:2
nh = cosmo_spherical_neighborhood(ds, ...
'radius', radius, ...
'progress', false);
center_pos = nh.a.fdim.values{1}(:, nh.fa.pos);
assertEqual(numel(nh.neighbors), n_pos);
for k = 1:n_pos
sel_idx = nh.neighbors{k};
delta = bsxfun(@minus, center_pos(:, k), ...
ds_pos);
distance = sqrt(sum((delta / voxel_size).^2, 1));
expected_sel_idx = find(distance <= radius);
assertEqual(sort(sel_idx(:)), sort(expected_sel_idx(:)));
end
end
function test_small_meeg_source_dataset_with_inside_field
ds = cosmo_synthetic_dataset('type', 'source', 'size', 'small');
voxel_size = 10;
nf = size(ds.samples, 2);
idx = cosmo_index_unique(ds.fa.pos');
n_pos = numel(idx);
for keep_ratio = .4:.3:1
keep = randperm(n_pos);
n_keep = round(n_pos * keep_ratio);
keep = keep(1:n_keep);
ds.fa.inside = false(1, nf);
for k = 1:numel(keep)
ds.fa.inside(idx{keep(k)}) = true;
end
ds_pos = ds.a.fdim.values{1}(:, ds.fa.pos);
for radius = 0:.4:2
nh = cosmo_spherical_neighborhood(ds, ...
'radius', radius, ...
'progress', false);
center_pos = nh.a.fdim.values{1}(:, nh.fa.pos);
assertEqual(numel(nh.neighbors), n_keep);
for k = 1:n_keep
sel_idx = nh.neighbors{k};
delta = bsxfun(@minus, center_pos(:, k), ...
ds_pos);
distance = sqrt(sum((delta / voxel_size).^2, 1));
expected_sel_idx = find(distance <= radius & ds.fa.inside);
assertEqual(sort(sel_idx(:)), sort(expected_sel_idx(:)));
end
end
end
function test_meeg_source_illegal_inside()
ds = cosmo_synthetic_dataset('type', 'source', 'size', 'small');
n_features = size(ds.samples, 1);
illegal_values = {min(max(ds.samples(1, :), 0), 1), ...
repmat({'foo'}, 1, n_features)};
for k = 1:numel(illegal_values)
ds.fa.inside = illegal_values{k};
assertExceptionThrown(@() ...
cosmo_spherical_neighborhood(ds, 'radius', 1), '');
end
function test_meeg_missing_dimension_label()
ds = cosmo_synthetic_dataset();
ds = cosmo_dim_remove(ds, 'i');
assertExceptionThrown(@() ...
cosmo_spherical_neighborhood(ds, 'radius', 1), '');
function test_meeg_wrong_dimension_order()
ds = cosmo_synthetic_dataset();
ds.fa.j(:) = 1;
ds.fa.k(:) = 1;
ds.a.fdim.labels([2, 3]) = ds.a.fdim.labels([3, 2]);
assertExceptionThrown(@() ...
cosmo_spherical_neighborhood(ds, 'radius', 1), '');
function test_meeg_source_sdim_time
ds = cosmo_synthetic_dataset('type', 'source', 'size', 'big');
ds_time = cosmo_dim_transpose(ds, 'time', 1);
nh = cosmo_spherical_neighborhood(ds_time, 'radius', 0, 'progress', false);
assert(~isfield(nh.a, 'sdim'));
assert(~isfield(nh, 'sa'));
function test_fmri_fixed_number_of_features()
ds = cosmo_synthetic_dataset('size', 'normal');
nf = size(ds.samples, 2);
[unused, idxs] = sort(cosmo_rand(1, nf * 3, 'seed', 1));
rps = mod(idxs - 1, nf) + 1;
id_full = rps(round(nf / 2) + (1:(2 * nf)));
ds_full = cosmo_slice(ds, id_full, 2);
id_idxs = cosmo_index_unique(id_full');
id_idx = cellfun(@(x)x(1), id_idxs);
ds_sel = cosmo_slice(ds_full, id_idx, 2);
count = 20;
nh = cosmo_spherical_neighborhood(ds_full, 'count', count, ...
'progress', false);
ijk_sel = {ds_sel.fa.i; ds_sel.fa.j; ds_sel.fa.k};
ijk_nh = {nh.fa.i; nh.fa.j; nh.fa.k};
mp = cosmo_align(ijk_sel, ijk_nh);
ds_al = cosmo_slice(ds_sel, mp, 2);
assertEqual(nh.fa.i, ds_al.fa.i);
assertEqual(nh.fa.j, ds_al.fa.j);
assertEqual(nh.fa.k, ds_al.fa.k);
pos_nh = [nh.fa.i; nh.fa.j; nh.fa.k];
pos_full = [ds_full.fa.i; ds_full.fa.j; ds_full.fa.k];
rp = randperm(numel(id_idx));
rp = rp(1:10);
for r = rp
d = sum(bsxfun(@minus, pos_nh(:, r), pos_full).^2, 1).^.5;
idxs = nh.neighbors{r};
d_inside = d(idxs);
d_outside = d(setdiff(1:nf, idxs));
assert(max(d_inside) <= min(d_outside));
assertElementsAlmostEqual(max(d_inside), nh.fa.radius(r), ...
'absolute', 1e-4);
end