Closed mmagnuski closed 1 year ago
The data are here.
The code to reproduce the problem:
import os
import os.path as op
import numpy as np
import matplotlib.pyplot as plt
import mne
import nibabel as nib
def plot_overlay(image, compare, title, thresh=None):
"""Define a helper function for comparing plots."""
image = nib.orientations.apply_orientation(
np.asarray(image.dataobj), nib.orientations.axcodes2ornt(
nib.orientations.aff2axcodes(image.affine))).astype(np.float32)
compare = nib.orientations.apply_orientation(
np.asarray(compare.dataobj), nib.orientations.axcodes2ornt(
nib.orientations.aff2axcodes(compare.affine))).astype(np.float32)
if thresh is not None:
compare[compare < np.quantile(compare, thresh)] = np.nan
fig, axes = plt.subplots(1, 3, figsize=(12, 4))
fig.suptitle(title)
for i, ax in enumerate(axes):
ax.imshow(np.take(image, [image.shape[i] // 2], axis=i).squeeze().T,
cmap='gray')
ax.imshow(np.take(compare, [compare.shape[i] // 2],
axis=i).squeeze().T, cmap='gist_heat', alpha=0.5)
ax.invert_yaxis()
ax.axis('off')
fig.tight_layout()
data_dir = r'CHANGE TO THE CORRECT PATH ON YOUR COMPUTER'
ct_fname = 'sub-U06_ct_Tilt_1.nii'
mri_fname = 'T1.mgz'
T1 = nib.load(op.join(data_dir, mri_fname))
CT_orig = nib.load(op.join(data_dir, ct_fname))
# load transform
manual_reg_affine_vox = mne.read_lta(op.join(
data_dir, 'sub-U06_ct_aligned_manual.nii.lta'))
# convert from vox->vox to ras->ras
manual_reg_affine = \
CT_orig.affine @ np.linalg.inv(manual_reg_affine_vox) \
@ np.linalg.inv(CT_orig.affine)
# show alignment
CT_aligned = mne.transforms.apply_volume_registration(
CT_orig, T1, manual_reg_affine, cval='1%')
plot_overlay(T1, CT_aligned, 'Aligned CT Overlaid on T1', thresh=0.95)
But the alignment looks correct in freeview:
freeview T1.mgz sub-U06_ct_Tilt_1.nii:colormap=heat:opacity=0.6:reg=sub-U06_ct_aligned_manual.nii.lta
@alexrockhill can you look?
Hmm the lta looks normal but from the error I would infer that there might be some scaling in the lta that's not rigid. Is that possible? Otherwise, it's just for logging, so you could try commenting those lines out and check if the result works. I looked in my own code and realized I was using:
manual_reg_affine_vox = mne.read_lta(
CT_fname.replace('.mgz', '_manual.mgz.lta'))
# convert from vox->vox to ras->ras
manual_reg_affine = \
CT_orig.affine @ np.linalg.inv(manual_reg_affine_vox) \
@ np.linalg.inv(CT_orig.affine)
CT_aligned_fix_img, reg_affine_fix = affine_registration(
moving=np.array(CT_orig.dataobj), static=np.array(T1.dataobj),
moving_affine=CT_orig.affine, static_affine=T1.affine,
pipeline=['rigid'], starting_affine=manual_reg_affine,
level_iters=[100], sigmas=[0], factors=[1])
for this step. It would be ideal if it were all encapsulated within MNE but this is a workaround until we figure it out. I can make a PR if we can determine if the lta is purely rigid.
I also looks like you skipped this step from the tutorial:
reg_affine, _ = mne.transforms.compute_volume_registration(
CT_orig, T1, pipeline=['rigid'],
starting_affine=manual_reg_affine)
which re-aligns based on the manual affine for machine-precision alignment. Was that on purpose? Doing this will give basically the same lta and I have tested that it runs for the sample data. That would fix any non-rigid alignment if it exists or if it's a rounding error or something.
@alexrockhill
It also looks like you skipped this step from the tutorial: (...)
I did not skip the starting_affine
step, this is exactly the step that raises the error I pasted in the first post. And it most likely raises the error because the manual transformation is not correct (ie it is different in mne to what I see in freeview). For more screenshots take a look at the discourse post.
Hmm the lta looks normal but from the error I would infer that there might be some scaling in the lta that's not rigid. Is that possible?
I don't think so, the only steps I performed in freeview when pre-aligning were translations and rotations. The only potentially problematic part is that the nii file of the ct has some tilt correction applied. This was done when creating the nii from dicoms (I think I did that with dcm2niix
, but I'll have to check) - could it be present in the CT_orig.affine
?
I have two other subjects that require manual pre-alignment of the CT, but that did not have and corrections applied to the CT (at least looking at the postfixes that dcm2niix normally creates when it applies these corrections). I can check if the lta for these files is read and displayed correctly.
Can you try the dev version of mne? I fixed a read_lta bug recently
oh, sure, I will!
Ok, great, the dev mne version of read_lta does the trick! Sorry for the noise!
I've outlined the problem in more detail on the discourse, but because I think it might be either a bug or somthing not taken into account in the instructions in the tutorial, I post it here too.
I have to follow the "Note" section in the "Locating intracranial electrode contacts" tutorial, because automatic CT-MRI alignment is not satisfactory in a few patients. I successfully align the CT and MRI by hand in freeview and save the .lta transformation file. However when I read the .lta and transform it from vox->vox to ras->ras, as outlined in the Note section of the tutorial, I see that the CT and MRI are not correctly aligned (this is before running the registration again with
starting_affine
):as a result running the registration with
starting_affine
fails, with the following error:I'll post a link to the files and a reproducible code example below.