-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
ENH: Add hpi_colors and hpi_labels for clear visualization #13533
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -553,6 +553,8 @@ def plot_alignment( | |
| interaction="terrain", | ||
| sensor_colors=None, | ||
| *, | ||
| hpi_colors="auto", | ||
| hpi_labels=False, | ||
| sensor_scales=None, | ||
| verbose=None, | ||
| ): | ||
|
|
@@ -644,7 +646,18 @@ def plot_alignment( | |
| %(sensor_colors)s | ||
|
|
||
| .. versionchanged:: 1.6 | ||
| Support for passing a ``dict`` was added. | ||
| Support for passing a ``dict`` was added. | ||
| hpi_colors : 'auto' | list | dict | ||
| Colors for HPI coils when ``dig=True``. | ||
| ``'auto'`` (default): use official MEGIN/Elekta cable colors | ||
| (1=red, 2=blue, 3=green, 4=yellow, 5=magenta, 6=cyan). | ||
| Can also be a list of colors or ``{ident: color}`` dict. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These need |
||
|
|
||
| .. versionadded:: 1.11 | ||
| hpi_labels : bool | ||
| If ``True``, show the HPI coil ident number as 3D text above each coil. | ||
|
|
||
| .. versionadded:: 1.11 | ||
| %(sensor_scales)s | ||
|
|
||
| .. versionadded:: 1.9 | ||
|
|
@@ -900,7 +913,9 @@ def plot_alignment( | |
| _check_option("dig", dig, (True, False, "fiducials")) | ||
| if dig: | ||
| if dig is True: | ||
| _plot_hpi_coils(renderer, info, to_cf_t) | ||
| _plot_hpi_coils( | ||
| renderer, info, to_cf_t, hpi_colors=hpi_colors, hpi_labels=hpi_labels | ||
| ) | ||
| _plot_head_shape_points(renderer, info, to_cf_t) | ||
| _plot_head_fiducials(renderer, info, to_cf_t, fid_colors) | ||
|
|
||
|
|
@@ -1292,34 +1307,90 @@ def _plot_hpi_coils( | |
| surf=None, | ||
| check_inside=None, | ||
| nearest=None, | ||
| hpi_colors="auto", | ||
| hpi_labels=False, | ||
| ): | ||
| from matplotlib.colors import to_rgba | ||
|
|
||
| defaults = DEFAULTS["coreg"] | ||
| scale = defaults["hpi_scale"] if scale is None else scale | ||
| hpi_loc = np.array( | ||
| [ | ||
| d["r"] | ||
| for d in (info["dig"] or []) | ||
| if ( | ||
| d["kind"] == FIFF.FIFFV_POINT_HPI | ||
| and d["coord_frame"] == FIFF.FIFFV_COORD_HEAD | ||
| ) | ||
|
|
||
| hpi_digs = [ | ||
| d | ||
| for d in (info["dig"] or []) | ||
| if ( | ||
| d["kind"] == FIFF.FIFFV_POINT_HPI | ||
| and d["coord_frame"] == FIFF.FIFFV_COORD_HEAD | ||
| ) | ||
| ] | ||
| if not hpi_digs: | ||
| return [] | ||
|
|
||
| hpi_idents = [d["ident"] for d in hpi_digs] | ||
| hpi_locs = apply_trans(to_cf_t["head"], [d["r"] for d in hpi_digs]) | ||
|
|
||
| if hpi_colors == "auto": | ||
| # MEGIN/Elekta HPI coil cable colors(MNE community convention from user reports) | ||
| # 1 = red, 2 = blue, 3 = green, 4 = yellow, 5 = magenta, 6 = cyan | ||
| # Coil 1 is confirmed as "red" in Elekta TRIUX manual | ||
| # Full mapping is standard practice in MNE; no official 6-color list. | ||
| megin_colors = { | ||
| 1: "red", | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For future ref can you add a code comment about where you got this color mapping? |
||
| 2: "blue", | ||
| 3: "green", | ||
| 4: "yellow", | ||
| 5: "magenta", | ||
| 6: "cyan", | ||
| } | ||
| colors = [ | ||
| megin_colors.get(ident, defaults["hpi_color"]) for ident in hpi_idents | ||
| ] | ||
| ) | ||
| hpi_loc = apply_trans(to_cf_t["head"], hpi_loc) | ||
| actor, _ = _plot_glyphs( | ||
| renderer=renderer, | ||
| loc=hpi_loc, | ||
| color=defaults["hpi_color"], | ||
| scale=scale, | ||
| opacity=opacity, | ||
| orient_glyphs=orient_glyphs, | ||
| scale_by_distance=scale_by_distance, | ||
| surf=surf, | ||
| backface_culling=True, | ||
| check_inside=check_inside, | ||
| nearest=nearest, | ||
| ) | ||
| return actor | ||
| elif isinstance(hpi_colors, dict): | ||
| colors = [hpi_colors.get(ident, defaults["hpi_color"]) for ident in hpi_idents] | ||
| elif isinstance(hpi_colors, (list, tuple)): | ||
| if len(hpi_colors) != len(hpi_digs): | ||
| raise ValueError( | ||
| f"""hpi_colors list length | ||
| {len(hpi_colors)} != number of HPI coils {len(hpi_digs)} | ||
| """ | ||
| ) | ||
| colors = hpi_colors | ||
| else: | ||
| colors = [hpi_colors] * len(hpi_digs) | ||
|
|
||
| actors = [] | ||
|
|
||
| for loc, color, ident in zip(hpi_locs, colors, hpi_idents): | ||
| color_rgba = to_rgba(color) | ||
|
|
||
| result = _plot_glyphs( | ||
| renderer=renderer, | ||
| loc=np.array([loc]), | ||
| color=color_rgba, | ||
| scale=scale, | ||
| opacity=opacity, | ||
| orient_glyphs=orient_glyphs, | ||
| scale_by_distance=scale_by_distance, | ||
| surf=surf, | ||
| backface_culling=True, | ||
| check_inside=check_inside, | ||
| nearest=nearest, | ||
| ) | ||
|
|
||
| actors.append(result) | ||
|
|
||
| if hpi_labels: | ||
| offset = np.array([0, 0, scale * 1.3]) | ||
| renderer.text3d( | ||
| x=loc[0], | ||
| y=loc[1], | ||
| z=loc[2] + offset[2], | ||
| text=str(ident), | ||
| scale=scale * 0.7, | ||
| color=color_rgba, | ||
| ) | ||
|
|
||
| return actors | ||
|
|
||
|
|
||
| def _get_nearest(nearest, check_inside, project_to_trans, proj_rr): | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -899,6 +899,51 @@ def test_plot_alignment_basic(tmp_path, renderer, mixed_fwd_cov_evoked): | |
| ) | ||
|
|
||
|
|
||
| @testing.requires_testing_data | ||
| def test_plot_alignment_hpi_colors_and_labels(renderer): | ||
| """Test hpi_colors and hpi_labels parameters.""" | ||
| import mne | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't be nested or needed |
||
|
|
||
| raw_path = ( | ||
| mne.datasets.testing.data_path(download=False) | ||
| / "MEG" | ||
| / "sample" | ||
| / "sample_audvis_raw.fif" | ||
| ) | ||
| raw = mne.io.read_raw_fif(raw_path, preload=False) | ||
| info = raw.info | ||
|
|
||
| cases = [ | ||
| ("auto", False), | ||
| ("auto", True), | ||
| (["red", "red", "blue", "green", "yellow"], False), | ||
| (["red", "red", "blue", "green", "yellow"], True), | ||
| ({1: "purple", 4: "orange"}, False), | ||
| ({1: "purple", 4: "orange"}, True), | ||
| ("pink", False), | ||
| ("pink", True), | ||
| ] | ||
|
|
||
| for hpi_colors, hpi_labels in cases: | ||
| fig = plot_alignment( | ||
| info=info, | ||
| dig=True, | ||
| surfaces=[], | ||
| coord_frame="head", | ||
| hpi_colors=hpi_colors, | ||
| hpi_labels=hpi_labels, | ||
| ) | ||
| assert len(fig.plotter.renderer.actors) > 0 | ||
|
|
||
| fig1 = plot_alignment( | ||
| info=info, dig=True, surfaces=[], hpi_colors="auto", hpi_labels=False | ||
| ) | ||
| fig2 = plot_alignment( | ||
| info=info, dig=True, surfaces=[], hpi_colors="auto", hpi_labels=True | ||
| ) | ||
| assert len(fig2.plotter.renderer.actors) > len(fig1.plotter.renderer.actors) | ||
|
|
||
|
|
||
| @testing.requires_testing_data | ||
| def test_plot_alignment_fnirs(renderer, tmp_path): | ||
| """Test fNIRS plotting.""" | ||
|
|
||
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.
These should come after
*