-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Add support for importing channel location from XYZ/CSV files. #9203
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
Conversation
agramfort
left a comment
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.
tell us when you added test and a what's new entry and it's good to go from your end.
|
I think feature-wise it's done, but I'm not sure what's good enough for the tests. Should I just import from a CSV and compare the resulting montage to the CSV data? That's how I "tested" it locally, but I'm not sure if it's good and comprehensive enough. If that's the case then I think the existing |
agramfort
left a comment
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.
do you need help to add some unit tests?
|
|
||
|
|
||
| def _read_csv(fname, delimiter=','): | ||
| """Import eeg channel locations from CSV files into MNE instance. |
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.
| """Import eeg channel locations from CSV files into MNE instance. | |
| """Import EEG channel locations from CSV files into MNE instance. |
| delimiter : str | ||
| Delimiter used by the CSV file | ||
| include_ch_names : bool | ||
| Whether the CSV file include channel names as the first column |
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.
this parameter does not exist
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.
Ah, I forgot to remove this after I removed the actual parameter. Would you say it's beneficial to add these parameters (delimiter and ch_names) to read_custom_montage so the CSV files can be more flexible? Or should I just remove both of them?
Some help would be great since I'm still not really familiar with the MNE testing standards. |
| Whether the CSV file include channel names as the first column | ||
| """ | ||
| include_ch_names = True | ||
| import csv |
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.
python standard library imports should go top-of-file, not nested inside function. We only nest optional dependencies (or internal imports to avoid circular import errors)
| # chs = inst.info['chs'] | ||
| # if not include_ch_names: | ||
| # for ch, coord in zip(chs, coords): | ||
| # ch['loc'][:3] = coord | ||
| # else: | ||
| # for ch in chs: | ||
| # try: | ||
| # coord = coords[ch_names.index(ch['ch_name'])] | ||
| # except ValueError: # ch_name not in channel list, default to 0 | ||
| # coord = (0, 0, 0) | ||
| # ch['loc'][:3] = coord |
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.
cruft?
| include_ch_names : bool | ||
| Whether the CSV file include channel names as the first column | ||
| """ | ||
| include_ch_names = True |
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.
this is now always true. remove it and eliminate the conditionals that depend on it.
| f = open(fname, "r") | ||
| f.readline() | ||
| ch_names = [] | ||
| pos = [] | ||
| for row in csv.reader(f, delimiter=delimiter): | ||
| if include_ch_names: | ||
| ch_name, x, y, z, *_ = row | ||
| ch_names.append(ch_name) | ||
| else: | ||
| x, y, z, *_ = row | ||
| pos.append((float(x), float(y), float(z))) | ||
| f.close() |
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.
we typically use the with open(fname, 'r') as fid: context manager, so we don't have to expressly close the file afterward. For consistency we should do the same here.
| CSV files should have columns x, y, and z, each row represents one channel. | ||
| Optionally the first column can contain the channel names. |
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.
the way the function is now written, channel names as first column are not in fact optional. Revise this text to reflect that.
| ch_names.append(ch_name) | ||
| else: | ||
| x, y, z, *_ = row | ||
| pos.append((float(x), float(y), float(z))) |
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.
(nitpick) you could omit the three float() calls here, and just add dtype=float when you later convert pos to a numpy array. Seems cleaner to me that way.
| return make_dig_montage(ch_pos=_check_dupes_odict(ch_names, pos)) | ||
|
|
||
|
|
||
| def _read_csv(fname, delimiter=','): |
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.
not clear to me why delimiter is exposed here. It's a private function (so users can't actually change it) and it's only called once in the codebase and there's no triaging based on file extension (csv vs tsv). Do you know there to be XYZ channel location text files that use a different delimiter? If so, we should expose delimiter in the public API somehow. If not, we should assume comma-delimiter and remove this param.
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.
delimiter is a leftover parameter, the only different ones are probably tabs in TSV files. Should this be checked in read_custom_montage or in _read_csv? As for the channel names, I've seen files with channel names (mainly CSV files) and ones without (mainly XYZ files).
|
pushed a commit to add a test and address some of the comments
… |
drammock
left a comment
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.
just 2 small docstring fixes.
|
|
||
| CSV files should have columns 4 columns containing | ||
| ch_name, x, y, and z. Each row represents one channel. | ||
| The first column can contain the channel names. |
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.
| The first column can contain the channel names. |
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.
(cruft)
| Parameters | ||
| ---------- | ||
| fname : str | ||
| Name of the csv file to read channel locations from |
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.
| Name of the csv file to read channel locations from | |
| Name of the csv file to read channel locations from. |
|
@jackz314 do you want to take over? |
|
I'm finalizing the changes now. Should we change |
|
Csv is not a standard. We cannot do magic read. With what you say I am now tempted to say user should just call make dig montage and deal with reading the csv on their side. We cannot claim to support more. Take home message : avoid black magic as it will backfire
|
|
I pushed a commit with some code to adapt for TSV, CSV, and XYZ files. Since CSV isn't a standard, I just specified and used the |
|
@jackz314 I added some tests. See my commit To run the tests you can use:
|
|
Thanks for the tests, I ran them locally and it seems like they are passing. I think this should be ready unless there's anything else needed. |
agramfort
left a comment
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.
@jackz314 you need now to add a what's new entry in the file latest.inc in the doc folder
also I feel that we should update the page https://mne.tools/dev/auto_tutorials/intro/plot_40_sensor_locations.html
@drammock you confirm it would be the right place?
Not sure I agree. |
|
Just added the what's new entry. I think it would make a lot of sense to add |
I guess it would be OK to add a section between "working with built-in montages" and "controlling channel projection" called "working with custom montages" that shows a quick example of |
agramfort
left a comment
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.
anyone merge if happy
thx @jackz314
|
@jackz314 are you planning to add to the tutorial in this PR, or in a separate one? |
I wasn't planning on adding the tutorials myself, mostly because I'm not really familiar with how the tutorial part or the other kinds of montages work yet. But if no one else is working on it, I can look into it and open another PR later if I have time. |
|
Ok I'll merge this one now then. |
|
🎉 Congrats on merging your first pull request! 🥳 Looking forward to seeing more from you in the future! 💪 |
Reference issue
"Spin-off" from #9192.
What does this implement/fix?
Add support for importing channel location from XYZ/CSV files as an extra XYZ option (to be finalized) for
read_custom_montageas per suggestion from here.Additional information
The naming (XYZ?) & formatting of this feature isn't fully finalized yet, I'm still waiting for input from more qualified people on that.