@@ -35,7 +35,7 @@ class VolumeSlicer:
35
35
color (str): the color for this slicer. By default the color is
36
36
red, green, or blue, depending on the axis. Set to empty string
37
37
for "no color".
38
- thumbnail (int or bool): linear size of low-resolution data to be
38
+ thumbnail (int or bool): preferred size of low-resolution data to be
39
39
uploaded to the client. If ``False``, the full-resolution data are
40
40
uploaded client-side. If ``True`` (default), a default value of 32 is
41
41
used.
@@ -108,12 +108,18 @@ def __init__(
108
108
# Check and store thumbnail
109
109
if not (isinstance (thumbnail , (int , bool ))):
110
110
raise ValueError ("thumbnail must be a boolean or an integer." )
111
- # No thumbnail if thumbnail size is larger than image size
112
- if isinstance (thumbnail , int ) and thumbnail > np .max (volume .shape ):
113
- thumbnail = False
114
- if thumbnail is True :
115
- thumbnail = 32 # default size
116
- self ._thumbnail = thumbnail
111
+ if thumbnail is False :
112
+ self ._thumbnail = False
113
+ elif thumbnail is None or thumbnail is True :
114
+ self ._thumbnail = 32 # default size
115
+ else :
116
+ thumbnail = int (thumbnail )
117
+ if thumbnail >= np .max (volume .shape [:3 ]):
118
+ self ._thumbnail = False # dont go larger than image size
119
+ elif thumbnail <= 0 :
120
+ self ._thumbnail = False # consider 0 and -1 the same as False
121
+ else :
122
+ self ._thumbnail = thumbnail
117
123
118
124
# Check and store scene id, and generate
119
125
if scene_id is None :
@@ -299,15 +305,16 @@ def _create_dash_components(self):
299
305
"""Create the graph, slider, figure, etc."""
300
306
info = self ._slice_info
301
307
302
- # Prep low-res slices
303
- if self ._thumbnail is False :
308
+ # Prep low-res slices. The get_thumbnail_size() is a bit like
309
+ # a simulation to get the low-res size.
310
+ if not self ._thumbnail :
304
311
thumbnail_size = None
305
- info ["lowres_size " ] = info ["size" ]
312
+ info ["thumbnail_size " ] = info ["size" ]
306
313
else :
307
- thumbnail_size = get_thumbnail_size (
308
- info ["size" ][:2 ], (self ._thumbnail , self ._thumbnail )
314
+ thumbnail_size = self ._thumbnail
315
+ info ["thumbnail_size" ] = get_thumbnail_size (
316
+ info ["size" ][:2 ], thumbnail_size
309
317
)
310
- info ["lowres_size" ] = thumbnail_size
311
318
thumbnails = [
312
319
img_array_to_uri (self ._slice (i ), thumbnail_size )
313
320
for i in range (info ["size" ][2 ])
@@ -361,8 +368,8 @@ def _create_dash_components(self):
361
368
# A dict of static info for this slicer
362
369
self ._info = Store (id = self ._subid ("info" ), data = info )
363
370
364
- # A list of low-res slices (encoded as base64-png)
365
- self ._lowres_data = Store (id = self ._subid ("lowres " ), data = thumbnails )
371
+ # A list of low-res slices, or the full-res data (encoded as base64-png)
372
+ self ._thumbs_data = Store (id = self ._subid ("thumbs " ), data = thumbnails )
366
373
367
374
# A list of mask slices (encoded as base64-png or null)
368
375
self ._overlay_data = Store (id = self ._subid ("overlay" ), data = [])
@@ -389,7 +396,7 @@ def _create_dash_components(self):
389
396
390
397
self ._stores = [
391
398
self ._info ,
392
- self ._lowres_data ,
399
+ self ._thumbs_data ,
393
400
self ._overlay_data ,
394
401
self ._server_data ,
395
402
self ._img_traces ,
@@ -490,16 +497,20 @@ def _create_client_callbacks(self):
490
497
491
498
app .clientside_callback (
492
499
"""
493
- function update_index_rate_limiting(index, relayoutData, n_intervals, interval, info, figure) {
500
+ function update_index_rate_limiting(index, relayoutData, n_intervals, info, figure) {
494
501
495
502
if (!window._slicer_{{ID}}) window._slicer_{{ID}} = {};
496
503
let private_state = window._slicer_{{ID}};
497
504
let now = window.performance.now();
498
505
499
506
// Get whether the slider was moved
500
- let slider_was_moved = false;
507
+ let slider_value_changed = false;
508
+ let graph_layout_changed = false;
509
+ let timer_ticked = false;
501
510
for (let trigger of dash_clientside.callback_context.triggered) {
502
- if (trigger.prop_id.indexOf('slider') >= 0) slider_was_moved = true;
511
+ if (trigger.prop_id.indexOf('slider') >= 0) slider_value_changed = true;
512
+ if (trigger.prop_id.indexOf('graph') >= 0) graph_layout_changed = true;
513
+ if (trigger.prop_id.indexOf('timer') >= 0) timer_ticked = true;
503
514
}
504
515
505
516
// Calculate view range based on the volume
@@ -513,17 +524,8 @@ def _create_client_callbacks(self):
513
524
];
514
525
515
526
// Get view range from the figure. We make range[0] < range[1]
516
- let range_was_changed = false;
517
527
let xrangeFig = figure.layout.xaxis.range
518
528
let yrangeFig = figure.layout.yaxis.range;
519
- if (relayoutData && relayoutData.xaxis && relayoutData.xaxis.range) {
520
- xrangeFig = relayoutData.xaxis.range;
521
- range_was_changed = true;
522
- }
523
- if (relayoutData && relayoutData.yaxis && relayoutData.yaxis.range) {
524
- yrangeFig = relayoutData.yaxis.range;
525
- range_was_changed = true
526
- }
527
529
xrangeFig = [Math.min(xrangeFig[0], xrangeFig[1]), Math.max(xrangeFig[0], xrangeFig[1])];
528
530
yrangeFig = [Math.min(yrangeFig[0], yrangeFig[1]), Math.max(yrangeFig[0], yrangeFig[1])];
529
531
@@ -549,18 +551,25 @@ def _create_client_callbacks(self):
549
551
// If the slider moved, remember the time when this happened
550
552
private_state.new_time = private_state.new_time || 0;
551
553
552
- if (slider_was_moved || range_was_changed) {
554
+
555
+ if (slider_value_changed) {
556
+ private_state.new_time = now;
557
+ private_state.timeout = 200;
558
+ } else if (graph_layout_changed) {
553
559
private_state.new_time = now;
560
+ private_state.timeout = 400; // need longer timeout for smooth scroll zoom
554
561
} else if (!n_intervals) {
555
562
private_state.new_time = now;
563
+ private_state.timeout = 100;
556
564
}
557
565
558
- // We can either update the rate-limited index interval ms after
559
- // the real index changed, or interval ms after it stopped
566
+ // We can either update the rate-limited index timeout ms after
567
+ // the real index changed, or timeout ms after it stopped
560
568
// changing. The former makes the indicators come along while
561
569
// dragging the slider, the latter is better for a smooth
562
- // experience, and the interval can be set much lower.
563
- if (now - private_state.new_time >= interval) {
570
+ // experience, and the timeout can be set much lower.
571
+ if (private_state.timeout && timer_ticked && now - private_state.new_time >= private_state.timeout) {
572
+ private_state.timeout = 0;
564
573
disable_timer = true;
565
574
new_state = {
566
575
index: index,
@@ -574,7 +583,6 @@ def _create_client_callbacks(self):
574
583
if (index != private_state.index) {
575
584
private_state.index = index;
576
585
new_state.index_changed = true;
577
- console.log('requesting slice ' + index);
578
586
}
579
587
}
580
588
@@ -593,7 +601,6 @@ def _create_client_callbacks(self):
593
601
Input (self ._timer .id , "n_intervals" ),
594
602
],
595
603
[
596
- State (self ._timer .id , "interval" ),
597
604
State (self ._info .id , "data" ),
598
605
State (self ._graph .id , "figure" ),
599
606
],
@@ -604,7 +611,7 @@ def _create_client_callbacks(self):
604
611
605
612
app .clientside_callback (
606
613
"""
607
- function update_image_traces(index, server_data, overlays, lowres , info, current_traces) {
614
+ function update_image_traces(index, server_data, overlays, thumbnails , info, current_traces) {
608
615
609
616
// Prepare traces
610
617
let slice_trace = {
@@ -621,16 +628,16 @@ def _create_client_callbacks(self):
621
628
overlay_trace.hovertemplate = '';
622
629
let new_traces = [slice_trace, overlay_trace];
623
630
624
- // Use full data, or use lowres
631
+ // Use full data, or use thumbnails
625
632
if (index == server_data.index) {
626
633
slice_trace.source = server_data.slice;
627
634
} else {
628
- slice_trace.source = lowres [index];
635
+ slice_trace.source = thumbnails [index];
629
636
// Scale the image to take the exact same space as the full-res
630
637
// version. Note that depending on how the low-res data is
631
638
// created, the pixel centers may not be correctly aligned.
632
- slice_trace.dx *= info.size[0] / info.lowres_size [0];
633
- slice_trace.dy *= info.size[1] / info.lowres_size [1];
639
+ slice_trace.dx *= info.size[0] / info.thumbnail_size [0];
640
+ slice_trace.dy *= info.size[1] / info.thumbnail_size [1];
634
641
slice_trace.x0 += 0.5 * slice_trace.dx - 0.5 * info.stepsize[0];
635
642
slice_trace.y0 += 0.5 * slice_trace.dy - 0.5 * info.stepsize[1];
636
643
}
@@ -654,7 +661,7 @@ def _create_client_callbacks(self):
654
661
Input (self ._overlay_data .id , "data" ),
655
662
],
656
663
[
657
- State (self ._lowres_data .id , "data" ),
664
+ State (self ._thumbs_data .id , "data" ),
658
665
State (self ._info .id , "data" ),
659
666
State (self ._img_traces .id , "data" ),
660
667
],
@@ -737,6 +744,7 @@ def _create_client_callbacks(self):
737
744
State (self ._info .id , "data" ),
738
745
State (self ._state .id , "data" ),
739
746
],
747
+ prevent_initial_call = True ,
740
748
)
741
749
742
750
# ----------------------------------------------------------------------
0 commit comments