unveil-python 0.7.0__tar.gz → 0.7.1__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.7.0/src/unveil_python.egg-info → unveil_python-0.7.1}/PKG-INFO +1 -2
- {unveil_python-0.7.0 → unveil_python-0.7.1}/pyproject.toml +0 -1
- {unveil_python-0.7.0 → unveil_python-0.7.1}/src/unveil/__init__.py +1 -1
- {unveil_python-0.7.0 → unveil_python-0.7.1}/src/unveil/core.py +33 -30
- unveil_python-0.7.1/src/unveil/utils.py +58 -0
- {unveil_python-0.7.0 → unveil_python-0.7.1/src/unveil_python.egg-info}/PKG-INFO +1 -2
- {unveil_python-0.7.0 → unveil_python-0.7.1}/src/unveil_python.egg-info/SOURCES.txt +1 -0
- {unveil_python-0.7.0 → unveil_python-0.7.1}/src/unveil_python.egg-info/requires.txt +0 -1
- {unveil_python-0.7.0 → unveil_python-0.7.1}/LICENSE +0 -0
- {unveil_python-0.7.0 → unveil_python-0.7.1}/README.md +0 -0
- {unveil_python-0.7.0 → unveil_python-0.7.1}/setup.cfg +0 -0
- {unveil_python-0.7.0 → unveil_python-0.7.1}/src/unveil/__main__.py +0 -0
- {unveil_python-0.7.0 → unveil_python-0.7.1}/src/unveil_python.egg-info/dependency_links.txt +0 -0
- {unveil_python-0.7.0 → unveil_python-0.7.1}/src/unveil_python.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: unveil-python
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.1
|
|
4
4
|
Summary: Implementation of UNVEIL
|
|
5
5
|
Author-email: Nicolas Delinte <nicolas.delinte@uclouvain.be>
|
|
6
6
|
License-Expression: GPL-3.0-only
|
|
@@ -15,7 +15,6 @@ Requires-Dist: dipy
|
|
|
15
15
|
Requires-Dist: pyvista
|
|
16
16
|
Requires-Dist: pyvistaqt
|
|
17
17
|
Requires-Dist: PyQt6
|
|
18
|
-
Requires-Dist: binama
|
|
19
18
|
Dynamic: license-file
|
|
20
19
|
|
|
21
20
|
# UNVEIL
|
|
@@ -12,7 +12,7 @@ from pyvistaqt import QtInteractor
|
|
|
12
12
|
from PyQt6.QtCore import Qt
|
|
13
13
|
from PyQt6.QtWidgets import (QApplication, QWidget, QHBoxLayout, QVBoxLayout,
|
|
14
14
|
QPushButton, QFileDialog, QCheckBox, QSlider,
|
|
15
|
-
QLabel, QComboBox, QMainWindow,
|
|
15
|
+
QLabel, QComboBox, QMainWindow,
|
|
16
16
|
QDockWidget, QTreeWidget, QTreeWidgetItem,
|
|
17
17
|
QColorDialog, QTabWidget, QDoubleSpinBox,
|
|
18
18
|
QGroupBox)
|
|
@@ -23,7 +23,7 @@ from matplotlib.colors import LinearSegmentedColormap
|
|
|
23
23
|
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg
|
|
24
24
|
from matplotlib.figure import Figure
|
|
25
25
|
# from unravel.utils import get_streamline_density
|
|
26
|
-
from
|
|
26
|
+
from unveil.utils import dilate_atlas_labels
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
def gifti_to_pyvista(gii_path):
|
|
@@ -186,13 +186,15 @@ def plot_trk(trk_file, scalar=None, color_map='plasma', opacity: float = 1,
|
|
|
186
186
|
|
|
187
187
|
p.background_color = background
|
|
188
188
|
|
|
189
|
+
|
|
189
190
|
def load_gii_data(gii_path):
|
|
190
191
|
"""
|
|
191
192
|
Load all data contained in the GifTi file except vertex coordinates and triangles.
|
|
192
193
|
Returns : dictionnary {"Gii table name" : data}
|
|
193
194
|
"""
|
|
194
195
|
gii_img = nib.load(gii_path)
|
|
195
|
-
vertex_coords = gii_img.get_arrays_from_intent(
|
|
196
|
+
vertex_coords = gii_img.get_arrays_from_intent(
|
|
197
|
+
"NIFTI_INTENT_POINTSET")[0].data
|
|
196
198
|
gii_data = {}
|
|
197
199
|
|
|
198
200
|
# Iterate over all tables of the GIfTI
|
|
@@ -200,11 +202,12 @@ def load_gii_data(gii_path):
|
|
|
200
202
|
# Vertex coords and triangles are stored with None as 'Name'
|
|
201
203
|
if darray.meta.get("Name") is not None:
|
|
202
204
|
# Check that the table contains 1 value per vertex
|
|
203
|
-
if darray.data.shape == vertex_coords[:,0].shape:
|
|
204
|
-
gii_data[darray.meta.get("Name")] =
|
|
205
|
+
if darray.data.shape == vertex_coords[:, 0].shape:
|
|
206
|
+
gii_data[darray.meta.get("Name")] = darray.data
|
|
205
207
|
|
|
206
208
|
return gii_data
|
|
207
209
|
|
|
210
|
+
|
|
208
211
|
class TrkViewer(QWidget):
|
|
209
212
|
def __init__(self):
|
|
210
213
|
super().__init__()
|
|
@@ -518,10 +521,10 @@ class TrkViewer(QWidget):
|
|
|
518
521
|
options = QFileDialog.Options()
|
|
519
522
|
filePath, _ = QFileDialog.getOpenFileName(
|
|
520
523
|
self, "Open .nii.gz File", "", "Nifti Files (*.nii.gz)", options=options)
|
|
521
|
-
|
|
524
|
+
|
|
522
525
|
if not filePath:
|
|
523
526
|
return
|
|
524
|
-
|
|
527
|
+
|
|
525
528
|
print(f"Loaded NIfTI file: {filePath}")
|
|
526
529
|
img = nib.load(filePath)
|
|
527
530
|
self.nii_affine = img.affine
|
|
@@ -544,11 +547,12 @@ class TrkViewer(QWidget):
|
|
|
544
547
|
self.nii_data_dilated = dilate_atlas_labels(
|
|
545
548
|
self.nii_data, dilation_width=4)
|
|
546
549
|
self.nii_data_dilated = np.where(self.nii_data_dilated == 0, np.nan,
|
|
547
|
-
|
|
548
|
-
|
|
550
|
+
self.nii_data_dilated)
|
|
551
|
+
|
|
549
552
|
if hasattr(self, "gii_mesh"):
|
|
550
553
|
# Sample NIfTI values at each surface vertex
|
|
551
|
-
self.nii_surface_values = self.sample_nifti_on_surface(
|
|
554
|
+
self.nii_surface_values = self.sample_nifti_on_surface(
|
|
555
|
+
self.gii_mesh)
|
|
552
556
|
self.update_gii_viewer(reset_camera=False)
|
|
553
557
|
else:
|
|
554
558
|
self.nii_surface_values = None
|
|
@@ -558,8 +562,6 @@ class TrkViewer(QWidget):
|
|
|
558
562
|
ortho_data = self.nii_data.copy()
|
|
559
563
|
ortho_data[ortho_data == 0] = None
|
|
560
564
|
|
|
561
|
-
|
|
562
|
-
|
|
563
565
|
self.window().ortho_viewer.set_volume(ortho_data, self.nii_affine)
|
|
564
566
|
self.window().ortho_viewer.set_colormap(self.niiColorMapComboBox.currentText())
|
|
565
567
|
|
|
@@ -615,19 +617,20 @@ class TrkViewer(QWidget):
|
|
|
615
617
|
self.gii_mesh = gifti_to_pyvista(filePath)
|
|
616
618
|
|
|
617
619
|
self.gii_data = load_gii_data(filePath)
|
|
618
|
-
|
|
620
|
+
|
|
619
621
|
self.giiColorModeComboBox.setCurrentIndex(0)
|
|
620
|
-
|
|
622
|
+
|
|
621
623
|
for _ in range(self.giiColorModeComboBox.count()-2):
|
|
622
624
|
self.giiColorModeComboBox.removeItem(2)
|
|
623
|
-
|
|
625
|
+
|
|
624
626
|
self.giiColorModeComboBox.addItems(list(self.gii_data.keys()))
|
|
625
|
-
|
|
627
|
+
|
|
626
628
|
self.actor_types["gii_surface"] = "gii"
|
|
627
629
|
|
|
628
630
|
if hasattr(self, "nii_data"):
|
|
629
631
|
# Sample NIfTI values at each surface vertex
|
|
630
|
-
self.nii_surface_values = self.sample_nifti_on_surface(
|
|
632
|
+
self.nii_surface_values = self.sample_nifti_on_surface(
|
|
633
|
+
self.gii_mesh)
|
|
631
634
|
self.update_gii_viewer(reset_camera=False)
|
|
632
635
|
else:
|
|
633
636
|
self.nii_surface_values = None
|
|
@@ -777,7 +780,7 @@ class TrkViewer(QWidget):
|
|
|
777
780
|
reset_camera=reset_camera, clim=[vmin, vmax],
|
|
778
781
|
user_matrix=self.nii_affine)
|
|
779
782
|
|
|
780
|
-
def update_gii_viewer(self, reset_camera=False, update_color_range
|
|
783
|
+
def update_gii_viewer(self, reset_camera=False, update_color_range=False):
|
|
781
784
|
"""Update the GIFTI surface rendering."""
|
|
782
785
|
|
|
783
786
|
if not hasattr(self, "gii_mesh"):
|
|
@@ -799,16 +802,16 @@ class TrkViewer(QWidget):
|
|
|
799
802
|
|
|
800
803
|
else:
|
|
801
804
|
if color_mode == "NIfTI values":
|
|
802
|
-
scalars = self.nii_surface_values
|
|
805
|
+
scalars = self.nii_surface_values
|
|
803
806
|
else:
|
|
804
807
|
scalars = self.gii_data[color_mode]
|
|
805
808
|
|
|
806
809
|
if scalars is None:
|
|
807
810
|
self.plotter.add_mesh(self.gii_mesh, color="ghostwhite",
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
811
|
+
culling="back", smooth_shading=True,
|
|
812
|
+
opacity=opacity, name="gii_surface",
|
|
813
|
+
reset_camera=reset_camera,
|
|
814
|
+
point_size=0, render_lines_as_tubes=True)
|
|
812
815
|
return
|
|
813
816
|
|
|
814
817
|
# Copy the mesh so that the original GIFTI mesh is not modified
|
|
@@ -828,12 +831,12 @@ class TrkViewer(QWidget):
|
|
|
828
831
|
vmax = vmin + 1e-6
|
|
829
832
|
|
|
830
833
|
self.plotter.add_mesh(surface_mesh, scalars=color_mode,
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
834
|
+
cmap=cmap_name, clim=[vmin, vmax],
|
|
835
|
+
culling="back", smooth_shading=True,
|
|
836
|
+
opacity=opacity, name="gii_surface",
|
|
837
|
+
reset_camera=reset_camera, point_size=0,
|
|
838
|
+
render_lines_as_tubes=True,
|
|
839
|
+
show_scalar_bar=False)
|
|
837
840
|
|
|
838
841
|
self.plotter.render()
|
|
839
842
|
|
|
@@ -867,7 +870,7 @@ class TrkViewer(QWidget):
|
|
|
867
870
|
self.niiMinSpinBox.blockSignals(False)
|
|
868
871
|
self.niiMaxSpinBox.blockSignals(False)
|
|
869
872
|
|
|
870
|
-
def update_surface_color_range(self,data):
|
|
873
|
+
def update_surface_color_range(self, data):
|
|
871
874
|
|
|
872
875
|
if data is None:
|
|
873
876
|
return
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Created on Mon Oct 26 10:03:52 2026
|
|
4
|
+
|
|
5
|
+
@author: N. Delinte
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
from scipy.ndimage import distance_transform_edt
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def dilate_atlas_labels(atlas, brain_mask=None, dilation_width: float = 1,
|
|
13
|
+
min_label: int = 0):
|
|
14
|
+
"""
|
|
15
|
+
Dilates cortical atlas labels to include nearby unlabeled voxels (0),
|
|
16
|
+
constrained by a brain mask.
|
|
17
|
+
Copied from 'pilab-binama' package.
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
atlas : np.ndarray (3D, int)
|
|
22
|
+
Cortical atlas with integer labels (0 = unlabeled).
|
|
23
|
+
brain_mask : np.ndarray (3D, bool or int), optional
|
|
24
|
+
Binary mask of the brain (same shape as atlas). The default is one
|
|
25
|
+
dilation_width : float, optional
|
|
26
|
+
Maximum dilation distance in voxels. The default is None.
|
|
27
|
+
min_label : int, optional
|
|
28
|
+
Labels below this values are not dilated
|
|
29
|
+
|
|
30
|
+
Returns
|
|
31
|
+
-------
|
|
32
|
+
dilated_atlas : np.ndarray (3D, int)
|
|
33
|
+
Atlas with dilated labels.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
# Mask unlabeled voxels inside the brain
|
|
37
|
+
unlabeled = np.where(atlas <= min_label, 1, 0)
|
|
38
|
+
if brain_mask is not None:
|
|
39
|
+
unlabeled *= brain_mask.astype('int32')
|
|
40
|
+
|
|
41
|
+
# Compute distance transform from labeled voxels
|
|
42
|
+
# Also retrieve indices of nearest labeled voxel for each position
|
|
43
|
+
distances, nearest_idx = distance_transform_edt(unlabeled,
|
|
44
|
+
return_indices=True)
|
|
45
|
+
|
|
46
|
+
# Copy atlas to output
|
|
47
|
+
dilated_atlas = atlas.copy()
|
|
48
|
+
|
|
49
|
+
# For unlabeled voxels within dilation_width, assign nearest label
|
|
50
|
+
within_dilation = unlabeled & (distances <= dilation_width)
|
|
51
|
+
|
|
52
|
+
# Map nearest labeled voxel indices back to atlas labels
|
|
53
|
+
coords = np.argwhere(within_dilation)
|
|
54
|
+
for x, y, z in coords:
|
|
55
|
+
nx, ny, nz = nearest_idx[:, x, y, z]
|
|
56
|
+
dilated_atlas[x, y, z] = atlas[nx, ny, nz]
|
|
57
|
+
|
|
58
|
+
return dilated_atlas
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: unveil-python
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.1
|
|
4
4
|
Summary: Implementation of UNVEIL
|
|
5
5
|
Author-email: Nicolas Delinte <nicolas.delinte@uclouvain.be>
|
|
6
6
|
License-Expression: GPL-3.0-only
|
|
@@ -15,7 +15,6 @@ Requires-Dist: dipy
|
|
|
15
15
|
Requires-Dist: pyvista
|
|
16
16
|
Requires-Dist: pyvistaqt
|
|
17
17
|
Requires-Dist: PyQt6
|
|
18
|
-
Requires-Dist: binama
|
|
19
18
|
Dynamic: license-file
|
|
20
19
|
|
|
21
20
|
# UNVEIL
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|