pyNIBS 0.2024.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyNIBS-0.2024.8.dist-info/LICENSE +623 -0
- pyNIBS-0.2024.8.dist-info/METADATA +723 -0
- pyNIBS-0.2024.8.dist-info/RECORD +107 -0
- pyNIBS-0.2024.8.dist-info/WHEEL +5 -0
- pyNIBS-0.2024.8.dist-info/top_level.txt +1 -0
- pynibs/__init__.py +34 -0
- pynibs/coil.py +1367 -0
- pynibs/congruence/__init__.py +15 -0
- pynibs/congruence/congruence.py +1108 -0
- pynibs/congruence/ext_metrics.py +257 -0
- pynibs/congruence/stimulation_threshold.py +318 -0
- pynibs/data/configuration_exp0.yaml +59 -0
- pynibs/data/configuration_linear_MEP.yaml +61 -0
- pynibs/data/configuration_linear_RT.yaml +61 -0
- pynibs/data/configuration_sigmoid4.yaml +68 -0
- pynibs/data/network mapping configuration/configuration guide.md +238 -0
- pynibs/data/network mapping configuration/configuration_TEMPLATE.yaml +42 -0
- pynibs/data/network mapping configuration/configuration_for_testing.yaml +43 -0
- pynibs/data/network mapping configuration/configuration_modelTMS.yaml +43 -0
- pynibs/data/network mapping configuration/configuration_reg_isi_05.yaml +43 -0
- pynibs/data/network mapping configuration/output_documentation.md +185 -0
- pynibs/data/network mapping configuration/recommendations_for_accuracy_threshold.md +77 -0
- pynibs/data/neuron/models/L23_PC_cADpyr_biphasic_v1.csv +1281 -0
- pynibs/data/neuron/models/L23_PC_cADpyr_monophasic_v1.csv +1281 -0
- pynibs/data/neuron/models/L4_LBC_biphasic_v1.csv +1281 -0
- pynibs/data/neuron/models/L4_LBC_monophasic_v1.csv +1281 -0
- pynibs/data/neuron/models/L4_NBC_biphasic_v1.csv +1281 -0
- pynibs/data/neuron/models/L4_NBC_monophasic_v1.csv +1281 -0
- pynibs/data/neuron/models/L4_SBC_biphasic_v1.csv +1281 -0
- pynibs/data/neuron/models/L4_SBC_monophasic_v1.csv +1281 -0
- pynibs/data/neuron/models/L5_TTPC2_cADpyr_biphasic_v1.csv +1281 -0
- pynibs/data/neuron/models/L5_TTPC2_cADpyr_monophasic_v1.csv +1281 -0
- pynibs/expio/Mep.py +1518 -0
- pynibs/expio/__init__.py +8 -0
- pynibs/expio/brainsight.py +979 -0
- pynibs/expio/brainvis.py +71 -0
- pynibs/expio/cobot.py +239 -0
- pynibs/expio/exp.py +1876 -0
- pynibs/expio/fit_funs.py +287 -0
- pynibs/expio/localite.py +1987 -0
- pynibs/expio/signal_ced.py +51 -0
- pynibs/expio/visor.py +624 -0
- pynibs/freesurfer.py +502 -0
- pynibs/hdf5_io/__init__.py +10 -0
- pynibs/hdf5_io/hdf5_io.py +1857 -0
- pynibs/hdf5_io/xdmf.py +1542 -0
- pynibs/mesh/__init__.py +3 -0
- pynibs/mesh/mesh_struct.py +1394 -0
- pynibs/mesh/transformations.py +866 -0
- pynibs/mesh/utils.py +1103 -0
- pynibs/models/_TMS.py +211 -0
- pynibs/models/__init__.py +0 -0
- pynibs/muap.py +392 -0
- pynibs/neuron/__init__.py +2 -0
- pynibs/neuron/neuron_regression.py +284 -0
- pynibs/neuron/util.py +58 -0
- pynibs/optimization/__init__.py +5 -0
- pynibs/optimization/multichannel.py +278 -0
- pynibs/optimization/opt_mep.py +152 -0
- pynibs/optimization/optimization.py +1445 -0
- pynibs/optimization/workhorses.py +698 -0
- pynibs/pckg/__init__.py +0 -0
- pynibs/pckg/biosig/biosig4c++-1.9.5.src_fixed.tar.gz +0 -0
- pynibs/pckg/libeep/__init__.py +0 -0
- pynibs/pckg/libeep/pyeep.so +0 -0
- pynibs/regression/__init__.py +11 -0
- pynibs/regression/dual_node_detection.py +2375 -0
- pynibs/regression/regression.py +2984 -0
- pynibs/regression/score_types.py +0 -0
- pynibs/roi/__init__.py +2 -0
- pynibs/roi/roi.py +895 -0
- pynibs/roi/roi_structs.py +1233 -0
- pynibs/subject.py +1009 -0
- pynibs/tensor_scaling.py +144 -0
- pynibs/tests/data/InstrumentMarker20200225163611937.xml +19 -0
- pynibs/tests/data/TriggerMarkers_Coil0_20200225163443682.xml +14 -0
- pynibs/tests/data/TriggerMarkers_Coil1_20200225170337572.xml +6373 -0
- pynibs/tests/data/Xdmf.dtd +89 -0
- pynibs/tests/data/brainsight_niiImage_nifticoord.txt +145 -0
- pynibs/tests/data/brainsight_niiImage_nifticoord_largefile.txt +1434 -0
- pynibs/tests/data/brainsight_niiImage_niifticoord_mixedtargets.txt +47 -0
- pynibs/tests/data/create_subject_testsub.py +332 -0
- pynibs/tests/data/data.hdf5 +0 -0
- pynibs/tests/data/geo.hdf5 +0 -0
- pynibs/tests/test_coil.py +474 -0
- pynibs/tests/test_elements2nodes.py +100 -0
- pynibs/tests/test_hdf5_io/test_xdmf.py +61 -0
- pynibs/tests/test_mesh_transformations.py +123 -0
- pynibs/tests/test_mesh_utils.py +143 -0
- pynibs/tests/test_nnav_imports.py +101 -0
- pynibs/tests/test_quality_measures.py +117 -0
- pynibs/tests/test_regressdata.py +289 -0
- pynibs/tests/test_roi.py +17 -0
- pynibs/tests/test_rotations.py +86 -0
- pynibs/tests/test_subject.py +71 -0
- pynibs/tests/test_util.py +24 -0
- pynibs/tms_pulse.py +34 -0
- pynibs/util/__init__.py +4 -0
- pynibs/util/dosing.py +233 -0
- pynibs/util/quality_measures.py +562 -0
- pynibs/util/rotations.py +340 -0
- pynibs/util/simnibs.py +763 -0
- pynibs/util/util.py +727 -0
- pynibs/visualization/__init__.py +2 -0
- pynibs/visualization/para.py +4372 -0
- pynibs/visualization/plot_2D.py +137 -0
- pynibs/visualization/render_3D.py +347 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import pynibs
|
|
2
|
+
import matplotlib
|
|
3
|
+
import numpy as np
|
|
4
|
+
from matplotlib import pyplot as plt
|
|
5
|
+
from matplotlib import ticker
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
matplotlib.use("Qt5Agg")
|
|
10
|
+
except ImportError:
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def plot_io_curve(
|
|
15
|
+
mep_data,
|
|
16
|
+
local_mag_e,
|
|
17
|
+
title="I/O curve",
|
|
18
|
+
y_axis_label="EMG p2p-amplitude, µV",
|
|
19
|
+
x_axis_label="mag(E), V/m, scaled",
|
|
20
|
+
fit_fun=None,
|
|
21
|
+
screenshot_fn=None,
|
|
22
|
+
interactive=False,
|
|
23
|
+
mso=None):
|
|
24
|
+
"""
|
|
25
|
+
Plot scattered data of MEP-mag(E) pairs (=> I/O curve).
|
|
26
|
+
Optionally the fit of the proided fit function can be overlaid.
|
|
27
|
+
|
|
28
|
+
Parameters
|
|
29
|
+
----------
|
|
30
|
+
mep_data: np.ndarray[float], [n_stims]
|
|
31
|
+
The acquired MEPs fo each stimulation.
|
|
32
|
+
local_mag_e: np.ndarray[float], [n_stims]
|
|
33
|
+
The local electric field magnitude at the ROI element the I/O curve should be plotted from.
|
|
34
|
+
title: str, optional
|
|
35
|
+
The title of the plot; default: "I/O curve"
|
|
36
|
+
y_axis_label: str, optional
|
|
37
|
+
The title of the y-axis; default: "EMG p2p-amplitude, µV"
|
|
38
|
+
x_axis_label: str, optional
|
|
39
|
+
The title of the x-axis; default: "mag(E), V/m, scaled"
|
|
40
|
+
fit_fun: Callable, optional
|
|
41
|
+
If provided, the fit of the I/O curve with the provided function will be computed and displayed.
|
|
42
|
+
Currently supported: pynibs.simgoid4, pynibs.sigmoid4_log, pynibs.linear
|
|
43
|
+
screenshot_fn: str, optional
|
|
44
|
+
If provided, output the plotted image to that file location.
|
|
45
|
+
interactive: bool
|
|
46
|
+
If True, spawn plot in blocking window.
|
|
47
|
+
mso : np.ndarray or list, optional
|
|
48
|
+
MSO values.
|
|
49
|
+
|
|
50
|
+
Returns
|
|
51
|
+
-------
|
|
52
|
+
True if successful, False otherwise.
|
|
53
|
+
"""
|
|
54
|
+
if interactive:
|
|
55
|
+
matplotlib.use('Qt5Agg')
|
|
56
|
+
else:
|
|
57
|
+
matplotlib.use('Agg')
|
|
58
|
+
|
|
59
|
+
plt.figure(figsize=(11, 11))
|
|
60
|
+
plt.scatter(y=np.log10(mep_data) if fit_fun == pynibs.expio.fit_funs.sigmoid4_log else mep_data,
|
|
61
|
+
x=local_mag_e,
|
|
62
|
+
cmap=plt.cm.get_cmap('spring_r'),
|
|
63
|
+
c=mso if mso is not None else list(range(len(local_mag_e))),
|
|
64
|
+
vmin=0, vmax=100 if mso is not None else len(local_mag_e))
|
|
65
|
+
|
|
66
|
+
plt.scatter(y=np.log10(mep_data[-1]) if fit_fun == pynibs.expio.fit_funs.sigmoid4_log else mep_data[-1],
|
|
67
|
+
x=local_mag_e[-1],
|
|
68
|
+
cmap=plt.cm.get_cmap('spring_r'),
|
|
69
|
+
c=mso[-1] if mso is not None else len(local_mag_e),
|
|
70
|
+
s=500,
|
|
71
|
+
vmin=0, vmax=100 if mso is not None else len(local_mag_e))
|
|
72
|
+
|
|
73
|
+
if fit_fun is not None:
|
|
74
|
+
local_mag_e_np = np.zeros((len(local_mag_e), 1))
|
|
75
|
+
local_mag_e_np[:, 0] = local_mag_e
|
|
76
|
+
|
|
77
|
+
fit_scores, fit_params = pynibs.regression.regress_data(
|
|
78
|
+
e_matrix=np.array(local_mag_e_np),
|
|
79
|
+
mep=mep_data,
|
|
80
|
+
fun=fit_fun,
|
|
81
|
+
n_cpu=16,
|
|
82
|
+
con=None,
|
|
83
|
+
n_refit=10,
|
|
84
|
+
return_fits=True,
|
|
85
|
+
verbose=False,
|
|
86
|
+
pool=None,
|
|
87
|
+
refit_discontinuities=False,
|
|
88
|
+
select_signed_data=False
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
num_samples = 100
|
|
92
|
+
x = np.linspace(0, np.min((120, np.max(local_mag_e))), num_samples)
|
|
93
|
+
if fit_fun == pynibs.expio.fit_funs.sigmoid4_log or fit_fun == pynibs.expio.fit_funs.sigmoid4:
|
|
94
|
+
y = fit_fun(x,
|
|
95
|
+
x0=fit_params[0]['x0'],
|
|
96
|
+
r=fit_params[0]['r'],
|
|
97
|
+
amp=fit_params[0]['amp'],
|
|
98
|
+
y0=fit_params[0]['y0']
|
|
99
|
+
)
|
|
100
|
+
elif fit_fun == pynibs.expio.fit_funs.linear:
|
|
101
|
+
y = fit_fun(x,
|
|
102
|
+
m=fit_params[0]["m"],
|
|
103
|
+
n=fit_params[0]["n"]
|
|
104
|
+
)
|
|
105
|
+
else:
|
|
106
|
+
y = np.zeros(num_samples)
|
|
107
|
+
|
|
108
|
+
title += f", R2: {round(fit_scores[0], 5): <5}"
|
|
109
|
+
plt.plot(x, y, color='black')
|
|
110
|
+
props = dict(boxstyle='round', facecolor='wheat', alpha=0.5)
|
|
111
|
+
# for k, v in fit_params.items()
|
|
112
|
+
text = '\n'.join(f'{k: >7}: {np.round(v,2):2.2f}' for k, v in fit_params[0].items())
|
|
113
|
+
text = f'Fitted params:\n‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n{text}\n n_zaps: {len(mep_data): >6}'
|
|
114
|
+
text += f'\n% MSO: {int(mso[-1]): >5}' if mso is not None else ''
|
|
115
|
+
plt.text(local_mag_e.max() - 5, 2, text, fontsize=18,
|
|
116
|
+
verticalalignment='top', bbox=props, horizontalalignment='right')
|
|
117
|
+
cb = plt.colorbar()
|
|
118
|
+
cb.ax.set_ylabel("% MSO" if mso is not None else "Stimulation Id", fontsize=18)
|
|
119
|
+
|
|
120
|
+
if screenshot_fn is not None:
|
|
121
|
+
if fit_fun == pynibs.expio.fit_funs.sigmoid4_log:
|
|
122
|
+
tick_log_rev = lambda x, y: int(pow(10, x))
|
|
123
|
+
ytick_formatter = ticker.FuncFormatter(tick_log_rev)
|
|
124
|
+
y_axis_label += ",\n log10-scaled"
|
|
125
|
+
ax = plt.gca()
|
|
126
|
+
ax.yaxis.set_major_formatter(ytick_formatter)
|
|
127
|
+
|
|
128
|
+
plt.title(title, fontsize=32)
|
|
129
|
+
plt.xlabel(x_axis_label, fontsize=30)
|
|
130
|
+
plt.ylabel(y_axis_label, fontsize=30)
|
|
131
|
+
plt.xticks(fontsize=28)
|
|
132
|
+
plt.yticks(fontsize=28)
|
|
133
|
+
plt.savefig(screenshot_fn)
|
|
134
|
+
|
|
135
|
+
if interactive:
|
|
136
|
+
plt.show()
|
|
137
|
+
plt.close()
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import h5py
|
|
3
|
+
import os.path
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def render_coil_positions(
|
|
8
|
+
coil_conf_set_1_positions,
|
|
9
|
+
coil_conf_set_1_orientations,
|
|
10
|
+
coil_conf_set_2_positions=None,
|
|
11
|
+
coil_conf_set_2_orientations=None,
|
|
12
|
+
coil_conf_set_3_positions=None,
|
|
13
|
+
coil_conf_set_3_orientations=None,
|
|
14
|
+
fn_mesh=None,
|
|
15
|
+
surf_type="skin",
|
|
16
|
+
viewport_dim=(1280, 720),
|
|
17
|
+
camera_polar_coords=(-175, 66, 110),
|
|
18
|
+
screenshot_fn=None,
|
|
19
|
+
interactive=False):
|
|
20
|
+
"""
|
|
21
|
+
Plots coil positions (and their orientation) in space,
|
|
22
|
+
optionally also display a second set of coil positions,
|
|
23
|
+
optionally also display a surface (e.g. gray matter or skin)
|
|
24
|
+
|
|
25
|
+
Parameters:
|
|
26
|
+
-----------
|
|
27
|
+
coil_conf_set_1_positions : np.ndarray
|
|
28
|
+
The "center" coordinates of the primary coil configurations
|
|
29
|
+
coil_conf_set_1_orientations : np.ndarray
|
|
30
|
+
Either the "m0", "m1" or "m2" orientation vector of the primary coil position.
|
|
31
|
+
coil_conf_set_2_positions : np.ndarray | None, optional
|
|
32
|
+
The "center" coordinates of the secondary coil configurations. Can be omitted.
|
|
33
|
+
coil_conf_set_2_orientations : np.ndarray | None, optional
|
|
34
|
+
Either the "m0", "m1" or "m2" orientation vector of the secondary coil position. Can be omitted.
|
|
35
|
+
fn_mesh : str, optional
|
|
36
|
+
Path to the mesh hdf5-file containing the geometry information of a head model. Can be omitted.
|
|
37
|
+
surf_type : str, optional
|
|
38
|
+
Name of the surface to display. Currently supported: "skin", "skull", "csf", "gm", "wm"
|
|
39
|
+
Only valid if 'fn_mesh' has been provided.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
--------
|
|
43
|
+
True if successful, False in case of a fatal error.
|
|
44
|
+
"""
|
|
45
|
+
try:
|
|
46
|
+
import mayavi.mlab
|
|
47
|
+
except ModuleNotFoundError:
|
|
48
|
+
print("[render_3D.py] Error: Cannot import module mayavi. Most rendering functions will not work.")
|
|
49
|
+
if "mayavi" in sys.modules:
|
|
50
|
+
if not interactive:
|
|
51
|
+
mayavi.mlab.options.offscreen = True
|
|
52
|
+
else:
|
|
53
|
+
mayavi.mlab.options.offscreen = False
|
|
54
|
+
|
|
55
|
+
if not isinstance(viewport_dim, tuple) or len(viewport_dim) != 2:
|
|
56
|
+
viewport_dim = (1280, 720)
|
|
57
|
+
print(
|
|
58
|
+
"[render_coil_positions] Error: Provided viewport dimensions are not in the required format: (x,y),"
|
|
59
|
+
"expected 2 integer representing x,y coordinates of the viewport. Will use default dimensions instead:"
|
|
60
|
+
"(1280,720)")
|
|
61
|
+
|
|
62
|
+
random_figure_id = int(np.random.default_rng().random(1)[0] * 1e9)
|
|
63
|
+
fig = mayavi.mlab.figure(figure=random_figure_id, bgcolor=(1, 1, 1), engine=None, fgcolor=(0., 0., 0.),
|
|
64
|
+
size=viewport_dim)
|
|
65
|
+
|
|
66
|
+
if fn_mesh is not None and os.path.exists(fn_mesh):
|
|
67
|
+
with h5py.File(fn_mesh, 'r') as mesh_h5:
|
|
68
|
+
|
|
69
|
+
# tissue tags from SimNIBS v2 - v4
|
|
70
|
+
tissue_tag = 1005 # default to skin
|
|
71
|
+
|
|
72
|
+
if surf_type == "wm":
|
|
73
|
+
tissue_tag = 1001
|
|
74
|
+
elif surf_type == "gm":
|
|
75
|
+
tissue_tag = 1002
|
|
76
|
+
elif surf_type == "csf":
|
|
77
|
+
tissue_tag = 1003
|
|
78
|
+
elif surf_type == "skull":
|
|
79
|
+
tissue_tag = 1007
|
|
80
|
+
elif surf_type == "skin":
|
|
81
|
+
tissue_tag = 1005
|
|
82
|
+
else:
|
|
83
|
+
print(f"[render_coil_positions] Error: Unsupported surface type '{surf_type}'. Will "
|
|
84
|
+
f"render skin surface per default.")
|
|
85
|
+
|
|
86
|
+
mesh_tissue_type = np.array(mesh_h5["/mesh/elm/tri_tissue_type"])
|
|
87
|
+
mesh_tris = np.array(mesh_h5["/mesh/elm/node_number_list"][:, :3])
|
|
88
|
+
mesh_pts = np.array(mesh_h5["/mesh/nodes/node_coord"])
|
|
89
|
+
surf_tris_idcs = np.where(mesh_tissue_type == tissue_tag)
|
|
90
|
+
surf_tris = mesh_tris[surf_tris_idcs]
|
|
91
|
+
|
|
92
|
+
scalp_surf = mayavi.mlab.triangular_mesh(
|
|
93
|
+
mesh_pts[:, 0],
|
|
94
|
+
mesh_pts[:, 1],
|
|
95
|
+
mesh_pts[:, 2],
|
|
96
|
+
surf_tris,
|
|
97
|
+
representation='surface',
|
|
98
|
+
opacity=1,
|
|
99
|
+
color=(0.8, 0.8, 0.8)
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
if coil_conf_set_2_positions is not None and coil_conf_set_2_orientations is not None:
|
|
103
|
+
mayavi.mlab.points3d(
|
|
104
|
+
coil_conf_set_2_positions[:, 0],
|
|
105
|
+
coil_conf_set_2_positions[:, 1],
|
|
106
|
+
coil_conf_set_2_positions[:, 2],
|
|
107
|
+
scale_factor=0.3,
|
|
108
|
+
color=(.9, .9, .9)
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
mayavi.mlab.quiver3d(
|
|
112
|
+
coil_conf_set_2_positions[:, 0],
|
|
113
|
+
coil_conf_set_2_positions[:, 1],
|
|
114
|
+
coil_conf_set_2_positions[:, 2],
|
|
115
|
+
coil_conf_set_2_orientations[:, 0],
|
|
116
|
+
coil_conf_set_2_orientations[:, 1],
|
|
117
|
+
coil_conf_set_2_orientations[:, 2],
|
|
118
|
+
mode="arrow",
|
|
119
|
+
scale_factor=1,
|
|
120
|
+
color=(.9, .9, .9)
|
|
121
|
+
)
|
|
122
|
+
mayavi.mlab.points3d(
|
|
123
|
+
coil_conf_set_1_positions[:, 0],
|
|
124
|
+
coil_conf_set_1_positions[:, 1],
|
|
125
|
+
coil_conf_set_1_positions[:, 2],
|
|
126
|
+
scale_factor=0.5,
|
|
127
|
+
color=(1., .0, .0)
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
mayavi.mlab.quiver3d(
|
|
131
|
+
coil_conf_set_1_positions[:, 0],
|
|
132
|
+
coil_conf_set_1_positions[:, 1],
|
|
133
|
+
coil_conf_set_1_positions[:, 2],
|
|
134
|
+
coil_conf_set_1_orientations[:, 0],
|
|
135
|
+
coil_conf_set_1_orientations[:, 1],
|
|
136
|
+
coil_conf_set_1_orientations[:, 2],
|
|
137
|
+
mode="arrow",
|
|
138
|
+
scale_factor=2,
|
|
139
|
+
color=(1., .0, .0)
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
if coil_conf_set_3_positions is not None and coil_conf_set_3_orientations is not None:
|
|
143
|
+
mayavi.mlab.points3d(
|
|
144
|
+
coil_conf_set_3_positions[:, 0],
|
|
145
|
+
coil_conf_set_3_positions[:, 1],
|
|
146
|
+
coil_conf_set_3_positions[:, 2],
|
|
147
|
+
scale_factor=2,
|
|
148
|
+
color=(0.161, 0.812, 0.247)
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
mayavi.mlab.quiver3d(
|
|
152
|
+
coil_conf_set_3_positions[:, 0],
|
|
153
|
+
coil_conf_set_3_positions[:, 1],
|
|
154
|
+
coil_conf_set_3_positions[:, 2],
|
|
155
|
+
coil_conf_set_3_orientations[:, 0],
|
|
156
|
+
coil_conf_set_3_orientations[:, 1],
|
|
157
|
+
coil_conf_set_3_orientations[:, 2],
|
|
158
|
+
mode="arrow",
|
|
159
|
+
scale_factor=4,
|
|
160
|
+
color=(0.161, 0.812, 0.247)
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
if not isinstance(camera_polar_coords, tuple) or len(camera_polar_coords) != 3:
|
|
165
|
+
print(
|
|
166
|
+
"[render_coil_positions] Error: Provided camera coordinates are not in the required format: (a,p,r), "
|
|
167
|
+
"3 floats "
|
|
168
|
+
"representing azimuthal angle, polar angle and radius. Will use default coordinates instead:"
|
|
169
|
+
"(-175, 66, 110)")
|
|
170
|
+
camera_polar_coords = (-175, 66, 110)
|
|
171
|
+
|
|
172
|
+
mean_pts = np.mean(coil_conf_set_1_positions, axis=0)
|
|
173
|
+
mayavi.mlab.view(
|
|
174
|
+
azimuth=camera_polar_coords[0],
|
|
175
|
+
elevation=camera_polar_coords[1],
|
|
176
|
+
distance=camera_polar_coords[2],
|
|
177
|
+
focalpoint=np.array(mean_pts),
|
|
178
|
+
roll=None, reset_roll=None, figure=None
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
if screenshot_fn is not None:
|
|
182
|
+
# Bug? Must execute the command twice to generate an output screenshot of the requested dimensions.
|
|
183
|
+
# Otherwise its dimensions will be always (300,300).
|
|
184
|
+
mayavi.mlab.savefig(filename=screenshot_fn, figure=fig)
|
|
185
|
+
mayavi.mlab.savefig(filename=screenshot_fn, figure=fig)
|
|
186
|
+
|
|
187
|
+
if interactive:
|
|
188
|
+
mayavi.mlab.show()
|
|
189
|
+
|
|
190
|
+
# In theory, passing a reference of the to-be closed figure should be
|
|
191
|
+
# sufficient as well. In practice, it did not work reliably yielding
|
|
192
|
+
# error after too many calls to that function because of too many open
|
|
193
|
+
# figures. Introducing a (random) figure-ID and using this ID to refer
|
|
194
|
+
# to the figure seemed to have solved the isse.
|
|
195
|
+
mayavi.mlab.close(random_figure_id)
|
|
196
|
+
mayavi.mlab.close(all=True)
|
|
197
|
+
|
|
198
|
+
return True
|
|
199
|
+
|
|
200
|
+
else:
|
|
201
|
+
print("[render_coil_positions] Error: Required module 'mayavi' could be not successfully imported."
|
|
202
|
+
"Is it installed? Cannot execute 'render_data_on_surface'.")
|
|
203
|
+
|
|
204
|
+
return False
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def render_data_on_surface(
|
|
208
|
+
points,
|
|
209
|
+
tris,
|
|
210
|
+
data,
|
|
211
|
+
viewport_dim=(1280, 720),
|
|
212
|
+
camera_polar_coords=(-175, 66, 110),
|
|
213
|
+
title=None,
|
|
214
|
+
data_name="Data",
|
|
215
|
+
colormap="jet",
|
|
216
|
+
screenshot_fn=None,
|
|
217
|
+
interactive=False
|
|
218
|
+
):
|
|
219
|
+
"""
|
|
220
|
+
Plots data on surface:
|
|
221
|
+
- If the number of data points equals the number of vertices in the mesh, the data will be displayed as point data.
|
|
222
|
+
- If the number of data points equals the number of triangles in the mesh, the data will be displayed as cell data.
|
|
223
|
+
|
|
224
|
+
Parameters
|
|
225
|
+
----------
|
|
226
|
+
points : np.darray[float], [n_points x 3]
|
|
227
|
+
Points (vertices) of surface mesh.
|
|
228
|
+
tris : np.array[float], [n_tris x 3]
|
|
229
|
+
Connectivity list of triangles.
|
|
230
|
+
data : np.array[float], [n_tris]
|
|
231
|
+
Data in triangular center
|
|
232
|
+
viewport_dim : Tuple[int, int]
|
|
233
|
+
Size of the viewport. Default: (1280, 720)
|
|
234
|
+
camera_polar_coords : Tuple[float, float, float)
|
|
235
|
+
The coordinate of the camera around the object in polar coordinates: (azimuthal angle, polar angle, radius)
|
|
236
|
+
title : str
|
|
237
|
+
Tile of the rendering window.
|
|
238
|
+
data_name : str
|
|
239
|
+
Name of the visualized data set.
|
|
240
|
+
colormap : str
|
|
241
|
+
Identifier of the desired color map. Available colormaps are:
|
|
242
|
+
'Accent', 'Blues', 'BrBG', 'BuGn', 'BuPu', 'CMRmap', 'Dark2', 'GnBu', 'Greens', 'Greys'
|
|
243
|
+
'OrRd', 'Oranges', 'PRGn', 'Paired', 'Pastel1', 'Pastel2', 'PiYG', 'PuBu', 'PuBuGn', 'PuOr',
|
|
244
|
+
'PuRd', 'Purples', 'RdBu', 'RdGy', 'RdPu', 'RdYlBu', 'RdYlGn', 'Reds', 'Set1', 'Set2', 'Set3'
|
|
245
|
+
'Spectral', 'Vega10', 'Vega20', 'Vega20b', 'Vega20c', 'Wistia', 'YlGn', 'YlGnBu', 'YlOrBr',
|
|
246
|
+
'YlOrRd', 'afmhot', 'autumn', 'binary', 'black-white', 'blue-red', 'bone', 'brg', 'bwr',
|
|
247
|
+
'cool', 'coolwarm', 'copper', 'cubehelix', 'file', 'flag', 'gist_earth', 'gist_gray',
|
|
248
|
+
'gist_heat', 'gist_ncar', 'gist_rainbow', 'gist_stern', 'gist_yarg', 'gnuplot', 'gnuplot2', 'gray',
|
|
249
|
+
'hot', 'hsv', 'inferno', 'jet', 'magma', 'nipy_spectral', 'ocean', 'pink', 'plasma', 'prism',
|
|
250
|
+
'rainbow', 'seismic', 'spectral', 'spring', 'summer', 'terrain', 'viridis', 'winter'
|
|
251
|
+
screenshot_fn : str | None
|
|
252
|
+
If provided a screenshot will be saved to that path.
|
|
253
|
+
interactive : bool
|
|
254
|
+
If true, a blocking window will be spawned.
|
|
255
|
+
|
|
256
|
+
Returns
|
|
257
|
+
-------
|
|
258
|
+
True if successful, False in case of a fatal error.
|
|
259
|
+
"""
|
|
260
|
+
try:
|
|
261
|
+
import mayavi.mlab
|
|
262
|
+
except ModuleNotFoundError:
|
|
263
|
+
print("[render_3D.py] Error: Cannot import module mayavi. Most rendering functions will not work.")
|
|
264
|
+
if "mayavi" in sys.modules:
|
|
265
|
+
if not interactive:
|
|
266
|
+
mayavi.mlab.options.offscreen = True
|
|
267
|
+
else:
|
|
268
|
+
mayavi.mlab.options.offscreen = False
|
|
269
|
+
|
|
270
|
+
if not isinstance(viewport_dim, tuple) or len(viewport_dim) != 2:
|
|
271
|
+
viewport_dim = (1280, 720)
|
|
272
|
+
print(
|
|
273
|
+
"[render_data_on_surface] Error: Provided viewport dimensions are not in the required format: (x,y),"
|
|
274
|
+
"expected 2 integer representing x,y coordinates of the viewport. Will use default dimensions instead:"
|
|
275
|
+
"(1280,720)")
|
|
276
|
+
|
|
277
|
+
random_figure_id = int(np.random.default_rng().random(1)[0] * 1e9)
|
|
278
|
+
fig = mayavi.mlab.figure(figure=random_figure_id, bgcolor=(1, 1, 1), engine=None, fgcolor=(0., 0., 0.),
|
|
279
|
+
size=viewport_dim)
|
|
280
|
+
mesh = mayavi.mlab.triangular_mesh(points[:, 0], points[:, 1], points[:, 2], tris, representation='wireframe',
|
|
281
|
+
opacity=0)
|
|
282
|
+
|
|
283
|
+
if data.shape[0] == tris.shape[0]:
|
|
284
|
+
mesh.mlab_source.dataset.cell_data.scalars = data
|
|
285
|
+
mesh.mlab_source.dataset.cell_data.scalars.name = data_name
|
|
286
|
+
mesh.mlab_source.update()
|
|
287
|
+
mesh.parent.update()
|
|
288
|
+
mesh = mayavi.mlab.pipeline.set_active_attribute(mesh, cell_scalars=data_name)
|
|
289
|
+
elif data.shape[0] == points.shape[0]:
|
|
290
|
+
mesh.mlab_source.dataset.point_data.scalars = data
|
|
291
|
+
mesh.mlab_source.dataset.point_data.scalars.name = data_name
|
|
292
|
+
mesh.mlab_source.update()
|
|
293
|
+
mesh.parent.update()
|
|
294
|
+
mesh = mayavi.mlab.pipeline.set_active_attribute(mesh, point_scalars=data_name)
|
|
295
|
+
else:
|
|
296
|
+
print(
|
|
297
|
+
"[render_data_on_surface] Error: Provided data does neither equal the number of ertices nor triangles "
|
|
298
|
+
"of the "
|
|
299
|
+
"surface mesh. Cannot map data onto mesh.")
|
|
300
|
+
return False
|
|
301
|
+
|
|
302
|
+
surf = mayavi.mlab.pipeline.surface(mesh, colormap=colormap, vmin=0, vmax=1)
|
|
303
|
+
|
|
304
|
+
if not isinstance(camera_polar_coords, tuple) or len(camera_polar_coords) != 3:
|
|
305
|
+
print(
|
|
306
|
+
"[render_data_on_surface] Error: Provided camera coordinates are not in the required format: (a,p,r), "
|
|
307
|
+
"3 floats "
|
|
308
|
+
"representing azimuthal angle, polar angle and radius. Will use default coordinates instead:"
|
|
309
|
+
"(-175, 66, 110)")
|
|
310
|
+
camera_polar_coords = (-175, 66, 110)
|
|
311
|
+
|
|
312
|
+
mean_pts = np.mean(points, axis=0)
|
|
313
|
+
mayavi.mlab.view(
|
|
314
|
+
azimuth=camera_polar_coords[0],
|
|
315
|
+
elevation=camera_polar_coords[1],
|
|
316
|
+
distance=camera_polar_coords[2],
|
|
317
|
+
focalpoint=np.array(mean_pts),
|
|
318
|
+
roll=None, reset_roll=None, figure=None
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
mayavi.mlab.colorbar(
|
|
322
|
+
surf,
|
|
323
|
+
title=title
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
if screenshot_fn is not None:
|
|
327
|
+
# Bug? Must execute the command twice to generate an output screenshot of the requested dimensions.
|
|
328
|
+
# Otherwise its dimensions will be always (300,300).
|
|
329
|
+
mayavi.mlab.savefig(filename=screenshot_fn, figure=fig)
|
|
330
|
+
mayavi.mlab.savefig(filename=screenshot_fn, figure=fig)
|
|
331
|
+
|
|
332
|
+
if interactive:
|
|
333
|
+
mayavi.mlab.show()
|
|
334
|
+
|
|
335
|
+
# In theory, passing a reference of the to-be closed figure should be
|
|
336
|
+
# sufficient as well. In practice, it did not work reliably yielding
|
|
337
|
+
# error after too many calls to that function because of too many open
|
|
338
|
+
# figures. Introducing a (random) figure-ID and using this ID to refer
|
|
339
|
+
# to the figure seemed to have solved the isse.
|
|
340
|
+
mayavi.mlab.close(random_figure_id)
|
|
341
|
+
|
|
342
|
+
return True
|
|
343
|
+
else:
|
|
344
|
+
print("[render_data_on_surface] Error: Required module 'mayavi' could not be successfully imported."
|
|
345
|
+
"Is it installed? Cannot execute 'render_data_on_surface'.")
|
|
346
|
+
|
|
347
|
+
return False
|