Skip to content

Axial to planar gradiometer transformation #9609

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
PiusKern opened this issue Jul 23, 2021 · 22 comments · May be fixed by #13196
Open

Axial to planar gradiometer transformation #9609

PiusKern opened this issue Jul 23, 2021 · 22 comments · May be fixed by #13196
Milestone

Comments

@PiusKern
Copy link

For axial gradiometer data, e.g. from CTF 275 systems, a transformation to virtual planar gradiometers would be helpful. This is a standard procedure in many data analysis pipelines for axial systems. It would facilitate plotting and subsequent interpretation.

The issue has been briefly discussed during the office hour on 2021-07-23.

@britta-wstnr

@PiusKern PiusKern added the ENH label Jul 23, 2021
@welcome
Copy link

welcome bot commented Jul 23, 2021

Hello! 👋 Thanks for opening your first issue here! ❤️ We will try to get back to you soon. 🚴🏽‍♂️

@britta-wstnr
Copy link
Member

Thanks @PiusKern.
I agree, some methods for this would be useful. During the office hours, we also briefly touched upon how to solve this computationally. As discussed, I will check in detail what FieldTrip does and report back, then we can discuss how we want to proceed.

@larsoner larsoner added this to the 0.24 milestone Jul 26, 2021
@britta-wstnr
Copy link
Member

FieldTrip supports different methods on how to compute planar from axial grads: sourceproject using a minimum current estimate, fitplane using the neighborhood structure, and sincos using the neighborhood structure as well, but more flexibly than the fitplane option. sincos is default here.

For the combination of the vertical and horizontal parts, using an SVD is one option, sum (sqrt(horizontal ** 2 + vertical ** 2)) and complex are others.

@larsoner
Copy link
Member

I would start with just the sourceproject method since we have it implemented already and I think @agramfort suggested it should be the best in principle. We can assume YAGNI on the others until we see otherwise (especially if the values aren't that different, or they do differ but the sourceproject is better for example in round-trip test to a different system/sensor type and back).

@britta-wstnr
Copy link
Member

FWIW, there is some rather old opinion by Robert, referring to simulations, saying the sincos method outperforms the others. See here: https://mailman.science.ru.nl/pipermail/fieldtrip/2005-August/026154.html

But I agree, if it's there, let's start with it and then we can always add this (and compare again, if we are feeling adventurous 😉 )

@britta-wstnr
Copy link
Member

@larsoner What would be a good API for this / where should this live?

@larsoner
Copy link
Member

larsoner commented Aug 18, 2021

We have inst.interpolate_bads as a mixin for raw/epochs/evoked, I think it should be part of that same mixin as a new method:

def interpolate_to(self, sensors=None, dev_head_t=None):

where sensors can be (to start) only None, 'ctf planar grad', 'ctf', and 'neuromag'? If sensors is None then it's useful just for transforming to a new dev_head_t.

I suggest just these three string options to start because in this issue 'ctf planar grad' is desired, and I could see people wanting to know what their data would look like if they had been acquired on these other systems. Having ctf and neuromag is also nice because we can round-trip from actual Neuromag data to CTF and back to Neuromag and check the agreement, same with CTF to Neuromag to CTF.

Under the hood this should use the same underlying machinery as interpolate_bads -- it's actually quite simple hopefully as we "just" need to set info_to properly by replacing some info_from['chs'] with the known canonical ones for the given system.

I like sensors as the name because in theory we could eventually support remapping other channel types (e.g., EEG) someday. But for now the function should operate only on MEG data.

@britta-wstnr
Copy link
Member

Thanks for the detailed overview @larsoner ! I hope to give this a shot soon, maybe together with @PiusKern ?

@larsoner
Copy link
Member

@britta-wstnr I'm bumping the milestone as 0.24 is going to be released in just a week or so, but it could still make 0.24 if you have time for it

@larsoner larsoner modified the milestones: 0.24, 1.0 Oct 22, 2021
@larsoner larsoner modified the milestones: 1.0, 1.1 Feb 11, 2022
@britta-wstnr
Copy link
Member

This dropped off my to do list, but now there seems to be new interest in seeing this implemented by @chsquare
Maybe a good incentive to get this done together, @chsquare? The thread should have all info necessary for implementation.

@larsoner
Copy link
Member

I forgot we had come up with an API for this! Just one more hint to add to what I wrote above, I think internally you should "just" need an appropriate call to _map_meg_or_eeg_channels to do the heavy lifting here, as it's what's used by interpolate_bads.

@britta-wstnr
Copy link
Member

nice, thanks @larsoner !

@larsoner larsoner modified the milestones: 1.1, 1.2 Jun 4, 2022
@larsoner larsoner modified the milestones: 1.2, 1.3 Sep 13, 2022
@larsoner larsoner removed this from the 1.3 milestone Dec 8, 2022
@finch-f
Copy link

finch-f commented Mar 5, 2024

Hi, I am wondering if there has been a function to transform axial gradiometers to virtual planar gradiometers? I have tried the as_type() function. However, it fails on my CTF275 data.

@larsoner
Copy link
Member

larsoner commented Mar 5, 2024

No it has not been implemented yet, still needs a volunteer to give it a shot

@contsili
Copy link

Hi all, after a discussion with @britta-wstnr I would like to take over this issue.

We have inst.interpolate_bads as a mixin for raw/epochs/evoked, I think it should be part of that same mixin as a new method:

def interpolate_to(self, sensors=None, dev_head_t=None):

@larsoner do you still think this is still a good API?

Additionally, I see that you decided on sourceproject method. Shall we still proceed with this method?

@larsoner
Copy link
Member

Yeah @contsili that would be great! We can always add other projection methods via a new keyword argument later. I think in principle the PR should be fairly straightforward 🤞

One issue is that we'll need to store the canonical CTF and Neuromag sensor definitions somewhere (positions, orientations, and coil def number). I don't think we currently do that anywhere as we rely on this info to be stored in a given file being read with read_raw_fif or similar. So you'll probably need to add two new files to mne/channels/data/montages/ to store this info, one for Neuromag and one for CTF. It's a bit of a misnomer to call these "montages" but it's the closest thing we have at the moment I think. And at some point we might want to use the montage mechanics to be able to store / set OPM channel info. I would save this info just as a simple human-readable CSV probably with cols name,coil,x,y,z,ex,ey,ez (I think that's all we need?), I don't think it will end up being too large.

@contsili
Copy link

I would start with just the sourceproject method since we have it implemented already and I think @agramfort suggested it should be the best in principle. We can assume YAGNI on the others until we see otherwise (especially if the values aren't that different, or they do differ but the sourceproject is better for example in round-trip test to a different system/sensor type and back).

@larsoner, could you point me to where the sourceproject method is implemented in the mne repo?

@larsoner
Copy link
Member

I think it is implicitly what we use already in

def _interpolate_bads_meeg(

which wraps to

def _map_meg_or_eeg_channels(info_from, info_to, mode, origin, miss=None):

But really I don't think you need to worry about the mapping method for now. The critical part is that we already have a function that allows mapping from one sensor type (given a suitable info) to another (given its suitable info). What method is used under the hood is a second-level detail/consideration, and we can worry/think about it once we do the work of getting the API right as above.

@britta-wstnr
Copy link
Member

Hi @contsili! 👋 Are you still interested in tackling this? 🙂

@contsili
Copy link

contsili commented Feb 5, 2025

Hi @britta-wstnr . Yes I am still interested. I started working and I have less time but I will do it!

I think a good plan is to wait for this PR #13044 to be merged and then I can extend the functionality of interpolate_to to MEG.

@britta-wstnr
Copy link
Member

Hi @contsili! Great to hear 🙌
Don't hesitate to open a PR with snippets already, it's better to have early discussions on implementations (less work for you and the code reviewers! 🙂 ). You can open a PR in DRAFT mode to signal that this is not a fully-working implementation yet. Looking forward to it!

@larsoner
Copy link
Member

@contsili #13044 is in so you should be good to go!

@larsoner larsoner added this to the 1.10 milestone Feb 10, 2025
@contsili contsili linked a pull request Apr 7, 2025 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants