Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
e0f8cc6
Add raw montage files sent by Artinis
HanBnrd Mar 17, 2021
2159fb3
Initial pseudo code
rob-luke Mar 17, 2021
44cbe02
Tweaks
rob-luke Mar 17, 2021
5dae7fa
Draft _set_montage_fnirs
HanBnrd Mar 17, 2021
e55859a
Tweaks
rob-luke Mar 17, 2021
cebdf10
Example with optode montage as elc file
HanBnrd Mar 17, 2021
9064883
Clean up and add Brite 23
HanBnrd Mar 17, 2021
4091332
Merge branch 'main' into artinis-montages
HanBnrd Mar 17, 2021
9bbf7a8
Tweak skeleton code and push example
rob-luke Mar 18, 2021
d65d8f8
Add TODOs
HanBnrd Mar 18, 2021
7428812
Update also dig
HanBnrd Mar 18, 2021
599bbfd
Fix optodes in the brain
HanBnrd Mar 18, 2021
b77459c
Add fetch_fsaverage
HanBnrd Mar 18, 2021
801c27f
Merge branch 'main' into artinis-montages
HanBnrd Mar 18, 2021
28ff382
Set montage with str in tutorial
HanBnrd Mar 19, 2021
69bc74c
Add example of how to load unstructured data [ci skip]
rob-luke Mar 20, 2021
eef41f3
Typo [skip github] [azp skip]
rob-luke Mar 20, 2021
0a35722
More typos [skip github] [azp skip]
rob-luke Mar 20, 2021
03e6944
Typos [skip github] [azp skip]
rob-luke Mar 20, 2021
8033643
Merge remote-tracking branch 'upstream/main' into artinis-montages
rob-luke Mar 20, 2021
c04ad5c
Fix linking [azp skip] [skip github]
rob-luke Mar 20, 2021
059ae2f
Small doc tweaks [azp skip] [github skip]
rob-luke Mar 20, 2021
7fc5db9
Add tests. More needed
rob-luke Mar 20, 2021
7e00eb5
More tests
rob-luke Mar 20, 2021
a47743e
Update doc and tests
HanBnrd Mar 20, 2021
4675c64
Fix pydocstyle
HanBnrd Mar 20, 2021
4fb7463
Add more warning and limitations
rob-luke Mar 20, 2021
27b8123
added wrong file
rob-luke Mar 21, 2021
d60f88e
Fix reverted source and detector
HanBnrd Mar 21, 2021
e465a1e
Merge branch 'artinis-montages' of https://github.com/HanBnrd/mne-pyt…
HanBnrd Mar 21, 2021
b7cfe66
Typo
rob-luke Mar 21, 2021
9a4db53
More warnings
rob-luke Mar 21, 2021
e4ed607
Check fNIRS ch_names and add wavelength
HanBnrd Mar 21, 2021
acc71eb
Merge branch 'main' into artinis-montages
HanBnrd Apr 7, 2021
e3a6c7b
No modification of wavelength
HanBnrd Apr 9, 2021
28e2a50
Test Brite, intensity, OD
HanBnrd Apr 11, 2021
11c30ee
Merge branch 'main' into artinis-montages
HanBnrd Apr 11, 2021
4d4a80c
Use _check_channels_ordered with new API
HanBnrd Apr 11, 2021
c990c8b
Merge branch 'main' into artinis-montages
HanBnrd Apr 12, 2021
9228a01
Simplify _set_montage_fnirs
HanBnrd Apr 13, 2021
e971fe8
Add tests for channel variations
HanBnrd Apr 13, 2021
5fac262
TST: Test rough equivalent with fsaverage
larsoner Apr 14, 2021
2640d12
Test 2 more built in montages (OctaMon & Brite 23)
HanBnrd Apr 16, 2021
2b17fb0
Update tutorial
HanBnrd Apr 16, 2021
1ffa727
Merge branch 'main' into artinis-montages
HanBnrd Apr 16, 2021
227fc24
Describe changes
HanBnrd Apr 16, 2021
717f2e6
Update latest
HanBnrd Apr 16, 2021
4690830
Apply suggestions from code review
HanBnrd Apr 22, 2021
726e282
Merge branch 'main' into artinis-montages
HanBnrd Apr 22, 2021
20aa229
Fix ref
HanBnrd Apr 22, 2021
de264a0
Update in-line comments
HanBnrd Apr 22, 2021
bade068
Merge branch 'main' into artinis-montages
HanBnrd Apr 22, 2021
c695f04
Fix refs
HanBnrd Apr 22, 2021
0ea038a
Move channel info and add read_custom_montage
HanBnrd Apr 23, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions doc/changes/latest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,8 @@ Enhancements

- Improve performance of :func:`mne.set_bipolar_reference` (:gh:`9270` by `Martin Schulz`_)

- Add support for setting montages on fNIRS data, with built in standard montages for Artinis OctaMon and Artinis Brite23 devices (:gh:`9141` by `Johann Benerradi`_, `Robert Luke`_ and `Eric Larson`_)

Bugs
~~~~
- Fix bug with :func:`mne.viz.plot_evoked_topo` where set ylim parameters gets swapped across channel types. (:gh:`9207` by |Ram Pari|_)
Expand Down
8 changes: 6 additions & 2 deletions mne/channels/_standard_montage_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def _biosemi(basename, head_size):
return _read_theta_phi_in_degrees(fname, head_size, fid_names)


def _mgh_or_standard(basename, head_size):
def _mgh_or_standard(basename, head_size, coord_frame='unknown'):
fid_names = ('Nz', 'LPA', 'RPA')
fname = op.join(MONTAGE_PATH, basename)

Expand Down Expand Up @@ -103,7 +103,7 @@ def _mgh_or_standard(basename, head_size):
lpa *= scale
rpa *= scale

return make_dig_montage(ch_pos=ch_pos, coord_frame='unknown',
return make_dig_montage(ch_pos=ch_pos, coord_frame=coord_frame,
nasion=nasion, lpa=lpa, rpa=rpa)


Expand Down Expand Up @@ -144,6 +144,10 @@ def _mgh_or_standard(basename, head_size):
basename='standard_prefixed.elc'),
'standard_primed': partial(_mgh_or_standard,
basename='standard_primed.elc'),
'artinis-octamon': partial(_mgh_or_standard, coord_frame='mri',
basename='artinis-octamon.elc'),
'artinis-brite23': partial(_mgh_or_standard, coord_frame='mri',
basename='artinis-brite23.elc'),
}


Expand Down
48 changes: 48 additions & 0 deletions mne/channels/data/montages/artinis-brite23.elc
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# ASA optode file
ReferenceLabel avg
UnitPosition mm
NumberPositions= 21
Positions
-4.62 82.33 -45.74
79.66 -18.72 -45.89
-81.41 -17.18 -45.56
65.18 27.28 35.31
48.62 59.71 22.68
18.95 72.41 38.32
-3.97 79.74 30.28
-25.96 72.19 35.16
-52.51 60.53 14.54
-66.37 32.04 31.08
76.10 -0.29 31.24
65.61 -0.26 56.15
64.93 42.43 8.29
43.32 46.36 50.77
21.58 82.45 1.06
-2.91 59.57 61.59
-29.62 79.35 2.38
-48.13 44.76 49.15
-67.68 43.26 -3.18
-65.37 4.89 56.36
-77.24 5.88 27.58
Labels
Nz
RPA
LPA
D1
D2
D3
D4
D5
D6
D7
S1
S2
S3
S4
S5
S6
S7
S8
S9
S10
S11
32 changes: 32 additions & 0 deletions mne/channels/data/montages/artinis-octamon.elc
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# ASA optode file
ReferenceLabel avg
UnitPosition mm
NumberPositions= 13
Positions
0.96 83.56 -48.63
80.25 -19.67 -43.88
-82.58 -20.09 -43.10
47.77 65.28 7.28
-46.45 67.76 8.81
63.88 34.84 28.34
64.96 45.02 -10.31
22.07 74.86 31.03
17.84 84.96 -10.84
-10.81 77.96 32.10
-15.96 85.24 -7.41
-61.78 40.78 29.92
-65.28 48.14 -10.73
Labels
Nz
RPA
LPA
D1
D2
S1
S2
S3
S4
S5
S6
S7
S8
61 changes: 59 additions & 2 deletions mne/channels/montage.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
_get_data_as_dict_from_dig)
from ..io.meas_info import create_info
from ..io.open import fiff_open
from ..io.pick import pick_types
from ..io.pick import pick_types, _picks_to_idx
from ..io.constants import FIFF, CHANNEL_LOC_ALIASES
from ..utils import (warn, copy_function_doc_to_method_doc, _pl, verbose,
_check_option, _validate_type, _check_fname, _on_missing,
Expand All @@ -52,7 +52,8 @@
'easycap-M1', 'easycap-M10',
'mgh60', 'mgh70',
'standard_1005', 'standard_1020', 'standard_alphabetic',
'standard_postfixed', 'standard_prefixed', 'standard_primed'
'standard_postfixed', 'standard_prefixed', 'standard_primed',
'artinis-octamon', 'artinis-brite23'
]


Expand Down Expand Up @@ -725,6 +726,47 @@ def _get_montage_in_head(montage):
return transform_to_head(montage.copy())


def _set_montage_fnirs(info, montage):
"""Set the montage for fNIRS data.

This needs to be different to electrodes as each channel has three
coordinates that need to be set. For each channel there is a source optode
location, a detector optode location, and a channel midpoint that must be
stored. This function modifies info['chs'][#]['loc'] and info['dig'] in
place.
"""
from ..preprocessing.nirs import (_channel_frequencies,
_channel_chromophore,
_check_channels_ordered)
# Validate that the fNIRS info is correctly formatted
freqs = np.unique(_channel_frequencies(info))
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
freqs = np.unique(_channel_frequencies(info))
# First we must validate that the fNIRS info is correctly formatted
freqs = np.unique(_channel_frequencies(info))

if freqs.size > 0:
picks = _check_channels_ordered(info, freqs)
else:
picks = _check_channels_ordered(info,
np.unique(_channel_chromophore(info)))

# Modify info['chs'][#]['loc'] in place
num_ficiduals = len(montage.dig) - len(montage.ch_names)
for ch_idx in picks:
ch = info['chs'][ch_idx]['ch_name']
source, detector = ch.split(' ')[0].split('_')
source_pos = montage.dig[montage.ch_names.index(source)
+ num_ficiduals]['r']
detector_pos = montage.dig[montage.ch_names.index(detector)
+ num_ficiduals]['r']

info['chs'][ch_idx]['loc'][3:6] = source_pos
info['chs'][ch_idx]['loc'][6:9] = detector_pos
midpoint = (source_pos + detector_pos) / 2
info['chs'][ch_idx]['loc'][:3] = midpoint

# Modify info['dig'] in place
info['dig'] = montage.dig

return info


@fill_doc
def _set_montage(info, montage, match_case=True, match_alias=False,
on_missing='raise'):
Expand Down Expand Up @@ -758,6 +800,7 @@ def _set_montage(info, montage, match_case=True, match_alias=False,

if isinstance(montage, DigMontage):
mnt_head = _get_montage_in_head(montage)
del montage

def _backcompat_value(pos, ref_pos):
if any(np.isnan(pos)):
Expand Down Expand Up @@ -862,6 +905,7 @@ def _backcompat_value(pos, ref_pos):

for name, use in zip(info_names, info_names_use):
_loc_view = info['chs'][info['ch_names'].index(name)]['loc']
# Next line modifies info['chs'][#]['loc'] in place
_loc_view[:6] = _backcompat_value(ch_pos_use[use], eeg_ref_pos)

del ch_pos_use
Expand Down Expand Up @@ -890,14 +934,23 @@ def _backcompat_value(pos, ref_pos):
# in the old dig
if ref_dig_point in old_dig:
digpoints.append(ref_dig_point)
# Next line modifies info['dig'] in place
info['dig'] = _format_dig_points(digpoints, enforce_order=True)

if mnt_head.dev_head_t is not None:
# Next line modifies info['dev_head_t'] in place
info['dev_head_t'] = Transform('meg', 'head', mnt_head.dev_head_t)

# Handle fNIRS with source, detector and channel
fnirs_picks = _picks_to_idx(info, 'fnirs', allow_empty=True)
if len(fnirs_picks) > 0:
info = _set_montage_fnirs(info, mnt_head)

else: # None case
# Next line modifies info['dig'] in place
info['dig'] = None
for ch in info['chs']:
# Next line modifies info['chs'][#]['loc'] in place
ch['loc'] = np.full(12, np.nan)


Expand Down Expand Up @@ -1361,6 +1414,10 @@ def make_standard_montage(kind, head_size=HEAD_SIZE_DEFAULT):
MGH (60+3 locations)
mgh70 The (newer) 70-channel BrainVision cap used at
MGH (70+3 locations)

artinis-octamon Artinis OctaMon fNIRS (8 sources, 2 detectors)

artinis-brite23 Artinis Brite23 fNIRS (11 sources, 7 detectors)
=================== =====================================================

.. versionadded:: 0.19.0
Expand Down
2 changes: 1 addition & 1 deletion mne/channels/tests/test_montage.py
Original file line number Diff line number Diff line change
Expand Up @@ -1498,7 +1498,7 @@ def test_read_dig_hpts():

def test_get_builtin_montages():
"""Test help function to obtain builtin montages."""
EXPECTED_NUM = 24
EXPECTED_NUM = 26
assert len(get_builtin_montages()) == EXPECTED_NUM


Expand Down
Loading