unveil-python 0.2.0__tar.gz → 0.3.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: unveil-python
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Implementation of UNVEIL
5
5
  Author-email: Nicolas Delinte <nicolas.delinte@uclouvain.be>
6
6
  License-Expression: GPL-3.0-only
@@ -27,8 +27,14 @@ A simple tractography viewer implemented in python.
27
27
 
28
28
  ![image](https://github.com/user-attachments/assets/4fbefa7a-3fc2-446a-8835-2844266b14fa)
29
29
 
30
- The GUI is launched with the following python command:
30
+
31
+ The GUI is launched with the following python command in a python console:
31
32
 
32
33
  ```
33
34
  import unveil.__main__
34
35
  ```
36
+ or with
37
+ ```
38
+ python -c "import unveil.__main__"
39
+ ```
40
+ from a terminal with a python environment activated.
@@ -8,8 +8,14 @@ A simple tractography viewer implemented in python.
8
8
 
9
9
  ![image](https://github.com/user-attachments/assets/4fbefa7a-3fc2-446a-8835-2844266b14fa)
10
10
 
11
- The GUI is launched with the following python command:
11
+
12
+ The GUI is launched with the following python command in a python console:
12
13
 
13
14
  ```
14
15
  import unveil.__main__
15
16
  ```
17
+ or with
18
+ ```
19
+ python -c "import unveil.__main__"
20
+ ```
21
+ from a terminal with a python environment activated.
@@ -1,5 +1,5 @@
1
1
  """
2
2
  This package contains an UNVEIL, a lightweight tractography viewer implemented in python."""
3
3
 
4
- __version__ = "0.2.0"
4
+ __version__ = "0.3.0"
5
5
  __author__ = 'Nicolas Delinte'
@@ -13,7 +13,8 @@ from PyQt6.QtCore import Qt
13
13
  from PyQt6.QtGui import QAction
14
14
  from PyQt6.QtWidgets import (QApplication, QWidget, QHBoxLayout, QVBoxLayout,
15
15
  QPushButton, QFileDialog, QCheckBox, QSlider,
16
- QLabel, QComboBox, QMainWindow, QLineEdit)
16
+ QLabel, QComboBox, QMainWindow, QLineEdit,
17
+ QDockWidget, QTreeWidget, QTreeWidgetItem)
17
18
  from dipy.io.streamline import load_tractogram
18
19
  from matplotlib import pyplot as plt
19
20
  from matplotlib.colors import LinearSegmentedColormap
@@ -27,17 +28,6 @@ def gifti_to_pyvista(gii_path):
27
28
  using the inverse of the given NIfTI affine.
28
29
  """
29
30
 
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
31
  # Load GIFTI file
42
32
  img = nib.load(gii_path)
43
33
  coords_list = img.get_arrays_from_intent('NIFTI_INTENT_POINTSET')
@@ -51,15 +41,12 @@ def gifti_to_pyvista(gii_path):
51
41
  coords_world = coords_list[0].data.astype(np.float32)
52
42
  faces = face_list[0].data.astype(np.int32)
53
43
 
54
- # Transform to voxel space
55
- coords_voxel = nib.affines.apply_affine(inv_affine, coords_world)
56
-
57
44
  # Build PyVista face array (VTK expects [3, i1, i2, i3, 3, ...])
58
45
  faces_pv = np.hstack(
59
46
  (np.full((faces.shape[0], 1), 3, dtype=np.int32), faces)).astype(np.int32)
60
47
 
61
48
  # Create PolyData mesh in voxel space
62
- mesh = pv.PolyData(coords_voxel, faces_pv)
49
+ mesh = pv.PolyData(coords_world, faces_pv)
63
50
 
64
51
  return mesh
65
52
 
@@ -157,20 +144,23 @@ def plot_trk(trk_file, scalar=None, color_map='plasma', opacity: float = 1,
157
144
  interpolate_before_map=False, render_lines_as_tubes=True,
158
145
  line_width=2, point_size=point_size, rgb=rgb,
159
146
  cmap=color_map, clim=color_lim, scalars=scalars, name=name,
160
- reset_camera=reset_camera)
147
+ reset_camera=reset_camera,
148
+ user_matrix=trk.affine)
161
149
 
162
150
  elif color_map == 'flesh':
163
151
  p.add_mesh(mesh, opacity=opacity, diffuse=0.4, ambient=ambient,
164
152
  interpolate_before_map=False, render_lines_as_tubes=True,
165
153
  line_width=2, point_size=point_size, rgb=rgb,
166
154
  color=[250, 225, 210], name=name,
167
- reset_camera=reset_camera)
155
+ reset_camera=reset_camera,
156
+ user_matrix=trk.affine)
168
157
  else:
169
158
  p.add_mesh(mesh, opacity=opacity, diffuse=diffuse, ambient=ambient,
170
159
  interpolate_before_map=False, render_lines_as_tubes=True,
171
160
  line_width=2, point_size=point_size, rgb=rgb,
172
161
  cmap=color_map, scalars=scalars, name=name,
173
- reset_camera=reset_camera)
162
+ reset_camera=reset_camera,
163
+ user_matrix=trk.affine)
174
164
 
175
165
  p.background_color = background
176
166
  # Do not call p.show() here when using an embedded interactor
@@ -184,7 +174,6 @@ class TrkViewer(QWidget):
184
174
  self.nii_data = None
185
175
  self.grid = None
186
176
  self.names = []
187
- self.selected_name = None
188
177
  self.x = 0
189
178
  self.y = 0
190
179
  self.z = 0
@@ -257,9 +246,8 @@ class TrkViewer(QWidget):
257
246
  'Insert color map name. Name must be in the matplotlib library. Scalar nii.gz must be loaded.')
258
247
  control_layout.addWidget(self.colorMapEdit)
259
248
 
260
- self.showVolumeCheckbox = QCheckBox('Show Volume')
261
- self.showVolumeCheckbox.stateChanged.connect(self.update_nii_viewer)
262
- control_layout.addWidget(self.showVolumeCheckbox)
249
+ self.volume_label = QLabel('Volume')
250
+ control_layout.addWidget(self.volume_label)
263
251
 
264
252
  self.nii_opacitySlider = QSlider(Qt.Orientation.Horizontal, self)
265
253
  self.nii_opacitySlider.setMinimum(0)
@@ -272,27 +260,24 @@ class TrkViewer(QWidget):
272
260
  self.showSlicesCheckbox.stateChanged.connect(self.update_nii_viewer)
273
261
  control_layout.addWidget(self.showSlicesCheckbox)
274
262
 
275
- self.showXCheckbox = QCheckBox('X')
276
- self.showXCheckbox.stateChanged.connect(self._update_nii_x)
277
- control_layout.addWidget(self.showXCheckbox)
263
+ self.x_label = QLabel('X')
264
+ control_layout.addWidget(self.x_label)
278
265
  self.XSlider = QSlider(Qt.Orientation.Horizontal, self)
279
266
  self.XSlider.setMinimum(0)
280
267
  self.XSlider.setMaximum(100)
281
268
  self.XSlider.setValue(100)
282
269
  self.XSlider.sliderReleased.connect(self._update_nii_x)
283
270
  control_layout.addWidget(self.XSlider)
284
- self.showYCheckbox = QCheckBox('Y')
285
- self.showYCheckbox.stateChanged.connect(self._update_nii_y)
286
- control_layout.addWidget(self.showYCheckbox)
271
+ self.y_label = QLabel('Y')
272
+ control_layout.addWidget(self.y_label)
287
273
  self.YSlider = QSlider(Qt.Orientation.Horizontal, self)
288
274
  self.YSlider.setMinimum(0)
289
275
  self.YSlider.setMaximum(100)
290
276
  self.YSlider.setValue(100)
291
277
  self.YSlider.sliderReleased.connect(self._update_nii_y)
292
278
  control_layout.addWidget(self.YSlider)
293
- self.showZCheckbox = QCheckBox('Z')
294
- self.showZCheckbox.stateChanged.connect(self._update_nii_z)
295
- control_layout.addWidget(self.showZCheckbox)
279
+ self.z_label = QLabel('Z')
280
+ control_layout.addWidget(self.z_label)
296
281
  self.ZSlider = QSlider(Qt.Orientation.Horizontal, self)
297
282
  self.ZSlider.setMinimum(0)
298
283
  self.ZSlider.setMaximum(100)
@@ -301,9 +286,8 @@ class TrkViewer(QWidget):
301
286
  control_layout.addWidget(self.ZSlider)
302
287
 
303
288
  # Gifti
304
- self.showSurfaceCheckbox = QCheckBox('Show Surface')
305
- self.showSurfaceCheckbox.stateChanged.connect(self.update_gii_viewer)
306
- control_layout.addWidget(self.showSurfaceCheckbox)
289
+ self.surface_label = QLabel('Surface')
290
+ control_layout.addWidget(self.surface_label)
307
291
 
308
292
  self.gii_opacitySlider = QSlider(Qt.Orientation.Horizontal, self)
309
293
  self.gii_opacitySlider.setMinimum(0)
@@ -324,15 +308,19 @@ class TrkViewer(QWidget):
324
308
 
325
309
  def loadTrkFile(self):
326
310
  options = QFileDialog.Options()
327
- filePath, _ = QFileDialog.getOpenFileName(
328
- self, "Open TRK File", "", "Tractography Files (*.trk)", options=options)
329
- if filePath:
330
- self.trk_file = filePath
311
+ filePaths, _ = QFileDialog.getOpenFileNames(self, "Open TRK Files", "",
312
+ "Tractography Files (*.trk)",
313
+ options=options)
314
+ if not filePaths:
315
+ return
316
+
317
+ for trk_path in filePaths:
318
+ self.trk_file = trk_path
331
319
  print(f"Loaded TRK file: {self.trk_file}")
332
320
  self.names.append(self.trk_file)
333
- self.selected_name = self.trk_file
334
321
 
335
322
  self.update_trk_viewer(reset_camera=True)
323
+ self.parent().refreshActorList()
336
324
 
337
325
  def loadNiftiFile(self):
338
326
  options = QFileDialog.Options()
@@ -342,12 +330,10 @@ class TrkViewer(QWidget):
342
330
  self.nii_file = filePath
343
331
  print(f"Loaded NIfTI file: {self.nii_file}")
344
332
  img = nib.load(filePath)
333
+ self.nii_affine = img.affine
345
334
  self.nii_data = img.get_fdata()
346
335
  grid = pv.ImageData()
347
336
  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]
351
337
  grid.cell_data['values'] = self.nii_data.flatten(order='F')
352
338
  self.grid = grid
353
339
 
@@ -355,7 +341,45 @@ class TrkViewer(QWidget):
355
341
  self.YSlider.setMaximum(self.nii_data.shape[1])
356
342
  self.ZSlider.setMaximum(self.nii_data.shape[2])
357
343
 
358
- self.update_nii_viewer(reset_camera=True)
344
+ self.update_nii_viewer(reset_camera=False)
345
+ self.parent().refreshActorList()
346
+
347
+ def loadROIFile(self):
348
+ """Load one or multiple ROI nifti volumes and render as surfaces."""
349
+ options = QFileDialog.Options()
350
+ filePaths, _ = QFileDialog.getOpenFileNames(self, "Open ROI Files", "",
351
+ "Nifti Files (*.nii *.nii.gz)",
352
+ options=options)
353
+
354
+ if not filePaths:
355
+ return
356
+
357
+ for roi_path in filePaths:
358
+ img = nib.load(roi_path)
359
+ roi = img.get_fdata()
360
+ affine = img.affine
361
+
362
+ datapv = pv.wrap(roi)
363
+ datapv.cell_data['labels'] = roi[:-1, :-1, :-1].flatten(order='F')
364
+ vol = datapv.threshold(value=1, scalars='labels')
365
+ mesh = vol.extract_surface()
366
+
367
+ smooth = mesh.smooth_taubin(n_iter=12)
368
+
369
+ actor_name = f"roi_{roi_path.split('/')[-1]}"
370
+
371
+ self.plotter.add_mesh(
372
+ smooth,
373
+ name=actor_name,
374
+ opacity=1,
375
+ color="white",
376
+ user_matrix=affine,
377
+ reset_camera=False,
378
+ point_size=0,
379
+ render_lines_as_tubes=True,
380
+ )
381
+
382
+ self.parent().refreshActorList()
359
383
 
360
384
  def loadGiftiFile(self):
361
385
  options = QFileDialog.Options()
@@ -366,7 +390,8 @@ class TrkViewer(QWidget):
366
390
  print(f"Loaded GIfTI file: {self.gii_file}")
367
391
  self.gii_mesh = gifti_to_pyvista(filePath)
368
392
 
369
- self.update_gii_viewer(reset_camera=True)
393
+ self.update_gii_viewer(reset_camera=False)
394
+ self.parent().refreshActorList()
370
395
 
371
396
  def set_background_color(self):
372
397
 
@@ -446,7 +471,7 @@ class TrkViewer(QWidget):
446
471
  self.plotter.render()
447
472
  offscreen.close()
448
473
 
449
- def update_trk_viewer(self, reset_camera=False):
474
+ def update_trk_viewer(self, reset_camera: bool = False):
450
475
 
451
476
  opacity = self.opacitySlider.value() / 100.0
452
477
  show_points = self.showPointsCheckbox.isChecked()
@@ -476,14 +501,14 @@ class TrkViewer(QWidget):
476
501
  center = (self.XSlider.value(),
477
502
  self.YSlider.value(), self.ZSlider.value())
478
503
 
479
- if self.showXCheckbox.isChecked():
480
- slice_x = self.grid.slice('x', center)
481
- self.plotter.add_mesh(slice_x, cmap='grey', name='nii_x',
482
- show_scalar_bar=False, point_size=0,
483
- render_lines_as_tubes=True,
484
- reset_camera=False)
485
- else:
486
- self.plotter.remove_actor('nii_x')
504
+ slice_x = self.grid.slice('x', center)
505
+ self.plotter.add_mesh(slice_x, cmap='grey', name='nii_x',
506
+ show_scalar_bar=False, point_size=0,
507
+ render_lines_as_tubes=True,
508
+ reset_camera=False,
509
+ user_matrix=self.nii_affine)
510
+ else:
511
+ self.plotter.remove_actor('nii_x')
487
512
 
488
513
  def _update_nii_y(self):
489
514
 
@@ -492,14 +517,14 @@ class TrkViewer(QWidget):
492
517
  center = (self.XSlider.value(),
493
518
  self.YSlider.value(), self.ZSlider.value())
494
519
 
495
- if self.showYCheckbox.isChecked():
496
- slice_y = self.grid.slice('y', center)
497
- self.plotter.add_mesh(slice_y, cmap='grey', name='nii_y',
498
- show_scalar_bar=False, point_size=0,
499
- render_lines_as_tubes=True,
500
- reset_camera=False)
501
- else:
502
- self.plotter.remove_actor('nii_y')
520
+ slice_y = self.grid.slice('y', center)
521
+ self.plotter.add_mesh(slice_y, cmap='grey', name='nii_y',
522
+ show_scalar_bar=False, point_size=0,
523
+ render_lines_as_tubes=True,
524
+ reset_camera=False,
525
+ user_matrix=self.nii_affine)
526
+ else:
527
+ self.plotter.remove_actor('nii_y')
503
528
 
504
529
  def _update_nii_z(self):
505
530
 
@@ -508,48 +533,35 @@ class TrkViewer(QWidget):
508
533
  center = (self.XSlider.value(),
509
534
  self.YSlider.value(), self.ZSlider.value())
510
535
 
511
- if self.showZCheckbox.isChecked():
512
- slice_z = self.grid.slice('z', center)
513
- self.plotter.add_mesh(slice_z, cmap='grey', name='nii_z',
514
- show_scalar_bar=False, point_size=0,
515
- render_lines_as_tubes=True,
516
- reset_camera=False)
517
- else:
518
- self.plotter.remove_actor('nii_z')
519
-
520
- def update_nii_viewer(self, reset_camera=False):
521
-
522
- if self.showSlicesCheckbox.isChecked():
523
-
524
- self._update_nii_x()
525
- self._update_nii_y()
526
- self._update_nii_z()
527
-
536
+ slice_z = self.grid.slice('z', center)
537
+ self.plotter.add_mesh(slice_z, cmap='grey', name='nii_z',
538
+ show_scalar_bar=False, point_size=0,
539
+ render_lines_as_tubes=True,
540
+ reset_camera=False,
541
+ user_matrix=self.nii_affine)
528
542
  else:
529
- self.plotter.remove_actor('nii_x')
530
- self.plotter.remove_actor('nii_y')
531
543
  self.plotter.remove_actor('nii_z')
532
544
 
533
- if self.showVolumeCheckbox.isChecked():
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)
538
- else:
539
- self.plotter.remove_actor('nii_volume', reset_camera=reset_camera)
545
+ def update_nii_viewer(self, reset_camera: bool = False):
540
546
 
541
- def update_gii_viewer(self, reset_camera=False):
547
+ self._update_nii_x()
548
+ self._update_nii_y()
549
+ self._update_nii_z()
542
550
 
543
- if self.showSurfaceCheckbox.isChecked():
551
+ opacity = self.nii_opacitySlider.value()/1000
552
+ self.plotter.add_volume(self.grid, cmap='gray', opacity=[0, opacity],
553
+ show_scalar_bar=False, name='nii_volume',
554
+ reset_camera=reset_camera,
555
+ user_matrix=self.nii_affine)
544
556
 
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
557
+ def update_gii_viewer(self, reset_camera: bool = False):
558
+
559
+ opacity = self.gii_opacitySlider.value()/100
560
+ self.plotter.add_mesh(self.gii_mesh, color="ghostwhite",
561
+ culling='back', smooth_shading=True,
562
+ opacity=opacity, name='gii_surface',
563
+ reset_camera=reset_camera, point_size=0,
564
+ render_lines_as_tubes=True,)
553
565
 
554
566
 
555
567
  class MainWindow(QMainWindow):
@@ -557,6 +569,7 @@ class MainWindow(QMainWindow):
557
569
  super().__init__()
558
570
  self.viewer = TrkViewer()
559
571
  self.setCentralWidget(self.viewer)
572
+ self.initActorDock()
560
573
  self.initMenuBar()
561
574
  self.setWindowTitle("UNVEIL - Tractography Viewer")
562
575
 
@@ -581,16 +594,104 @@ class MainWindow(QMainWindow):
581
594
  loadGiftiAction.triggered.connect(self.viewer.loadGiftiFile)
582
595
  fileMenu.addAction(loadGiftiAction)
583
596
 
597
+ # Load ROIs
598
+ loadROIAction = QAction('Load ROIs (.nii/.nii.gz)', self)
599
+ loadROIAction.triggered.connect(self.viewer.loadROIFile)
600
+ fileMenu.addAction(loadROIAction)
601
+
584
602
  # Optional: Exit action
585
603
  exitAction = QAction('Exit', self)
586
604
  exitAction.triggered.connect(self.close)
587
605
  fileMenu.addAction(exitAction)
588
606
 
589
607
  # View menu
590
- fileMenu = menubar.addMenu('View')
608
+ viewMenu = menubar.addMenu('View')
591
609
  setBackColorAction = QAction('Background Color', self)
592
610
  setBackColorAction.triggered.connect(self.viewer.set_background_color)
593
- fileMenu.addAction(setBackColorAction)
611
+ viewMenu.addAction(setBackColorAction)
612
+ toggleActorListAction = QAction(
613
+ "Toggle Actor List", self, checkable=True)
614
+ toggleActorListAction.triggered.connect(self.toggleActorDock)
615
+ viewMenu.addAction(toggleActorListAction)
616
+
617
+ def initActorDock(self):
618
+ self.actorDock = QDockWidget("Scene Actors", self)
619
+ self.actorDock.setAllowedAreas(Qt.DockWidgetArea.RightDockWidgetArea)
620
+
621
+ self.actorTree = QTreeWidget()
622
+ self.actorTree.setHeaderLabels(["Actor", "Visible"])
623
+ self.actorTree.setColumnCount(2)
624
+ self.actorTree.setColumnWidth(0, 200)
625
+
626
+ self.actorDock.setWidget(self.actorTree)
627
+ self.addDockWidget(
628
+ Qt.DockWidgetArea.RightDockWidgetArea, self.actorDock)
629
+
630
+ self.actorDock.hide() # start hidden
631
+
632
+ # Refresh the list whenever actors change
633
+ self.viewer.plotter.mesh_added_callback = self.refreshActorList
634
+ self.viewer.plotter.mesh_removed_callback = self.refreshActorList
635
+
636
+ def toggleActorDock(self, checked):
637
+ if checked:
638
+
639
+ self.actorDock.show()
640
+ else:
641
+ self.actorDock.hide()
642
+
643
+ def refreshActorList(self):
644
+ """Rebuild the hierarchical actor list grouped by class."""
645
+ self.actorTree.clear()
646
+
647
+ # Groups
648
+ group_trk = QTreeWidgetItem(["TRK", ""])
649
+ group_nii = QTreeWidgetItem(["NIfTI Volume", ""])
650
+ group_roi = QTreeWidgetItem(["ROI Surfaces", ""])
651
+ group_gii = QTreeWidgetItem(["GIFTI", ""])
652
+
653
+ self.actorTree.addTopLevelItem(group_trk)
654
+ self.actorTree.addTopLevelItem(group_nii)
655
+ self.actorTree.addTopLevelItem(group_gii)
656
+ self.actorTree.addTopLevelItem(group_roi)
657
+ # Loop through PyVista actors
658
+ for name, actor in self.viewer.plotter.actors.items():
659
+ item = QTreeWidgetItem([name, ""])
660
+ item.setCheckState(
661
+ 1, Qt.CheckState.Checked if actor.GetVisibility() else Qt.CheckState.Unchecked)
662
+
663
+ # Store name for callback
664
+ item.actor_name = name
665
+
666
+ # Insert into the correct group
667
+ if name.endswith(".trk"):
668
+ group_trk.addChild(item)
669
+ elif name.startswith("nii_"):
670
+ group_nii.addChild(item)
671
+ elif name.startswith("gii_"):
672
+ group_gii.addChild(item)
673
+ elif name.startswith("roi_"):
674
+ group_roi.addChild(item)
675
+ else:
676
+ group_trk.addChild(item) # default bucket
677
+
678
+ # Connect visibility toggles
679
+ self.actorTree.itemChanged.connect(self.onActorVisibilityChanged)
680
+
681
+ def onActorVisibilityChanged(self, item, column):
682
+ """Toggle visibility when user clicks checkbox."""
683
+ if column != 1:
684
+ return
685
+
686
+ actor_name = item.actor_name
687
+ visible = item.checkState(1) == Qt.CheckState.Checked
688
+
689
+ try:
690
+ self.viewer.plotter.actors[actor_name].SetVisibility(visible)
691
+ except KeyError:
692
+ pass
693
+
694
+ self.viewer.plotter.render()
594
695
 
595
696
 
596
697
  if __name__ == '__main__':
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: unveil-python
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Implementation of UNVEIL
5
5
  Author-email: Nicolas Delinte <nicolas.delinte@uclouvain.be>
6
6
  License-Expression: GPL-3.0-only
@@ -27,8 +27,14 @@ A simple tractography viewer implemented in python.
27
27
 
28
28
  ![image](https://github.com/user-attachments/assets/4fbefa7a-3fc2-446a-8835-2844266b14fa)
29
29
 
30
- The GUI is launched with the following python command:
30
+
31
+ The GUI is launched with the following python command in a python console:
31
32
 
32
33
  ```
33
34
  import unveil.__main__
34
35
  ```
36
+ or with
37
+ ```
38
+ python -c "import unveil.__main__"
39
+ ```
40
+ from a terminal with a python environment activated.
File without changes
File without changes