unveil-python 0.0.2__tar.gz → 0.2.0__tar.gz
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.
- {unveil_python-0.0.2/src/unveil_python.egg-info → unveil_python-0.2.0}/PKG-INFO +14 -1
- unveil_python-0.2.0/README.md +15 -0
- {unveil_python-0.0.2 → unveil_python-0.2.0}/pyproject.toml +3 -1
- {unveil_python-0.0.2 → unveil_python-0.2.0}/src/unveil/__init__.py +1 -1
- {unveil_python-0.0.2 → unveil_python-0.2.0}/src/unveil/core.py +224 -56
- {unveil_python-0.0.2 → unveil_python-0.2.0/src/unveil_python.egg-info}/PKG-INFO +14 -1
- {unveil_python-0.0.2 → unveil_python-0.2.0}/src/unveil_python.egg-info/requires.txt +1 -0
- unveil_python-0.0.2/README.md +0 -4
- {unveil_python-0.0.2 → unveil_python-0.2.0}/LICENSE +0 -0
- {unveil_python-0.0.2 → unveil_python-0.2.0}/setup.cfg +0 -0
- {unveil_python-0.0.2 → unveil_python-0.2.0}/src/unveil/__main__.py +0 -0
- {unveil_python-0.0.2 → unveil_python-0.2.0}/src/unveil_python.egg-info/SOURCES.txt +0 -0
- {unveil_python-0.0.2 → unveil_python-0.2.0}/src/unveil_python.egg-info/dependency_links.txt +0 -0
- {unveil_python-0.0.2 → unveil_python-0.2.0}/src/unveil_python.egg-info/top_level.txt +0 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: unveil-python
|
|
3
|
-
Version: 0.0
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Implementation of UNVEIL
|
|
5
5
|
Author-email: Nicolas Delinte <nicolas.delinte@uclouvain.be>
|
|
6
|
+
License-Expression: GPL-3.0-only
|
|
6
7
|
Project-URL: GitHub, https://github.com/DelinteNicolas/UNVEIL
|
|
7
8
|
Keywords: tractography,viewer
|
|
8
9
|
Classifier: Intended Audience :: Science/Research
|
|
@@ -12,10 +13,22 @@ Description-Content-Type: text/markdown
|
|
|
12
13
|
License-File: LICENSE
|
|
13
14
|
Requires-Dist: dipy
|
|
14
15
|
Requires-Dist: pyvista
|
|
16
|
+
Requires-Dist: pyvistaqt
|
|
15
17
|
Requires-Dist: PyQt6
|
|
16
18
|
Dynamic: license-file
|
|
17
19
|
|
|
18
20
|
# UNVEIL
|
|
21
|
+
[](https://pypi.org/project/unveil-python/)
|
|
22
|
+
[](https://pypi.org/project/unveil-python/)
|
|
23
|
+

|
|
24
|
+
|
|
25
|
+
|
|
19
26
|
A simple tractography viewer implemented in python.
|
|
20
27
|
|
|
21
28
|

|
|
29
|
+
|
|
30
|
+
The GUI is launched with the following python command:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
import unveil.__main__
|
|
34
|
+
```
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# UNVEIL
|
|
2
|
+
[](https://pypi.org/project/unveil-python/)
|
|
3
|
+
[](https://pypi.org/project/unveil-python/)
|
|
4
|
+

|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
A simple tractography viewer implemented in python.
|
|
8
|
+
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
The GUI is launched with the following python command:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
import unveil.__main__
|
|
15
|
+
```
|
|
@@ -9,6 +9,7 @@ authors = [
|
|
|
9
9
|
]
|
|
10
10
|
readme="README.md"
|
|
11
11
|
description = "Implementation of UNVEIL"
|
|
12
|
+
license = "GPL-3.0-only"
|
|
12
13
|
keywords = ["tractography", "viewer"]
|
|
13
14
|
classifiers = ['Intended Audience :: Science/Research',
|
|
14
15
|
'Natural Language :: English',
|
|
@@ -16,6 +17,7 @@ classifiers = ['Intended Audience :: Science/Research',
|
|
|
16
17
|
]
|
|
17
18
|
dependencies = ['dipy',
|
|
18
19
|
'pyvista',
|
|
20
|
+
'pyvistaqt',
|
|
19
21
|
'PyQt6'
|
|
20
22
|
]
|
|
21
23
|
dynamic = ["version"]
|
|
@@ -27,4 +29,4 @@ version = {attr = "unveil.__version__"}
|
|
|
27
29
|
"GitHub" = "https://github.com/DelinteNicolas/UNVEIL"
|
|
28
30
|
|
|
29
31
|
[tool.setuptools.packages.find]
|
|
30
|
-
where = ["src"]
|
|
32
|
+
where = ["src"]
|
|
@@ -2,14 +2,13 @@
|
|
|
2
2
|
"""
|
|
3
3
|
Created on Tue Mar 4 13:03:28 2025
|
|
4
4
|
|
|
5
|
-
@author:
|
|
5
|
+
@author: DELINTE Nicolas
|
|
6
6
|
"""
|
|
7
|
-
|
|
8
7
|
import sys
|
|
9
8
|
import numpy as np
|
|
10
9
|
import nibabel as nib
|
|
11
10
|
import pyvista as pv
|
|
12
|
-
from pyvistaqt import QtInteractor
|
|
11
|
+
from pyvistaqt import QtInteractor
|
|
13
12
|
from PyQt6.QtCore import Qt
|
|
14
13
|
from PyQt6.QtGui import QAction
|
|
15
14
|
from PyQt6.QtWidgets import (QApplication, QWidget, QHBoxLayout, QVBoxLayout,
|
|
@@ -22,9 +21,52 @@ from matplotlib.colors import LinearSegmentedColormap
|
|
|
22
21
|
# from unravel.viz import plot_trk # We'll use our local version below
|
|
23
22
|
|
|
24
23
|
|
|
24
|
+
def gifti_to_pyvista(gii_path):
|
|
25
|
+
"""
|
|
26
|
+
Load a GIFTI surface and transform coordinates into voxel space
|
|
27
|
+
using the inverse of the given NIfTI affine.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
# Affine from MNI152 1mm template (voxel→world)
|
|
31
|
+
affine = np.array([
|
|
32
|
+
[-1.0, 0.0, 0.0, 90.0],
|
|
33
|
+
[0.0, 1.0, 0.0, -126.0],
|
|
34
|
+
[0.0, 0.0, 1.0, -72.0],
|
|
35
|
+
[0.0, 0.0, 0.0, 1.0]
|
|
36
|
+
])
|
|
37
|
+
|
|
38
|
+
# Compute inverse to go from world (mm) → voxel coordinates
|
|
39
|
+
inv_affine = np.linalg.inv(affine)
|
|
40
|
+
|
|
41
|
+
# Load GIFTI file
|
|
42
|
+
img = nib.load(gii_path)
|
|
43
|
+
coords_list = img.get_arrays_from_intent('NIFTI_INTENT_POINTSET')
|
|
44
|
+
face_list = img.get_arrays_from_intent('NIFTI_INTENT_TRIANGLE')
|
|
45
|
+
|
|
46
|
+
if len(coords_list) == 0 or len(face_list) == 0:
|
|
47
|
+
raise RuntimeError(
|
|
48
|
+
"Could not find POINTSET or TRIANGLE arrays in GIFTI file.")
|
|
49
|
+
|
|
50
|
+
# Extract data
|
|
51
|
+
coords_world = coords_list[0].data.astype(np.float32)
|
|
52
|
+
faces = face_list[0].data.astype(np.int32)
|
|
53
|
+
|
|
54
|
+
# Transform to voxel space
|
|
55
|
+
coords_voxel = nib.affines.apply_affine(inv_affine, coords_world)
|
|
56
|
+
|
|
57
|
+
# Build PyVista face array (VTK expects [3, i1, i2, i3, 3, ...])
|
|
58
|
+
faces_pv = np.hstack(
|
|
59
|
+
(np.full((faces.shape[0], 1), 3, dtype=np.int32), faces)).astype(np.int32)
|
|
60
|
+
|
|
61
|
+
# Create PolyData mesh in voxel space
|
|
62
|
+
mesh = pv.PolyData(coords_voxel, faces_pv)
|
|
63
|
+
|
|
64
|
+
return mesh
|
|
65
|
+
|
|
66
|
+
|
|
25
67
|
def plot_trk(trk_file, scalar=None, color_map='plasma', opacity: float = 1,
|
|
26
68
|
show_points: bool = False, background: str = 'black', plotter=None,
|
|
27
|
-
name=None):
|
|
69
|
+
name=None, reset_camera=False):
|
|
28
70
|
'''
|
|
29
71
|
3D render for .trk files.
|
|
30
72
|
|
|
@@ -114,18 +156,21 @@ def plot_trk(trk_file, scalar=None, color_map='plasma', opacity: float = 1,
|
|
|
114
156
|
p.add_mesh(mesh, ambient=ambient, opacity=opacity, diffuse=diffuse,
|
|
115
157
|
interpolate_before_map=False, render_lines_as_tubes=True,
|
|
116
158
|
line_width=2, point_size=point_size, rgb=rgb,
|
|
117
|
-
cmap=color_map, clim=color_lim, scalars=scalars, name=name
|
|
159
|
+
cmap=color_map, clim=color_lim, scalars=scalars, name=name,
|
|
160
|
+
reset_camera=reset_camera)
|
|
118
161
|
|
|
119
162
|
elif color_map == 'flesh':
|
|
120
163
|
p.add_mesh(mesh, opacity=opacity, diffuse=0.4, ambient=ambient,
|
|
121
164
|
interpolate_before_map=False, render_lines_as_tubes=True,
|
|
122
165
|
line_width=2, point_size=point_size, rgb=rgb,
|
|
123
|
-
color=[250, 225, 210], name=name
|
|
166
|
+
color=[250, 225, 210], name=name,
|
|
167
|
+
reset_camera=reset_camera)
|
|
124
168
|
else:
|
|
125
169
|
p.add_mesh(mesh, opacity=opacity, diffuse=diffuse, ambient=ambient,
|
|
126
170
|
interpolate_before_map=False, render_lines_as_tubes=True,
|
|
127
171
|
line_width=2, point_size=point_size, rgb=rgb,
|
|
128
|
-
cmap=color_map, scalars=scalars, name=name
|
|
172
|
+
cmap=color_map, scalars=scalars, name=name,
|
|
173
|
+
reset_camera=reset_camera)
|
|
129
174
|
|
|
130
175
|
p.background_color = background
|
|
131
176
|
# Do not call p.show() here when using an embedded interactor
|
|
@@ -147,14 +192,42 @@ class TrkViewer(QWidget):
|
|
|
147
192
|
self.background = 'white'
|
|
148
193
|
|
|
149
194
|
def initUI(self):
|
|
150
|
-
# Create the main
|
|
151
|
-
|
|
195
|
+
# Create the main layout
|
|
196
|
+
global_layout = QVBoxLayout(self)
|
|
197
|
+
|
|
198
|
+
# Camera toolbar (buttons for orientation)
|
|
199
|
+
camera_label = QLabel('Camera: ')
|
|
200
|
+
cam_layout = QHBoxLayout()
|
|
201
|
+
btn_iso = QPushButton("Isometric")
|
|
202
|
+
btn_xy = QPushButton("Axe 1")
|
|
203
|
+
btn_xz = QPushButton("Axe 2")
|
|
204
|
+
btn_yz = QPushButton("Axe 3")
|
|
205
|
+
btn_iso.clicked.connect(self.view_isometric)
|
|
206
|
+
btn_xy.clicked.connect(self.view_xy)
|
|
207
|
+
btn_xz.clicked.connect(self.view_xz)
|
|
208
|
+
btn_yz.clicked.connect(self.view_yz)
|
|
209
|
+
cam_layout.addWidget(camera_label)
|
|
210
|
+
cam_layout.addWidget(btn_iso)
|
|
211
|
+
cam_layout.addWidget(btn_xy)
|
|
212
|
+
cam_layout.addWidget(btn_xz)
|
|
213
|
+
cam_layout.addWidget(btn_yz)
|
|
214
|
+
btn_screenshot = QPushButton("Screenshot")
|
|
215
|
+
btn_screenshot.clicked.connect(self.take_screenshot)
|
|
216
|
+
cam_layout.addWidget(btn_screenshot)
|
|
217
|
+
btn_gif = QPushButton("Create GIF")
|
|
218
|
+
btn_gif.setToolTip("This takes ~2min to save after animation end.")
|
|
219
|
+
btn_gif.clicked.connect(self.create_gif)
|
|
220
|
+
cam_layout.addWidget(btn_gif)
|
|
221
|
+
|
|
222
|
+
global_layout.addLayout(cam_layout)
|
|
223
|
+
|
|
224
|
+
main_layout = QHBoxLayout()
|
|
225
|
+
global_layout.addLayout(main_layout)
|
|
152
226
|
|
|
153
227
|
# Left side: Control panel
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
# control_layout.addWidget(self.loadButton)
|
|
228
|
+
control_widget = QWidget()
|
|
229
|
+
control_layout = QVBoxLayout(control_widget)
|
|
230
|
+
control_widget.setMaximumWidth(250)
|
|
158
231
|
|
|
159
232
|
self.opacityLabel = QLabel('Opacity:')
|
|
160
233
|
control_layout.addWidget(self.opacityLabel)
|
|
@@ -188,6 +261,13 @@ class TrkViewer(QWidget):
|
|
|
188
261
|
self.showVolumeCheckbox.stateChanged.connect(self.update_nii_viewer)
|
|
189
262
|
control_layout.addWidget(self.showVolumeCheckbox)
|
|
190
263
|
|
|
264
|
+
self.nii_opacitySlider = QSlider(Qt.Orientation.Horizontal, self)
|
|
265
|
+
self.nii_opacitySlider.setMinimum(0)
|
|
266
|
+
self.nii_opacitySlider.setMaximum(1000)
|
|
267
|
+
self.nii_opacitySlider.setValue(45)
|
|
268
|
+
self.nii_opacitySlider.sliderReleased.connect(self.update_nii_viewer)
|
|
269
|
+
control_layout.addWidget(self.nii_opacitySlider)
|
|
270
|
+
|
|
191
271
|
self.showSlicesCheckbox = QCheckBox('Show Slices')
|
|
192
272
|
self.showSlicesCheckbox.stateChanged.connect(self.update_nii_viewer)
|
|
193
273
|
control_layout.addWidget(self.showSlicesCheckbox)
|
|
@@ -220,18 +300,26 @@ class TrkViewer(QWidget):
|
|
|
220
300
|
self.ZSlider.sliderReleased.connect(self._update_nii_z)
|
|
221
301
|
control_layout.addWidget(self.ZSlider)
|
|
222
302
|
|
|
223
|
-
#
|
|
224
|
-
|
|
225
|
-
|
|
303
|
+
# Gifti
|
|
304
|
+
self.showSurfaceCheckbox = QCheckBox('Show Surface')
|
|
305
|
+
self.showSurfaceCheckbox.stateChanged.connect(self.update_gii_viewer)
|
|
306
|
+
control_layout.addWidget(self.showSurfaceCheckbox)
|
|
307
|
+
|
|
308
|
+
self.gii_opacitySlider = QSlider(Qt.Orientation.Horizontal, self)
|
|
309
|
+
self.gii_opacitySlider.setMinimum(0)
|
|
310
|
+
self.gii_opacitySlider.setMaximum(100)
|
|
311
|
+
self.gii_opacitySlider.setValue(15)
|
|
312
|
+
self.gii_opacitySlider.sliderReleased.connect(self.update_gii_viewer)
|
|
313
|
+
control_layout.addWidget(self.gii_opacitySlider)
|
|
226
314
|
|
|
227
315
|
# Add the control panel layout to the main layout
|
|
228
|
-
main_layout.
|
|
316
|
+
main_layout.addWidget(control_widget)
|
|
229
317
|
|
|
230
318
|
# Right side: PyVista viewer embedded in the GUI using QtInteractor
|
|
231
319
|
self.plotter = QtInteractor(self)
|
|
232
320
|
main_layout.addWidget(self.plotter.interactor)
|
|
233
321
|
|
|
234
|
-
self.setLayout(
|
|
322
|
+
self.setLayout(global_layout)
|
|
235
323
|
self.setWindowTitle("Tractography Viewer")
|
|
236
324
|
|
|
237
325
|
def loadTrkFile(self):
|
|
@@ -244,7 +332,7 @@ class TrkViewer(QWidget):
|
|
|
244
332
|
self.names.append(self.trk_file)
|
|
245
333
|
self.selected_name = self.trk_file
|
|
246
334
|
|
|
247
|
-
self.update_trk_viewer()
|
|
335
|
+
self.update_trk_viewer(reset_camera=True)
|
|
248
336
|
|
|
249
337
|
def loadNiftiFile(self):
|
|
250
338
|
options = QFileDialog.Options()
|
|
@@ -253,9 +341,13 @@ class TrkViewer(QWidget):
|
|
|
253
341
|
if filePath:
|
|
254
342
|
self.nii_file = filePath
|
|
255
343
|
print(f"Loaded NIfTI file: {self.nii_file}")
|
|
256
|
-
|
|
344
|
+
img = nib.load(filePath)
|
|
345
|
+
self.nii_data = img.get_fdata()
|
|
257
346
|
grid = pv.ImageData()
|
|
258
347
|
grid.dimensions = np.array(self.nii_data.shape) + 1
|
|
348
|
+
# World coordinates
|
|
349
|
+
# grid.origin = img.affine[:3, 3]
|
|
350
|
+
# grid.spacing = np.diag(img.affine)[:3]
|
|
259
351
|
grid.cell_data['values'] = self.nii_data.flatten(order='F')
|
|
260
352
|
self.grid = grid
|
|
261
353
|
|
|
@@ -263,7 +355,18 @@ class TrkViewer(QWidget):
|
|
|
263
355
|
self.YSlider.setMaximum(self.nii_data.shape[1])
|
|
264
356
|
self.ZSlider.setMaximum(self.nii_data.shape[2])
|
|
265
357
|
|
|
266
|
-
self.update_nii_viewer()
|
|
358
|
+
self.update_nii_viewer(reset_camera=True)
|
|
359
|
+
|
|
360
|
+
def loadGiftiFile(self):
|
|
361
|
+
options = QFileDialog.Options()
|
|
362
|
+
filePath, _ = QFileDialog.getOpenFileName(
|
|
363
|
+
self, "Open .gii File", "", "Gifti Files (*.gii)", options=options)
|
|
364
|
+
if filePath:
|
|
365
|
+
self.gii_file = filePath
|
|
366
|
+
print(f"Loaded GIfTI file: {self.gii_file}")
|
|
367
|
+
self.gii_mesh = gifti_to_pyvista(filePath)
|
|
368
|
+
|
|
369
|
+
self.update_gii_viewer(reset_camera=True)
|
|
267
370
|
|
|
268
371
|
def set_background_color(self):
|
|
269
372
|
|
|
@@ -274,7 +377,76 @@ class TrkViewer(QWidget):
|
|
|
274
377
|
|
|
275
378
|
self.plotter.background_color = self.background
|
|
276
379
|
|
|
277
|
-
def
|
|
380
|
+
def view_isometric(self):
|
|
381
|
+
# equivalent to BackgroundPlotter 'iso' camera position
|
|
382
|
+
self.plotter.view_isometric()
|
|
383
|
+
self.plotter.render()
|
|
384
|
+
|
|
385
|
+
def view_xy(self):
|
|
386
|
+
# view from +Z axis, XY plane (axial)
|
|
387
|
+
self.plotter.view_xy() # pyvista method
|
|
388
|
+
self.plotter.render()
|
|
389
|
+
|
|
390
|
+
def view_xz(self):
|
|
391
|
+
# view from +Y axis, XZ plane (sagittal)
|
|
392
|
+
self.plotter.view_xz()
|
|
393
|
+
self.plotter.render()
|
|
394
|
+
|
|
395
|
+
def view_yz(self):
|
|
396
|
+
# view from +X axis, YZ plane (coronal)
|
|
397
|
+
self.plotter.view_yz()
|
|
398
|
+
self.plotter.render()
|
|
399
|
+
|
|
400
|
+
def take_screenshot(self):
|
|
401
|
+
"""Take a screenshot of the current 3D view."""
|
|
402
|
+
options = QFileDialog.Options()
|
|
403
|
+
file_path, _ = QFileDialog.getSaveFileName(self, "Save Screenshot", "",
|
|
404
|
+
"PNG Image (*.png);;JPEG Image (*.jpg);;TIFF Image (*.tif)",
|
|
405
|
+
options=options)
|
|
406
|
+
if file_path:
|
|
407
|
+
self.plotter.screenshot(file_path, transparent_background=True)
|
|
408
|
+
|
|
409
|
+
def create_gif(self):
|
|
410
|
+
"""Create a 360° rotation GIF of the current 3D view."""
|
|
411
|
+
options = QFileDialog.Options()
|
|
412
|
+
file_path, _ = QFileDialog.getSaveFileName(self, "Save GIF", "",
|
|
413
|
+
"GIF Files (*.gif)",
|
|
414
|
+
options=options)
|
|
415
|
+
|
|
416
|
+
if not file_path:
|
|
417
|
+
return
|
|
418
|
+
|
|
419
|
+
# Ensure the file has a .gif extension
|
|
420
|
+
if not file_path.lower().endswith(".gif"):
|
|
421
|
+
file_path += ".gif"
|
|
422
|
+
|
|
423
|
+
# Use an offscreen plotter so the main one remains visible
|
|
424
|
+
offscreen = pv.Plotter(
|
|
425
|
+
off_screen=True, window_size=self.plotter.window_size)
|
|
426
|
+
|
|
427
|
+
# Copy all meshes and volumes from the main plotter to the offscreen one
|
|
428
|
+
for actor_name, actor in self.plotter.actors.items():
|
|
429
|
+
offscreen.add_actor(actor.copy())
|
|
430
|
+
|
|
431
|
+
# Match background color and camera
|
|
432
|
+
offscreen.background_color = self.plotter.background_color
|
|
433
|
+
offscreen.camera_position = self.plotter.camera_position
|
|
434
|
+
offscreen.camera.SetPosition(self.plotter.camera.GetPosition())
|
|
435
|
+
offscreen.camera.SetFocalPoint(self.plotter.camera.GetFocalPoint())
|
|
436
|
+
offscreen.camera.SetViewUp(self.plotter.camera.GetViewUp())
|
|
437
|
+
|
|
438
|
+
# Create the 360° rotation GIF
|
|
439
|
+
offscreen.open_gif(file_path, fps=20)
|
|
440
|
+
n_frames = 360
|
|
441
|
+
for i in range(n_frames):
|
|
442
|
+
offscreen.camera.Azimuth(360 / n_frames)
|
|
443
|
+
offscreen.render()
|
|
444
|
+
offscreen.write_frame()
|
|
445
|
+
self.plotter.camera.azimuth += 360 / n_frames
|
|
446
|
+
self.plotter.render()
|
|
447
|
+
offscreen.close()
|
|
448
|
+
|
|
449
|
+
def update_trk_viewer(self, reset_camera=False):
|
|
278
450
|
|
|
279
451
|
opacity = self.opacitySlider.value() / 100.0
|
|
280
452
|
show_points = self.showPointsCheckbox.isChecked()
|
|
@@ -294,7 +466,8 @@ class TrkViewer(QWidget):
|
|
|
294
466
|
|
|
295
467
|
plot_trk(file, opacity=opacity, plotter=self.plotter, scalar=scalar,
|
|
296
468
|
show_points=show_points, color_map=color_map,
|
|
297
|
-
name=file, background=self.background
|
|
469
|
+
name=file, background=self.background,
|
|
470
|
+
reset_camera=reset_camera)
|
|
298
471
|
|
|
299
472
|
def _update_nii_x(self):
|
|
300
473
|
|
|
@@ -307,7 +480,8 @@ class TrkViewer(QWidget):
|
|
|
307
480
|
slice_x = self.grid.slice('x', center)
|
|
308
481
|
self.plotter.add_mesh(slice_x, cmap='grey', name='nii_x',
|
|
309
482
|
show_scalar_bar=False, point_size=0,
|
|
310
|
-
render_lines_as_tubes=True
|
|
483
|
+
render_lines_as_tubes=True,
|
|
484
|
+
reset_camera=False)
|
|
311
485
|
else:
|
|
312
486
|
self.plotter.remove_actor('nii_x')
|
|
313
487
|
|
|
@@ -322,7 +496,8 @@ class TrkViewer(QWidget):
|
|
|
322
496
|
slice_y = self.grid.slice('y', center)
|
|
323
497
|
self.plotter.add_mesh(slice_y, cmap='grey', name='nii_y',
|
|
324
498
|
show_scalar_bar=False, point_size=0,
|
|
325
|
-
render_lines_as_tubes=True
|
|
499
|
+
render_lines_as_tubes=True,
|
|
500
|
+
reset_camera=False)
|
|
326
501
|
else:
|
|
327
502
|
self.plotter.remove_actor('nii_y')
|
|
328
503
|
|
|
@@ -334,21 +509,18 @@ class TrkViewer(QWidget):
|
|
|
334
509
|
self.YSlider.value(), self.ZSlider.value())
|
|
335
510
|
|
|
336
511
|
if self.showZCheckbox.isChecked():
|
|
337
|
-
z_val = self.ZSlider.value()
|
|
338
512
|
slice_z = self.grid.slice('z', center)
|
|
339
513
|
self.plotter.add_mesh(slice_z, cmap='grey', name='nii_z',
|
|
340
514
|
show_scalar_bar=False, point_size=0,
|
|
341
|
-
render_lines_as_tubes=True
|
|
515
|
+
render_lines_as_tubes=True,
|
|
516
|
+
reset_camera=False)
|
|
342
517
|
else:
|
|
343
518
|
self.plotter.remove_actor('nii_z')
|
|
344
519
|
|
|
345
|
-
def update_nii_viewer(self):
|
|
520
|
+
def update_nii_viewer(self, reset_camera=False):
|
|
346
521
|
|
|
347
522
|
if self.showSlicesCheckbox.isChecked():
|
|
348
523
|
|
|
349
|
-
center = (self.XSlider.value(),
|
|
350
|
-
self.YSlider.value(), self.ZSlider.value())
|
|
351
|
-
|
|
352
524
|
self._update_nii_x()
|
|
353
525
|
self._update_nii_y()
|
|
354
526
|
self._update_nii_z()
|
|
@@ -359,34 +531,25 @@ class TrkViewer(QWidget):
|
|
|
359
531
|
self.plotter.remove_actor('nii_z')
|
|
360
532
|
|
|
361
533
|
if self.showVolumeCheckbox.isChecked():
|
|
362
|
-
|
|
363
|
-
self.plotter.add_volume(self.grid, cmap='gray', opacity=[0
|
|
364
|
-
show_scalar_bar=False, name='nii_volume'
|
|
534
|
+
opacity = self.nii_opacitySlider.value()/1000
|
|
535
|
+
self.plotter.add_volume(self.grid, cmap='gray', opacity=[0, opacity],
|
|
536
|
+
show_scalar_bar=False, name='nii_volume',
|
|
537
|
+
reset_camera=reset_camera)
|
|
365
538
|
else:
|
|
366
|
-
self.plotter.remove_actor('nii_volume')
|
|
367
|
-
|
|
368
|
-
def viewPlot(self):
|
|
369
|
-
|
|
370
|
-
# Clear previous meshes if necessary
|
|
371
|
-
self.plotter.clear()
|
|
372
|
-
|
|
373
|
-
if self.nii_data is not None and self.grid is not None:
|
|
374
|
-
# Add a slice of the NIfTI volume to the scene
|
|
375
|
-
# self.plotter.add_mesh_slice(self.grid, cmap='gray',
|
|
376
|
-
# show_scalar_bar=False)
|
|
539
|
+
self.plotter.remove_actor('nii_volume', reset_camera=reset_camera)
|
|
377
540
|
|
|
378
|
-
|
|
379
|
-
# show_scalar_bar=False,
|
|
380
|
-
# tubing=False)
|
|
541
|
+
def update_gii_viewer(self, reset_camera=False):
|
|
381
542
|
|
|
382
|
-
|
|
543
|
+
if self.showSurfaceCheckbox.isChecked():
|
|
383
544
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
545
|
+
opacity = self.gii_opacitySlider.value()/100
|
|
546
|
+
self.plotter.add_mesh(self.gii_mesh, color="ghostwhite",
|
|
547
|
+
culling='front', smooth_shading=True,
|
|
548
|
+
opacity=opacity, name='gii_surface',
|
|
549
|
+
reset_camera=reset_camera)
|
|
550
|
+
else:
|
|
551
|
+
self.plotter.remove_actor('gii_surface', reset_camera=reset_camera)
|
|
552
|
+
# self.plotter.actors['gii_surface'].visibility = False
|
|
390
553
|
|
|
391
554
|
|
|
392
555
|
class MainWindow(QMainWindow):
|
|
@@ -395,7 +558,7 @@ class MainWindow(QMainWindow):
|
|
|
395
558
|
self.viewer = TrkViewer()
|
|
396
559
|
self.setCentralWidget(self.viewer)
|
|
397
560
|
self.initMenuBar()
|
|
398
|
-
self.setWindowTitle("Tractography Viewer
|
|
561
|
+
self.setWindowTitle("UNVEIL - Tractography Viewer")
|
|
399
562
|
|
|
400
563
|
def initMenuBar(self):
|
|
401
564
|
menubar = self.menuBar()
|
|
@@ -413,6 +576,11 @@ class MainWindow(QMainWindow):
|
|
|
413
576
|
loadNiftiAction.triggered.connect(self.viewer.loadNiftiFile)
|
|
414
577
|
fileMenu.addAction(loadNiftiAction)
|
|
415
578
|
|
|
579
|
+
# Action to load .gii file
|
|
580
|
+
loadGiftiAction = QAction('Load .gii File', self)
|
|
581
|
+
loadGiftiAction.triggered.connect(self.viewer.loadGiftiFile)
|
|
582
|
+
fileMenu.addAction(loadGiftiAction)
|
|
583
|
+
|
|
416
584
|
# Optional: Exit action
|
|
417
585
|
exitAction = QAction('Exit', self)
|
|
418
586
|
exitAction.triggered.connect(self.close)
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: unveil-python
|
|
3
|
-
Version: 0.0
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Implementation of UNVEIL
|
|
5
5
|
Author-email: Nicolas Delinte <nicolas.delinte@uclouvain.be>
|
|
6
|
+
License-Expression: GPL-3.0-only
|
|
6
7
|
Project-URL: GitHub, https://github.com/DelinteNicolas/UNVEIL
|
|
7
8
|
Keywords: tractography,viewer
|
|
8
9
|
Classifier: Intended Audience :: Science/Research
|
|
@@ -12,10 +13,22 @@ Description-Content-Type: text/markdown
|
|
|
12
13
|
License-File: LICENSE
|
|
13
14
|
Requires-Dist: dipy
|
|
14
15
|
Requires-Dist: pyvista
|
|
16
|
+
Requires-Dist: pyvistaqt
|
|
15
17
|
Requires-Dist: PyQt6
|
|
16
18
|
Dynamic: license-file
|
|
17
19
|
|
|
18
20
|
# UNVEIL
|
|
21
|
+
[](https://pypi.org/project/unveil-python/)
|
|
22
|
+
[](https://pypi.org/project/unveil-python/)
|
|
23
|
+

|
|
24
|
+
|
|
25
|
+
|
|
19
26
|
A simple tractography viewer implemented in python.
|
|
20
27
|
|
|
21
28
|

|
|
29
|
+
|
|
30
|
+
The GUI is launched with the following python command:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
import unveil.__main__
|
|
34
|
+
```
|
unveil_python-0.0.2/README.md
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|