-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
MRG: Add nirx file reader #6674
Conversation
What are the types / units of the data for these channels? We can add new channel types to the FIF standard if necessary. |
The NIRX devices are continuous wave, single phase machines. The signal measured and stored to disk is light intensity. That is what I am reading in this pull request. The next processing step (beyond the scope of this pull request, but maybe relevant if we are discussing adding types) is to convert from intensity to optical density (script example1:nirstoolbox, example2:homer2) which is unitless. The final preprocessing step is to convert optical density to concentration changes of HbO and HbR usually using the modified Beer Lambert law (script example 1:nirstoolbox, example 2:homer2). The units are usually change in μMol. There is a nice overview of this in [1] that includes this nice graphic: [1] Pinti, Paola, et al. "Current status and issues regarding pre-processing of fNIRS neuroimaging data: An investigation of diverse signal filtering methods within a General Linear Model framework." Frontiers in human neuroscience 12 (2018): 505. |
Codecov Report
@@ Coverage Diff @@
## master #6674 +/- ##
==========================================
+ Coverage 89.65% 89.67% +0.01%
==========================================
Files 422 425 +3
Lines 76690 76898 +208
Branches 12546 12579 +33
==========================================
+ Hits 68759 68956 +197
- Misses 5116 5121 +5
- Partials 2815 2821 +6 |
I would store these as interleaved like
I would actually put this toward the top. You already have code in this PR that you could use to make |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like it's shaping up, a few ideas for how to move forward
@larsoner I have hit a bit of a road block, could you please help me understand why the circleci checks are failing. Am I missing some docs for the
|
Thanks @larsoner and @agramfort. All green now! I will keep working on functionality and improving the code, and ping you when its ready for a review. |
Check location info is stored in accessible mannerLocations are now stored in
The locations are in MNI coordinates. Plotting them using following script produces... for idx in range(len(raw.info['chs'])):
if idx == 1: label = 'Channel';
else: label = ''
ax.scatter(raw.info['chs'][idx]['loc'][0],
raw.info['chs'][idx]['loc'][1],
raw.info['chs'][idx]['loc'][2],
marker = 'x', edgecolors='k', color='k', label=label)
if idx == 1: label = 'Source';
else: label = ''
ax.scatter(raw.info['chs'][idx]['loc'][3],
raw.info['chs'][idx]['loc'][4],
raw.info['chs'][idx]['loc'][5],
marker = 'o', edgecolors='b', color='b', label=label)
if idx == 1: label = 'Detector';
else: label = ''
ax.scatter(raw.info['chs'][idx]['loc'][6],
raw.info['chs'][idx]['loc'][7],
raw.info['chs'][idx]['loc'][8],
marker = 'o', edgecolors='r', color='r', label=label) which matches nicely with the figure from mne-tools/mne-testing-data#51 If I change the type to eeg I can run raw.plot_sensors(ch_type='eeg') # Force to treat as eeg so uses channel info stored in first 3 loc values |
@rob-luke we should see how to handle the different channel type for fnirs_raw there is no way you can use an existing channel type? |
Thanks for the feedback @agramfort. I am new to MNE and FIFF, so don’t know anything about channel types. Is there a type you would recommend I use? Most fNIRS machines are continuous wave two wavelength devices. The raw data saved to file by the device is a measure of light intensity per wavelength. This needs to be converted to optical density. Finally the optical density is converted to chromophore concentration concentration. So if I wrote my own software from scratch I would add I see there is already a But to keep things simple could we store the channel type as I am thinking out loud here, so please let me know if you have any suggestions. |
In short, no, there doesn't seem to be. The existing "coil types" are actually derived from an earlier actual measured quantity (which this reader reads) so we need a new coil type for it. Thus I proposed working with |
I would let @rob-luke get this PR mostly ready to go to make sure we have all the constants that we need, then ping the MEGIN folks. I'd rather bother them once then multiple times. |
ok but we need to agree of the constants and units then. I don't have any
strong opinion. Note
also that as you introduce a new data type every pick_types function should
be updated.
… |
We still only have FYI this PR is harder than most "add a reader" PR because we're adding new channel types, but it seems to be converging to something usable! |
BTW ideally we would with your reader add some new example/tutorial for dealing with fNIRS data showing that it works. (It also helps reviewers quickly test your new functionality.) If you have some simple paradigm (N100?) that you have or could record quickly to show that "things are working" that would be good as a new MNE dataset. Basically it can be a shorter version of #4257 (no need for That way, if/once the conversion steps are added, they can basically just be appended to this tutorial, and at the end of the day the fNIRS pipeline is be nicely documented for people. |
Hopefully you can just edit https://github.com/mne-tools/mne-python/blob/master/mne/io/pick.py And update the existing fnirs-picking tests like https://github.com/mne-tools/mne-python/blob/master/mne/io/tests/test_pick.py#L260 |
Already started something with the same idea here: https://github.com/rob-luke/mne-fnirs-demo/blob/master/NIRx%20Reader.ipynb |
Thanks for the help @larsoner and @agramfort . How about this plan?
Thoughts? Suggestions? Approval to proceed with the plan? (@hamishib any thoughts, details on logic, terminology, units etc?) Additional information that may help make this decision.
|
This proposal IIUC is basically:
It is a bit unusual to have one coil type used for multiple channel types. Something that might be closer to the MNE way of doing things (and also ends up backward compatible) is to do it the other way:
I think this is closer to the way MNE operates on these quantities. This is true even though the units for the different coil types will end up differing. For example, in MEG we have magnetometers and gradiometers with different units (T and T/m), but these are all FIFFV_MEG_CH type with different coil types associated with them. And in some systems, channels can be operated on by a linear denoising Ultimately we could go either way, but to me the simplicity and backward compatibility of the unified FIFFV_FNIRS_CH approach gets my vote.
This seems reasonable to me. I think we are limited to 14 chars for ch names. But I can't think of a better place to store this information. We can drop the |
Great, me too.
Agreed, encoding information in the channel name sounds dangerous. And doesn't work well with the limit on characters.
I'm still learning MNE terminology here, so excuse if I have misunderstood your comment. For each source-detector pair there is a measurement for each wavelength. The NIRx machine uses 760 and 850 nm, but other machines use different (and possibly more than two) wavelengths. What's an alternative that we could use? Place a field like Side noteMaybe it will help guide some of these software design decisions for me to illustrate how fNIRS data is usually displayed in publications. For an fNIRS analysis we want to plot the haemodynamic response function (below, taken from great short description here). For each channel (source-detector pair) we want to plot the change in chromophore concentration after a stimulus onset. It would be great if after reading the data, conversion to chromophore, and epoching, that we could call |
Epoching and plotting now seems to work (example). Now back to getting those test ticks green. |
This pull request introduces 3 alerts when merging 2bd56e9 into d247e55 - view on LGTM.com new alerts:
|
@larsoner I think I have made all the changes you requested. Please let me know if I need to do anything else. Could you also point me to a function that changes one coil type to another? So I can use this as the basis for the conversion to optical density. |
There is no real magic it's just a FYI -- we try to stay close to Python builtin types in MNE where possible to make this stuff painless. For example Will look at code tomorrow and hopefully merge! |
Co-Authored-By: Alexandre Gramfort <alexandre.gramfort@m4x.org>
So here are the things from above I'll try to restore:
I had deleted my branch locally and never pushed to my origin, but this SO thread had some tips. Nothing in my
Then doing:
Which is awesome because this is the last commit that I think I pushed. Then a simple TL;DR: You somehow omitted up my commits during rebase (probably forgot to pull them before rebasing/adding commits, then rebased, then force-pushed) but |
Thanks for catching my mistake @larsoner, I had incorrectly assumed your commits were in there. All good by me to merge. |
Awesome, go go go @rob-luke ! |
Thanks @larsoner and @agramfort for all your patience and help. Very excited to have merged my first PR. |
* Initial add of nirx reader files * Initial add of test files * Add test call to nirx reader. Ensure required files are present * Add reader for header file * Add _read_segment_file for nirx * Ensure nirx files adhere to style guidlines * Check nirx file format version is supported * Extract wavelength information from file. Tidy info variable names * Add `subject_info` field based on .inf file * Point tests to newly added nirx file * Fix assert bug raised by larsoner * Add new lines where indicated by larsoner mne-tools#6674 (review) * Add read_raw_nirx to python_reference.rst * larsoner suffestion accepted. Update mne/io/nirx/nirx.py Co-Authored-By: Eric Larson <larson.eric.d@gmail.com> * larsoner suffestion accepted. Update mne/io/nirx/nirx.py Co-Authored-By: Eric Larson <larson.eric.d@gmail.com> * larsoner suffestion accepted. Update mne/io/nirx/nirx.py Co-Authored-By: Eric Larson <larson.eric.d@gmail.com> * larsoner suffestion accepted. Update mne/io/nirx/nirx.py Co-Authored-By: Eric Larson <larson.eric.d@gmail.com> * Nest pandas import, use meaningful channel names * Import FIFF. Change HDR reader. Couldnt get one liner from larsoner to work. Split to two lines. Will come back to this once CI is green * Add montage reader. Pydocstyle fix * Fix typo in comments * Tell nirx tests that pandas is required * Fix nirx reader typo * Add RawNIRX to numpydoc_xref_ignore * Fix nirx reading bug that dropped first sample. Check names * Ensure channel interleaving matches channel names. Tested results match MATLAB by checking std of channels is same in both programs * Store nirx channel, source, and detector locations * Fixed position bug for nirx channels * Remove code repetition in nirx reader * Dont import configparser as cp in nirx reader * Proper check and assert for nirx file format * Scale locations to mm. Add test for S-D distances against NIRSite values * Add function to determine nirx short channels * Extract nirx triggers. Commented out until I read where events live * Add annotations to nirx reader * Tidy comments and remove unused code * Add two new fNIRS types `fnirs_raw` and `fnirs_od` fNIRS raw is for measurements directly from machine (V) fNIRS od is for measurements converted to optical density (unitless) Then you convert to hbo/hbr * Specify types for arrays in nirx reader * Changes required to raw.plot nirx files * Use fNIRS channel type, create three fnirs coil types mne-tools#6674 (comment) * Add fnirs chroma channel * Ensure fnirs_raw type works in picks * Fix picks for other added types * Add new FIFF to tag ignore, flake fixes * Add fnirs to cov.py * Add od and chroma to plotting * Allow raw.plot_psd() for nirx files * Remove chroma based on discussion with larsoner mne-tools#6674 (comment) * Use new fiff release * Remove old comments * Add documentation for NIRS * Add tests for picking fnirs_raw and fnirs_od * Fix type on fNIRS module * Add nirx io tutorial * Add nirx reader to changes * Update tutorials/io/plot_30_reading_fnirs_data.py Co-Authored-By: Alexandre Gramfort <alexandre.gramfort@m4x.org> * ENH: Use NumPy instead of Pandas
@rob-luke sorry to come back to this after we have something working -- but it occurred to me that this can probably also just live in
? Then we don't have to put the wavelength in the channel name or worry about parsing it, lengths, etc. |
brillant idea !
… |
Great idea. I will get on to it once I clear the current backlog of PRs. |
I like it too
…On Mon, Oct 28, 2019, 22:36 Robert Luke ***@***.***> wrote:
Great idea. I will get on to it once I clear the current backlog of PRs.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#6674?email_source=notifications&email_token=ABVX5YY3Z3UZ5WXHYBQKAV3QQ5LNLA5CNFSM4IMUZEE2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOECOPGBQ#issuecomment-547156742>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ABVX5Y5N7LE54KGLX6DZLQDQQ5LNLANCNFSM4IMUZEEQ>
.
|
@larsoner I just took a first crack at this and I get an error that the channel names are not unique if I remove the frequency from them. This might not make much sense without the code changes that I made, but the error is below. Is having unique channel names something that is essential for some functions to work in MNE? mne/io/nirx/nirx.py:39: in read_raw_nirx
return RawNIRX(fname, preload, verbose)
</Repositories/mne-python/mne/externals/decorator.py:decorator-gen-193>:2: in __init__
???
mne/utils/_logging.py:90: in wrapper
return function(*args, **kwargs)
mne/io/nirx/nirx.py:208: in __init__
ch_types='fnirs_raw')
<//Repositories/mne-python/mne/externals/decorator.py:decorator-gen-27>:2: in create_info
???
mne/utils/_logging.py:90: in wrapper
return function(*args, **kwargs)
mne/io/meas_info.py:1799: in create_info
info._check_consistency()
mne/io/meas_info.py:615: in _check_consistency
self['ch_names'] = _unique_channel_names(self['ch_names'])
mne/io/meas_info.py:146: in _unique_channel_names
'%s. Applying running numbers for duplicates.' % dups)
_ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
message = "Channel names are not unique, found duplicates for: {'S2-D3', 'S5-D5', 'S4-D4', 'S5-D8', 'S5-D6', 'S1-D1', 'S5-D7', 'S5-D13', 'S3-D11', 'S1-D9', 'S4-D12', 'S3-D2', 'S2-D10'}. Applying running numbers for duplicates."
category = <class 'RuntimeWarning'>, module = 'mne' |
arfff true. Channel names must be different with mne. I would keep the names different but don't parse the wavelength from the channel name but from loc |
Makes sense, I'll give that a go. Thanks |
Add support for reading NIRX fNIRS files.
Reference issue
As discussed in #4257 and in person with @larsoner
What does this implement/fix?
Add
mne/io/nirx.
which allows reading of data collected from NIRX machineAdditional information
A rough list of things to be completed:
doc/_includes/data_formats.rst
doc/whats_new.rst
Demo
Example of functionality in use on real data can be found at https://github.com/rob-luke/mne-fnirs-demo/blob/master/NIRx%20Reader.ipynb