|
| 1 | +# -*- coding: utf-8 -*- |
| 2 | +""" |
| 3 | +.. _ex-ieeg-micro: |
| 4 | +
|
| 5 | +==================================================== |
| 6 | +Locating micro-scale intracranial electrode contacts |
| 7 | +==================================================== |
| 8 | +
|
| 9 | +When intracranial electrode contacts are very small, sometimes |
| 10 | +the computed tomography (CT) scan is higher resolution than the |
| 11 | +magnetic resonance (MR) image and so you want to find the contacts |
| 12 | +on the CT without downsampling to the MR resolution. This example |
| 13 | +shows how to do this. |
| 14 | +""" |
| 15 | + |
| 16 | +# Authors: Alex Rockhill <aprockhill@mailbox.org> |
| 17 | +# |
| 18 | +# License: BSD-3-Clause |
| 19 | + |
| 20 | +import numpy as np |
| 21 | +import nibabel as nib |
| 22 | +import mne |
| 23 | + |
| 24 | +# path to sample sEEG |
| 25 | +misc_path = mne.datasets.misc.data_path() |
| 26 | +subjects_dir = misc_path / 'seeg' |
| 27 | + |
| 28 | +# GUI requires pyvista backend |
| 29 | +mne.viz.set_3d_backend('pyvistaqt') |
| 30 | + |
| 31 | +# we need three things: |
| 32 | +# 1) The electrophysiology file which contains the channels names |
| 33 | +# that we would like to associate with positions in the brain |
| 34 | +# 2) The CT where the electrode contacts show up with high intensity |
| 35 | +# 3) The MR where the brain is best visible (low contrast in CT) |
| 36 | +raw = mne.io.read_raw(misc_path / 'seeg' / 'sample_seeg_ieeg.fif') |
| 37 | +CT_orig = nib.load(misc_path / 'seeg' / 'sample_seeg_CT.mgz') |
| 38 | +T1 = nib.load(misc_path / 'seeg' / 'sample_seeg' / 'mri' / 'T1.mgz') |
| 39 | + |
| 40 | +# we'll also need a head-CT surface RAS transform, this can be faked with an |
| 41 | +# identify matrix but we'll find the fiducials on the CT in freeview (be sure |
| 42 | +# to find them in surface RAS (TkReg RAS in freeview) and not scanner RAS |
| 43 | +# (RAS in freeview)) (also be sure to note left is generally on the right in |
| 44 | +# freeview) and reproduce them here: |
| 45 | +montage = mne.channels.make_dig_montage( |
| 46 | + nasion=[-28.97, -5.88, -76.40], lpa=[-96.35, -16.26, 17.63], |
| 47 | + rpa=[31.28, -52.95, -0.69], coord_frame='mri') |
| 48 | +raw.set_montage(montage, on_missing='ignore') # haven't located yet! |
| 49 | +head_ct_t = mne.transforms.invert_transform( |
| 50 | + mne.channels.compute_native_head_t(montage)) |
| 51 | + |
| 52 | +# note: coord_frame = 'mri' is a bit of a misnormer, it is a reference to |
| 53 | +# the surface RAS coordinate frame, here it is of the CT |
| 54 | + |
| 55 | + |
| 56 | +# launch the viewer with only the CT (note, we won't be able to use |
| 57 | +# the MR in this case to help determine which brain area the contact is |
| 58 | +# in), and use the user interface to find the locations of the contacts |
| 59 | +gui = mne.gui.locate_ieeg(raw.info, head_ct_t, CT_orig) |
| 60 | + |
| 61 | +# we'll programmatically mark all the contacts on one electrode shaft |
| 62 | +for i, pos in enumerate([(-52.66, -40.84, -26.99), (-55.47, -38.03, -27.92), |
| 63 | + (-57.68, -36.27, -28.85), (-59.89, -33.81, -29.32), |
| 64 | + (-62.57, -31.35, -30.37), (-65.13, -29.07, -31.30), |
| 65 | + (-67.57, -26.26, -31.88)]): |
| 66 | + gui.set_RAS(pos) |
| 67 | + gui.mark_channel(f'LENT {i + 1}') |
| 68 | + |
| 69 | +# finally, the coordinates will be in "head" (unless the trans was faked |
| 70 | +# as the identity, in which case they will be in surface RAS of the CT already) |
| 71 | +# so we need to convert them to scanner RAS of the CT, apply the alignment so |
| 72 | +# that they are in scanner RAS of the MRI and from there to surface RAS |
| 73 | +# of the MRI for viewing using freesurfer recon-all surfaces--fortunately |
| 74 | +# that is done for us in `mne.transforms.apply_volume_registration_points` |
| 75 | + |
| 76 | +# note that since we didn't fake the head->CT surface RAS transform, we |
| 77 | +# could apply the head->mri transform directly but that relies of the |
| 78 | +# fiducial points being marked exactly the same on the CT as on the MRI-- |
| 79 | +# the error from this is not precise enough for intracranial electrophysiology, |
| 80 | +# better is to rely on the precision of the CT-MR image registration |
| 81 | + |
| 82 | +reg_affine = np.array([ # CT-MR registration |
| 83 | + [0.99270756, -0.03243313, 0.11610254, -133.094156], |
| 84 | + [0.04374389, 0.99439665, -0.09623816, -97.58320673], |
| 85 | + [-0.11233068, 0.10061512, 0.98856381, -84.45551601], |
| 86 | + [0., 0., 0., 1.]]) |
| 87 | + |
| 88 | +raw.info, head_mri_t = mne.transforms.apply_volume_registration_points( |
| 89 | + raw.info, head_ct_t, CT_orig, T1, reg_affine) |
| 90 | + |
| 91 | +brain = mne.viz.Brain(subject='sample_seeg', subjects_dir=subjects_dir, |
| 92 | + alpha=0.5) |
| 93 | +brain.add_sensors(raw.info, head_mri_t) |
| 94 | +brain.show_view(azimuth=120, elevation=100) |
0 commit comments