def test_warp_montage_volume(): """Test warping an montage based on intracranial electrode positions.""" import nibabel as nib subject_T1 = nib.load(op.join(subjects_dir, 'sample', 'mri', 'T1.mgz')) subject_brain = nib.load( op.join(subjects_dir, 'sample', 'mri', 'brain.mgz')) template_brain = nib.load( op.join(subjects_dir, 'fsaverage', 'mri', 'brain.mgz')) reg_affine, sdr_morph = compute_volume_registration( subject_brain, template_brain, zooms=5, niter=dict(translation=[5, 5, 5], rigid=[5, 5, 5], sdr=[3, 3, 3]), pipeline=('translation', 'rigid', 'sdr')) # make fake image with three coordinates CT_data = np.zeros(subject_brain.shape) # make electrode contact hyperintensities CT_data[45:47, 39:41, 49:50] = 500 # surround high intensity CT_data[46, 40, 49] = 1000 # center even higher intensity CT_data[47:49, 39:40, 49:50] = 500 CT_data[48, 39, 50] = 1000 CT_data[50:52, 38:40, 50:51] = 500 CT_data[50, 39, 50] = 1000 CT = nib.Nifti1Image(CT_data, subject_T1.affine) ch_coords = np.array([[-8.7040273, 17.99938754, 10.29604017], [-14.03007764, 19.69978401, 12.07236939], [-21.1130506, 21.98310911, 13.25658887]]) ch_pos = dict(zip(['1', '2', '3'], ch_coords / 1000)) # mm -> m montage = make_dig_montage(ch_pos, coord_frame='mri') montage_warped, image_from, image_to = warp_montage_volume( montage, CT, reg_affine, sdr_morph, 'sample', subjects_dir=subjects_dir, thresh=0.99) # checked with nilearn plot from `tut-ieeg-localize` # check montage in surface RAS ground_truth_warped = np.array([[-0.27778788, 0.24251515, -0.35693939], [-0.30033333, 0.24785714, -0.35014286], [-0.32261947, 0.25295575, -0.34614159]]) for i in range(len(montage.ch_names)): assert np.linalg.norm( # off by less than 1.5 cm montage_warped.dig[i]['r'] - ground_truth_warped[i]) < 0.015 # check image_from assert_array_equal(np.array(np.where(_get_img_fdata(image_from) == 1)), np.array([[45, 46, 46], [40, 39, 40], [49, 49, 49]])) assert_array_equal(np.array(np.where(_get_img_fdata(image_from) == 2)), np.array([[48, 48], [39, 39], [49, 50]])) assert_array_equal(np.array(np.where(_get_img_fdata(image_from) == 3)), np.array([[50, 50, 51], [38, 39, 39], [50, 50, 50]])) # check image_to, too many, just check center ground_truth_warped_voxels = np.array( [[135.5959596, 161.97979798, 123.83838384], [143.11111111, 159.71428571, 125.61904762], [150.53982301, 158.38053097, 127.31858407]]) for i in range(len(montage.ch_names)): assert np.linalg.norm( np.array(np.where(_get_img_fdata(image_to) == i + 1)).mean( axis=1) - ground_truth_warped_voxels[i]) < 5 # test inputs with pytest.raises(ValueError, match='`thresh` must be between 0 and 1'): warp_montage_volume(montage, CT, reg_affine, sdr_morph, 'sample', thresh=11.) with pytest.raises(ValueError, match='subject folder is incorrect'): warp_montage_volume(montage, CT, reg_affine, sdr_morph, subject_from='foo') CT_unaligned = nib.Nifti1Image(CT_data, subject_brain.affine) with pytest.raises(RuntimeError, match='not aligned to Freesurfer'): warp_montage_volume(montage, CT_unaligned, reg_affine, sdr_morph, 'sample', subjects_dir=subjects_dir) bad_montage = make_dig_montage(ch_pos, coord_frame='mri') bad_montage.dig[0]['coord_frame'] = 99 with pytest.raises(RuntimeError, match='Only single coordinate frame in ' 'dig is supported'): warp_montage_volume(bad_montage, CT, reg_affine, sdr_morph, 'sample', subjects_dir=subjects_dir) wrong_montage = make_dig_montage(ch_pos, coord_frame='head') with pytest.raises(RuntimeError, match='Coordinate frame not supported'): warp_montage_volume(wrong_montage, CT, reg_affine, sdr_morph, 'sample', subjects_dir=subjects_dir) # check channel not warped ch_pos_doubled = ch_pos.copy() ch_pos_doubled.update(zip(['4', '5', '6'], ch_coords / 1000)) doubled_montage = make_dig_montage(ch_pos_doubled, coord_frame='mri') with pytest.warns(RuntimeWarning, match='not assigned'): warp_montage_volume(doubled_montage, CT, reg_affine, sdr_morph, 'sample', subjects_dir=subjects_dir)
def test_warp_montage_volume(): """Test warping an montage based on intracranial electrode positions.""" import nibabel as nib subject_brain = nib.load( op.join(subjects_dir, 'sample', 'mri', 'brain.mgz')) template_brain = nib.load( op.join(subjects_dir, 'fsaverage', 'mri', 'brain.mgz')) zooms = dict(translation=10, rigid=10, sdr=10) reg_affine, sdr_morph = compute_volume_registration( subject_brain, template_brain, zooms=zooms, niter=[3, 3, 3], pipeline=('translation', 'rigid', 'sdr')) # make an info object with three channels with positions ch_coords = np.array([[-8.7040273, 17.99938754, 10.29604017], [-14.03007764, 19.69978401, 12.07236939], [-21.1130506, 21.98310911, 13.25658887]]) ch_pos = dict(zip(['1', '2', '3'], ch_coords / 1000)) # mm -> m lpa, nasion, rpa = get_mni_fiducials('sample', subjects_dir) montage = make_dig_montage(ch_pos, lpa=lpa['r'], nasion=nasion['r'], rpa=rpa['r'], coord_frame='mri') # make fake image based on the info CT_data = np.zeros(subject_brain.shape) # convert to voxels ch_coords_vox = apply_trans( np.linalg.inv(subject_brain.header.get_vox2ras_tkr()), ch_coords) for (x, y, z) in ch_coords_vox.round().astype(int): # make electrode contact hyperintensities # first, make the surrounding voxels high intensity CT_data[x - 1:x + 2, y - 1:y + 2, z - 1:z + 2] = 500 # then, make the center even higher intensity CT_data[x, y, z] = 1000 CT = nib.Nifti1Image(CT_data, subject_brain.affine) ch_coords = np.array([[-8.7040273, 17.99938754, 10.29604017], [-14.03007764, 19.69978401, 12.07236939], [-21.1130506, 21.98310911, 13.25658887]]) ch_pos = dict(zip(['1', '2', '3'], ch_coords / 1000)) # mm -> m lpa, nasion, rpa = get_mni_fiducials('sample', subjects_dir) montage = make_dig_montage(ch_pos, lpa=lpa['r'], nasion=nasion['r'], rpa=rpa['r'], coord_frame='mri') montage_warped, image_from, image_to = warp_montage_volume( montage, CT, reg_affine, sdr_morph, 'sample', subjects_dir_from=subjects_dir, thresh=0.99) # checked with nilearn plot from `tut-ieeg-localize` # check montage in surface RAS ground_truth_warped = np.array([[-0.009, -0.00133333, -0.033], [-0.01445455, 0.00127273, -0.03163636], [-0.022, 0.00285714, -0.031]]) for i, d in enumerate(montage_warped.dig): assert np.linalg.norm( # off by less than 1.5 cm d['r'] - ground_truth_warped[i]) < 0.015 # check image_from for idx, contact in enumerate(range(1, len(ch_pos) + 1)): voxels = np.array(np.where(np.array(image_from.dataobj) == contact)).T assert ch_coords_vox.round()[idx] in voxels assert ch_coords_vox.round()[idx] + 5 not in voxels # check image_to, too many, just check center ground_truth_warped_voxels = np.array( [[135.5959596, 161.97979798, 123.83838384], [143.11111111, 159.71428571, 125.61904762], [150.53982301, 158.38053097, 127.31858407]]) for i in range(len(montage.ch_names)): assert np.linalg.norm( np.array(np.where(np.array(image_to.dataobj) == i + 1) ).mean(axis=1) - ground_truth_warped_voxels[i]) < 8 # test inputs with pytest.raises(ValueError, match='`thresh` must be between 0 and 1'): warp_montage_volume( montage, CT, reg_affine, sdr_morph, 'sample', thresh=11.) with pytest.raises(ValueError, match='subject folder is incorrect'): warp_montage_volume( montage, CT, reg_affine, sdr_morph, subject_from='foo') CT_unaligned = nib.Nifti1Image(CT_data, template_brain.affine) with pytest.raises(RuntimeError, match='not aligned to Freesurfer'): warp_montage_volume(montage, CT_unaligned, reg_affine, sdr_morph, 'sample', subjects_dir_from=subjects_dir) bad_montage = montage.copy() for d in bad_montage.dig: d['coord_frame'] = 99 with pytest.raises(RuntimeError, match='Coordinate frame not supported'): warp_montage_volume(bad_montage, CT, reg_affine, sdr_morph, 'sample', subjects_dir_from=subjects_dir) # check channel not warped ch_pos_doubled = ch_pos.copy() ch_pos_doubled.update(zip(['4', '5', '6'], ch_coords / 1000)) doubled_montage = make_dig_montage( ch_pos_doubled, lpa=lpa['r'], nasion=nasion['r'], rpa=rpa['r'], coord_frame='mri') with pytest.warns(RuntimeWarning, match='not assigned'): warp_montage_volume(doubled_montage, CT, reg_affine, None, 'sample', subjects_dir_from=subjects_dir)