def _compare_positions(a, b, max_dist=0.003, max_angle=5.): """Compare estimated cHPI positions""" from scipy.interpolate import interp1d trans, rot, t = a trans_est, rot_est, t_est = b quats_est = rot_to_quat(rot_est) # maxfilter produces some times that are implausibly large (weird) 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) # < 3 mm translation difference between MF and our estimation trans_est_interp = interp1d(t_est, trans_est, axis=0)(t) worst = np.sqrt(np.sum((trans - trans_est_interp)**2, axis=1)).max() assert_true(worst <= max_dist, '%0.1f > %0.1f mm' % (1000 * worst, 1000 * max_dist)) # < 5 degrees 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) worst = 180 * _angle_between_quats(quats_est_interp, quats).max() / np.pi assert_true(worst <= max_angle, '%0.1f > %0.1f deg' % ( worst, max_angle, ))
def _compare_positions(a, b, max_dist=0.003, max_angle=5.): """Compare estimated cHPI positions""" from scipy.interpolate import interp1d trans, rot, t = a trans_est, rot_est, t_est = b quats_est = rot_to_quat(rot_est) # maxfilter produces some times that are implausibly large (weird) 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) # < 3 mm translation difference between MF and our estimation trans_est_interp = interp1d(t_est, trans_est, axis=0)(t) worst = np.sqrt(np.sum((trans - trans_est_interp) ** 2, axis=1)).max() assert_true(worst <= max_dist, '%0.1f > %0.1f mm' % (1000 * worst, 1000 * max_dist)) # < 5 degrees 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) worst = 180 * _angle_between_quats(quats_est_interp, quats).max() / np.pi assert_true(worst <= max_angle, '%0.1f > %0.1f deg' % (worst, max_angle,))
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_vector_rotation(): """Test basic rotation matrix math.""" x = np.array([1., 0., 0.]) y = np.array([0., 1., 0.]) rot = _find_vector_rotation(x, y) assert_array_equal(rot, [[0, -1, 0], [1, 0, 0], [0, 0, 1]]) quat_1 = rot_to_quat(rot) quat_2 = rot_to_quat(np.eye(3)) assert_allclose(_angle_between_quats(quat_1, quat_2), np.pi / 2.)
def test_quaternions(): """Test quaternion calculations.""" rots = [np.eye(3)] for fname in [test_fif_fname, ctf_fname, hp_fif_fname]: rots += [read_info(fname)['dev_head_t']['trans'][:3, :3]] # nasty numerical cases rots += [ np.array([ [-0.99978541, -0.01873462, -0.00898756], [-0.01873462, 0.62565561, 0.77987608], [-0.00898756, 0.77987608, -0.62587152], ]) ] rots += [ np.array([ [0.62565561, -0.01873462, 0.77987608], [-0.01873462, -0.99978541, -0.00898756], [0.77987608, -0.00898756, -0.62587152], ]) ] rots += [ np.array([ [-0.99978541, -0.00898756, -0.01873462], [-0.00898756, -0.62587152, 0.77987608], [-0.01873462, 0.77987608, 0.62565561], ]) ] for rot in rots: assert_allclose(rot, quat_to_rot(rot_to_quat(rot)), rtol=1e-5, atol=1e-5) rot = rot[np.newaxis, np.newaxis, :, :] assert_allclose(rot, quat_to_rot(rot_to_quat(rot)), rtol=1e-5, atol=1e-5) # let's make sure our angle function works in some reasonable way for ii in range(3): for jj in range(3): a = np.zeros(3) b = np.zeros(3) a[ii] = 1. b[jj] = 1. expected = np.pi if ii != jj else 0. assert_allclose(_angle_between_quats(a, b), expected, atol=1e-5) y_180 = np.array([[-1, 0, 0], [0, 1, 0], [0, 0, -1.]]) assert_allclose(_angle_between_quats(rot_to_quat(y_180), np.zeros(3)), np.pi) h_180_attitude_90 = np.array([[0, 1, 0], [1, 0, 0], [0, 0, -1.]]) assert_allclose( _angle_between_quats(rot_to_quat(h_180_attitude_90), np.zeros(3)), np.pi)
def _assert_trans(actual, desired, dist_tol=0.003, angle_tol=5.): trans_est = actual[0:3, 3] quat_est = rot_to_quat(actual[0:3, 0:3]) trans = desired[0:3, 3] quat = rot_to_quat(desired[0:3, 0:3]) angle = 180 * _angle_between_quats(quat_est, quat) / np.pi dist = np.sqrt(np.sum((trans - trans_est) ** 2)) assert dist <= dist_tol, '%0.3f > %0.3f mm' % (1000 * dist, 1000 * dist_tol) assert angle <= angle_tol, '%0.3f > %0.3f deg' % (angle, angle_tol)
def _assert_trans(actual, desired, dist_tol=0.003, angle_tol=5.): trans_est = actual[0:3, 3] quat_est = rot_to_quat(actual[0:3, 0:3]) trans = desired[0:3, 3] quat = rot_to_quat(desired[0:3, 0:3]) angle = 180 * _angle_between_quats(quat_est, quat) / np.pi dist = np.sqrt(np.sum((trans - trans_est)**2)) assert_true(dist <= dist_tol, '%0.3f > %0.3f mm' % (1000 * dist, 1000 * dist_tol)) assert_true(angle <= angle_tol, '%0.3f > %0.3f deg' % (angle, angle_tol))
def _assert_trans(actual, desired, dist_tol=0.017, angle_tol=5.): __tracebackhide__ = True trans_est = actual[0:3, 3] quat_est = rot_to_quat(actual[0:3, 0:3]) trans = desired[0:3, 3] quat = rot_to_quat(desired[0:3, 0:3]) angle = np.rad2deg(_angle_between_quats(quat_est, quat)) dist = np.linalg.norm(trans - trans_est) assert dist <= dist_tol, \ '%0.3f > %0.3f mm translation' % (1000 * dist, 1000 * dist_tol) assert angle <= angle_tol, \ '%0.3f > %0.3f° rotation' % (angle, angle_tol)
def test_set_montage_artinis_fsaverage(kind): """Test that artinis montages match fsaverage's head<->MRI transform.""" # Compare OctaMon and Brite23 to fsaverage trans_fs, _ = _get_trans('fsaverage') montage = make_standard_montage(f'artinis-{kind}') trans = compute_native_head_t(montage) assert trans['to'] == trans_fs['to'] assert trans['from'] == trans_fs['from'] translation = 1000 * np.linalg.norm(trans['trans'][:3, 3] - trans_fs['trans'][:3, 3]) assert 0 < translation < 1 # mm rotation = np.rad2deg( _angle_between_quats(rot_to_quat(trans['trans'][:3, :3]), rot_to_quat(trans_fs['trans'][:3, :3]))) assert 0 < rotation < 1 # degrees
def test_talxfm_rigid(): """Test that talxfm_rigid gives reasonable results.""" rigid = _estimate_talxfm_rigid('fsaverage', subjects_dir=subjects_dir) assert_allclose(rigid, np.eye(4), atol=1e-6) rigid = _estimate_talxfm_rigid('sample', subjects_dir=subjects_dir) assert_allclose(np.linalg.norm(rigid[:3, :3], axis=1), 1., atol=1e-6) move = 1000 * np.linalg.norm(rigid[:3, 3]) assert 30 < move < 70 ang = np.rad2deg(_angle_between_quats(rot_to_quat(rigid[:3, :3]))) assert 20 < ang < 25
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 test_quaternions(): """Test quaternion calculations """ rots = [np.eye(3)] for fname in [test_fif_fname, ctf_fname, hp_fif_fname]: rots += [read_info(fname)['dev_head_t']['trans'][:3, :3]] # nasty numerical cases rots += [np.array([ [-0.99978541, -0.01873462, -0.00898756], [-0.01873462, 0.62565561, 0.77987608], [-0.00898756, 0.77987608, -0.62587152], ])] rots += [np.array([ [0.62565561, -0.01873462, 0.77987608], [-0.01873462, -0.99978541, -0.00898756], [0.77987608, -0.00898756, -0.62587152], ])] rots += [np.array([ [-0.99978541, -0.00898756, -0.01873462], [-0.00898756, -0.62587152, 0.77987608], [-0.01873462, 0.77987608, 0.62565561], ])] for rot in rots: assert_allclose(rot, quat_to_rot(rot_to_quat(rot)), rtol=1e-5, atol=1e-5) rot = rot[np.newaxis, np.newaxis, :, :] assert_allclose(rot, quat_to_rot(rot_to_quat(rot)), rtol=1e-5, atol=1e-5) # let's make sure our angle function works in some reasonable way for ii in range(3): for jj in range(3): a = np.zeros(3) b = np.zeros(3) a[ii] = 1. b[jj] = 1. expected = np.pi if ii != jj else 0. assert_allclose(_angle_between_quats(a, b), expected, atol=1e-5)
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))
def test_initial_fit_redo(): """Test that initial fits can be redone based on moments.""" raw = read_raw_fif(chpi_fif_fname, allow_maxshield='yes') slopes = np.array( [[c['slopes'] for c in raw.info['hpi_meas'][0]['hpi_coils']]]) amps = np.linalg.norm(slopes, axis=-1) amps /= slopes.shape[-1] assert_array_less(amps, 5e-11) assert_array_less(1e-12, amps) proj, _, _ = _setup_ext_proj(raw.info, ext_order=1) chpi_amplitudes = dict(times=np.zeros(1), slopes=slopes, proj=proj) chpi_locs = compute_chpi_locs(raw.info, chpi_amplitudes) # check GOF coil_gof = raw.info['hpi_results'][0]['goodness'] assert_allclose(chpi_locs['gofs'][0], coil_gof, atol=0.3) # XXX not good # check moment # XXX our forward and theirs differ by an extra mult by _MAG_FACTOR coil_moment = raw.info['hpi_results'][0]['moments'] / _MAG_FACTOR py_moment = chpi_locs['moments'][0] coil_amp = np.linalg.norm(coil_moment, axis=-1, keepdims=True) py_amp = np.linalg.norm(py_moment, axis=-1, keepdims=True) assert_allclose(coil_amp, py_amp, rtol=0.2) coil_ori = coil_moment / coil_amp py_ori = py_moment / py_amp angles = np.rad2deg(np.arccos(np.abs(np.sum(coil_ori * py_ori, axis=1)))) assert_array_less(angles, 20) # check resulting dev_head_t head_pos = compute_head_pos(raw.info, chpi_locs) assert head_pos.shape == (1, 10) nm_pos = raw.info['dev_head_t']['trans'] dist = 1000 * np.linalg.norm(nm_pos[:3, 3] - head_pos[0, 4:7]) assert 0.1 < dist < 2 angle = np.rad2deg( _angle_between_quats(rot_to_quat(nm_pos[:3, :3]), head_pos[0, 1:4])) assert 0.1 < angle < 2 gof = head_pos[0, 7] assert_allclose(gof, 0.9999, atol=1e-4)
def test_simulate_calculate_head_pos_chpi(): """Test calculation of cHPI positions with simulated data.""" # Read info dict from raw FIF file info = read_info(raw_fname) # Tune the info structure chpi_channel = u'STI201' ncoil = len(info['hpi_results'][0]['order']) coil_freq = 10 + np.arange(ncoil) * 5 hpi_subsystem = { 'event_channel': chpi_channel, 'hpi_coils': [{ 'event_bits': np.array([256, 0, 256, 256], dtype=np.int32) }, { 'event_bits': np.array([512, 0, 512, 512], dtype=np.int32) }, { 'event_bits': np.array([1024, 0, 1024, 1024], dtype=np.int32) }, { 'event_bits': np.array([2048, 0, 2048, 2048], dtype=np.int32) }], 'ncoil': ncoil } info['hpi_subsystem'] = hpi_subsystem for l, freq in enumerate(coil_freq): info['hpi_meas'][0]['hpi_coils'][l]['coil_freq'] = freq picks = pick_types(info, meg=True, stim=True, eeg=False, exclude=[]) info['sfreq'] = 100. # this will speed it up a lot info = pick_info(info, picks) info['chs'][info['ch_names'].index('STI 001')]['ch_name'] = 'STI201' info._update_redundant() info['projs'] = [] info_trans = info['dev_head_t']['trans'].copy() dev_head_pos_ini = np.concatenate( [rot_to_quat(info_trans[:3, :3]), info_trans[:3, 3]]) ez = np.array([0, 0, 1]) # Unit vector in z-direction of head coordinates # Define some constants duration = 10 # Time / s # Quotient of head position sampling frequency # and raw sampling frequency head_pos_sfreq_quotient = 0.01 # Round number of head positions to the next integer S = int(duration * info['sfreq'] * head_pos_sfreq_quotient) assert S == 10 dz = 0.001 # Shift in z-direction is 0.1mm for each step dev_head_pos = np.zeros((S, 10)) dev_head_pos[:, 0] = np.arange(S) * info['sfreq'] * head_pos_sfreq_quotient dev_head_pos[:, 1:4] = dev_head_pos_ini[:3] dev_head_pos[:, 4:7] = dev_head_pos_ini[3:] + \ np.outer(np.arange(S) * dz, ez) dev_head_pos[:, 7] = 1.0 # m/s dev_head_pos[:, 9] = dz / (info['sfreq'] * head_pos_sfreq_quotient) # Round number of samples to the next integer raw_data = np.zeros((len(picks), int(duration * info['sfreq'] + 0.5))) raw = RawArray(raw_data, info) add_chpi(raw, dev_head_pos) quats = _calculate_chpi_positions( raw, t_step_min=raw.info['sfreq'] * head_pos_sfreq_quotient, t_step_max=raw.info['sfreq'] * head_pos_sfreq_quotient, t_window=1.0) _assert_quats(quats, dev_head_pos, dist_tol=0.001, angle_tol=1., vel_atol=4e-3) # 4 mm/s
mymz_rot = np.dot(y_rot.T, z_rot.T) # Create different head rotations, one per second rots = [x_rot, y_rot, z_rot, xz_rot, xmz_rot, yz_rot, mymz_rot] # The transpose of a rotation matrix is a rotation in the opposite direction rots += [rot.T for rot in rots] raw = mne.io.Raw(raw_fname).crop(0, len(rots)) raw.load_data().pick_types(meg=True, stim=True, copy=False) raw.add_proj([], remove_existing=True) center = (0., 0., 0.04) # a bit lower than device origin raw.info['dev_head_t']['trans'] = np.eye(4) raw.info['dev_head_t']['trans'][:3, 3] = center pos = np.zeros((len(rots), 10)) for ii in range(len(pos)): pos[ii] = np.concatenate([[ii], rot_to_quat(rots[ii]), center, [0] * 3]) pos[:, 0] += raw.first_samp / raw.info['sfreq'] # initial offset # Let's activate a vertices bilateral auditory cortices src = mne.read_source_spaces(src_fname) labels = mne.read_labels_from_annot('sample', 'aparc.a2009s', 'both', regexp='G_temp_sup-Plan_tempo', subjects_dir=subjects_dir) assert len(labels) == 2 # one left, one right vertices = [ np.intersect1d(l.vertices, s['vertno']) for l, s in zip(labels, src) ] data = np.zeros([sum(len(v) for v in vertices), int(raw.info['sfreq'])]) activation = np.hanning(int(raw.info['sfreq'] * 0.2)) * 1e-9 # nAm
def test_simulate_calculate_chpi_positions(): """Test calculation of cHPI positions with simulated data.""" # Read info dict from raw FIF file info = read_info(raw_fname) # Tune the info structure chpi_channel = u'STI201' ncoil = len(info['hpi_results'][0]['order']) coil_freq = 10 + np.arange(ncoil) * 5 hpi_subsystem = {'event_channel': chpi_channel, 'hpi_coils': [{'event_bits': np.array([256, 0, 256, 256], dtype=np.int32)}, {'event_bits': np.array([512, 0, 512, 512], dtype=np.int32)}, {'event_bits': np.array([1024, 0, 1024, 1024], dtype=np.int32)}, {'event_bits': np.array([2048, 0, 2048, 2048], dtype=np.int32)}], 'ncoil': ncoil} info['hpi_subsystem'] = hpi_subsystem for l, freq in enumerate(coil_freq): info['hpi_meas'][0]['hpi_coils'][l]['coil_freq'] = freq picks = pick_types(info, meg=True, stim=True, eeg=False, exclude=[]) info['sfreq'] = 100. # this will speed it up a lot info = pick_info(info, picks) info['chs'][info['ch_names'].index('STI 001')]['ch_name'] = 'STI201' info._update_redundant() info['projs'] = [] info_trans = info['dev_head_t']['trans'].copy() dev_head_pos_ini = np.concatenate([rot_to_quat(info_trans[:3, :3]), info_trans[:3, 3]]) ez = np.array([0, 0, 1]) # Unit vector in z-direction of head coordinates # Define some constants duration = 10 # Time / s # Quotient of head position sampling frequency # and raw sampling frequency head_pos_sfreq_quotient = 0.01 # Round number of head positions to the next integer S = int(duration * info['sfreq'] * head_pos_sfreq_quotient) assert S == 10 dz = 0.001 # Shift in z-direction is 0.1mm for each step dev_head_pos = np.zeros((S, 10)) dev_head_pos[:, 0] = np.arange(S) * info['sfreq'] * head_pos_sfreq_quotient dev_head_pos[:, 1:4] = dev_head_pos_ini[:3] dev_head_pos[:, 4:7] = dev_head_pos_ini[3:] + \ np.outer(np.arange(S) * dz, ez) dev_head_pos[:, 7] = 1.0 # cm/s dev_head_pos[:, 9] = 100 * dz / (info['sfreq'] * head_pos_sfreq_quotient) # Round number of samples to the next integer raw_data = np.zeros((len(picks), int(duration * info['sfreq'] + 0.5))) raw = RawArray(raw_data, info) add_chpi(raw, dev_head_pos) quats = _calculate_chpi_positions( raw, t_step_min=raw.info['sfreq'] * head_pos_sfreq_quotient, t_step_max=raw.info['sfreq'] * head_pos_sfreq_quotient, t_window=1.0) _assert_quats(quats, dev_head_pos, dist_tol=0.001, angle_tol=1.)
def test_compute_fine_cal(): """Test computing fine calibration coefficients.""" raw = read_raw_fif(erm_fname) want_cal = read_fine_calibration(cal_mf_fname) got_cal, counts = compute_fine_calibration(raw, cross_talk=ctc, n_imbalance=1, verbose='debug') assert counts == 1 assert set(got_cal.keys()) == set(want_cal.keys()) assert got_cal['ch_names'] == want_cal['ch_names'] # in practice these should never be exactly 1. assert sum([(ic == 1.).any() for ic in want_cal['imb_cals']]) == 0 assert sum([(ic == 1.).any() for ic in got_cal['imb_cals']]) == 0 got_imb = np.array(got_cal['imb_cals'], float) want_imb = np.array(want_cal['imb_cals'], float) assert got_imb.shape == want_imb.shape == (306, 1) got_imb, want_imb = got_imb[:, 0], want_imb[:, 0] orig_locs = np.array([ch['loc'] for ch in raw.info['chs'][:306]]) want_locs = want_cal['locs'] got_locs = got_cal['locs'] assert want_locs.shape == got_locs.shape orig_trans = _loc_to_coil_trans(orig_locs) want_trans = _loc_to_coil_trans(want_locs) got_trans = _loc_to_coil_trans(got_locs) dist = np.linalg.norm(got_trans[:, :3, 3] - want_trans[:, :3, 3], axis=1) assert_allclose(dist, 0., atol=1e-6) dist = np.linalg.norm(got_trans[:, :3, 3] - orig_trans[:, :3, 3], axis=1) assert_allclose(dist, 0., atol=1e-6) orig_quat = rot_to_quat(orig_trans[:, :3, :3]) want_quat = rot_to_quat(want_trans[:, :3, :3]) got_quat = rot_to_quat(got_trans[:, :3, :3]) want_orig_angles = np.rad2deg(_angle_between_quats(want_quat, orig_quat)) got_want_angles = np.rad2deg(_angle_between_quats(got_quat, want_quat)) got_orig_angles = np.rad2deg(_angle_between_quats(got_quat, orig_quat)) for key in ('mag', 'grad'): # imb_cals value p = pick_types(raw.info, meg=key, exclude=()) r2 = np.dot(got_imb[p], want_imb[p]) / (np.linalg.norm(want_imb[p]) * np.linalg.norm(got_imb[p])) assert 0.99 < r2 <= 1.00001, f'{key}: {r2:0.3f}' # rotation angles want_orig_max_angle = want_orig_angles[p].max() got_orig_max_angle = got_orig_angles[p].max() got_want_max_angle = got_want_angles[p].max() if key == 'mag': assert 8 < want_orig_max_angle < 11, want_orig_max_angle assert 1 < got_orig_max_angle < 2, got_orig_max_angle assert 9 < got_want_max_angle < 11, got_want_max_angle else: # Some of these angles are large, but mostly this has to do with # processing a very short (one 10-sec segment), downsampled (90 Hz) # file assert 66 < want_orig_max_angle < 68, want_orig_max_angle assert 67 < got_orig_max_angle < 107, got_orig_max_angle assert 53 < got_want_max_angle < 60, got_want_max_angle kwargs = dict(bad_condition='warning', cross_talk=ctc, coord_frame='meg') raw_sss = maxwell_filter(raw, **kwargs) raw_sss_mf = maxwell_filter(raw, calibration=cal_mf_fname, **kwargs) raw_sss_py = maxwell_filter(raw, calibration=got_cal, **kwargs) _assert_shielding(raw_sss, raw, 26, 27) _assert_shielding(raw_sss_mf, raw, 61, 63) _assert_shielding(raw_sss_py, raw, 61, 63) # redoing with given mag data should yield same result got_cal_redo, _ = compute_fine_calibration(raw, cross_talk=ctc, n_imbalance=1, calibration=got_cal, verbose='debug') assert got_cal['ch_names'] == got_cal_redo['ch_names'] assert_allclose(got_cal['imb_cals'], got_cal_redo['imb_cals'], atol=5e-5) assert_allclose(got_cal['locs'], got_cal_redo['locs'], atol=1e-6) assert sum([(ic == 1.).any() for ic in got_cal['imb_cals']]) == 0 # redoing with 3 imlabance parameters should improve the shielding factor grad_picks = pick_types(raw.info, meg='grad') assert len(grad_picks) == 204 and grad_picks[0] == 0 got_grad_imbs = np.array( [got_cal['imb_cals'][pick] for pick in grad_picks]) assert got_grad_imbs.shape == (204, 1) got_cal_3, _ = compute_fine_calibration(raw, cross_talk=ctc, n_imbalance=3, calibration=got_cal, verbose='debug') got_grad_3_imbs = np.array( [got_cal_3['imb_cals'][pick] for pick in grad_picks]) assert got_grad_3_imbs.shape == (204, 3) corr = np.corrcoef(got_grad_3_imbs[:, 0], got_grad_imbs[:, 0])[0, 1] assert 0.6 < corr < 0.7 raw_sss_py = maxwell_filter(raw, calibration=got_cal_3, **kwargs) _assert_shielding(raw_sss_py, raw, 68, 70)
def test_coregistration(scale_mode, ref_scale, grow_hair, fiducials, fid_match): """Test automated coregistration.""" subject = 'sample' if fiducials is None: fiducials, coord_frame = read_fiducials(fid_fname) assert coord_frame == FIFF.FIFFV_COORD_MRI info = read_info(raw_fname) for d in info['dig']: d['r'] = d['r'] * ref_scale trans = read_trans(trans_fname) coreg = Coregistration(info, subject=subject, subjects_dir=subjects_dir, fiducials=fiducials) assert np.allclose(coreg._last_parameters, coreg._parameters) coreg.set_fid_match(fid_match) default_params = list(coreg._default_parameters) coreg.set_rotation(default_params[:3]) coreg.set_translation(default_params[3:6]) coreg.set_scale(default_params[6:9]) coreg.set_grow_hair(grow_hair) coreg.set_scale_mode(scale_mode) # Identity transform errs_id = coreg.compute_dig_mri_distances() is_scaled = ref_scale != [1., 1., 1.] id_max = 0.03 if is_scaled and scale_mode == '3-axis' else 0.02 assert 0.005 < np.median(errs_id) < id_max # Fiducial transform + scale coreg.fit_fiducials(verbose=True) assert coreg._extra_points_filter is None coreg.omit_head_shape_points(distance=0.02) assert coreg._extra_points_filter is not None errs_fid = coreg.compute_dig_mri_distances() assert_array_less(0, errs_fid) if is_scaled or scale_mode is not None: fid_max = 0.05 fid_med = 0.02 else: fid_max = 0.03 fid_med = 0.01 assert_array_less(errs_fid, fid_max) assert 0.001 < np.median(errs_fid) < fid_med assert not np.allclose(coreg._parameters, default_params) coreg.omit_head_shape_points(distance=-1) coreg.omit_head_shape_points(distance=5. / 1000) assert coreg._extra_points_filter is not None # ICP transform + scale coreg.fit_icp(verbose=True) assert isinstance(coreg.trans, Transform) errs_icp = coreg.compute_dig_mri_distances() assert_array_less(0, errs_icp) if is_scaled or scale_mode == '3-axis': icp_max = 0.015 else: icp_max = 0.01 assert_array_less(errs_icp, icp_max) assert 0.001 < np.median(errs_icp) < 0.004 assert np.rad2deg( _angle_between_quats(rot_to_quat(coreg.trans['trans'][:3, :3]), rot_to_quat(trans['trans'][:3, :3]))) < 13 if scale_mode is None: atol = 1e-7 else: atol = 0.35 assert_allclose(coreg._scale, ref_scale, atol=atol) coreg.reset() assert_allclose(coreg._parameters, default_params)
def test_simulate_calculate_chpi_positions(): """Test calculation of cHPI positions with simulated data.""" # Read info dict from raw FIF file info = read_info(raw_fname) # Tune the info structure chpi_channel = u'STI201' ncoil = len(info['hpi_results'][0]['order']) coil_freq = 10 + np.arange(ncoil) * 5 hpi_subsystem = { 'event_channel': chpi_channel, 'hpi_coils': [{ 'event_bits': np.array([256, 0, 256, 256], dtype=np.int32) }, { 'event_bits': np.array([512, 0, 512, 512], dtype=np.int32) }, { 'event_bits': np.array([1024, 0, 1024, 1024], dtype=np.int32) }, { 'event_bits': np.array([2048, 0, 2048, 2048], dtype=np.int32) }], 'ncoil': ncoil } info['hpi_subsystem'] = hpi_subsystem for l, freq in enumerate(coil_freq): info['hpi_meas'][0]['hpi_coils'][l]['coil_freq'] = freq picks = pick_types(info, meg=True, stim=True, eeg=False, exclude=[]) info['sfreq'] = 100. # this will speed it up a lot info = pick_info(info, picks) info['chs'][info['ch_names'].index('STI 001')]['ch_name'] = 'STI201' info._update_redundant() info['projs'] = [] info_trans = info['dev_head_t']['trans'].copy() dev_head_pos_ini = np.concatenate( [rot_to_quat(info_trans[:3, :3]), info_trans[:3, 3]]) ez = np.array([0, 0, 1]) # Unit vector in z-direction of head coordinates # Define some constants duration = 30 # Time / s # Quotient of head position sampling frequency # and raw sampling frequency head_pos_sfreq_quotient = 0.1 # Round number of head positions to the next integer S = int(duration / (info['sfreq'] * head_pos_sfreq_quotient)) dz = 0.001 # Shift in z-direction is 0.1mm for each step dev_head_pos = np.zeros((S, 10)) dev_head_pos[:, 0] = np.arange(S) * info['sfreq'] * head_pos_sfreq_quotient dev_head_pos[:, 1:4] = dev_head_pos_ini[:3] dev_head_pos[:, 4:7] = dev_head_pos_ini[3:] + \ np.outer(np.arange(S) * dz, ez) dev_head_pos[:, 7] = 1.0 # cm/s dev_head_pos[:, 9] = 100 * dz / (info['sfreq'] * head_pos_sfreq_quotient) # Round number of samples to the next integer raw_data = np.zeros((len(picks), int(duration * info['sfreq'] + 0.5))) raw = RawArray(raw_data, info) dip = Dipole(np.array([0.0, 0.1, 0.2]), np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]]), np.array([1e-9, 1e-9, 1e-9]), np.array([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]), np.array([1.0, 1.0, 1.0]), 'dip') sphere = make_sphere_model('auto', 'auto', info=info, relative_radii=(1.0, 0.9), sigmas=(0.33, 0.3)) fwd, stc = make_forward_dipole(dip, sphere, info) stc.resample(info['sfreq']) raw = simulate_raw(raw, stc, None, fwd['src'], sphere, cov=None, blink=False, ecg=False, chpi=True, head_pos=dev_head_pos, mindist=1.0, interp='zero', verbose=None) quats = _calculate_chpi_positions( raw, t_step_min=raw.info['sfreq'] * head_pos_sfreq_quotient, t_step_max=raw.info['sfreq'] * head_pos_sfreq_quotient, t_window=1.0) _assert_quats(quats, dev_head_pos, dist_tol=0.001, angle_tol=1.)
def test_simulate_calculate_chpi_positions(): """Test calculation of cHPI positions with simulated data.""" # Read info dict from raw FIF file info = read_info(raw_fname) # Tune the info structure chpi_channel = u'STI201' ncoil = len(info['hpi_results'][0]['order']) coil_freq = 10 + np.arange(ncoil) * 5 hpi_subsystem = {'event_channel': chpi_channel, 'hpi_coils': [{'event_bits': np.array([256, 0, 256, 256], dtype=np.int32)}, {'event_bits': np.array([512, 0, 512, 512], dtype=np.int32)}, {'event_bits': np.array([1024, 0, 1024, 1024], dtype=np.int32)}, {'event_bits': np.array([2048, 0, 2048, 2048], dtype=np.int32)}], 'ncoil': ncoil} info['hpi_subsystem'] = hpi_subsystem for l, freq in enumerate(coil_freq): info['hpi_meas'][0]['hpi_coils'][l]['coil_freq'] = freq picks = pick_types(info, meg=True, stim=True, eeg=False, exclude=[]) info['sfreq'] = 100. # this will speed it up a lot info = pick_info(info, picks) info['chs'][info['ch_names'].index('STI 001')]['ch_name'] = 'STI201' info._update_redundant() info['projs'] = [] info_trans = info['dev_head_t']['trans'].copy() dev_head_pos_ini = np.concatenate([rot_to_quat(info_trans[:3, :3]), info_trans[:3, 3]]) ez = np.array([0, 0, 1]) # Unit vector in z-direction of head coordinates # Define some constants duration = 30 # Time / s # Quotient of head position sampling frequency # and raw sampling frequency head_pos_sfreq_quotient = 0.1 # Round number of head positions to the next integer S = int(duration / (info['sfreq'] * head_pos_sfreq_quotient)) dz = 0.001 # Shift in z-direction is 0.1mm for each step dev_head_pos = np.zeros((S, 10)) dev_head_pos[:, 0] = np.arange(S) * info['sfreq'] * head_pos_sfreq_quotient dev_head_pos[:, 1:4] = dev_head_pos_ini[:3] dev_head_pos[:, 4:7] = dev_head_pos_ini[3:] + \ np.outer(np.arange(S) * dz, ez) dev_head_pos[:, 7] = 1.0 # cm/s dev_head_pos[:, 9] = 100 * dz / (info['sfreq'] * head_pos_sfreq_quotient) # Round number of samples to the next integer raw_data = np.zeros((len(picks), int(duration * info['sfreq'] + 0.5))) raw = RawArray(raw_data, info) dip = Dipole(np.array([0.0, 0.1, 0.2]), np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]]), np.array([1e-9, 1e-9, 1e-9]), np.array([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]), np.array([1.0, 1.0, 1.0]), 'dip') sphere = make_sphere_model('auto', 'auto', info=info, relative_radii=(1.0, 0.9), sigmas=(0.33, 0.3)) fwd, stc = make_forward_dipole(dip, sphere, info) stc.resample(info['sfreq']) raw = simulate_raw(raw, stc, None, fwd['src'], sphere, cov=None, blink=False, ecg=False, chpi=True, head_pos=dev_head_pos, mindist=1.0, interp='zero', verbose=None, use_cps=True) quats = _calculate_chpi_positions( raw, t_step_min=raw.info['sfreq'] * head_pos_sfreq_quotient, t_step_max=raw.info['sfreq'] * head_pos_sfreq_quotient, t_window=1.0) _assert_quats(quats, dev_head_pos, dist_tol=0.001, angle_tol=1.)
# Create different head rotations, one per second rots = [x_rot, y_rot, z_rot, xz_rot, xmz_rot, yz_rot, mymz_rot] # The transpose of a rotation matrix is a rotation in the opposite direction rots += [rot.T for rot in rots] raw = mne.io.Raw(raw_fname).crop(0, len(rots)) raw.load_data().pick_types(meg=True, stim=True, copy=False) raw.add_proj([], remove_existing=True) center = (0., 0., 0.04) # a bit lower than device origin raw.info['dev_head_t']['trans'] = np.eye(4) raw.info['dev_head_t']['trans'][:3, 3] = center pos = np.zeros((len(rots), 10)) for ii in range(len(pos)): pos[ii] = np.concatenate([[ii], rot_to_quat(rots[ii]), center, [0] * 3]) pos[:, 0] += raw.first_samp / raw.info['sfreq'] # initial offset # Let's activate a vertices bilateral auditory cortices src = mne.read_source_spaces(src_fname) labels = mne.read_labels_from_annot('sample', 'aparc.a2009s', 'both', regexp='G_temp_sup-Plan_tempo', subjects_dir=subjects_dir) assert len(labels) == 2 # one left, one right vertices = [np.intersect1d(l.vertices, s['vertno']) for l, s in zip(labels, src)] data = np.zeros([sum(len(v) for v in vertices), int(raw.info['sfreq'])]) activation = np.hanning(int(raw.info['sfreq'] * 0.2)) * 1e-9 # nAm t_offset = int(np.ceil(0.2 * raw.info['sfreq'])) # 200 ms in (after baseline) data[:, t_offset:t_offset + len(activation)] = activation stc = mne.SourceEstimate(data, vertices, tmin=-0.2,