def test_sym_mat_to_vector(): """Test converting between vectors and symmetric matrices.""" mat = np.array([[0, 1, 2, 3], [1, 4, 5, 6], [2, 5, 7, 8], [3, 6, 8, 9]]) assert_array_equal(_sym_mat_to_vector(mat), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) vec = np.arange(10) assert_array_equal(_vector_to_sym_mat(vec), [[0, 1, 2, 3], [1, 4, 5, 6], [2, 5, 7, 8], [3, 6, 8, 9]]) # Test complex values: diagonals should be complex conjugates comp_vec = np.arange(3) + 1j assert_array_equal(_vector_to_sym_mat(comp_vec), [[0. + 0.j, 1. + 1.j], [1. - 1.j, 2. + 0.j]]) # Test preservation of data type assert _sym_mat_to_vector(mat.astype(np.int8)).dtype == np.int8 assert _vector_to_sym_mat(vec.astype(np.int8)).dtype == np.int8 assert _sym_mat_to_vector(mat.astype(np.float16)).dtype == np.float16 assert _vector_to_sym_mat(vec.astype(np.float16)).dtype == np.float16
def test_sym_mat_to_vector(): """Test converting between vectors and symmetric matrices.""" mat = np.array([[0, 1, 2, 3], [1, 4, 5, 6], [2, 5, 7, 8], [3, 6, 8, 9]]) assert_array_equal(_sym_mat_to_vector(mat), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) vec = np.arange(10) assert_array_equal(_vector_to_sym_mat(vec), [[0, 1, 2, 3], [1, 4, 5, 6], [2, 5, 7, 8], [3, 6, 8, 9]]) # Test complex values: diagonals should be complex conjugates comp_vec = np.arange(3) + 1j assert_array_equal(_vector_to_sym_mat(comp_vec), [[0. + 0.j, 1. + 1.j], [1. - 1.j, 2. + 0.j]]) # Test preservation of data type assert _sym_mat_to_vector(mat.astype(np.int8)).dtype == np.int8 assert _vector_to_sym_mat(vec.astype(np.int8)).dtype == np.int8 assert _sym_mat_to_vector(mat.astype(np.float16)).dtype == np.float16 assert _vector_to_sym_mat(vec.astype(np.float16)).dtype == np.float16
def _cov_as_csd(cov, info): rng = np.random.RandomState(0) assert cov['data'].ndim == 2 assert len(cov['data']) == len(cov['names']) # we need to make this have at least some complex structure data = cov['data'] + 1e-1 * _rand_csd(rng, info) assert data.dtype == np.complex128 return CrossSpectralDensity(_sym_mat_to_vector(data), cov['names'], 0., 16)
def test_make_dics_rank(_load_forward, idx, whiten): """Test making DICS beamformer filters with rank param.""" _, fwd_surf, fwd_fixed, _ = _load_forward epochs, _, csd, _, label, _, _ = _simulate_data(fwd_fixed, idx) if whiten: noise_csd, want_rank = _make_rand_csd(epochs.info, csd) kind = 'mag + grad' else: noise_csd = None epochs.pick_types(meg='grad') want_rank = len(epochs.ch_names) assert want_rank == 41 kind = 'grad' with catch_logging() as log: filters = make_dics(epochs.info, fwd_surf, csd, label=label, noise_csd=noise_csd, verbose=True) log = log.getvalue() assert f'Estimated rank ({kind}): {want_rank}' in log, log stc, _ = apply_dics_csd(csd, filters) other_rank = want_rank - 1 # shouldn't make a huge difference use_rank = dict(meg=other_rank) if not whiten: # XXX it's a bug that our rank functions don't treat "meg" # properly here... use_rank['grad'] = use_rank.pop('meg') with catch_logging() as log: filters_2 = make_dics(epochs.info, fwd_surf, csd, label=label, noise_csd=noise_csd, rank=use_rank, verbose=True) log = log.getvalue() assert f'Computing rank from covariance with rank={use_rank}' in log, log stc_2, _ = apply_dics_csd(csd, filters_2) corr = np.corrcoef(stc_2.data.ravel(), stc.data.ravel())[0, 1] assert 0.8 < corr < 0.99999 # degenerate conditions if whiten: # make rank deficient data = noise_csd.get_data(0.) data[0] = data[:0] = 0 noise_csd._data[:, 0] = _sym_mat_to_vector(data) with pytest.raises(ValueError, match='meg data rank.*the noise rank'): filters = make_dics(epochs.info, fwd_surf, csd, label=label, noise_csd=noise_csd, verbose=True)
def _make_rand_csd(info, csd): rng = np.random.RandomState(0) data = _rand_csd(rng, info) # now we need to have the same null space as the data csd s, u = np.linalg.eigh(csd.get_data(csd.frequencies[0])) mask = np.abs(s) >= s[-1] * 1e-7 rank = mask.sum() assert rank == len(data) == len(info['ch_names']) noise_csd = CrossSpectralDensity( _sym_mat_to_vector(data), info['ch_names'], 0., csd.n_fft) return noise_csd, rank
def __init__( self, data, info, metadata=None, comment=None, method=None, fmin=None, fmax=None, ): if data.ndim != 3: raise ValueError("data should be 3d. Got %d." % data.ndim) n_channels, _, n_times = data.shape if n_channels != len(info["chs"]): raise ValueError("Number of channels and data size don't match" " (%d != %d)." % (n_channels, len(info["chs"]))) if fmin is not None and fmax is not None: if fmin > fmax: raise ValueError(f"'fmin' ({fmin}) should not be " f"greater then 'fmax' ({fmax})") self.data = data self.fmin = fmin self.fmax = fmax self.comment = comment self.method = method self.preload = True data = np.asarray(data) if data.ndim == 3: _data = [] for itime in range(data.shape[-1]): _data.append(_sym_mat_to_vector(data[..., itime])) data = np.asarray(_data).T elif data.ndim != 3: raise ValueError("`data` should be a 3D array.") self._data = data ch_names = info["ch_names"] if len(ch_names) != _n_dims_from_triu(data.shape[0]): raise ValueError( "Number of ch_names does not match the number of " f"time series in the CSD matrix {_n_dims_from_triu(len(data))}." ) self.info = ResultInfo(source_info=info, **metadata) self.times = np.arange( self.n_times) / self.metadata["model_params"]["stepsize"]
def pick_channels_connectivity(csd, include=[], exclude=[], ordered=False, copy=True): """Pick channels from connectivity matrix. Parameters ---------- csd : instance of ResultConnectivity The Result object to select the channels from. include : list of str List of channels to include (if empty, include all available). exclude : list of str Channels to exclude (if empty, do not exclude any). ordered : bool If True (default False), ensure that the order of the channels in the modified instance matches the order of ``include``. .. versionadded:: 0.20.0 copy : bool If True (the default), return a copy of the CSD matrix with the modified channels. If False, channels are modified in-place. .. versionadded:: 0.20.0 Returns ------- res : instance of CrossSpectralDensity Cross-spectral density restricted to selected channels. """ if copy: csd = csd.copy() sel = pick_channels(csd.ch_names, include=include, exclude=exclude, ordered=ordered) data = [] for vec in csd._data.T: mat = _vector_to_sym_mat(vec) mat = mat[sel, :][:, sel] data.append(_sym_mat_to_vector(mat)) ch_names = [csd.ch_names[i] for i in sel] csd._data = np.array(data).T csd.ch_names = ch_names return csd
def test_make_dics(tmpdir, _load_forward, idx, whiten): """Test making DICS beamformer filters.""" # We only test proper handling of parameters here. Testing the results is # done in test_apply_dics_timeseries and test_apply_dics_csd. fwd_free, fwd_surf, fwd_fixed, fwd_vol = _load_forward epochs, _, csd, _, label, vertices, source_ind = \ _simulate_data(fwd_fixed, idx) with pytest.raises(ValueError, match='several sensor types'): make_dics(epochs.info, fwd_surf, csd, label=label, pick_ori=None) if whiten: rng = np.random.RandomState(0) scales = mne.make_ad_hoc_cov(epochs.info).data n = scales.size # Some random complex correlation structure (with channel scalings) data = rng.randn(n, n) + 1j * rng.randn(n, n) data = data @ data.conj().T data *= scales data *= scales[:, np.newaxis] data.flat[::n + 1] = scales noise_csd = CrossSpectralDensity(_sym_mat_to_vector(data), epochs.ch_names, 0., csd.n_fft) else: noise_csd = None epochs.pick_types(meg='grad') with pytest.raises(ValueError, match="Invalid value for the 'pick_ori'"): make_dics(epochs.info, fwd_fixed, csd, pick_ori="notexistent", noise_csd=noise_csd) with pytest.raises(ValueError, match='rank, if str'): make_dics(epochs.info, fwd_fixed, csd, rank='foo', noise_csd=noise_csd) with pytest.raises(TypeError, match='rank must be'): make_dics(epochs.info, fwd_fixed, csd, rank=1., noise_csd=noise_csd) # Test if fixed forward operator is detected when picking normal # orientation with pytest.raises(ValueError, match='forward operator with free ori'): make_dics(epochs.info, fwd_fixed, csd, pick_ori="normal", noise_csd=noise_csd) # Test if non-surface oriented forward operator is detected when picking # normal orientation with pytest.raises(ValueError, match='oriented in surface coordinates'): make_dics(epochs.info, fwd_free, csd, pick_ori="normal", noise_csd=noise_csd) # Test if volume forward operator is detected when picking normal # orientation with pytest.raises(ValueError, match='oriented in surface coordinates'): make_dics(epochs.info, fwd_vol, csd, pick_ori="normal", noise_csd=noise_csd) # Test invalid combinations of parameters with pytest.raises(ValueError, match='reduce_rank cannot be used with'): make_dics(epochs.info, fwd_free, csd, inversion='single', reduce_rank=True, noise_csd=noise_csd) # TODO: Restore this? # with pytest.raises(ValueError, match='not stable with depth'): # make_dics(epochs.info, fwd_free, csd, weight_norm='unit-noise-gain', # inversion='single', depth=None) # Sanity checks on the returned filters n_freq = len(csd.frequencies) vertices = np.intersect1d(label.vertices, fwd_free['src'][0]['vertno']) n_verts = len(vertices) n_orient = 3 n_channels = len(epochs.ch_names) # Test return values weight_norm = 'unit-noise-gain' inversion = 'single' filters = make_dics(epochs.info, fwd_surf, csd, label=label, pick_ori=None, weight_norm=weight_norm, depth=None, noise_csd=noise_csd, inversion=inversion) assert filters['weights'].shape == (n_freq, n_verts * n_orient, n_channels) assert np.iscomplexobj(filters['weights']) assert filters['csd'].ch_names == epochs.ch_names assert isinstance(filters['csd'], CrossSpectralDensity) assert filters['ch_names'] == epochs.ch_names assert_array_equal(filters['proj'], np.eye(n_channels)) assert_array_equal(filters['vertices'][0], vertices) assert_array_equal(filters['vertices'][1], []) # Label was on the LH assert filters['subject'] == fwd_free['src']._subject assert filters['pick_ori'] is None assert filters['is_free_ori'] assert filters['inversion'] == inversion assert filters['weight_norm'] == weight_norm assert 'DICS' in repr(filters) assert 'subject "sample"' in repr(filters) assert str(len(vertices)) in repr(filters) assert str(n_channels) in repr(filters) assert 'rank' not in repr(filters) _, noise_cov = _prepare_noise_csd(csd, noise_csd, real_filter=False) _, _, _, _, G, _, _, _ = _prepare_beamformer_input(epochs.info, fwd_surf, label, 'vector', combine_xyz=False, exp=None, noise_cov=noise_cov) G.shape = (n_channels, n_verts, n_orient) G = G.transpose(1, 2, 0).conj() # verts, orient, ch _assert_weight_norm(filters, G) inversion = 'matrix' filters = make_dics(epochs.info, fwd_surf, csd, label=label, pick_ori=None, weight_norm=weight_norm, depth=None, noise_csd=noise_csd, inversion=inversion) _assert_weight_norm(filters, G) weight_norm = 'unit-noise-gain-invariant' inversion = 'single' filters = make_dics(epochs.info, fwd_surf, csd, label=label, pick_ori=None, weight_norm=weight_norm, depth=None, noise_csd=noise_csd, inversion=inversion) _assert_weight_norm(filters, G) # Test picking orientations. Also test weight norming under these different # conditions. weight_norm = 'unit-noise-gain' filters = make_dics(epochs.info, fwd_surf, csd, label=label, pick_ori='normal', weight_norm=weight_norm, depth=None, noise_csd=noise_csd, inversion=inversion) n_orient = 1 assert filters['weights'].shape == (n_freq, n_verts * n_orient, n_channels) assert not filters['is_free_ori'] _assert_weight_norm(filters, G) filters = make_dics(epochs.info, fwd_surf, csd, label=label, pick_ori='max-power', weight_norm=weight_norm, depth=None, noise_csd=noise_csd, inversion=inversion) n_orient = 1 assert filters['weights'].shape == (n_freq, n_verts * n_orient, n_channels) assert not filters['is_free_ori'] _assert_weight_norm(filters, G) # From here on, only work on a single frequency csd = csd[0] # Test using a real-valued filter filters = make_dics(epochs.info, fwd_surf, csd, label=label, pick_ori='normal', real_filter=True, noise_csd=noise_csd) assert not np.iscomplexobj(filters['weights']) # Test forward normalization. When inversion='single', the power of a # unit-noise CSD should be 1, even without weight normalization. if not whiten: csd_noise = csd.copy() inds = np.triu_indices(csd.n_channels) # Using [:, :] syntax for in-place broadcasting csd_noise._data[:, :] = np.eye(csd.n_channels)[inds][:, np.newaxis] filters = make_dics(epochs.info, fwd_surf, csd_noise, label=label, weight_norm=None, depth=1., noise_csd=noise_csd, inversion='single') w = filters['weights'][0][:3] assert_allclose(np.diag(w.dot(w.conjugate().T)), 1.0, rtol=1e-6, atol=0) # Test turning off both forward and weight normalization filters = make_dics(epochs.info, fwd_surf, csd, label=label, weight_norm=None, depth=None, noise_csd=noise_csd) w = filters['weights'][0][:3] assert not np.allclose( np.diag(w.dot(w.conjugate().T)), 1.0, rtol=1e-2, atol=0) # Test neural-activity-index weight normalization. It should be a scaled # version of the unit-noise-gain beamformer. filters_nai = make_dics(epochs.info, fwd_surf, csd, label=label, pick_ori='max-power', weight_norm='nai', depth=None, noise_csd=noise_csd) w_nai = filters_nai['weights'][0] filters_ung = make_dics(epochs.info, fwd_surf, csd, label=label, pick_ori='max-power', weight_norm='unit-noise-gain', depth=None, noise_csd=noise_csd) w_ung = filters_ung['weights'][0] assert_allclose(np.corrcoef(np.abs(w_nai).ravel(), np.abs(w_ung).ravel()), 1, atol=1e-7) # Test whether spatial filter contains src_type assert 'src_type' in filters fname = op.join(str(tmpdir), 'filters-dics.h5') filters.save(fname) filters_read = read_beamformer(fname) assert isinstance(filters, Beamformer) assert isinstance(filters_read, Beamformer) for key in ['tmin', 'tmax']: # deal with strictness of object_diff setattr(filters['csd'], key, np.float64(getattr(filters['csd'], key))) assert object_diff(filters, filters_read) == ''