def test_calculate_chpi_positions(): """Test calculation of cHPI positions """ trans, rot, t = head_pos_to_trans_rot_t(read_head_pos(pos_fname)) with warnings.catch_warnings(record=True): raw = Raw(chpi_fif_fname, allow_maxshield=True, preload=True) t -= raw.first_samp / raw.info['sfreq'] quats = _calculate_chpi_positions(raw, verbose='debug') trans_est, rot_est, t_est = head_pos_to_trans_rot_t(quats) _compare_positions((trans, rot, t), (trans_est, rot_est, t_est), 0.003) # degenerate conditions raw_no_chpi = Raw(test_fif_fname) assert_raises(RuntimeError, _calculate_chpi_positions, raw_no_chpi) raw_bad = raw.copy() for d in raw_bad.info['dig']: if d['kind'] == FIFF.FIFFV_POINT_HPI: d['coord_frame'] = 999 break assert_raises(RuntimeError, _calculate_chpi_positions, raw_bad) raw_bad = raw.copy() for d in raw_bad.info['dig']: if d['kind'] == FIFF.FIFFV_POINT_HPI: d['r'] = np.ones(3) raw_bad.crop(0, 1., copy=False) with warnings.catch_warnings(record=True): # bad pos with catch_logging() as log_file: _calculate_chpi_positions(raw_bad, verbose=True) # ignore HPI info header and [done] footer for line in log_file.getvalue().strip().split('\n')[4:-1]: assert_true('0/5 good' in line)
def test_calculate_chpi_positions(): """Test calculation of cHPI positions.""" trans, rot, t = head_pos_to_trans_rot_t(read_head_pos(pos_fname)) raw = read_raw_fif(chpi_fif_fname, allow_maxshield='yes', preload=True, add_eeg_ref=False) t -= raw.first_samp / raw.info['sfreq'] quats = _calculate_chpi_positions(raw, verbose='debug') trans_est, rot_est, t_est = head_pos_to_trans_rot_t(quats) _compare_positions((trans, rot, t), (trans_est, rot_est, t_est), 0.003) # degenerate conditions raw_no_chpi = read_raw_fif(test_fif_fname, add_eeg_ref=False) assert_raises(RuntimeError, _calculate_chpi_positions, raw_no_chpi) raw_bad = raw.copy() for d in raw_bad.info['dig']: if d['kind'] == FIFF.FIFFV_POINT_HPI: d['coord_frame'] = 999 break assert_raises(RuntimeError, _calculate_chpi_positions, raw_bad) raw_bad = raw.copy() for d in raw_bad.info['dig']: if d['kind'] == FIFF.FIFFV_POINT_HPI: d['r'] = np.ones(3) raw_bad.crop(0, 1., copy=False) with warnings.catch_warnings(record=True): # bad pos with catch_logging() as log_file: _calculate_chpi_positions(raw_bad, verbose=True) # ignore HPI info header and [done] footer assert_true('0/5 good' in log_file.getvalue().strip().split('\n')[-2]) # half the rate cuts off cHPI coils with warnings.catch_warnings(record=True): # uint cast suggestion raw.resample(300., npad='auto') assert_raises_regex(RuntimeError, 'above the', _calculate_chpi_positions, raw)
def test_calculate_chpi_positions(): """Test calculation of cHPI positions """ trans, rot, t = head_pos_to_trans_rot_t(read_head_pos(pos_fname)) raw = Raw(chpi_fif_fname, allow_maxshield='yes', preload=True) t -= raw.first_samp / raw.info['sfreq'] quats = _calculate_chpi_positions(raw, verbose='debug') trans_est, rot_est, t_est = head_pos_to_trans_rot_t(quats) _compare_positions((trans, rot, t), (trans_est, rot_est, t_est), 0.003) # degenerate conditions raw_no_chpi = Raw(test_fif_fname) assert_raises(RuntimeError, _calculate_chpi_positions, raw_no_chpi) raw_bad = raw.copy() for d in raw_bad.info['dig']: if d['kind'] == FIFF.FIFFV_POINT_HPI: d['coord_frame'] = 999 break assert_raises(RuntimeError, _calculate_chpi_positions, raw_bad) raw_bad = raw.copy() for d in raw_bad.info['dig']: if d['kind'] == FIFF.FIFFV_POINT_HPI: d['r'] = np.ones(3) raw_bad.crop(0, 1., copy=False) with warnings.catch_warnings(record=True): # bad pos with catch_logging() as log_file: _calculate_chpi_positions(raw_bad, verbose=True) # ignore HPI info header and [done] footer for line in log_file.getvalue().strip().split('\n')[4:-1]: assert_true('0/5 good' in line) # half the rate cuts off cHPI coils raw.resample(300., npad='auto') assert_raises_regex(RuntimeError, 'above the', _calculate_chpi_positions, raw)
def test_calculate_chpi_positions(): """Test calculation of cHPI positions """ trans, rot, t = head_pos_to_trans_rot_t(read_head_pos(pos_fname)) raw = Raw(chpi_fif_fname, allow_maxshield="yes", preload=True) t -= raw.first_samp / raw.info["sfreq"] quats = _calculate_chpi_positions(raw, verbose="debug") trans_est, rot_est, t_est = head_pos_to_trans_rot_t(quats) _compare_positions((trans, rot, t), (trans_est, rot_est, t_est), 0.003) # degenerate conditions raw_no_chpi = Raw(test_fif_fname) assert_raises(RuntimeError, _calculate_chpi_positions, raw_no_chpi) raw_bad = raw.copy() for d in raw_bad.info["dig"]: if d["kind"] == FIFF.FIFFV_POINT_HPI: d["coord_frame"] = 999 break assert_raises(RuntimeError, _calculate_chpi_positions, raw_bad) raw_bad = raw.copy() for d in raw_bad.info["dig"]: if d["kind"] == FIFF.FIFFV_POINT_HPI: d["r"] = np.ones(3) raw_bad.crop(0, 1.0, copy=False) with warnings.catch_warnings(record=True): # bad pos with catch_logging() as log_file: _calculate_chpi_positions(raw_bad, verbose=True) # ignore HPI info header and [done] footer for line in log_file.getvalue().strip().split("\n")[4:-1]: assert_true("0/5 good" in line) # half the rate cuts off cHPI coils with warnings.catch_warnings(record=True): # uint cast suggestion raw.resample(300.0, npad="auto") assert_raises_regex(RuntimeError, "above the", _calculate_chpi_positions, raw)
def _assert_quats(actual, desired, dist_tol=0.003, angle_tol=5., err_rtol=0.5, gof_rtol=0.001, vel_atol=2e-3): # 2 mm/s """Compare estimated cHPI positions.""" __tracebackhide__ = True trans_est, rot_est, t_est = head_pos_to_trans_rot_t(actual) trans, rot, t = head_pos_to_trans_rot_t(desired) quats_est = rot_to_quat(rot_est) gofs, errs, vels = desired[:, 7:].T gofs_est, errs_est, vels_est = actual[:, 7:].T del actual, desired # maxfilter produces some times that are implausibly large (weird) if not np.isclose(t[0], t_est[0], atol=1e-1): # within 100 ms raise AssertionError('Start times not within 100 ms: %0.3f != %0.3f' % (t[0], t_est[0])) use_mask = (t >= t_est[0]) & (t <= t_est[-1]) t = t[use_mask] trans = trans[use_mask] quats = rot_to_quat(rot) quats = quats[use_mask] gofs, errs, vels = gofs[use_mask], errs[use_mask], vels[use_mask] # double-check our angle function for q in (quats, quats_est): angles = _angle_between_quats(q, q) assert_allclose(angles, 0., atol=1e-5) # limit translation difference between MF and our estimation trans_est_interp = interp1d(t_est, trans_est, axis=0)(t) distances = np.sqrt(np.sum((trans - trans_est_interp) ** 2, axis=1)) assert np.isfinite(distances).all() arg_worst = np.argmax(distances) assert distances[arg_worst] <= dist_tol, ( '@ %0.3f seconds: %0.3f > %0.3f mm' % (t[arg_worst], 1000 * distances[arg_worst], 1000 * dist_tol)) # limit rotation difference between MF and our estimation # (note that the interpolation will make this slightly worse) quats_est_interp = interp1d(t_est, quats_est, axis=0)(t) angles = 180 * _angle_between_quats(quats_est_interp, quats) / np.pi arg_worst = np.argmax(angles) assert angles[arg_worst] <= angle_tol, ( '@ %0.3f seconds: %0.3f > %0.3f deg' % (t[arg_worst], angles[arg_worst], angle_tol)) # error calculation difference errs_est_interp = interp1d(t_est, errs_est)(t) assert_allclose(errs_est_interp, errs, rtol=err_rtol, atol=1e-3, err_msg='err') # 1 mm # gof calculation difference gof_est_interp = interp1d(t_est, gofs_est)(t) assert_allclose(gof_est_interp, gofs, rtol=gof_rtol, atol=1e-7, err_msg='gof') # velocity calculation difference vel_est_interp = interp1d(t_est, vels_est)(t) assert_allclose(vel_est_interp, vels, atol=vel_atol, err_msg='velocity')
def test_simulate_raw_chpi(): """Test simulation of raw data with cHPI.""" raw = read_raw_fif(raw_chpi_fname, allow_maxshield='yes') sphere = make_sphere_model('auto', 'auto', raw.info) # make sparse spherical source space sphere_vol = tuple(sphere['r0'] * 1000.) + (sphere.radius * 1000., ) src = setup_volume_source_space('sample', sphere=sphere_vol, pos=70.) stc = _make_stc(raw, src) # simulate data with cHPI on raw_sim = simulate_raw(raw, stc, None, src, sphere, cov=None, chpi=False) # need to trim extra samples off this one raw_chpi = simulate_raw(raw, stc, None, src, sphere, cov=None, chpi=True, head_pos=pos_fname) # test cHPI indication hpi_freqs, _, hpi_pick, hpi_ons = _get_hpi_info(raw.info)[:4] assert_allclose(raw_sim[hpi_pick][0], 0.) assert_allclose(raw_chpi[hpi_pick][0], hpi_ons.sum()) # test that the cHPI signals make some reasonable values picks_meg = pick_types(raw.info, meg=True, eeg=False) picks_eeg = pick_types(raw.info, meg=False, eeg=True) for picks in [picks_meg, picks_eeg]: psd_sim, freqs_sim = psd_welch(raw_sim, picks=picks) psd_chpi, freqs_chpi = psd_welch(raw_chpi, picks=picks) assert_array_equal(freqs_sim, freqs_chpi) freq_idx = np.sort( [np.argmin(np.abs(freqs_sim - f)) for f in hpi_freqs]) if picks is picks_meg: assert_true( (psd_chpi[:, freq_idx] > 100 * psd_sim[:, freq_idx]).all()) else: assert_allclose(psd_sim, psd_chpi, atol=1e-20) # test localization based on cHPI information quats_sim = _calculate_chpi_positions(raw_chpi) trans_sim, rot_sim, t_sim = head_pos_to_trans_rot_t(quats_sim) trans, rot, t = head_pos_to_trans_rot_t(read_head_pos(pos_fname)) t -= raw.first_samp / raw.info['sfreq'] _compare_positions((trans, rot, t), (trans_sim, rot_sim, t_sim), max_dist=0.005)
def test_simulate_raw_chpi(): """Test simulation of raw data with cHPI""" with warnings.catch_warnings(record=True): # MaxShield raw = Raw(raw_chpi_fname, allow_maxshield=True) sphere = make_sphere_model('auto', 'auto', raw.info) # make sparse spherical source space sphere_vol = tuple(sphere['r0'] * 1000.) + (sphere.radius * 1000.,) src = setup_volume_source_space('sample', sphere=sphere_vol, pos=70.) stc = _make_stc(raw, src) # simulate data with cHPI on raw_sim = simulate_raw(raw, stc, None, src, sphere, cov=None, chpi=False) # need to trim extra samples off this one raw_chpi = simulate_raw(raw, stc, None, src, sphere, cov=None, chpi=True, head_pos=pos_fname) # test cHPI indication hpi_freqs, _, hpi_pick, hpi_ons = _get_hpi_info(raw.info)[:4] assert_allclose(raw_sim[hpi_pick][0], 0.) assert_allclose(raw_chpi[hpi_pick][0], hpi_ons.sum()) # test that the cHPI signals make some reasonable values picks_meg = pick_types(raw.info, meg=True, eeg=False) picks_eeg = pick_types(raw.info, meg=False, eeg=True) for picks in [picks_meg, picks_eeg]: psd_sim, freqs_sim = psd_welch(raw_sim, picks=picks) psd_chpi, freqs_chpi = psd_welch(raw_chpi, picks=picks) assert_array_equal(freqs_sim, freqs_chpi) freq_idx = np.sort([np.argmin(np.abs(freqs_sim - f)) for f in hpi_freqs]) if picks is picks_meg: assert_true((psd_chpi[:, freq_idx] > 100 * psd_sim[:, freq_idx]).all()) else: assert_allclose(psd_sim, psd_chpi, atol=1e-20) # test localization based on cHPI information quats_sim = _calculate_chpi_positions(raw_chpi) trans_sim, rot_sim, t_sim = head_pos_to_trans_rot_t(quats_sim) trans, rot, t = head_pos_to_trans_rot_t(read_head_pos(pos_fname)) t -= raw.first_samp / raw.info['sfreq'] _compare_positions((trans, rot, t), (trans_sim, rot_sim, t_sim), max_dist=0.005)
def getHeadPosDist(headPosFile, returnDist = True): """Get Head Position and sample2sample distance from Maxfilter Pos file. Code is based on mne.viz.plot_head_positions (see https://mne.tools/stable/index.html) Parameters ---------- headPosFile : str Pos filepath returnDist : bool Return sample2sample distance? Returns ------- positions : array, shape (N, 3) head position (x,y,z) in mm movDist : array, sahep (N, 1) sample2sample Distance """ import mne import numpy as np from mne.chpi import head_pos_to_trans_rot_t headPos = mne.chpi.read_head_pos(headPosFile) pos = [headPos] for ii, p in enumerate(pos): p = np.array(p, float) if p.ndim != 2 or p.shape[1] != 10: raise ValueError('pos (or each entry in pos if a list) must be ' 'dimension (N, 10), got %s' % (p.shape,)) if ii > 0: p[:, 0] += pos[ii - 1][-1, 0] - p[0, 0] pos[ii] = p pos = np.concatenate(pos, axis=0) trans, rot, t = head_pos_to_trans_rot_t(pos) # also ensures pos is okay positions = mne.fixes.einsum('ijk,ik->ij', rot[:, :3, :3].transpose([0, 2, 1]),-trans) * 1000 if returnDist: movDist = [] from math import sqrt for iii in range(1,len(positions)): tmp = sqrt((positions[iii,0] - positions[iii-1,0])**2 + (positions[iii,1] - positions[iii-1,1])**2 + (positions[iii,2] - positions[iii-1,2])**2) movDist.append(tmp) return positions, np.array(movDist) else: return positions
def _assert_quats(actual, desired, dist_tol=0.003, angle_tol=5.): """Compare estimated cHPI positions.""" __tracebackhide__ = True trans_est, rot_est, t_est = head_pos_to_trans_rot_t(actual) trans, rot, t = head_pos_to_trans_rot_t(desired) quats_est = rot_to_quat(rot_est) # maxfilter produces some times that are implausibly large (weird) if not np.isclose(t[0], t_est[0], atol=1e-1): # within 100 ms raise AssertionError('Start times not within 100 ms: %0.3f != %0.3f' % (t[0], t_est[0])) use_mask = (t >= t_est[0]) & (t <= t_est[-1]) t = t[use_mask] trans = trans[use_mask] quats = rot_to_quat(rot) quats = quats[use_mask] # double-check our angle function for q in (quats, quats_est): angles = _angle_between_quats(q, q) assert_allclose(angles, 0., atol=1e-5) # limit translation difference between MF and our estimation trans_est_interp = interp1d(t_est, trans_est, axis=0)(t) distances = np.sqrt(np.sum((trans - trans_est_interp) ** 2, axis=1)) assert np.isfinite(distances).all() arg_worst = np.argmax(distances) assert distances[arg_worst] <= dist_tol, ( '@ %0.3f seconds: %0.3f > %0.3f mm' % (t[arg_worst], 1000 * distances[arg_worst], 1000 * dist_tol)) # limit rotation difference between MF and our estimation # (note that the interpolation will make this slightly worse) quats_est_interp = interp1d(t_est, quats_est, axis=0)(t) angles = 180 * _angle_between_quats(quats_est_interp, quats) / np.pi arg_worst = np.argmax(angles) assert angles[arg_worst] <= angle_tol, ( '@ %0.3f seconds: %0.3f > %0.3f deg' % (t[arg_worst], angles[arg_worst], angle_tol))
def _assert_quats(actual, desired, dist_tol=0.003, angle_tol=5.): """Compare estimated cHPI positions.""" from scipy.interpolate import interp1d trans_est, rot_est, t_est = head_pos_to_trans_rot_t(actual) trans, rot, t = head_pos_to_trans_rot_t(desired) quats_est = rot_to_quat(rot_est) # maxfilter produces some times that are implausibly large (weird) if not np.isclose(t[0], t_est[0], atol=1e-1): # within 100 ms raise AssertionError('Start times not within 100 ms: %0.3f != %0.3f' % (t[0], t_est[0])) use_mask = (t >= t_est[0]) & (t <= t_est[-1]) t = t[use_mask] trans = trans[use_mask] quats = rot_to_quat(rot) quats = quats[use_mask] # double-check our angle function for q in (quats, quats_est): angles = _angle_between_quats(q, q) assert_allclose(angles, 0., atol=1e-5) # limit translation difference between MF and our estimation trans_est_interp = interp1d(t_est, trans_est, axis=0)(t) distances = np.sqrt(np.sum((trans - trans_est_interp) ** 2, axis=1)) arg_worst = np.argmax(distances) assert_true(distances[arg_worst] <= dist_tol, '@ %0.3f seconds: %0.3f > %0.3f mm' % (t[arg_worst], 1000 * distances[arg_worst], 1000 * dist_tol)) # limit rotation difference between MF and our estimation # (note that the interpolation will make this slightly worse) quats_est_interp = interp1d(t_est, quats_est, axis=0)(t) angles = 180 * _angle_between_quats(quats_est_interp, quats) / np.pi arg_worst = np.argmax(angles) assert_true(angles[arg_worst] <= angle_tol, '@ %0.3f seconds: %0.3f > %0.3f deg' % (t[arg_worst], angles[arg_worst], angle_tol))