Skip to content

Commit 8a6c13e

Browse files
committed
ENH: Add label_map_weights traitlet
1 parent 4401035 commit 8a6c13e

File tree

2 files changed

+72
-28
lines changed

2 files changed

+72
-28
lines changed

itkwidgets/widget_viewer.py

Lines changed: 49 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,10 @@ class Viewer(ViewerParent):
149149
default_value=None,
150150
help="Names for labels in the label map.").tag(
151151
sync=True)
152+
label_map_weights = NDArray(dtype=np.float32, default_value=None, allow_none=True,
153+
help="Weights, from 0.0 to 1.0, for every label in the label map.")\
154+
.tag(sync=True, **array_serialization)\
155+
.valid(shape_constraints(None,))
152156
interpolation = CBool(
153157
default_value=True,
154158
help="Use linear interpolation in slicing planes.").tag(sync=True)
@@ -374,6 +378,7 @@ def __init__(self, **kwargs): # noqa: C901
374378

375379
self.observe(self._on_reset_crop_requested, ['_reset_crop_requested'])
376380
self.observe(self.update_rendered_image, ['image', 'label_map'])
381+
self.observe(self.update_rendered_image, ['image', 'label_map'])
377382

378383
def _on_roi_changed(self, change=None):
379384
if self._downsampling:
@@ -512,6 +517,17 @@ def f():
512517
if self.label_map:
513518
self.rendered_label_map = self.image
514519

520+
@validate('label_map_weights')
521+
def _validate_label_map_weights(self, proposal):
522+
"""Check the number of weights equals the number of labels."""
523+
value = proposal['value']
524+
value = np.array(value, dtype=np.float32)
525+
if self.rendered_label_map:
526+
labels = len(np.unique(itk.array_view_from_image(self.rendered_label_map)))
527+
if labels != len(value):
528+
raise TraitError('Number of labels, {0}, does not equal number of label weights, {1}'.format(labels, len(value)))
529+
return value
530+
515531
@validate('gradient_opacity')
516532
def _validate_gradient_opacity(self, proposal):
517533
"""Enforce 0 < value <= 1.0."""
@@ -656,6 +672,7 @@ def roi_slice(self):
656672
def view(image=None, # noqa: C901
657673
label_map=None, # noqa: C901
658674
label_map_names=None, # noqa: C901
675+
label_map_weights=None, # noqa: C901
659676
cmap=None,
660677
select_roi=False,
661678
interpolation=True,
@@ -691,26 +708,26 @@ def view(image=None, # noqa: C901
691708
General Interface
692709
^^^^^^^^^^^^^^^^^
693710
694-
ui_collapsed : bool, optional, default: False
711+
ui_collapsed : bool, default: False
695712
Collapse the native widget user interface.
696713
697-
rotate : bool, optional, default: False
714+
rotate : bool, default: False
698715
Continuously rotate the camera around the scene in volume rendering
699716
mode.
700717
701-
annotations : bool, optional, default: True
718+
annotations : bool, default: True
702719
Display annotations describing orientation and the value of a
703720
mouse-position-based data probe.
704721
705-
mode: 'x', 'y', 'z', or 'v', optional, default: 'v'
722+
mode: 'x', 'y', 'z', or 'v', default: 'v'
706723
Only relevant for 3D scenes.
707724
Viewing mode:
708725
'x': x-plane
709726
'y': y-plane
710727
'z': z-plane
711728
'v': volume rendering
712729
713-
camera: 3x3 numpy float32 array, optional
730+
camera: 3x3 numpy float32 array
714731
Camera parameters:
715732
[[position_x, position_y, position_z],
716733
[focal_point_x, focal_point_y, focal_point_z],
@@ -730,89 +747,92 @@ def view(image=None, # noqa: C901
730747
label_map_names : OrderedDict of (label_value, label_name)
731748
String names associated with the integer label values.
732749
733-
vmin: float, optional, default: None
750+
label_map_weights : 1D numpy float32 array, default: None
751+
Rendering weights, from 0.0 to 1.0, associated labels in the label map.
752+
753+
vmin: float, default: None
734754
Value that maps to the minimum of image colormap. Defaults to minimum of
735755
the image pixel buffer.
736756
737-
vmax: float, optional, default: None
757+
vmax: float, default: None
738758
Value that maps to the minimum of image colormap. Defaults to maximum of
739759
the image pixel buffer.
740760
741-
cmap: string, optional, default: viridis, grayscale with a label map
761+
cmap: string, default: viridis, grayscale with a label map
742762
Colormap. Some valid values available at itkwidgets.cm.*
743763
744-
select_roi: bool, optional, default: False
764+
select_roi: bool, default: False
745765
Enable an interactive region of interest widget for the image.
746766
747-
slicing_planes: bool, optional, default: False
767+
slicing_planes: bool, default: False
748768
Enable slicing planes on the volume rendering.
749769
750-
x_slice: float, optional, default: None
770+
x_slice: float, default: None
751771
World-space position of the X slicing plane.
752772
753-
y_slice: float, optional, default: None
773+
y_slice: float, default: None
754774
World-space position of the Y slicing plane.
755775
756-
z_slice: float, optional, default: None
776+
z_slice: float, default: None
757777
World-space position of the Z slicing plane.
758778
759-
interpolation: bool, optional, default: True
779+
interpolation: bool, default: True
760780
Linear as opposed to nearest neighbor interpolation for image slices.
761781
Note: Interpolation is not currently supported with label maps.
762782
763-
gradient_opacity: float, optional, default: 0.22
783+
gradient_opacity: float, default: 0.22
764784
Gradient opacity for composite volume rendering, in the range (0.0, 1.0].
765785
766-
shadow: bool, optional, default: True
786+
shadow: bool, default: True
767787
Use shadowing with composite volume rendering.
768788
769-
blend: 'composite', 'max', 'min', or 'average', optional, default: 'composite'
789+
blend: 'composite', 'max', 'min', or 'average', default: 'composite'
770790
Volume rendering blend mode.
771791
772792
Point Sets
773793
^^^^^^^^^^
774794
775-
point_sets: point set, or sequence of point sets, optional
795+
point_sets: point set, or sequence of point sets
776796
The point sets to visualize.
777797
778-
point_set_colors: list of RGB colors, optional
798+
point_set_colors: list of RGB colors
779799
Colors for the N geometries. See help(matplotlib.colors) for
780800
specification. Defaults to the Glasbey series of categorical colors.
781801
782-
point_set_opacities: list of floats, optional, default: [0.5,]*n
802+
point_set_opacities: list of floats, default: [0.5,]*n
783803
Opacity for the point sets, in the range (0.0, 1.0].
784804
785-
point_set_representations: list of strings, optional, default: ['points',]*n
805+
point_set_representations: list of strings, default: ['points',]*n
786806
How to represent the point set. One of 'hidden', 'points', or 'spheres'.
787807
788808
Geometries
789809
^^^^^^^^^^
790810
791-
geometries: geometries, or sequence of geometries, optional
811+
geometries: geometries, or sequence of geometries
792812
The geometries to visualize.
793813
794-
geometry_colors: list of RGB colors, optional
814+
geometry_colors: list of RGB colors
795815
Colors for the N geometries. See help(matplotlib.colors) for
796816
specification. Defaults to the Glasbey series of categorical colors.
797817
798-
geometry_opacities: list of floats, optional, default: [1.0,]*n
818+
geometry_opacities: list of floats, default: [1.0,]*n
799819
Opacity for the point sets, in the range (0.0, 1.0].
800820
801821
802822
Other Parameters
803823
----------------
804824
805-
units: string, optional, default: ''
825+
units: string, default: ''
806826
Units to display in the scale bar.
807827
808-
actors: vtkActor, vtkAssembly, vtkVolume, optional, default: None
828+
actors: vtkActor, vtkAssembly, vtkVolume, default: None
809829
List of standard vtk objects, colors are extracted from their properties
810830
811-
size_limit_2d: 2x1 numpy int64 array, optional, default: [1024, 1024]
831+
size_limit_2d: 2x1 numpy int64 array, default: [1024, 1024]
812832
Size limit for 2D image visualization. If the roi is larger than this
813833
size, it will be downsampled for visualization
814834
815-
size_limit_3d: 3x1 numpy int64 array, optional, default: [192, 192, 192]
835+
size_limit_3d: 3x1 numpy int64 array, default: [192, 192, 192]
816836
Size limit for 3D image visualization. If the roi is larger than this
817837
size, it will be downsampled for visualization.
818838
@@ -897,6 +917,7 @@ def view(image=None, # noqa: C901
897917
viewer = Viewer(image=image,
898918
label_map=label_map,
899919
label_map_names=label_map_names,
920+
label_map_weights=label_map_weights,
900921
cmap=cmap,
901922
select_roi=select_roi,
902923
interpolation=interpolation,

js/lib/viewer.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ const ViewerModel = widgets.DOMWidgetModel.extend(
117117
rendered_image: null,
118118
rendered_label_map: null,
119119
label_map_names: null,
120+
label_map_weights: null,
120121
_rendering_image: false,
121122
interpolation: true,
122123
cmap: 'Viridis (matplotlib)',
@@ -163,6 +164,7 @@ const ViewerModel = widgets.DOMWidgetModel.extend(
163164
serialize: serialize_itkimage,
164165
deserialize: deserialize_itkimage
165166
},
167+
label_map_weights: simplearray_serialization,
166168
clicked_slice_point: {
167169
serialize: serialize_image_point,
168170
deserialize: deserialize_image_point
@@ -722,6 +724,7 @@ const ViewerView = widgets.DOMWidgetView.extend({
722724
}
723725
if (rendered_label_map) {
724726
this.label_map_names_changed()
727+
this.label_map_weights_changed()
725728
}
726729

727730
const onUserInterfaceCollapsedToggle = (collapsed) => {
@@ -825,6 +828,17 @@ const ViewerView = widgets.DOMWidgetView.extend({
825828
onToggleCroppingPlanes
826829
)
827830

831+
const onLabelMapWeightsChanged = ({ weights }) => {
832+
const typedWeights = new Float32Array(weights)
833+
this.model.set('label_map_weights', { shape: [weights.length],
834+
array: typedWeights
835+
})
836+
this.model.save_changes()
837+
}
838+
this.model.itkVtkViewer.on('labelMapWeightsChanged',
839+
onLabelMapWeightsChanged
840+
)
841+
828842
if (!this.model.use2D) {
829843
const onBlendModeChanged = (blend) => {
830844
let pythonMode = null
@@ -1049,6 +1063,7 @@ const ViewerView = widgets.DOMWidgetView.extend({
10491063
this.model.on('change:units', this.units_changed, this)
10501064
this.model.on('change:camera', this.camera_changed, this)
10511065
this.model.on('change:label_map_names', this.label_map_names_changed, this)
1066+
this.model.on('change:label_map_weights', this.label_map_weights_changed, this)
10521067

10531068
let toDecompress = []
10541069
const rendered_image = this.model.get('rendered_image')
@@ -1174,6 +1189,14 @@ const ViewerView = widgets.DOMWidgetView.extend({
11741189
}
11751190
},
11761191

1192+
label_map_weights_changed: function () {
1193+
const label_map_weights = this.model.get('label_map_weights')
1194+
if (label_map_weights && this.model.hasOwnProperty('itkVtkViewer')) {
1195+
const labelMapWeights = !!label_map_weights.array ? Array.from(label_map_weights.array) : Array.from(label_map_weights)
1196+
this.model.itkVtkViewer.setLabelMapWeights(labelMapWeights)
1197+
}
1198+
},
1199+
11771200
point_sets_changed: function () {
11781201
const point_sets = this.model.get('point_sets')
11791202
if (point_sets && !!point_sets.length) {

0 commit comments

Comments
 (0)