Skip to content

Commit 6d948c7

Browse files
authored
Merge pull request #336 from thewtex/lut-point-set-traits
Lut point set traits
2 parents 6f9c448 + 0df41a6 commit 6d948c7

File tree

6 files changed

+236
-28
lines changed

6 files changed

+236
-28
lines changed

itkwidgets/lut.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Some aliases similar to matplotlib.cm
2+
glasbey = "glasbey"
3+
glasbey_light = "glasbey_light"
4+
glasbey_warm = "glasbey_warm"
5+
modulate = "modulate"
6+
7+
glasbey_bw = "glasbey_bw"
8+
glasbey_dark = "glasbey_dark"
9+
glasbey_cool = "glasbey_cool"
10+
modulate_dark = "modulate_dark"

itkwidgets/trait_types.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -680,3 +680,38 @@ def validate(self, obj, value):
680680
'Custom'):
681681
raise self.error('Invalid colormap')
682682
return super(Colormap, self).validate(obj, value)
683+
684+
class LookupTable(traitlets.Unicode):
685+
"""A trait type holding a lookup table."""
686+
687+
info_text = 'A lookup table, either a itk-vtk-viewer categorical colormap preset, todo: np.ndarray of RGB points, or matplotlib ListedColormap.'
688+
689+
_lookup_table_presets = ('glasbey',
690+
'glasbey_light',
691+
'glasbey_warm',
692+
'modulate',
693+
'glasbey_bw',
694+
'glasbey_dark',
695+
'glasbey_cool',
696+
'modulate_dark',
697+
)
698+
699+
def validate(self, obj, value):
700+
if value is None:
701+
return None
702+
# elif isinstance(value, np.ndarray):
703+
# custom_cmap = value.astype(np.float32)
704+
# custom_cmap = custom_cmap[:, :3]
705+
# obj._custom_cmap = custom_cmap
706+
# timestamp = str(datetime.timestamp(datetime.now()))
707+
# return 'Custom NumPy ' + timestamp
708+
# elif isinstance(value, matplotlib.colors.ListedColormap):
709+
# custom_cmap = value(np.linspace(0.0, 1.0, 64)).astype(np.float32)
710+
# custom_cmap = custom_cmap[:, :3]
711+
# obj._custom_cmap = custom_cmap
712+
# timestamp = str(datetime.timestamp(datetime.now()))
713+
# return 'Custom matplotlib ' + timestamp
714+
if value not in self._lookup_table_presets and not value.startswith(
715+
'Custom'):
716+
raise self.error('Invalid lookup table')
717+
return super(LookupTable, self).validate(obj, value)

itkwidgets/widget_viewer.py

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
In the future, will add optional segmentation mesh overlay.
66
"""
77

8-
from . import cm
98
import colorcet
109
import matplotlib
1110
import collections
@@ -17,7 +16,7 @@
1716
import ipywidgets as widgets
1817
from traitlets import CBool, CFloat, CInt, Unicode, CaselessStrEnum, List, validate, TraitError, Tuple
1918
from ipydatawidgets import NDArray, array_serialization, shape_constraints
20-
from .trait_types import ITKImage, ImagePointTrait, ImagePoint, PointSetList, PolyDataList, itkimage_serialization, image_point_serialization, polydata_list_serialization, Colormap
19+
from .trait_types import ITKImage, ImagePointTrait, ImagePoint, PointSetList, PolyDataList, itkimage_serialization, image_point_serialization, polydata_list_serialization, Colormap, LookupTable
2120

2221
try:
2322
import ipywebrtc
@@ -167,6 +166,12 @@ class Viewer(ViewerParent):
167166
help="RGB triples from 0.0 to 1.0 that define a custom linear, sequential colormap")\
168167
.tag(sync=True, **array_serialization)\
169168
.valid(shape_constraints(None, 3))
169+
lut = LookupTable('glasbey',
170+
help='Lookup table for the label map.').tag(sync=True)
171+
_custom_cmap = NDArray(dtype=np.float32, default_value=None, allow_none=True,
172+
help="RGB triples from 0.0 to 1.0 that define a custom linear, sequential colormap")\
173+
.tag(sync=True, **array_serialization)\
174+
.valid(shape_constraints(None, 3))
170175
shadow = CBool(
171176
default_value=True,
172177
help="Use shadowing in the volume rendering.").tag(sync=True)
@@ -267,6 +272,10 @@ class Viewer(ViewerParent):
267272
help="Opacities for the points sets")\
268273
.tag(sync=True, **array_serialization)\
269274
.valid(shape_constraints(None,))
275+
point_set_sizes = NDArray(dtype=np.uint8, default_value=np.zeros((0,), dtype=np.uint8),
276+
help="Sizes for the points sets")\
277+
.tag(sync=True, **array_serialization)\
278+
.valid(shape_constraints(None,))
270279
point_set_representations = List(
271280
trait=Unicode(),
272281
default_value=[],
@@ -327,6 +336,10 @@ def __init__(self, **kwargs): # noqa: C901
327336
proposal = {'value': kwargs['point_set_opacities']}
328337
opacities_array = self._validate_point_set_opacities(proposal)
329338
kwargs['point_set_opacities'] = opacities_array
339+
if 'point_set_sizes' in kwargs:
340+
proposal = {'value': kwargs['point_set_sizes']}
341+
sizes_array = self._validate_point_set_sizes(proposal)
342+
kwargs['point_set_sizes'] = sizes_array
330343
if 'point_set_representations' in kwargs:
331344
proposal = {'value': kwargs['point_set_representations']}
332345
representations_list = self._validate_point_set_representations(
@@ -645,6 +658,21 @@ def _validate_point_set_opacities(self, proposal):
645658
result[:n_values] = value
646659
return result
647660

661+
@validate('point_set_sizes')
662+
def _validate_point_set_sizes(self, proposal):
663+
value = proposal['value']
664+
n_values = 0
665+
if isinstance(value, float):
666+
n_values = 1
667+
else:
668+
n_values = len(value)
669+
n_sizes = n_values
670+
if self.point_sets:
671+
n_sizes = len(self.point_sets)
672+
result = 3 * np.ones((n_sizes,), dtype=np.uint8)
673+
result[:n_values] = value
674+
return result
675+
648676
@validate('point_set_representations')
649677
def _validate_point_set_representations(self, proposal):
650678
value = proposal['value']
@@ -667,6 +695,9 @@ def _on_point_sets_changed(self, change=None):
667695
# Make sure we have a sufficient number of opacities
668696
old_opacities = self.point_set_opacities
669697
self.point_set_opacities = old_opacities[:len(self.point_sets)]
698+
# Make sure we have a sufficient number of sizes
699+
old_sizes = self.point_set_sizes
700+
self.point_set_sizes = old_sizes[:len(self.point_sets)]
670701
# Make sure we have a sufficient number of representations
671702
old_representations = self.point_set_representations
672703
self.point_set_representations = old_representations[:len(
@@ -752,13 +783,14 @@ def view(image=None, # noqa: C901
752783
label_map_weights=None, # noqa: C901
753784
label_map_blend=0.5,
754785
cmap=None,
786+
lut='glasbey',
755787
select_roi=False,
756788
interpolation=True,
757789
gradient_opacity=0.22, opacity_gaussians=None, channels=None,
758790
slicing_planes=False, shadow=True, blend_mode='composite',
759791
point_sets=[],
760-
point_set_colors=[], point_set_opacities=[], point_set_representations=[],
761-
# point_set_sizes=[],
792+
point_set_colors=[], point_set_opacities=[],
793+
point_set_representations=[], point_set_sizes=[],
762794
geometries=[],
763795
geometry_colors=[], geometry_opacities=[],
764796
ui_collapsed=False, rotate=False, annotations=True, mode='v',
@@ -843,12 +875,20 @@ def view(image=None, # noqa: C901
843875
Value that maps to the minimum of image colormap. A single value can
844876
be provided or a list for multi-component images.
845877
846-
cmap: list of strings
878+
cmap: list of colormaps
847879
default:
848880
- single component: 'viridis', 'grayscale' with a label map,
849881
- two components: 'BkCy', 'BkMa'
850882
- three components: 'BkRd', 'BkGn', 'BkBu'
851-
Colormap for each image component. Some valid values available at itkwidgets.cm.*
883+
Colormap for each image component. Some valid values available at
884+
itkwidgets.cm.*
885+
Colormaps can also be Nx3 float NumPy arrays from 0.0 to 1.0 for the
886+
red, green, blue points on the map or a
887+
matplotlib.colors.LinearSegmentedColormap.
888+
889+
lut: lookup table, default: 'glasbey'
890+
Lookup table for the label map. Some valid values available at
891+
itkwidgets.lut.*
852892
853893
select_roi: bool, default: False
854894
Enable an interactive region of interest widget for the image.
@@ -893,13 +933,16 @@ def view(image=None, # noqa: C901
893933
point_sets: point set, or sequence of point sets
894934
The point sets to visualize.
895935
896-
point_set_colors: list of RGB colors
897-
Colors for the N geometries. See help(matplotlib.colors) for
936+
point_set_colors: list of (r, g, b) colors
937+
Colors for the N points. See help(matplotlib.colors) for
898938
specification. Defaults to the Glasbey series of categorical colors.
899939
900-
point_set_opacities: list of floats, default: [0.5,]*n
940+
point_set_opacities: array of floats, default: [0.5,]*n
901941
Opacity for the point sets, in the range (0.0, 1.0].
902942
943+
point_set_sizes: array of unsigned integers, default: [3,]*n
944+
Sizes for the point sets, in pixel size units.
945+
903946
point_set_representations: list of strings, default: ['points',]*n
904947
How to represent the point set. One of 'hidden', 'points', or 'spheres'.
905948
@@ -1017,6 +1060,7 @@ def view(image=None, # noqa: C901
10171060
label_map_names=label_map_names,
10181061
label_map_weights=label_map_weights,
10191062
cmap=cmap,
1063+
lut=lut,
10201064
select_roi=select_roi,
10211065
interpolation=interpolation,
10221066
gradient_opacity=gradient_opacity, slicing_planes=slicing_planes,

0 commit comments

Comments
 (0)