1
1
from dataclasses import dataclass
2
- from typing import Any
3
2
import os
4
3
import logging
5
4
import numpy as np
11
10
import nrrd
12
11
import ibllib .atlas
13
12
from ibllib .atlas .regions import BrainRegions
13
+ from iblutil .numerical import ismember
14
14
15
15
from iblviewer .application import Viewer
16
16
from iblviewer .volume import VolumeController , VolumeModel , LUTModel
17
17
import iblviewer .utils as utils
18
18
19
-
20
19
ALLEN_ATLAS_RESOLUTIONS = [10 , 25 , 50 , 100 ]
21
20
ALLEN_ATLASES = {'base_url' : 'http://download.alleninstitute.org/informatics-archive' ,
22
21
'mouse_ccf_folder' : '/current-release/mouse_ccf' ,
23
- 'atlas_folder' : '/annotation/ccf_2017' , 'atlas_prefix' : '/annotation_' ,
24
- 'dwi_folder' : '/average_template' , 'dwi_prefix' : '/average_template_' ,
22
+ 'atlas_folder' : '/annotation/ccf_2017' , 'atlas_prefix' : '/annotation_' ,
23
+ 'dwi_folder' : '/average_template' , 'dwi_prefix' : '/average_template_' ,
25
24
'volume_extension' : '.nrrd' }
26
25
27
26
# Default origin is "bregma", an origin defined at the center of the XY axes (not on Z)
37
36
class AllenAtlasExt (ibllib .atlas .AllenAtlas ):
38
37
"""
39
38
This overwrites the constructor of AllenAtlas that is not designed to be used for the
40
- public, that is people outside of IBL. Typically, you'd want to display the Allen volume
41
- data in this viewer and perform additional tasks (such as loading your own extra data)
39
+ public, that is people outside of IBL. Typically, you'd want to display the Allen volume
40
+ data in this viewer and perform additional tasks (such as loading your own extra data)
42
41
with other libraries. Dev note: I'm forced to copy and modify the whole constructor in this case.
43
42
44
43
Instantiates an atlas.BrainAtlas corresponding to the Allen CCF at the given resolution
@@ -54,7 +53,7 @@ def _read_volume(file_volume):
54
53
volume = np .load (str (file_volume ), allow_pickle = True )['arr_0' ]
55
54
return volume
56
55
57
- def __init__ (self , res_um = 25 , brainmap = 'Allen' , scaling = np .array ([1 , 1 , 1 ]),
56
+ def __init__ (self , res_um = 25 , brainmap = 'Allen' , scaling = np .array ([1 , 1 , 1 ]),
58
57
image_file_path = None , label_file_path = None ):
59
58
"""
60
59
:param res_um: 10, 25 or 50 um
@@ -80,7 +79,7 @@ def __init__(self, res_um=25, brainmap='Allen', scaling=np.array([1, 1, 1]),
80
79
label = self .remap_atlas (label_file_path , regions , ibregma )
81
80
82
81
# This calls BrainAtlas, the mother class of AllenAtlas because we want to overwrite it
83
- super (ibllib .atlas .AllenAtlas , self ).__init__ (image , label , dxyz , regions ,
82
+ super (ibllib .atlas .AllenAtlas , self ).__init__ (image , label , dxyz , regions ,
84
83
ibregma , dims2xyz = dims2xyz , xyz2dims = xyz2dims )
85
84
86
85
def remap_atlas (self , local_file_path , regions = None , ibregma = None ):
@@ -110,8 +109,6 @@ def lateralize(self, label, regions=None, ibregma=None):
110
109
:param label: Segmented volume
111
110
:return: Modified volume
112
111
"""
113
- # Another dependency, that probably comes along with ibllib
114
- from brainbox .numerical import ismember
115
112
_logger .info ("computing brain atlas annotations lookup table" )
116
113
if regions is None :
117
114
regions = BrainRegions ()
@@ -127,7 +124,7 @@ def lateralize(self, label, regions=None, ibregma=None):
127
124
_ , im = ismember (label , regions .id )
128
125
label = np .reshape (im .astype (np .uint16 ), label .shape )
129
126
return label
130
-
127
+
131
128
132
129
133
130
@dataclass
@@ -244,7 +241,7 @@ def get_value_from_scalar_map(self, scalar):
244
241
#print(scalar - scalar_map[value])
245
242
if scalar_map [value ] == scalar :
246
243
return value
247
-
244
+
248
245
def get_mapped_data (self , value ):
249
246
"""
250
247
Given a value from the segmented volume, we retrieve useful info
@@ -270,7 +267,7 @@ def get_mapped_data(self, value):
270
267
scalar = scalar_map .get (value )
271
268
else :
272
269
scalar = scalar_map [value ]
273
- return {'scalar' :scalar , 'region_id' :region_id , 'color' :color , 'alpha' :alpha ,
270
+ return {'scalar' :scalar , 'region_id' :region_id , 'color' :color , 'alpha' :alpha ,
274
271
'region_name' :region_name , 'region_data' :region_data }
275
272
276
273
def remap (self , ids , source = 'Allen' , dest = 'Beryl' ):
@@ -282,7 +279,6 @@ def remap(self, ids, source='Allen', dest='Beryl'):
282
279
:param dest: Destination mapping
283
280
"""
284
281
#from ibllib.atlas import BrainRegions as br
285
- from brainbox .numerical import ismember
286
282
_ , inds = ismember (ids , self .atlas .regions .mappings [source ])
287
283
return self .atlas .regions .mappings [dest ][inds ]
288
284
@@ -313,7 +309,7 @@ def load_volume(self, file_path, remap_scalars=False, mapping=None, make_current
313
309
"""
314
310
Load a volume data file. Supports NRRD and many other formats thanks to vedo/VTK
315
311
:param file_path: Volume file path. Could support other file types easily.
316
- :param remap_scalars: Whether scalar values in the volume are replaced by
312
+ :param remap_scalars: Whether scalar values in the volume are replaced by
317
313
their row id from a mapping that stores. This is necessary in the case of segmented
318
314
volumes with regions that have a discontinuous id.
319
315
:param mapping: Pandas Series or a Dictionary
@@ -333,7 +329,7 @@ def get_mapped_volume(self, volume, atlas_mapping=None, ibl_back_end=True):
333
329
"""
334
330
Set the volume data according to a mapping
335
331
:param volume: Given volume to display
336
- :param atlas_mapping: Mapping, either a string for the name of the mapping or an integer.
332
+ :param atlas_mapping: Mapping, either a string for the name of the mapping or an integer.
337
333
:param ibl_back_end: If you are not using ibllib and want to load your own volume, set this to False
338
334
so that there will be no transposition of the volume (needed for the ones from IBL)
339
335
:return: Volume nd array
@@ -404,10 +400,10 @@ def __init__(self):
404
400
self .ibl_model = None
405
401
# Shortcut for users
406
402
self .ibl_transpose = IBLAtlasModel .IBL_TRANSPOSE
407
-
408
- def initialize (self , resolution = 25 , mapping = 'Beryl' , add_atlas = True , add_dwi = False ,
403
+
404
+ def initialize (self , resolution = 25 , mapping = 'Beryl' , add_atlas = True , add_dwi = False ,
409
405
dwi_color_map = 'viridis' , dwi_alpha_map = None , local_allen_volumes_path = None ,
410
- offscreen = False , jupyter = False , embed_ui = False , embed_font_size = 15 ,
406
+ offscreen = False , jupyter = False , embed_ui = False , embed_font_size = 15 ,
411
407
plot = None , plot_window_id = 0 , num_windows = 1 , render = False , dark_mode = False ):
412
408
"""
413
409
Initialize the controller, main entry point to the viewer
@@ -430,7 +426,7 @@ def initialize(self, resolution=25, mapping='Beryl', add_atlas=True, add_dwi=Fal
430
426
"""
431
427
self .model .title = 'IBL Viewer'
432
428
433
- # ibllib works with two volumes at the same time: the segmented volume (called 'label')
429
+ # ibllib works with two volumes at the same time: the segmented volume (called 'label')
434
430
# and the DWI volume (called 'image')
435
431
self .ibl_model = IBLAtlasModel ()
436
432
self .ibl_model .initialize (resolution , local_allen_volumes_path )
@@ -459,7 +455,7 @@ def initialize(self, resolution=25, mapping='Beryl', add_atlas=True, add_dwi=Fal
459
455
self .load_bounding_mesh ()
460
456
except Exception :
461
457
pass
462
-
458
+
463
459
#light = vedo.Light(self.model.IBL_BREGMA_ORIGIN - [0, 0, 1000], c='w', intensity=0.2)
464
460
#self.plot.add(light)
465
461
@@ -480,7 +476,7 @@ def add_atlas_segmentation(self):
480
476
"""
481
477
if isinstance (self .atlas_controller , VolumeController ):
482
478
return
483
- self .atlas_controller = VolumeController (self .plot , self .ibl_model .atlas_volume ,
479
+ self .atlas_controller = VolumeController (self .plot , self .ibl_model .atlas_volume ,
484
480
alpha_unit_upper_offset = 0.1 , center_on_edges = True )
485
481
self .register_controller (self .atlas_controller , self .atlas_controller .get_related_actors ())
486
482
@@ -492,10 +488,10 @@ def add_atlas_dwi(self, color_map, alpha_map):
492
488
"""
493
489
if isinstance (self .dwi_controller , VolumeController ):
494
490
return
495
- self .dwi_controller = VolumeController (self .plot , self .ibl_model .dwi_volume ,
491
+ self .dwi_controller = VolumeController (self .plot , self .ibl_model .dwi_volume ,
496
492
center_on_edges = True )
497
493
self .dwi_controller .set_color_map (color_map , alpha_map )
498
- self .register_controller (self .dwi_controller , self .dwi_controller .get_related_actors ())
494
+ self .register_controller (self .dwi_controller , self .dwi_controller .get_related_actors ())
499
495
500
496
def load_bounding_mesh (self , add_to_scene = False , alpha_on_scene = 0.3 ):
501
497
"""
@@ -528,7 +524,7 @@ def get_region_names(self):
528
524
"""
529
525
return self .ibl_model .atlas .regions .name .tolist ()
530
526
531
- def _select (self , actor = None , controller = None , event = None ,
527
+ def _select (self , actor = None , controller = None , event = None ,
532
528
camera_position = None , position = None , value = None ):
533
529
"""
534
530
Define the current object selected
@@ -608,15 +604,15 @@ def add_many_points_test(self, positions, point_radius=2, auto_xy_rotate=True, a
608
604
return point_cloud
609
605
610
606
def add_spheres (self , positions , radius = 10 , values = None , color_map = 'Accent' , name = 'Spheres' ,
611
- use_origin = True , add_to_scene = True , noise_amount = 0 , trim_outliers = True ,
607
+ use_origin = True , add_to_scene = True , noise_amount = 0 , trim_outliers = True ,
612
608
bounding_mesh = None , ibl_flip_yz = True , ** kwargs ):
613
609
"""
614
610
Add new spheres
615
611
:param positions: 3D array of coordinates
616
612
:param radius: List same length as positions of radii. The default size is 5um, or 5 pixels
617
613
in case as_spheres is False.
618
614
:param values: 1D array of values, one per neuron or a time series of such 1D arrays (numpy format)
619
- :param color_map: A color map, it can be a color map built by IBLViewer or
615
+ :param color_map: A color map, it can be a color map built by IBLViewer or
620
616
a color map name (see vedo documentation), or a list of values, etc.
621
617
:param name: All point neurons are grouped into one object, you can give it a custom name
622
618
:param use_origin: Whether the origin is added as offset to the given positions
@@ -637,7 +633,7 @@ def add_spheres(self, positions, radius=10, values=None, color_map='Accent', nam
637
633
if noise_amount is not None :
638
634
positions += np .random .rand (len (positions ), 3 ) * noise_amount
639
635
link = True if add_to_scene and not trim_outliers else False
640
- spheres = super ().add_spheres (positions , radius , values , color_map ,
636
+ spheres = super ().add_spheres (positions , radius , values , color_map ,
641
637
name , use_origin , link , ** kwargs )
642
638
spheres .axes = axes
643
639
if bounding_mesh is None :
@@ -650,19 +646,19 @@ def add_spheres(self, positions, radius=10, values=None, color_map='Accent', nam
650
646
return spheres
651
647
652
648
def add_points (self , positions , radius = 10 , values = None , color_map = 'Accent' , name = 'Points' , screen_space = False ,
653
- use_origin = True , add_to_scene = True , noise_amount = 0 , trim_outliers = True , bounding_mesh = None ,
649
+ use_origin = True , add_to_scene = True , noise_amount = 0 , trim_outliers = True , bounding_mesh = None ,
654
650
ibl_flip_yz = True , ** kwargs ):
655
651
"""
656
652
Add new points
657
653
:param positions: 3D array of coordinates
658
654
:param radius: List same length as positions of radii. The default size is 5um, or 5 pixels
659
655
in case as_spheres is False.
660
656
:param values: 1D array of values, one per neuron or a time series of such 1D arrays (numpy format)
661
- :param color_map: A color map, it can be a color map built by IBLViewer or
657
+ :param color_map: A color map, it can be a color map built by IBLViewer or
662
658
a color map name (see vedo documentation), or a list of values, etc.
663
659
:param name: All point neurons are grouped into one object, you can give it a custom name
664
660
:param screen_space: Type of point, if True then the points are static screen-space points.
665
- If False, then the points are made to scale in 3D, ie you see them larger when you
661
+ If False, then the points are made to scale in 3D, ie you see them larger when you
666
662
zoom closer to them, while this is not the case with screen-space points. Defaults to False.
667
663
:param use_origin: Whether the origin is added as offset to the given positions
668
664
:param add_to_scene: Whether the new lines are added to scene/plot and rendered
@@ -682,7 +678,7 @@ def add_points(self, positions, radius=10, values=None, color_map='Accent', name
682
678
if noise_amount is not None :
683
679
positions += np .random .rand (len (positions ), 3 ) * noise_amount
684
680
link = True if add_to_scene and not trim_outliers else False
685
- points = super ().add_points (positions , radius , values , color_map , name ,
681
+ points = super ().add_points (positions , radius , values , color_map , name ,
686
682
screen_space , use_origin , link , ** kwargs )
687
683
points .axes = axes
688
684
if bounding_mesh is None :
@@ -694,24 +690,24 @@ def add_points(self, positions, radius=10, values=None, color_map='Accent', name
694
690
self .plot .add (points )
695
691
return points
696
692
697
- def add_segments (self , points , end_points = None , line_width = 2 , values = None , color_map = 'Accent' ,
698
- name = 'Segments' , use_origin = True , add_to_scene = True , relative_end_points = False ,
699
- spherical_angles = None , radians = True , trim_outliers = True , bounding_mesh = None ,
693
+ def add_segments (self , points , end_points = None , line_width = 2 , values = None , color_map = 'Accent' ,
694
+ name = 'Segments' , use_origin = True , add_to_scene = True , relative_end_points = False ,
695
+ spherical_angles = None , radians = True , trim_outliers = True , bounding_mesh = None ,
700
696
ibl_flip_yz = True ):
701
697
"""
702
698
Add a set of segments
703
699
:param points: 3D numpy array of points of length n
704
700
:param end_points: 3D numpy array of points of length n
705
701
:param line_width: Line width, defaults to 2px
706
702
:param values: 1D list of length n, for one scalar value per line
707
- :param color_map: A color map, it can be a color map built by IBLViewer or
703
+ :param color_map: A color map, it can be a color map built by IBLViewer or
708
704
a color map name (see vedo documentation), or a list of values, etc.
709
705
:param name: Name to give to the object
710
706
:param use_origin: Whether the current origin (not necessarily absolute 0) is used as offset
711
707
:param add_to_scene: Whether the new lines are added to scene/plot and rendered
712
708
:param relative_end_points: Whether the given end point is relative to the start point. False by default,
713
709
except is spherical coordinates are given
714
- :param spherical_angles: 3D numpy array of spherical angle data of length n
710
+ :param spherical_angles: 3D numpy array of spherical angle data of length n
715
711
In case end_points is None, this replaces end_points by finding the relative
716
712
coordinate to each start point with the given radius/depth, theta and phi
717
713
:param radians: Whether the given spherical angle data is in radians or in degrees
@@ -729,8 +725,8 @@ def add_segments(self, points, end_points=None, line_width=2, values=None, color
729
725
if end_points is not None :
730
726
end_points = np .array (end_points ) * axes
731
727
pre_add = True if add_to_scene and not trim_outliers else False
732
-
733
- #lines = super().add_segments(points, end_points, line_width, values, color_map, name, use_origin,
728
+
729
+ #lines = super().add_segments(points, end_points, line_width, values, color_map, name, use_origin,
734
730
#pre_add, relative_end_points, spherical_angles, radians)
735
731
'''
736
732
Crazy python stuff here [WARNING]
@@ -764,14 +760,14 @@ def add_segments(self, points, end_points=None, line_width=2, values=None, color
764
760
self .plot .add (lines )
765
761
return lines
766
762
767
- def add_lines (self , points , line_width = 2 , values = None , color_map = 'Accent' , name = 'Lines' ,
763
+ def add_lines (self , points , line_width = 2 , values = None , color_map = 'Accent' , name = 'Lines' ,
768
764
use_origin = True , add_to_scene = True , trim_outliers = True , bounding_mesh = None , ibl_flip_yz = True ):
769
765
"""
770
766
Create a set of lines with given point sets
771
767
:param points: List of lists of 3D coordinates
772
768
:param line_width: Line width, defaults to 2px
773
769
:param values: 1D list of length n, for one scalar value per line
774
- :param color_map: A color map, it can be a color map built by IBLViewer or
770
+ :param color_map: A color map, it can be a color map built by IBLViewer or
775
771
a color map name (see vedo documentation), or a list of values, etc.
776
772
:param name: Name to give to the object
777
773
:param use_origin: Whether the current origin (not necessarily absolute 0) is used as offset
@@ -811,7 +807,7 @@ def add_lines(self, points, line_width=2, values=None, color_map='Accent', name=
811
807
points = all_points
812
808
if values is None :
813
809
values = indices
814
-
810
+
815
811
pre_add = True if add_to_scene and not trim_outliers else False
816
812
lines = super ().add_lines (points , line_width , values , color_map , name , False , pre_add )
817
813
@@ -827,7 +823,7 @@ def add_lines(self, points, line_width=2, values=None, color_map='Accent', name=
827
823
self .plot .add (lines )
828
824
return lines
829
825
830
- def add_volume (self , data = None , resolution = None , file_path = None , color_map = 'viridis' ,
826
+ def add_volume (self , data = None , resolution = None , file_path = None , color_map = 'viridis' ,
831
827
alpha_map = None , select = False , add_to_scene = True , transpose = None ):
832
828
"""
833
829
Add a volume to the viewer with box clipping and slicing enabled by default
@@ -840,14 +836,14 @@ def add_volume(self, data=None, resolution=None, file_path=None, color_map='viri
840
836
are transparent and maximum values are opaque
841
837
:param select: Whether the volume is selected
842
838
:param add_to_scene: Whether the volume is added to scene
843
- :param transpose: Transposition parameter. If None. nothing happens. If True,
839
+ :param transpose: Transposition parameter. If None. nothing happens. If True,
844
840
then the default IBL transposition is applied. You can provide your own, that is,
845
841
a list of 3 elements to reorder the volume as desired.
846
842
:return: VolumeController
847
843
"""
848
844
if transpose == True :
849
845
transpose = self .ibl_transpose
850
- return super ().add_volume (data , resolution , file_path , color_map ,
846
+ return super ().add_volume (data , resolution , file_path , color_map ,
851
847
alpha_map , select , add_to_scene , transpose )
852
848
853
849
def set_left_view (self ):
0 commit comments