Closed jasmainak closed 5 years ago
You might have to finish the work of @monkeyman192 in #77 to make this work though. So you can do a round trip and testing is easier ...
I'll tackle this on Monday :+1: it'd be really cool to generate trans
from this ... without having to store any derivative beyond the three coordinates of LPA, RPA, NAS
And directly after, I'll build this in to the somato dataset PR to bring it closer to a merge: https://github.com/mne-tools/mne-python/pull/6414
great!
I am a bit stuck - let me present the issue:
Let's look at our subject folder (somato dataset):
└── sub-01
├── anat
│ └── sub-01_T1w.nii.gz
├── meg
│ ├── sub-01_coordsystem.json
│ ├── sub-01_task-somato_channels.tsv
│ ├── sub-01_task-somato_events.tsv
│ ├── sub-01_task-somato_meg.fif
│ └── sub-01_task-somato_meg.json
└── sub-01_scans.tsv
Important files for us are
In this file we have the original physical locations (3D) of Nasion, RPA, and LPA ... as digitized by some measurement device (e.g. Polhemus)
duplication of Nasion RPA and LPA (same as in .fif
file) ... see MEG section on coordsystem.json ... the relevant field is HeadCoilCoordinates
This is the MRI that we want to coregister the subject's head to. Currently, it's simply a NIfTI file ... there is no information on 3D physical locations of Nasion, LPA, and RPA.
sub-01_T1w.json
sub-01_T1w.json
we can fill the field AnatomicalLandmarkCoordinates
as described in the MRI (!!!) section on sidecar files... then we would have:
└── sub-01
├── anat
│ ├── sub-01_T1w.nii.gz
│ └── sub-01_T1w.json
├── meg
│ ├── sub-01_coordsystem.json
│ ├── sub-01_task-somato_channels.tsv
│ ├── sub-01_task-somato_events.tsv
│ ├── sub-01_task-somato_meg.fif
│ └── sub-01_task-somato_meg.json
└── sub-01_scans.tsv
and with this, we could use
AnatomicalLandmarkCoordinates
from sub-01_T1w.json
andHeadCoilCoordinates
from sub-01_coordsystem.json
... to end up with 2 sets of each LPA, RPA, NAS ...
sub-01_coordsystem.json
) to equal the other set? ... And how to save it as a trans
file?we also have AnatomicalLandmarkCoordinates
in the sub-01_ccordsystem.json
, no?
You could use "fit fiducials" in MNE. Another way to look at this is that we want to learn a rotation matrix. I am not quite sure why MNE-Python does not do it this way. Maybe @larsoner can shed more light on it. In any case, you have the function in MNE that finds you the trans
file.
You don't need to save it, MNE almost everywhere accept the trans
object ...
It's not just a rotation matrix, but rotation + translation
Okay, but one question I have is still not resolved. These are the required inputs:
src_pts : array, shape = (n, 3)
Points to which the transform should be applied.
tgt_pts : array, shape = (n, 3)
Points to which src_pts should be fitted. Each point in tgt_pts should
correspond to the point in src_pts with the same index.
src_pts
or tgt_pts
?) corresponds to the 3D coords of NAS, LPA, RPA from the .fif
file and the digitization during the recording session... but how do I obtain the other one (src_pts
or tgt_pts
) from the MRI?
Typically you do not get trans
from doing this matched point business, but instead from a manual process. It should be more accurate than just using the LPA/Nasion/RPA, even once you mark them (manually or automatically) on the MRI.
The info['dev_head_t']
is often calculated from matched HPI point locations, though.
You have AnatomicalLandmarkCoordinates in _coordsystem.json?
You have
AnatomicalLandmarkCoordinates
in _coordsystem.json?
yes ... I would have that for MEG data ... but I wouldn't have that for the MRI data. UNLESS somebody took the extra effort to extract these points from the MRI (using fiducials, which are objects placed on anatomical landmarks to identify them later in an MRI or similar) and then save them in the T1w.json
BUT T1w.json
is optional ... to give an impression: Of all files in bids-examples, these are the T1w.json files
(my point is: only very few files have been specified, because it's merely recommended, not required)
./ds000248/acq-epi_T1w.json
./hcp_example_bids/sub-100307/anat/sub-100307_T1w.json
./eeg_ds000117/sub-13/anat/sub-13_T1w.json
./eeg_ds000117/sub-04/anat/sub-04_T1w.json
./eeg_ds000117/sub-15/anat/sub-15_T1w.json
./eeg_ds000117/sub-12/anat/sub-12_T1w.json
./eeg_ds000117/sub-16/anat/sub-16_T1w.json
./eeg_ds000117/sub-06/anat/sub-06_T1w.json
./eeg_ds000117/sub-14/anat/sub-14_T1w.json
./eeg_ds000117/sub-02/anat/sub-02_T1w.json
./eeg_ds000117/sub-11/anat/sub-11_T1w.json
./eeg_ds000117/sub-08/anat/sub-08_T1w.json
./eeg_ds000117/sub-07/anat/sub-07_T1w.json
./eeg_ds000117/sub-05/anat/sub-05_T1w.json
./eeg_ds000117/sub-01/anat/sub-01_T1w.json
./eeg_ds000117/sub-10/anat/sub-10_T1w.json
./eeg_ds000117/sub-09/anat/sub-09_T1w.json
./eeg_ds000117/sub-03/anat/sub-03_T1w.json
./ieeg_visual/sub-02/ses-01/anat/sub-02_ses-01_T1w.json
./ieeg_visual/sub-01/ses-01/anat/sub-01_ses-01_T1w.json
./eeg_rest_fmri/acq-y232_T1w.json
./eeg_rest_fmri/acq-y224_T1w.json
./ieeg_visual_multimodal/sub-som682/ses-nyu3t01/anat/sub-som682_ses-nyu3t01_T1w.json
./ds000117/sub-13/ses-mri/anat/sub-13_ses-mri_acq-mprage_T1w.json
./ds000117/sub-04/ses-mri/anat/sub-04_ses-mri_acq-mprage_T1w.json
./ds000117/sub-15/ses-mri/anat/sub-15_ses-mri_acq-mprage_T1w.json
./ds000117/sub-12/ses-mri/anat/sub-12_ses-mri_acq-mprage_T1w.json
./ds000117/sub-16/ses-mri/anat/sub-16_ses-mri_acq-mprage_T1w.json
./ds000117/sub-06/ses-mri/anat/sub-06_ses-mri_acq-mprage_T1w.json
./ds000117/sub-14/ses-mri/anat/sub-14_ses-mri_acq-mprage_T1w.json
./ds000117/sub-02/ses-mri/anat/sub-02_ses-mri_acq-mprage_T1w.json
./ds000117/sub-11/ses-mri/anat/sub-11_ses-mri_acq-mprage_T1w.json
./ds000117/sub-08/ses-mri/anat/sub-08_ses-mri_acq-mprage_T1w.json
./ds000117/sub-07/ses-mri/anat/sub-07_ses-mri_acq-mprage_T1w.json
./ds000117/sub-05/ses-mri/anat/sub-05_ses-mri_acq-mprage_T1w.json
./ds000117/sub-01/ses-mri/anat/sub-01_ses-mri_acq-mprage_T1w.json
./ds000117/sub-10/ses-mri/anat/sub-10_ses-mri_acq-mprage_T1w.json
./ds000117/sub-09/ses-mri/anat/sub-09_ses-mri_acq-mprage_T1w.json
./ds000117/acq-mprage_T1w.json
./ds000117/sub-03/ses-mri/anat/sub-03_ses-mri_acq-mprage_T1w.json
./ds000117/acq-epi_T1w.json
and of these, only the ds000117
actually has the AnatomicalLandmarkCoordinates
:grimacing:
Typically you do not get trans from doing this matched point business, but instead from a manual process. It should be more accurate than just using the LPA/Nasion/RPA, even once you mark them (manually or automatically) on the MRI.
Mmmh okay ... we want to automate it, because in BIDS it is very convenient to specify two sets of points (from which to calculate trans) ... but it's very underspecified how to store a trans file itself.
But if quality suffers, perhaps we are on the wrong path?
UNLESS somebody took the extra effort to extract these points from the MRI (using fiducials, which are objects placed on anatomical landmarks to identify them later in an MRI or similar) and then save them in the T1w.json
If the trans file exists, you could reverse engineer this and update some of the datasets? At least it's possible for the sample data, somato data etc.
only the ds000117 actually has the AnatomicalLandmarkCoordinates
which is fair because nobody actually tried to use BIDS so far for doing anything real. That's why we go with the crash-testing approach. You may need to make small PRs to BIDS-examples/ to add this.
But if quality suffers, perhaps we are on the wrong path?
You can use the "tol" parameter in fit_matched_points
perhaps. But it's true that it may not necessarily work always. Pinging @agramfort to pitch in
why do you want to actually fit anything from the bids files? it could contain the info from the trans file. It just stores the fid points in the MRI space from which the trans is analytically obtained. For MNE users we want a way to make this points from the trans.
do I miss something @larsoner ?
I'm not sure what the process should be here, I'm just helping people understand coordinate frames...
it could contain the info from the trans file.
It doesn't as far as I know. That's why we were considering using the BIDS specification for saving transforms. Which led to the discussion at OHBM that we could fit it from the points in MRI space and MEG space.
This came up in the context of somato data where @sappelhoff was not sure how to store the trans
files ...
Hearing what @agramfort says:
could contain the info from the trans file. It just stores the fid points in the MRI space from which the trans is analytically obtained. For MNE users we want a way to make this points from the trans.
I interpret this as the following:
.fif
file with landmarks in MEG spacetrans
(created manually)*_coordsystem.json
for MEG*_T1w.json
for MRItrans
again at some point, I can calculate it from the two sets of landmarks (MEG and MRI)--> If it is possible like this, it would solve my question "How do I get the landmarks in MRI space from the .nii
?" .. in short: I would get them if I have a trans
and the MEG landmarks ... because in that case somebody already has manually determined the MRI landmarks
--> Remaining problem (given my interpretation is correct so far) would be that calculating trans from two set of points is imprecise? Although I don't see how this can be the case, if we obtained the MRI landmarks from the trans
to begin with (it's just the transformation in the other direction again?)
yes it's what I have in mind
let me know if you need help to know where to find these info.
info['dig'] will give you LPA, RPA and NAS in head coord system and trans gives you transformation from Head to MRI
something like this:
trans = read_trans(...) trans = _get_trans(trans, 'head', 'mri) # https://github.com/mne-tools/mne-python/blob/master//mne/transforms.py#L433
then use apply_trans to dig points to get then in MRI coord.
HTH
calculating trans from two set of points is imprecise
I thought about this. If you have 3 points in either coordinates and they are not collinear, then it uniquely defines the transform. They would be in a plane, so there is only one set of transforms which can take them to the other coordinate frame. If you had 2 points, then you would have problems but with 3 points you are good.
The handling of raw.info['dig']
feels a bit user unfriendly, e.g., I expected to be able to do something like raw.info['dig']['Nasion']
, but this errors
TypeError: list indices must be integers or slices, not str
anyhow, this is my current solution:
"""Try handling trans files."""
import os.path as op
import numpy as np
import mne
from mne.datasets import somato
from mne.transforms import _get_trans, apply_trans
data_path = somato.data_path()
raw_path = op.join(data_path, 'MEG', 'somato', 'sef_raw_sss.fif')
trans_path = op.join(data_path, 'MEG', 'somato', 'sef_raw_sss-trans.fif')
raw = mne.io.read_raw_fif(raw_path)
def _extract_landmarks(raw):
# Assuming that LPA, Nasion, RPA are always first and in that order
meg_landmarks = np.asarray([raw.info['dig'][i]['r'] for i in range(3)])
return meg_landmarks
meg_landmarks = _extract_landmarks(raw)
trans = mne.read_trans(trans_path)
from_to_t, trans = _get_trans(trans)
transformed_pts = apply_trans(from_to_t, meg_landmarks)
print(meg_landmarks)
print(transformed_pts)
Opening raw data file /home/stefanappelhoff/mne_data/MNE-somato-data/MEG/somato/sef_raw_sss.fif...
Range : 237600 ... 506999 = 791.189 ... 1688.266 secs
Ready.
Current compensation grade : 0
[[-7.1441993e-02 0.0000000e+00 -1.0244548e-08]
[ 3.7252903e-09 1.0662061e-01 -9.3132257e-10]
[ 7.4268661e-02 -2.9802322e-08 -8.3819032e-09]]
[[-0.07086861 0.02738484 0.02025573]
[ 0.00304659 0.12651449 -0.01412104]
[ 0.07467662 0.0222522 0.01558245]]
Is there some nice way to check this visually?
not that I know of. You just need to make sure that when mne-bids reads it back you are able to retrieve the original trans. That would be your test
I have a couple of questions, please see my current progress:
meg_landmarks
meg_landmarks = np.asarray([raw.info['dig'][i]['r'] for i in range(3)])
trans = mne.read_trans(trans_path)
trans
is head to MRI: <Transform | head->MRI (surface RAS)>
... transforming points on the head to the position they would have if they were on the MRI.from_to_t, trans = _get_trans(trans, fro='mri', to='head')
trans
(now called from_to_t
), it's now from mri to head: `<Transform | MRI (surface RAS)->head>fro
and to
... should I actually edit the default values of this private function so that it stays fro='head'
, to='mri'
?trans
called from_to_t
now? Previous trans
gets assigned a string value nowfrom_to_t
to the meg_landmarks
to obtain mri_landmarks
: mri_landmarks = apply_trans(from_to_t, meg_landmarks)
from_to_t
is in "MRI TO HEAD" format ... it seems to me we should use a "HEAD TO MRI" format (like our original trans
was before the call to _get_trans
) ... because after all, we are transforming MEG landmarks (head) to an MRI ... not the other way aroundtrans
(see step 2): trans_fitted = fit_matched_points(src_pts=meg_landmarks, tgt_pts=mri_landmarks)
trans_reproduced = mne.transforms.Transform(fro='head', to='mri', trans=trans_fitted)
trans
with trans_reproduced
and find only minor differences (on the order e-02 to e-08)src_pts
so that they correspond with the fro
field in the mne.transforms.Transform
(and vice versa for tgt_pts
and the to
field)? ... i.e., head=meg_landmarks, ... mri=mri_landmarksfor 1 use:
In [10]: raw.info['dig'][2]['ident'] Out[10]: 3 (FIFFV_POINT_RPA)
In [11]: raw.info['dig'][0]['ident'] Out[11]: 2 (FIFFV_POINT_NASION)
In [12]: raw.info['dig'][1]['ident'] Out[12]: 1 (FIFFV_POINT_LPA)
for the rest, make it work and we'll make it nice after :)
should I actually edit the default values of this private function so that it stays fro='head', to='mri'?
I wouldn't bother. Remember you are already in a rabbit hole. You want to have datasets to test on study template, which led to somato PR, which led to the discussion on trans and that led to this.
why do we change naming --> why is our actual trans called from_to_t now?
It's confusing as I said :) But just use the function for now as Alex suggests. Otherwise, it's combinatorial branching of PRs
+1 for make it work first. Seems like you've figured out the way forward. I'm waiting for a PR ;-)
See the anatomical landmarks section here. And from this you should be able to generate
trans
for coregistration automatically.Based on our discussion at OHBM