From 782f0510dc25a261142a04e3a0ffceff27c3dd9b Mon Sep 17 00:00:00 2001 From: Alex Rockhill Date: Fri, 19 Apr 2024 16:19:05 -0700 Subject: [PATCH 1/9] fix errors --- doc/changes/devel/XXX.newfeature.rst | 1 + mne/viz/_brain/_brain.py | 44 +++++++ mne/viz/backends/_abstract.py | 60 ++++++++++ mne/viz/backends/_pyvista.py | 41 +++++++ mne/viz/backends/tests/test_renderer.py | 11 ++ tutorials/clinical/20_seeg.py | 153 ++++++++++++++++++++++++ 6 files changed, 310 insertions(+) create mode 100644 doc/changes/devel/XXX.newfeature.rst diff --git a/doc/changes/devel/XXX.newfeature.rst b/doc/changes/devel/XXX.newfeature.rst new file mode 100644 index 00000000000..c93de3ab653 --- /dev/null +++ b/doc/changes/devel/XXX.newfeature.rst @@ -0,0 +1 @@ +Add diffusion imaging to :ref:`tut-working-with-seeg` including adding :meth:`mne.viz.Brain.add_streamlines` and :meth:`mne.viz.Brain.remove_streamlines` to visualize the fiber tracts, by `Alex Rockhill`_. \ No newline at end of file diff --git a/mne/viz/_brain/_brain.py b/mne/viz/_brain/_brain.py index da5ca5c3cd1..180b97fef60 100644 --- a/mne/viz/_brain/_brain.py +++ b/mne/viz/_brain/_brain.py @@ -226,6 +226,8 @@ class Brain: +-------------------------------------+--------------+---------------+ | :meth:`add_skull` | | ✓ | +-------------------------------------+--------------+---------------+ + | :meth:`add_streamlines` | | ✓ | + +-------------------------------------+--------------+---------------+ | :meth:`add_text` | ✓ | ✓ | +-------------------------------------+--------------+---------------+ | :meth:`add_volume_labels` | | ✓ | @@ -254,6 +256,8 @@ class Brain: +-------------------------------------+--------------+---------------+ | :meth:`remove_skull` | | ✓ | +-------------------------------------+--------------+---------------+ + | :meth:`remove_streamlines` | | ✓ | + +-------------------------------------+--------------+---------------+ | :meth:`remove_text` | | ✓ | +-------------------------------------+--------------+---------------+ | :meth:`remove_volume_labels` | | ✓ | @@ -2530,6 +2534,46 @@ def remove_skull(self): """Remove skull objects from the rendered scene.""" self._remove("skull", render=True) + @fill_doc + def add_streamlines(self, streamlines, line_width=1, color="red", alpha=1): + """Add a streamlines to render fiber tracts. + + Parameters + ---------- + streamlines : list + A list of array-like points that form lines in units of m. + line_width : int + The width of the lines. + %(color_matplotlib)s + %(alpha)s + + Notes + ----- + .. versionadded:: 0.24 + """ + streamlines = [ + streamline * (1e3 if self._units == "mm" else 1) + for streamline in streamlines + ] + color = _to_rgb(color) + + for _ in self._iter_views("vol"): + actor, _ = self._renderer.lines( + streamlines, + color=color, + opacity=alpha, + line_width=line_width, + reset_camera=False, + render=False, + ) + self._add_actor("streamlines", actor) + + self._renderer._update() + + def remove_streamlines(self): + """Remove streamline objects from the rendered scene.""" + self._remove("streamlines", render=True) + @fill_doc def add_volume_labels( self, diff --git a/mne/viz/backends/_abstract.py b/mne/viz/backends/_abstract.py index c31023401ed..cc7a34d5f35 100644 --- a/mne/viz/backends/_abstract.py +++ b/mne/viz/backends/_abstract.py @@ -194,6 +194,66 @@ def mesh( """ pass + @abstractclassmethod + def lines( + self, + lines, + colors, + opacity=1.0, + backface_culling=False, + scalars=None, + colormap=None, + vmin=None, + vmax=None, + interpolate_before_map=True, + line_width=1.0, + polygon_offset=None, + **kwargs, + ): + """Add a mesh in the scene. + + Parameters + ---------- + lines : list + A list of array-like points defining the lines. + colors : list of tuple | str + The colors of the lines as tuples (red, green, blue) of float + values between 0 and 1 or a valid color name (i.e. 'white' + or 'w'). + opacity : float + The opacity of the mesh. + shading : bool + If True, enable the mesh shading. + backface_culling : bool + If True, enable backface culling on the mesh. + scalars : ndarray, shape (n_vertices,) + The scalar valued associated to the vertices. + vmin : float | None + vmin is used to scale the colormap. + If None, the min of the data will be used. + vmax : float | None + vmax is used to scale the colormap. + If None, the max of the data will be used. + colormap : str | np.ndarray | matplotlib.colors.Colormap | None + The colormap to use. + interpolate_before_map : + Enabling makes for a smoother scalars display. Default is True. + When False, OpenGL will interpolate the mapped colors which can + result is showing colors that are not present in the color map. + line_width : int + The width of the lines when representation='wireframe'. + polygon_offset : float + If not None, the factor used to resolve coincident topology. + kwargs : args + The arguments to pass to triangular_mesh + + Returns + ------- + lines : + Handle of the lines in the scene. + """ + pass + @abstractclassmethod def contour( self, diff --git a/mne/viz/backends/_pyvista.py b/mne/viz/backends/_pyvista.py index 3e5062b593b..e86d7be50dc 100644 --- a/mne/viz/backends/_pyvista.py +++ b/mne/viz/backends/_pyvista.py @@ -466,6 +466,47 @@ def mesh( **kwargs, ) + def lines( + self, + lines, + colors, + opacity=1.0, + backface_culling=False, + scalars=None, + colormap=None, + vmin=None, + vmax=None, + interpolate_before_map=True, + line_width=1.0, + polygon_offset=None, + **kwargs, + ): + actors = list() + meshes = list() + colors = colors if np.iterable(colors) else [colors] * len(lines) + if scalars is None: + scalars_lines = [None] * len(lines) + else: + scalars_lines = scalars if np.iterable(scalars) else [scalars] * len(lines) + for line, color, scalars in zip(lines, colors, scalars_lines): + actor, mesh = self.polydata( + mesh=pyvista.MultipleLines(line), + color=color, + opacity=opacity, + backface_culling=backface_culling, + scalars=scalars, + colormap=colormap, + vmin=vmin, + vmax=vmax, + interpolate_before_map=interpolate_before_map, + line_width=line_width, + polygon_offset=polygon_offset, + **kwargs, + ) + actors.append(actor) + meshes.append(mesh) + return actors, meshes + def contour( self, surface, diff --git a/mne/viz/backends/tests/test_renderer.py b/mne/viz/backends/tests/test_renderer.py index b20bb6e4865..e1de4b569bf 100644 --- a/mne/viz/backends/tests/test_renderer.py +++ b/mne/viz/backends/tests/test_renderer.py @@ -101,6 +101,9 @@ def test_3d_backend(renderer): txt_text = "renderer" txt_size = 14 + lines_data = [[0, 0, 0], [1, 1, 1]] + lines_color = "red" + cam_distance = 5 * tet_size # init scene @@ -123,6 +126,14 @@ def test_3d_backend(renderer): ) rend.remove_mesh(mesh_data) + # use lines + lines_actor = rend.lines( + lines_data, + colors=lines_color, + line_width=5, + ) + rend.remove_mesh(lines_actor) + # use contour rend.contour( surface=ct_surface, scalars=ct_scalars, contours=ct_levels, kind="line" diff --git a/tutorials/clinical/20_seeg.py b/tutorials/clinical/20_seeg.py index 6166001c075..5e089660a4f 100644 --- a/tutorials/clinical/20_seeg.py +++ b/tutorials/clinical/20_seeg.py @@ -39,7 +39,21 @@ # %% +import dipy.reconst.dti as dti +import matplotlib.pyplot as plt +import nibabel as nib import numpy as np +from dipy.core.gradients import gradient_table +from dipy.data import default_sphere +from dipy.denoise.gibbs import gibbs_removal +from dipy.denoise.patch2self import patch2self +from dipy.direction import DeterministicMaximumDirectionGetter +from dipy.direction.peaks import peaks_from_model +from dipy.segment.mask import median_otsu +from dipy.tracking.local_tracking import LocalTracking +from dipy.tracking.stopping_criterion import ThresholdStoppingCriterion +from dipy.tracking.streamline import Streamlines +from dipy.tracking.utils import seeds_from_mask import mne from mne.datasets import fetch_fsaverage @@ -158,6 +172,130 @@ fig, ax = mne.viz.plot_channel_labels_circle(labels, colors, picks=picks) fig.text(0.3, 0.9, "Anatomical Labels", color="white") +# %% +# For electrode contacts in white matter, it can be helpful to visualize +# fiber tracts that pass nearby as well. For that we need to do fiber +# tracking on diffusion MR data. + +# load the diffusion MR data +dwi = nib.load(misc_path / "seeg" / "sample_seeg_dwi.nii.gz") +bvals = np.loadtxt(misc_path / "seeg" / "sample_seeg_dwi.bval") +bvecs = np.loadtxt(misc_path / "seeg" / "sample_seeg_dwi.bvec") +gtab = gradient_table(bvals, bvecs) + +# use B0 diffusion data to align with the T1 +b0_idx = tuple(np.where(bvals < 50)[0]) +dwi_masked, mask = median_otsu(np.array(dwi.dataobj), vol_idx=b0_idx) + +fig, ax = plt.subplots() +ax.imshow(np.rot90(dwi_masked[65, ..., 0]), aspect="auto") + +t1 = nib.load(misc_path / "seeg" / "sample_seeg" / "mri", "T1.mgz") +dwi_b0_register = nib.Nifti1Image(dwi_masked[..., b0_idx].mean(axis=-1), dwi.affine) + +# %% +# The code below was run once to find the registration matrix, but to +# save computer resources when building the documentation, we won't +# run it every time:: +# +# reg_affine = mne.transforms.compute_volume_registration( +# moving=dwi_b0_register, static=t1, pipeline='rigids') + +reg_affine = np.array( + [ + [0.99804908, -0.05071631, 0.03641263, 1.36631239], + [0.049687, 0.99835418, 0.0286378, 36.79845134], + [-0.03780511, -0.02677269, 0.99892642, 8.30634414], + [0.0, 0.0, 0.0, 1.0], + ] +) +reg_affine_inv = np.linalg.inv(reg_affine) + +# use registration to move the white matter mask computed +# by freesurfer to the diffusion space +wm = nib.load(misc_path / "seeg" / "sample_seeg" / "mri" / "wm.mgz") +wm_data = np.array(wm.dataobj) +wm_mask = (wm_data == 109) | (wm_data == 110) # white matter values +wm = nib.MGHImage(wm_mask.astype(np.float32), wm.affine) +del wm_data, wm_mask + +# apply the backward registration by using the inverse +wm_dwi = mne.transforms.apply_volume_registration( + moving=wm, static=dwi_b0_register, reg_affine=reg_affine_inv +) + +# check that white matter is aligned properly +fig, ax = plt.subplots() +ax.imshow(np.rot90(dwi_b0_register.dataobj[56]), aspect="auto") +ax.imshow(np.rot90(wm_dwi.dataobj[56]), aspect="auto", cmap="hot", alpha=0.5) + +# now, preprocess the diffusion data to remove noise and do +# fiber tracking +denoised = patch2self(dwi_masked, bvals) +denoised = gibbs_removal(denoised) + +# %% +# You may also want to do the following, but it registers each direction +# of the diffusion image to the T1, so it takes a lot of computational +# resources so we'll skip it for now:: +# +# from dipy.align import motion_correction +# denoised = motion_correction(denoised, dwi.affine, b0_ref=0) + +# compute diffusion tensor imaging to find the peak direction +# for each voxel +tenmodel = dti.TensorModel(gtab) +tenfit = tenmodel.fit(denoised) +pam = peaks_from_model( + tenmodel, + denoised, + default_sphere, + relative_peak_threshold=0.5, + min_separation_angle=25, + mask=wm_dwi.dataobj, +) + +# do fiber tracking +stopping_criterion = ThresholdStoppingCriterion( + pam.gfa, # use generalized fractional anisotropy from the DTI model + 0.25, # threshold for stopping is when FA goes below 0.25 (default) +) +dg = DeterministicMaximumDirectionGetter.from_shcoeff( + pam.shm_coeff, # use spherical harmonic coefficients from the DTI model + max_angle=30.0, # max angle fiber can change at each voxel + sphere=default_sphere, # use default sphere + sh_to_pmf=True, # speeds up computations, takes more memory +) +# use the white matter mask to seed where the fibers start, +# with 1 mm density in all three dimensions +seeds = seeds_from_mask(wm_dwi.dataobj, dwi.affine, density=(1, 1, 1)) +# generate streamlines to represent tracts using the stopping +# criteria, direction getter and seeds +streamline_generator = LocalTracking( + dg, stopping_criterion, seeds, dwi.affine, step_size=0.5 +) +streamlines = Streamlines(streamline_generator) + +# move streamlines from diffusion space to T1 anatomical space, +# only keep non-singleton streamlines +streamlines = [ + mne.transforms.apply_trans(reg_affine_inv, streamline) + for streamline in streamlines + if len(streamline) > 1 +] + +# now convert from scanner RAS to surface RAS +ras2mri = mne.transforms.combine_transforms( + mne.transforms.Transform("ras", "mri_voxel", t1.header.get_ras2vox()), + mne.transforms.Transform("mri_voxel", "mri", t1.header.get_vox2ras_tkr()), + fro="ras", + to="mri", +) +streamlines = [ + mne.transforms.apply_trans(ras2mri, streamline) / 1000 # mm -> m + for streamline in streamlines +] + # %% # Now, let's the electrodes and a few regions of interest that the contacts # of the electrode are proximal to. @@ -192,6 +330,21 @@ figure=fig, ) brain.add_volume_labels(aseg="aparc+aseg", labels=labels) + +# find streamlines near LSMA1 +montage = epochs.get_montage() +montage.apply_trans(mne.transforms.invert_transform(trans)) # head -> mri +ch_pos = montage.get_positions()["ch_pos"] + +thresh = 0.05 # pick streamlines within 3 mm +streamlines_pick = [ + streamline + for streamline in streamlines + if np.linalg.norm(streamline - ch_pos["LPM 1"]).min() < thresh +] + +brain.add_streamlines(streamlines_pick, color="white") + brain.show_view(azimuth=120, elevation=90, distance=0.25) # %% From 1a6a83ed28cf1f1ec7cd79077a3ed57706b18a90 Mon Sep 17 00:00:00 2001 From: Alex Rockhill Date: Fri, 19 Apr 2024 16:22:49 -0700 Subject: [PATCH 2/9] add PR number --- doc/changes/devel/{XXX.newfeature.rst => 12555.newfeature.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename doc/changes/devel/{XXX.newfeature.rst => 12555.newfeature.rst} (100%) diff --git a/doc/changes/devel/XXX.newfeature.rst b/doc/changes/devel/12555.newfeature.rst similarity index 100% rename from doc/changes/devel/XXX.newfeature.rst rename to doc/changes/devel/12555.newfeature.rst From 31a38986cc0c831b48e2a5d4dec50f212fbcf965 Mon Sep 17 00:00:00 2001 From: Alex Rockhill Date: Mon, 22 Apr 2024 13:21:47 -0700 Subject: [PATCH 3/9] style --- mne/utils/docs.py | 2 ++ mne/viz/_brain/_brain.py | 44 ++++++++++++++++++++++++---------- mne/viz/backends/_abstract.py | 16 ++++++------- mne/viz/backends/_pyvista.py | 45 +++++++++++++---------------------- tutorials/clinical/20_seeg.py | 27 ++++++++++++--------- 5 files changed, 75 insertions(+), 59 deletions(-) diff --git a/mne/utils/docs.py b/mne/utils/docs.py index f29ff9508a5..26716817d74 100644 --- a/mne/utils/docs.py +++ b/mne/utils/docs.py @@ -4862,6 +4862,8 @@ def _reflow_param_docstring(docstring, has_first_line=True, width=75): ``min(data)`` or ``max(data)``, respectively.{extra} """ +docdict + docdict["vmin_vmax_tfr_plot"] = """ vmin, vmax : float | None Lower and upper bounds of the colormap. See ``vlim``. diff --git a/mne/viz/_brain/_brain.py b/mne/viz/_brain/_brain.py index 180b97fef60..73543787746 100644 --- a/mne/viz/_brain/_brain.py +++ b/mne/viz/_brain/_brain.py @@ -226,7 +226,7 @@ class Brain: +-------------------------------------+--------------+---------------+ | :meth:`add_skull` | | ✓ | +-------------------------------------+--------------+---------------+ - | :meth:`add_streamlines` | | ✓ | + | :meth:`add_streamline` | | ✓ | +-------------------------------------+--------------+---------------+ | :meth:`add_text` | ✓ | ✓ | +-------------------------------------+--------------+---------------+ @@ -2535,34 +2535,54 @@ def remove_skull(self): self._remove("skull", render=True) @fill_doc - def add_streamlines(self, streamlines, line_width=1, color="red", alpha=1): + def add_streamline( + self, + streamline, + line_width=1, + color="red", + scalars=None, + colormap=None, + vmin=None, + vmax=None, + alpha=1, + ): """Add a streamlines to render fiber tracts. Parameters ---------- - streamlines : list - A list of array-like points that form lines in units of m. + streamline : array shape=(n_points, 3) + An array with 3D points forming a line in units of m. line_width : int - The width of the lines. - %(color_matplotlib)s + The width of the line. + color : list + A list with entries of anything matplotlib accepts: + string, RGB, hex, etc. + scalars : list + A list of scalar values associated with each vertex of + the streamline. + %(colormap)s + vmin : None | float + The minimum value for color scaling. + vmax : None | float + The maximum value for color scaling. %(alpha)s Notes ----- .. versionadded:: 0.24 """ - streamlines = [ - streamline * (1e3 if self._units == "mm" else 1) - for streamline in streamlines - ] color = _to_rgb(color) for _ in self._iter_views("vol"): - actor, _ = self._renderer.lines( - streamlines, + actor, _ = self._renderer.line( + streamline * (1e3 if self._units == "mm" else 1), color=color, opacity=alpha, line_width=line_width, + scalars=scalars, + colormap=colormap, + vmin=vmin, + vmax=vmax, reset_camera=False, render=False, ) diff --git a/mne/viz/backends/_abstract.py b/mne/viz/backends/_abstract.py index cc7a34d5f35..f8a06383ca7 100644 --- a/mne/viz/backends/_abstract.py +++ b/mne/viz/backends/_abstract.py @@ -195,9 +195,9 @@ def mesh( pass @abstractclassmethod - def lines( + def line( self, - lines, + line, colors, opacity=1.0, backface_culling=False, @@ -214,10 +214,10 @@ def lines( Parameters ---------- - lines : list + line : list A list of array-like points defining the lines. - colors : list of tuple | str - The colors of the lines as tuples (red, green, blue) of float + color : tuple | str + The color of the mesh as a tuple (red, green, blue) of float values between 0 and 1 or a valid color name (i.e. 'white' or 'w'). opacity : float @@ -241,7 +241,7 @@ def lines( When False, OpenGL will interpolate the mapped colors which can result is showing colors that are not present in the color map. line_width : int - The width of the lines when representation='wireframe'. + The width of the line. polygon_offset : float If not None, the factor used to resolve coincident topology. kwargs : args @@ -249,8 +249,8 @@ def lines( Returns ------- - lines : - Handle of the lines in the scene. + line : + Handle of the line in the scene. """ pass diff --git a/mne/viz/backends/_pyvista.py b/mne/viz/backends/_pyvista.py index 613ccedf8d3..58c6d999bac 100644 --- a/mne/viz/backends/_pyvista.py +++ b/mne/viz/backends/_pyvista.py @@ -458,10 +458,10 @@ def mesh( **kwargs, ) - def lines( + def line( self, - lines, - colors, + line, + color, opacity=1.0, backface_culling=False, scalars=None, @@ -473,31 +473,20 @@ def lines( polygon_offset=None, **kwargs, ): - actors = list() - meshes = list() - colors = colors if np.iterable(colors) else [colors] * len(lines) - if scalars is None: - scalars_lines = [None] * len(lines) - else: - scalars_lines = scalars if np.iterable(scalars) else [scalars] * len(lines) - for line, color, scalars in zip(lines, colors, scalars_lines): - actor, mesh = self.polydata( - mesh=pyvista.MultipleLines(line), - color=color, - opacity=opacity, - backface_culling=backface_culling, - scalars=scalars, - colormap=colormap, - vmin=vmin, - vmax=vmax, - interpolate_before_map=interpolate_before_map, - line_width=line_width, - polygon_offset=polygon_offset, - **kwargs, - ) - actors.append(actor) - meshes.append(mesh) - return actors, meshes + return self.polydata( + mesh=pyvista.MultipleLines(line), + color=color, + opacity=opacity, + backface_culling=backface_culling, + scalars=scalars, + colormap=colormap, + vmin=vmin, + vmax=vmax, + interpolate_before_map=interpolate_before_map, + line_width=line_width, + polygon_offset=polygon_offset, + **kwargs, + ) def contour( self, diff --git a/tutorials/clinical/20_seeg.py b/tutorials/clinical/20_seeg.py index 5e089660a4f..e8d3b09d399 100644 --- a/tutorials/clinical/20_seeg.py +++ b/tutorials/clinical/20_seeg.py @@ -39,16 +39,15 @@ # %% -import dipy.reconst.dti as dti import matplotlib.pyplot as plt import nibabel as nib import numpy as np from dipy.core.gradients import gradient_table from dipy.data import default_sphere -from dipy.denoise.gibbs import gibbs_removal from dipy.denoise.patch2self import patch2self from dipy.direction import DeterministicMaximumDirectionGetter from dipy.direction.peaks import peaks_from_model +from dipy.reconst.dti import TensorModel from dipy.segment.mask import median_otsu from dipy.tracking.local_tracking import LocalTracking from dipy.tracking.stopping_criterion import ThresholdStoppingCriterion @@ -190,7 +189,7 @@ fig, ax = plt.subplots() ax.imshow(np.rot90(dwi_masked[65, ..., 0]), aspect="auto") -t1 = nib.load(misc_path / "seeg" / "sample_seeg" / "mri", "T1.mgz") +t1 = nib.load(misc_path / "seeg" / "sample_seeg" / "mri" / "T1.mgz") dwi_b0_register = nib.Nifti1Image(dwi_masked[..., b0_idx].mean(axis=-1), dwi.affine) # %% @@ -232,19 +231,24 @@ # now, preprocess the diffusion data to remove noise and do # fiber tracking denoised = patch2self(dwi_masked, bvals) -denoised = gibbs_removal(denoised) + +# %% +# Optionally, you can also remove Gibbs artifact:: +# +# denoised = gibbs_removal(denoised) # %% # You may also want to do the following, but it registers each direction # of the diffusion image to the T1, so it takes a lot of computational # resources so we'll skip it for now:: # -# from dipy.align import motion_correction -# denoised = motion_correction(denoised, dwi.affine, b0_ref=0) +# from dipy.align import motion_correction +# denoised, _ = motion_correction(denoised, gtab, dwi.affine, b0_ref=0) +# denoised = np.array(denoised.dataobj) # compute diffusion tensor imaging to find the peak direction # for each voxel -tenmodel = dti.TensorModel(gtab) +tenmodel = TensorModel(gtab) tenfit = tenmodel.fit(denoised) pam = peaks_from_model( tenmodel, @@ -277,11 +281,11 @@ streamlines = Streamlines(streamline_generator) # move streamlines from diffusion space to T1 anatomical space, -# only keep non-singleton streamlines +# only keep long streamlines streamlines = [ mne.transforms.apply_trans(reg_affine_inv, streamline) for streamline in streamlines - if len(streamline) > 1 + if len(streamline) > 10 ] # now convert from scanner RAS to surface RAS @@ -336,14 +340,15 @@ montage.apply_trans(mne.transforms.invert_transform(trans)) # head -> mri ch_pos = montage.get_positions()["ch_pos"] -thresh = 0.05 # pick streamlines within 3 mm +thresh = 0.03 # pick streamlines within 30 mm streamlines_pick = [ streamline for streamline in streamlines if np.linalg.norm(streamline - ch_pos["LPM 1"]).min() < thresh ] -brain.add_streamlines(streamlines_pick, color="white") +for streamline in streamlines_pick: + brain.add_streamline(streamline, color="white") brain.show_view(azimuth=120, elevation=90, distance=0.25) From 9973872d0512a8aca4561729820f995ab4d700c2 Mon Sep 17 00:00:00 2001 From: Alex Rockhill Date: Mon, 22 Apr 2024 13:45:59 -0700 Subject: [PATCH 4/9] fix test --- mne/viz/backends/tests/test_renderer.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mne/viz/backends/tests/test_renderer.py b/mne/viz/backends/tests/test_renderer.py index e1de4b569bf..5691d977186 100644 --- a/mne/viz/backends/tests/test_renderer.py +++ b/mne/viz/backends/tests/test_renderer.py @@ -101,8 +101,8 @@ def test_3d_backend(renderer): txt_text = "renderer" txt_size = 14 - lines_data = [[0, 0, 0], [1, 1, 1]] - lines_color = "red" + line_data = [[0, 0, 0], [1, 1, 1]] + line_color = "red" cam_distance = 5 * tet_size @@ -127,12 +127,12 @@ def test_3d_backend(renderer): rend.remove_mesh(mesh_data) # use lines - lines_actor = rend.lines( - lines_data, - colors=lines_color, + line_actor = rend.line( + line_data, + color=line_color, line_width=5, ) - rend.remove_mesh(lines_actor) + rend.remove_mesh(line_actor) # use contour rend.contour( From 32b809700ba69d665dc306226b8f04fdef84a3d3 Mon Sep 17 00:00:00 2001 From: Alex Rockhill Date: Wed, 24 Apr 2024 16:44:08 -0700 Subject: [PATCH 5/9] wip --- mne/viz/backends/_pyvista.py | 1 + tutorials/clinical/20_seeg.py | 13 +++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/mne/viz/backends/_pyvista.py b/mne/viz/backends/_pyvista.py index 58c6d999bac..09820d520cd 100644 --- a/mne/viz/backends/_pyvista.py +++ b/mne/viz/backends/_pyvista.py @@ -477,6 +477,7 @@ def line( mesh=pyvista.MultipleLines(line), color=color, opacity=opacity, + normals=np.zeros_like(np.array(line)), backface_culling=backface_culling, scalars=scalars, colormap=colormap, diff --git a/tutorials/clinical/20_seeg.py b/tutorials/clinical/20_seeg.py index e8d3b09d399..51b59b2b0b4 100644 --- a/tutorials/clinical/20_seeg.py +++ b/tutorials/clinical/20_seeg.py @@ -197,8 +197,8 @@ # save computer resources when building the documentation, we won't # run it every time:: # -# reg_affine = mne.transforms.compute_volume_registration( -# moving=dwi_b0_register, static=t1, pipeline='rigids') +# reg_affine = mne.transforms.compute_volume_registration( +# moving=dwi_b0_register, static=t1, pipeline='rigids') reg_affine = np.array( [ @@ -235,16 +235,17 @@ # %% # Optionally, you can also remove Gibbs artifact:: # -# denoised = gibbs_removal(denoised) +# from dipy.denoise.gibbs import gibbs_removal +# denoised = gibbs_removal(denoised) # %% # You may also want to do the following, but it registers each direction # of the diffusion image to the T1, so it takes a lot of computational # resources so we'll skip it for now:: # -# from dipy.align import motion_correction -# denoised, _ = motion_correction(denoised, gtab, dwi.affine, b0_ref=0) -# denoised = np.array(denoised.dataobj) +# from dipy.align import motion_correction +# denoised, _ = motion_correction(denoised, gtab, dwi.affine, b0_ref=0) +# denoised = np.array(denoised.dataobj) # compute diffusion tensor imaging to find the peak direction # for each voxel From a5ddbbd6e05a5d6dc887cdd6bb28a768e3d2ce71 Mon Sep 17 00:00:00 2001 From: Alex Rockhill Date: Fri, 26 Apr 2024 11:19:29 -0700 Subject: [PATCH 6/9] use latest mne misc data --- mne/datasets/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mne/datasets/config.py b/mne/datasets/config.py index 22fd45475bc..6a979ae71fb 100644 --- a/mne/datasets/config.py +++ b/mne/datasets/config.py @@ -90,7 +90,7 @@ # here: ↓↓↓↓↓↓↓↓ RELEASES = dict( testing="0.152", - misc="0.27", + misc="0.29", phantom_kit="0.2", ucl_opm_auditory="0.2", ) @@ -131,7 +131,7 @@ ) MNE_DATASETS["misc"] = dict( archive_name=f"{MISC_VERSIONED}.tar.gz", # 'mne-misc-data', - hash="md5:e343d3a00cb49f8a2f719d14f4758afe", + hash="md5:19535192331d9e4e99d8886028c1f447", url=( "https://codeload.github.com/mne-tools/mne-misc-data/tar.gz/" f'{RELEASES["misc"]}' From 0145f0cade06cda9342f672fce41f7b8fc52e247 Mon Sep 17 00:00:00 2001 From: Alex Rockhill Date: Fri, 26 Apr 2024 11:46:34 -0700 Subject: [PATCH 7/9] style --- doc/changes/devel/12555.newfeature.rst | 2 +- tutorials/clinical/20_seeg.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/changes/devel/12555.newfeature.rst b/doc/changes/devel/12555.newfeature.rst index c93de3ab653..1b740088a85 100644 --- a/doc/changes/devel/12555.newfeature.rst +++ b/doc/changes/devel/12555.newfeature.rst @@ -1 +1 @@ -Add diffusion imaging to :ref:`tut-working-with-seeg` including adding :meth:`mne.viz.Brain.add_streamlines` and :meth:`mne.viz.Brain.remove_streamlines` to visualize the fiber tracts, by `Alex Rockhill`_. \ No newline at end of file +Add diffusion imaging to :ref:`tut-working-with-seeg` including adding :meth:`mne.viz.Brain.add_streamline` and :meth:`mne.viz.Brain.remove_streamlines` to visualize the fiber tracts, by `Alex Rockhill`_. \ No newline at end of file diff --git a/tutorials/clinical/20_seeg.py b/tutorials/clinical/20_seeg.py index 51b59b2b0b4..96e5810ebcb 100644 --- a/tutorials/clinical/20_seeg.py +++ b/tutorials/clinical/20_seeg.py @@ -258,6 +258,7 @@ relative_peak_threshold=0.5, min_separation_angle=25, mask=wm_dwi.dataobj, + legacy=False, ) # do fiber tracking From 312dc93ccd91346144b39c84f1989d72d649e009 Mon Sep 17 00:00:00 2001 From: Alex Rockhill Date: Fri, 26 Apr 2024 12:02:29 -0700 Subject: [PATCH 8/9] rerun From 98ce77f74813b7b1f185f071a50eab4013385c24 Mon Sep 17 00:00:00 2001 From: Alex Rockhill Date: Tue, 30 Apr 2024 08:10:58 -0700 Subject: [PATCH 9/9] fix warning --- mne/utils/docs.py | 2 -- tutorials/clinical/20_seeg.py | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/mne/utils/docs.py b/mne/utils/docs.py index 3240ae15dea..b1c15badcd3 100644 --- a/mne/utils/docs.py +++ b/mne/utils/docs.py @@ -4861,8 +4861,6 @@ def _reflow_param_docstring(docstring, has_first_line=True, width=75): ``min(data)`` or ``max(data)``, respectively.{extra} """ -docdict - docdict["vmin_vmax_tfr_plot"] = """ vmin, vmax : float | None Lower and upper bounds of the colormap. See ``vlim``. diff --git a/tutorials/clinical/20_seeg.py b/tutorials/clinical/20_seeg.py index 96e5810ebcb..a4e961f53cd 100644 --- a/tutorials/clinical/20_seeg.py +++ b/tutorials/clinical/20_seeg.py @@ -271,6 +271,7 @@ max_angle=30.0, # max angle fiber can change at each voxel sphere=default_sphere, # use default sphere sh_to_pmf=True, # speeds up computations, takes more memory + legacy=False, # use newer version ) # use the white matter mask to seed where the fibers start, # with 1 mm density in all three dimensions