vispy 0.9.5__cp38-cp38-win_amd64.whl → 0.14.0__cp38-cp38-win_amd64.whl
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.
Potentially problematic release.
This version of vispy might be problematic. Click here for more details.
- vispy/app/backends/_glfw.py +2 -2
- vispy/app/backends/_pyglet.py +8 -2
- vispy/app/backends/_qt.py +88 -63
- vispy/app/backends/_wx.py +6 -1
- vispy/app/canvas.py +4 -2
- vispy/app/tests/test_canvas.py +52 -1
- vispy/app/tests/test_context.py +5 -3
- vispy/color/color_array.py +8 -1
- vispy/color/colormap.py +5 -25
- vispy/geometry/meshdata.py +76 -38
- vispy/geometry/rect.py +6 -0
- vispy/geometry/tests/test_meshdata.py +72 -0
- vispy/gloo/buffer.py +12 -0
- vispy/gloo/gl/_constants.py +9 -5
- vispy/gloo/gl/_es2.py +8 -4
- vispy/gloo/gl/_gl2.py +2 -3
- vispy/gloo/gl/_proxy.py +1 -1
- vispy/gloo/gl/_pyopengl2.py +12 -7
- vispy/gloo/gl/tests/test_names.py +3 -0
- vispy/gloo/glir.py +26 -13
- vispy/gloo/program.py +39 -22
- vispy/gloo/tests/test_program.py +9 -2
- vispy/gloo/tests/test_texture.py +19 -2
- vispy/gloo/texture.py +46 -16
- vispy/gloo/wrappers.py +4 -2
- vispy/glsl/build_spatial_filters.py +241 -293
- vispy/glsl/misc/spatial-filters.frag +1299 -254
- vispy/io/_data/spatial-filters.npy +0 -0
- vispy/io/datasets.py +2 -2
- vispy/io/image.py +1 -1
- vispy/io/stl.py +3 -3
- vispy/scene/cameras/base_camera.py +6 -2
- vispy/scene/cameras/panzoom.py +10 -14
- vispy/scene/cameras/perspective.py +6 -0
- vispy/scene/cameras/tests/test_cameras.py +27 -0
- vispy/scene/cameras/tests/test_perspective.py +37 -0
- vispy/scene/cameras/turntable.py +39 -23
- vispy/scene/canvas.py +9 -5
- vispy/scene/events.py +9 -0
- vispy/scene/node.py +19 -2
- vispy/scene/tests/test_canvas.py +30 -1
- vispy/scene/tests/test_visuals.py +113 -0
- vispy/scene/visuals.py +6 -1
- vispy/scene/widgets/viewbox.py +3 -2
- vispy/testing/_runners.py +6 -12
- vispy/testing/_testing.py +3 -4
- vispy/util/check_environment.py +4 -4
- vispy/util/gallery_scraper.py +50 -32
- vispy/util/tests/test_gallery_scraper.py +2 -0
- vispy/util/transforms.py +1 -1
- vispy/util/wrappers.py +1 -1
- vispy/version.py +2 -3
- vispy/visuals/__init__.py +2 -0
- vispy/visuals/_scalable_textures.py +20 -17
- vispy/visuals/collections/array_list.py +3 -3
- vispy/visuals/collections/base_collection.py +1 -1
- vispy/visuals/ellipse.py +1 -1
- vispy/visuals/filters/__init__.py +3 -2
- vispy/visuals/filters/base_filter.py +120 -0
- vispy/visuals/filters/clipping_planes.py +24 -12
- vispy/visuals/filters/markers.py +28 -0
- vispy/visuals/filters/mesh.py +61 -6
- vispy/visuals/filters/tests/test_primitive_picking_filters.py +70 -0
- vispy/visuals/graphs/graph.py +1 -1
- vispy/visuals/image.py +114 -26
- vispy/visuals/image_complex.py +130 -0
- vispy/visuals/instanced_mesh.py +152 -0
- vispy/visuals/isocurve.py +1 -1
- vispy/visuals/line/dash_atlas.py +46 -41
- vispy/visuals/line/line.py +2 -5
- vispy/visuals/markers.py +310 -384
- vispy/visuals/mesh.py +2 -2
- vispy/visuals/shaders/function.py +3 -0
- vispy/visuals/shaders/tests/test_function.py +6 -0
- vispy/visuals/tests/test_axis.py +2 -2
- vispy/visuals/tests/test_image.py +92 -2
- vispy/visuals/tests/test_image_complex.py +36 -0
- vispy/visuals/tests/test_instanced_mesh.py +50 -0
- vispy/visuals/tests/test_markers.py +6 -0
- vispy/visuals/tests/test_mesh.py +17 -0
- vispy/visuals/tests/test_text.py +11 -0
- vispy/visuals/tests/test_volume.py +218 -12
- vispy/visuals/text/_sdf_cpu.cp38-win_amd64.pyd +0 -0
- vispy/visuals/text/_sdf_cpu.pyx +21 -23
- vispy/visuals/text/text.py +9 -3
- vispy/visuals/tube.py +2 -2
- vispy/visuals/visual.py +144 -3
- vispy/visuals/volume.py +300 -131
- {vispy-0.9.5.dist-info → vispy-0.14.0.dist-info}/LICENSE.txt +1 -1
- {vispy-0.9.5.dist-info → vispy-0.14.0.dist-info}/METADATA +218 -198
- {vispy-0.9.5.dist-info → vispy-0.14.0.dist-info}/RECORD +93 -96
- {vispy-0.9.5.dist-info → vispy-0.14.0.dist-info}/WHEEL +1 -1
- vispy/glsl/antialias/__init__.py +0 -0
- vispy/glsl/arrowheads/__init__.py +0 -0
- vispy/glsl/arrows/__init__.py +0 -0
- vispy/glsl/collections/__init__.py +0 -0
- vispy/glsl/colormaps/__init__.py +0 -0
- vispy/glsl/lines/__init__.py +0 -0
- vispy/glsl/markers/__init__.py +0 -0
- vispy/glsl/math/__init__.py +0 -0
- vispy/glsl/misc/__init__.py +0 -0
- vispy/glsl/transforms/__init__.py +0 -0
- {vispy-0.9.5.dist-info → vispy-0.14.0.dist-info}/top_level.txt +0 -0
vispy/visuals/volume.py
CHANGED
|
@@ -34,14 +34,18 @@ The ray is expressed in coordinates local to the volume (i.e. texture
|
|
|
34
34
|
coordinates).
|
|
35
35
|
|
|
36
36
|
"""
|
|
37
|
+
from __future__ import annotations
|
|
38
|
+
|
|
39
|
+
from typing import Optional
|
|
37
40
|
from functools import lru_cache
|
|
41
|
+
import warnings
|
|
38
42
|
|
|
39
|
-
from ._scalable_textures import CPUScaledTexture3D, GPUScaledTextured3D
|
|
43
|
+
from ._scalable_textures import CPUScaledTexture3D, GPUScaledTextured3D, Texture2D
|
|
40
44
|
from ..gloo import VertexBuffer, IndexBuffer
|
|
41
|
-
from ..gloo.texture import should_cast_to_f32
|
|
42
45
|
from . import Visual
|
|
43
46
|
from .shaders import Function
|
|
44
47
|
from ..color import get_colormap
|
|
48
|
+
from ..io import load_spatial_filters
|
|
45
49
|
|
|
46
50
|
import numpy as np
|
|
47
51
|
|
|
@@ -86,6 +90,8 @@ uniform float gamma;
|
|
|
86
90
|
uniform float u_threshold;
|
|
87
91
|
uniform float u_attenuation;
|
|
88
92
|
uniform float u_relative_step_size;
|
|
93
|
+
uniform float u_mip_cutoff;
|
|
94
|
+
uniform float u_minip_cutoff;
|
|
89
95
|
|
|
90
96
|
//varyings
|
|
91
97
|
varying vec3 v_position;
|
|
@@ -139,16 +145,16 @@ vec4 calculateColor(vec4 betterColor, vec3 loc, vec3 step)
|
|
|
139
145
|
|
|
140
146
|
// calculate normal vector from gradient
|
|
141
147
|
vec3 N; // normal
|
|
142
|
-
color1 = $
|
|
143
|
-
color2 = $
|
|
148
|
+
color1 = $get_data(loc+vec3(-step[0],0.0,0.0) );
|
|
149
|
+
color2 = $get_data(loc+vec3(step[0],0.0,0.0) );
|
|
144
150
|
N[0] = colorToVal(color1) - colorToVal(color2);
|
|
145
151
|
betterColor = max(max(color1, color2),betterColor);
|
|
146
|
-
color1 = $
|
|
147
|
-
color2 = $
|
|
152
|
+
color1 = $get_data(loc+vec3(0.0,-step[1],0.0) );
|
|
153
|
+
color2 = $get_data(loc+vec3(0.0,step[1],0.0) );
|
|
148
154
|
N[1] = colorToVal(color1) - colorToVal(color2);
|
|
149
155
|
betterColor = max(max(color1, color2),betterColor);
|
|
150
|
-
color1 = $
|
|
151
|
-
color2 = $
|
|
156
|
+
color1 = $get_data(loc+vec3(0.0,0.0,-step[2]) );
|
|
157
|
+
color2 = $get_data(loc+vec3(0.0,0.0,step[2]) );
|
|
152
158
|
N[2] = colorToVal(color1) - colorToVal(color2);
|
|
153
159
|
betterColor = max(max(color1, color2),betterColor);
|
|
154
160
|
float gm = length(N); // gradient magnitude
|
|
@@ -229,12 +235,10 @@ void main() {
|
|
|
229
235
|
// fragment.
|
|
230
236
|
view_ray = normalize(farpos.xyz - nearpos.xyz);
|
|
231
237
|
|
|
232
|
-
//
|
|
233
|
-
//
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
bool surface_found = false;
|
|
237
|
-
|
|
238
|
+
// Variables to keep track of where to set the frag depth.
|
|
239
|
+
// frag_depth_point is in data coordinates.
|
|
240
|
+
vec3 frag_depth_point;
|
|
241
|
+
|
|
238
242
|
// Set up the ray casting
|
|
239
243
|
// This snippet must define three variables:
|
|
240
244
|
// vec3 start_loc - the starting location of the ray in texture coordinates
|
|
@@ -255,8 +259,9 @@ void main() {
|
|
|
255
259
|
vec3 loc = start_loc;
|
|
256
260
|
int iter = 0;
|
|
257
261
|
|
|
258
|
-
//
|
|
259
|
-
|
|
262
|
+
// keep track if the texture is ever sampled; if not, fragment will be discarded
|
|
263
|
+
// this allows us to discard fragments that only traverse clipped parts of the texture
|
|
264
|
+
bool texture_sampled = false;
|
|
260
265
|
|
|
261
266
|
while (iter < nsteps) {
|
|
262
267
|
for (iter=iter; iter<nsteps; iter++)
|
|
@@ -266,9 +271,9 @@ void main() {
|
|
|
266
271
|
if (distance_from_clip >= 0)
|
|
267
272
|
{
|
|
268
273
|
// Get sample color
|
|
269
|
-
vec4 color = $
|
|
274
|
+
vec4 color = $get_data(loc);
|
|
270
275
|
float val = color.r;
|
|
271
|
-
texture_sampled =
|
|
276
|
+
texture_sampled = true;
|
|
272
277
|
|
|
273
278
|
$in_loop
|
|
274
279
|
}
|
|
@@ -276,28 +281,18 @@ void main() {
|
|
|
276
281
|
loc += step;
|
|
277
282
|
}
|
|
278
283
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
if ( texture_sampled != 1 ) {
|
|
284
|
+
|
|
285
|
+
if (!texture_sampled)
|
|
282
286
|
discard;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
$after_loop
|
|
286
287
|
|
|
287
|
-
|
|
288
|
-
// if a surface was found, use it to set the depth buffer
|
|
289
|
-
vec4 position2 = vec4(surface_point, 1);
|
|
290
|
-
vec4 iproj = $viewtransformf(position2);
|
|
291
|
-
iproj.z /= iproj.w;
|
|
292
|
-
gl_FragDepth = (iproj.z+1.0)/2.0;
|
|
293
|
-
}
|
|
294
|
-
else {
|
|
295
|
-
gl_FragDepth = gl_FragCoord.z;
|
|
296
|
-
}
|
|
288
|
+
$after_loop
|
|
297
289
|
|
|
290
|
+
// set frag depth
|
|
291
|
+
vec4 frag_depth_vector = vec4(frag_depth_point, 1);
|
|
292
|
+
vec4 iproj = $viewtransformf(frag_depth_vector);
|
|
293
|
+
iproj.z /= iproj.w;
|
|
294
|
+
gl_FragDepth = (iproj.z+1.0)/2.0;
|
|
298
295
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
296
|
""" # noqa
|
|
302
297
|
|
|
303
298
|
_RAYCASTING_SETUP_VOLUME = """
|
|
@@ -323,6 +318,9 @@ _RAYCASTING_SETUP_VOLUME = """
|
|
|
323
318
|
vec3 step = ((v_position - front) / u_shape) / f_nsteps;
|
|
324
319
|
// 0.5 offset needed to get back to correct texture coordinates (vispy#2239)
|
|
325
320
|
vec3 start_loc = (front + 0.5) / u_shape;
|
|
321
|
+
|
|
322
|
+
// set frag depth to the cube face; this can be overridden by projection snippets
|
|
323
|
+
frag_depth_point = front;
|
|
326
324
|
"""
|
|
327
325
|
|
|
328
326
|
_RAYCASTING_SETUP_PLANE = """
|
|
@@ -344,9 +342,8 @@ _RAYCASTING_SETUP_PLANE = """
|
|
|
344
342
|
out_of_bounds += float(intersection_tex.z > 1);
|
|
345
343
|
out_of_bounds += float(intersection_tex.z < 0);
|
|
346
344
|
|
|
347
|
-
if (out_of_bounds > 0)
|
|
345
|
+
if (out_of_bounds > 0)
|
|
348
346
|
discard;
|
|
349
|
-
}
|
|
350
347
|
|
|
351
348
|
|
|
352
349
|
// Decide how many steps to take
|
|
@@ -361,78 +358,125 @@ _RAYCASTING_SETUP_PLANE = """
|
|
|
361
358
|
vec3 step = N / u_shape;
|
|
362
359
|
vec3 start_loc = intersection_tex - ((step * f_nsteps) / 2);
|
|
363
360
|
|
|
364
|
-
//
|
|
365
|
-
|
|
366
|
-
surface_found = true;
|
|
361
|
+
// Ensure that frag depth value will be set to plane intersection
|
|
362
|
+
frag_depth_point = intersection;
|
|
367
363
|
"""
|
|
368
364
|
|
|
369
365
|
|
|
370
366
|
_MIP_SNIPPETS = dict(
|
|
371
367
|
before_loop="""
|
|
372
|
-
float maxval =
|
|
368
|
+
float maxval = u_mip_cutoff; // The maximum encountered value
|
|
373
369
|
int maxi = -1; // Where the maximum value was encountered
|
|
374
370
|
""",
|
|
375
371
|
in_loop="""
|
|
376
|
-
if( val > maxval ) {
|
|
372
|
+
if ( val > maxval ) {
|
|
377
373
|
maxval = val;
|
|
378
374
|
maxi = iter;
|
|
375
|
+
if ( maxval >= clim.y ) {
|
|
376
|
+
// stop if no chance of finding a higher maxval
|
|
377
|
+
iter = nsteps;
|
|
378
|
+
}
|
|
379
379
|
}
|
|
380
380
|
""",
|
|
381
381
|
after_loop="""
|
|
382
382
|
// Refine search for max value, but only if anything was found
|
|
383
383
|
if ( maxi > -1 ) {
|
|
384
|
-
|
|
384
|
+
// Calculate starting location of ray for sampling
|
|
385
|
+
vec3 start_loc_refine = start_loc + step * (float(maxi) - 0.5);
|
|
386
|
+
loc = start_loc_refine;
|
|
387
|
+
|
|
388
|
+
// Variables to keep track of current value and where max was encountered
|
|
389
|
+
vec3 max_loc_tex = start_loc_refine;
|
|
390
|
+
|
|
391
|
+
vec3 small_step = step * 0.1;
|
|
385
392
|
for (int i=0; i<10; i++) {
|
|
386
|
-
|
|
387
|
-
|
|
393
|
+
float val = $get_data(loc).r;
|
|
394
|
+
if ( val > maxval) {
|
|
395
|
+
maxval = val;
|
|
396
|
+
max_loc_tex = start_loc_refine + (small_step * i);
|
|
397
|
+
}
|
|
398
|
+
loc += small_step;
|
|
388
399
|
}
|
|
400
|
+
frag_depth_point = max_loc_tex * u_shape;
|
|
389
401
|
gl_FragColor = applyColormap(maxval);
|
|
402
|
+
} else {
|
|
403
|
+
discard;
|
|
390
404
|
}
|
|
391
405
|
""",
|
|
392
406
|
)
|
|
393
407
|
|
|
394
408
|
_ATTENUATED_MIP_SNIPPETS = dict(
|
|
395
409
|
before_loop="""
|
|
396
|
-
float maxval =
|
|
410
|
+
float maxval = u_mip_cutoff; // The maximum encountered value
|
|
397
411
|
float sumval = 0.0; // The sum of the encountered values
|
|
398
|
-
float
|
|
399
|
-
int maxi =
|
|
400
|
-
vec3
|
|
412
|
+
float scale = 0.0; // The cumulative attenuation
|
|
413
|
+
int maxi = -1; // Where the maximum value was encountered
|
|
414
|
+
vec3 max_loc_tex = vec3(0.0); // Location where the maximum value was encountered
|
|
401
415
|
""",
|
|
402
416
|
in_loop="""
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
417
|
+
// Scale and clamp accumulation in `sumval` by contrast limits so that:
|
|
418
|
+
// * attenuation value does not depend on data values
|
|
419
|
+
// * negative values do not amplify instead of attenuate
|
|
420
|
+
sumval = sumval + clamp((val - clim.x) / (clim.y - clim.x), 0.0, 1.0);
|
|
421
|
+
scale = exp(-u_attenuation * (sumval - 1) / u_relative_step_size);
|
|
422
|
+
if( maxval > scale * clim.y ) {
|
|
423
|
+
// stop if no chance of finding a higher maxval
|
|
424
|
+
iter = nsteps;
|
|
425
|
+
} else if( val * scale > maxval ) {
|
|
426
|
+
maxval = val * scale;
|
|
407
427
|
maxi = iter;
|
|
408
|
-
|
|
428
|
+
max_loc_tex = loc;
|
|
409
429
|
}
|
|
410
430
|
""",
|
|
411
431
|
after_loop="""
|
|
412
|
-
|
|
432
|
+
if ( maxi > -1 ) {
|
|
433
|
+
frag_depth_point = max_loc_tex * u_shape;
|
|
434
|
+
gl_FragColor = applyColormap(maxval);
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
discard;
|
|
438
|
+
}
|
|
413
439
|
""",
|
|
414
440
|
)
|
|
415
441
|
|
|
416
442
|
_MINIP_SNIPPETS = dict(
|
|
417
443
|
before_loop="""
|
|
418
|
-
float minval =
|
|
444
|
+
float minval = u_minip_cutoff; // The minimum encountered value
|
|
419
445
|
int mini = -1; // Where the minimum value was encountered
|
|
420
446
|
""",
|
|
421
447
|
in_loop="""
|
|
422
|
-
if( val < minval ) {
|
|
448
|
+
if ( val < minval ) {
|
|
423
449
|
minval = val;
|
|
424
450
|
mini = iter;
|
|
451
|
+
if ( minval <= clim.x ) {
|
|
452
|
+
// stop if no chance of finding a lower minval
|
|
453
|
+
iter = nsteps;
|
|
454
|
+
}
|
|
425
455
|
}
|
|
426
456
|
""",
|
|
427
457
|
after_loop="""
|
|
428
458
|
// Refine search for min value, but only if anything was found
|
|
429
459
|
if ( mini > -1 ) {
|
|
430
|
-
|
|
460
|
+
// Calculate starting location of ray for sampling
|
|
461
|
+
vec3 start_loc_refine = start_loc + step * (float(mini) - 0.5);
|
|
462
|
+
loc = start_loc_refine;
|
|
463
|
+
|
|
464
|
+
// Variables to keep track of current value and where max was encountered
|
|
465
|
+
vec3 min_loc_tex = start_loc_refine;
|
|
466
|
+
|
|
467
|
+
vec3 small_step = step * 0.1;
|
|
431
468
|
for (int i=0; i<10; i++) {
|
|
432
|
-
|
|
433
|
-
|
|
469
|
+
float val = $get_data(loc).r;
|
|
470
|
+
if ( val < minval) {
|
|
471
|
+
minval = val;
|
|
472
|
+
min_loc_tex = start_loc_refine + (small_step * i);
|
|
473
|
+
}
|
|
474
|
+
loc += small_step;
|
|
434
475
|
}
|
|
476
|
+
frag_depth_point = min_loc_tex * u_shape;
|
|
435
477
|
gl_FragColor = applyColormap(minval);
|
|
478
|
+
} else {
|
|
479
|
+
discard;
|
|
436
480
|
}
|
|
437
481
|
""",
|
|
438
482
|
)
|
|
@@ -442,24 +486,24 @@ _TRANSLUCENT_SNIPPETS = dict(
|
|
|
442
486
|
vec4 integrated_color = vec4(0., 0., 0., 0.);
|
|
443
487
|
""",
|
|
444
488
|
in_loop="""
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
489
|
+
color = applyColormap(val);
|
|
490
|
+
float a1 = integrated_color.a;
|
|
491
|
+
float a2 = color.a * (1 - a1);
|
|
492
|
+
float alpha = max(a1 + a2, 0.001);
|
|
493
|
+
|
|
494
|
+
// Doesn't work.. GLSL optimizer bug?
|
|
495
|
+
//integrated_color = (integrated_color * a1 / alpha) +
|
|
496
|
+
// (color * a2 / alpha);
|
|
497
|
+
// This should be identical but does work correctly:
|
|
498
|
+
integrated_color *= a1 / alpha;
|
|
499
|
+
integrated_color += color * a2 / alpha;
|
|
500
|
+
|
|
501
|
+
integrated_color.a = alpha;
|
|
502
|
+
|
|
503
|
+
if( alpha > 0.99 ){
|
|
504
|
+
// stop integrating if the fragment becomes opaque
|
|
505
|
+
iter = nsteps;
|
|
506
|
+
}
|
|
463
507
|
""",
|
|
464
508
|
after_loop="""
|
|
465
509
|
gl_FragColor = integrated_color;
|
|
@@ -485,20 +529,21 @@ _ISO_SNIPPETS = dict(
|
|
|
485
529
|
vec4 color3 = vec4(0.0); // final color
|
|
486
530
|
vec3 dstep = 1.5 / u_shape; // step to sample derivative
|
|
487
531
|
gl_FragColor = vec4(0.0);
|
|
532
|
+
bool discard_fragment = true;
|
|
488
533
|
""",
|
|
489
534
|
in_loop="""
|
|
490
535
|
if (val > u_threshold-0.2) {
|
|
491
536
|
// Take the last interval in smaller steps
|
|
492
537
|
vec3 iloc = loc - step;
|
|
493
538
|
for (int i=0; i<10; i++) {
|
|
494
|
-
color = $
|
|
539
|
+
color = $get_data(iloc);
|
|
495
540
|
if (color.r > u_threshold) {
|
|
496
541
|
color = calculateColor(color, iloc, dstep);
|
|
497
542
|
gl_FragColor = applyColormap(color.r);
|
|
498
543
|
|
|
499
544
|
// set the variables for the depth buffer
|
|
500
|
-
|
|
501
|
-
|
|
545
|
+
frag_depth_point = iloc * u_shape;
|
|
546
|
+
discard_fragment = false;
|
|
502
547
|
|
|
503
548
|
iter = nsteps;
|
|
504
549
|
break;
|
|
@@ -508,12 +553,9 @@ _ISO_SNIPPETS = dict(
|
|
|
508
553
|
}
|
|
509
554
|
""",
|
|
510
555
|
after_loop="""
|
|
511
|
-
|
|
512
|
-
if (!surface_found) {
|
|
556
|
+
if (discard_fragment)
|
|
513
557
|
discard;
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
""",
|
|
558
|
+
""",
|
|
517
559
|
)
|
|
518
560
|
|
|
519
561
|
|
|
@@ -535,6 +577,19 @@ _AVG_SNIPPETS = dict(
|
|
|
535
577
|
""",
|
|
536
578
|
)
|
|
537
579
|
|
|
580
|
+
_INTERPOLATION_TEMPLATE = """
|
|
581
|
+
#include "misc/spatial-filters.frag"
|
|
582
|
+
vec4 texture_lookup_filtered(vec3 texcoord) {
|
|
583
|
+
// no need to discard out of bounds, already checked during raycasting
|
|
584
|
+
return %s($texture, $shape, texcoord);
|
|
585
|
+
}"""
|
|
586
|
+
|
|
587
|
+
_TEXTURE_LOOKUP = """
|
|
588
|
+
vec4 texture_lookup(vec3 texcoord) {
|
|
589
|
+
// no need to discard out of bounds, already checked during raycasting
|
|
590
|
+
return texture3D($texture, texcoord);
|
|
591
|
+
}"""
|
|
592
|
+
|
|
538
593
|
|
|
539
594
|
class VolumeVisual(Visual):
|
|
540
595
|
"""Displays a 3D Volume
|
|
@@ -567,8 +622,16 @@ class VolumeVisual(Visual):
|
|
|
567
622
|
gamma : float
|
|
568
623
|
Gamma to use during colormap lookup. Final color will be cmap(val**gamma).
|
|
569
624
|
by default: 1.
|
|
570
|
-
interpolation :
|
|
571
|
-
Selects method of
|
|
625
|
+
interpolation : str
|
|
626
|
+
Selects method of texture interpolation. Makes use of the two hardware
|
|
627
|
+
interpolation methods and the available interpolation methods defined
|
|
628
|
+
in vispy/gloo/glsl/misc/spatial_filters.frag
|
|
629
|
+
|
|
630
|
+
* 'nearest': Default, uses 'nearest' with Texture interpolation.
|
|
631
|
+
* 'linear': uses 'linear' with Texture interpolation.
|
|
632
|
+
* 'hanning', 'hamming', 'hermite', 'kaiser', 'quadric', 'cubic',
|
|
633
|
+
'catrom', 'mitchell', 'spline16', 'spline36', 'gaussian',
|
|
634
|
+
'bessel', 'sinc', 'lanczos', 'blackman'
|
|
572
635
|
texture_format : numpy.dtype | str | None
|
|
573
636
|
How to store data on the GPU. OpenGL allows for many different storage
|
|
574
637
|
formats and schemes for the low-level texture data stored in the GPU.
|
|
@@ -615,8 +678,6 @@ class VolumeVisual(Visual):
|
|
|
615
678
|
|
|
616
679
|
"""
|
|
617
680
|
|
|
618
|
-
_interpolation_methods = ['linear', 'nearest']
|
|
619
|
-
|
|
620
681
|
_rendering_methods = {
|
|
621
682
|
'mip': _MIP_SNIPPETS,
|
|
622
683
|
'minip': _MINIP_SNIPPETS,
|
|
@@ -637,12 +698,18 @@ class VolumeVisual(Visual):
|
|
|
637
698
|
'fragment': _FRAGMENT_SHADER,
|
|
638
699
|
}
|
|
639
700
|
|
|
701
|
+
_func_templates = {
|
|
702
|
+
'texture_lookup_interpolated': _INTERPOLATION_TEMPLATE,
|
|
703
|
+
'texture_lookup': _TEXTURE_LOOKUP,
|
|
704
|
+
}
|
|
705
|
+
|
|
640
706
|
def __init__(self, vol, clim="auto", method='mip', threshold=None,
|
|
641
707
|
attenuation=1.0, relative_step_size=0.8, cmap='grays',
|
|
642
708
|
gamma=1.0, interpolation='linear', texture_format=None,
|
|
643
709
|
raycasting_mode='volume', plane_position=None,
|
|
644
710
|
plane_normal=None, plane_thickness=1.0, clipping_planes=None,
|
|
645
|
-
clipping_planes_coord_system='scene'
|
|
711
|
+
clipping_planes_coord_system='scene', mip_cutoff=None,
|
|
712
|
+
minip_cutoff=None):
|
|
646
713
|
|
|
647
714
|
tr = ['visual', 'scene', 'document', 'canvas', 'framebuffer', 'render']
|
|
648
715
|
if clipping_planes_coord_system not in tr:
|
|
@@ -661,7 +728,19 @@ class VolumeVisual(Visual):
|
|
|
661
728
|
# Create gloo objects
|
|
662
729
|
self._vertices = VertexBuffer()
|
|
663
730
|
|
|
731
|
+
kernel, interpolation_methods = load_spatial_filters()
|
|
732
|
+
self._kerneltex = Texture2D(kernel, interpolation='nearest')
|
|
733
|
+
interpolation_methods, interpolation_fun = self._init_interpolation(
|
|
734
|
+
interpolation_methods)
|
|
735
|
+
self._interpolation_methods = interpolation_methods
|
|
736
|
+
self._interpolation_fun = interpolation_fun
|
|
664
737
|
self._interpolation = interpolation
|
|
738
|
+
if self._interpolation not in self._interpolation_methods:
|
|
739
|
+
raise ValueError("interpolation must be one of %s" %
|
|
740
|
+
', '.join(self._interpolation_methods))
|
|
741
|
+
self._data_lookup_fn = None
|
|
742
|
+
self._need_interpolation_update = True
|
|
743
|
+
|
|
665
744
|
self._texture = self._create_texture(texture_format, vol)
|
|
666
745
|
# used to store current data for later CPU-side scaling if
|
|
667
746
|
# texture_format is None
|
|
@@ -685,6 +764,8 @@ class VolumeVisual(Visual):
|
|
|
685
764
|
|
|
686
765
|
# Set params
|
|
687
766
|
self.raycasting_mode = raycasting_mode
|
|
767
|
+
self.mip_cutoff = mip_cutoff
|
|
768
|
+
self.minip_cutoff = minip_cutoff
|
|
688
769
|
self.method = method
|
|
689
770
|
self.relative_step_size = relative_step_size
|
|
690
771
|
self.threshold = threshold if threshold is not None else vol.mean()
|
|
@@ -705,17 +786,41 @@ class VolumeVisual(Visual):
|
|
|
705
786
|
|
|
706
787
|
self.freeze()
|
|
707
788
|
|
|
789
|
+
def _init_interpolation(self, interpolation_methods):
|
|
790
|
+
# create interpolation shader functions for available
|
|
791
|
+
# interpolations
|
|
792
|
+
fun = [Function(self._func_templates['texture_lookup_interpolated'] % (n + '3D'))
|
|
793
|
+
for n in interpolation_methods]
|
|
794
|
+
interpolation_methods = [n.lower() for n in interpolation_methods]
|
|
795
|
+
|
|
796
|
+
interpolation_fun = dict(zip(interpolation_methods, fun))
|
|
797
|
+
interpolation_methods = tuple(sorted(interpolation_methods))
|
|
798
|
+
|
|
799
|
+
# overwrite "nearest" and "linear" spatial-filters
|
|
800
|
+
# with "hardware" interpolation _data_lookup_fn
|
|
801
|
+
hardware_lookup = Function(self._func_templates['texture_lookup'])
|
|
802
|
+
interpolation_fun['nearest'] = hardware_lookup
|
|
803
|
+
interpolation_fun['linear'] = hardware_lookup
|
|
804
|
+
# alias bicubic to cubic (but deprecate)
|
|
805
|
+
interpolation_methods = interpolation_methods + ('bicubic',)
|
|
806
|
+
return interpolation_methods, interpolation_fun
|
|
807
|
+
|
|
708
808
|
def _create_texture(self, texture_format, data):
|
|
709
809
|
if texture_format is not None:
|
|
710
810
|
tex_cls = GPUScaledTextured3D
|
|
711
811
|
else:
|
|
712
812
|
tex_cls = CPUScaledTexture3D
|
|
713
813
|
|
|
814
|
+
if self._interpolation == 'linear':
|
|
815
|
+
texture_interpolation = 'linear'
|
|
816
|
+
else:
|
|
817
|
+
texture_interpolation = 'nearest'
|
|
818
|
+
|
|
714
819
|
# clamp_to_edge means any texture coordinates outside of 0-1 should be
|
|
715
820
|
# clamped to 0 and 1.
|
|
716
821
|
# NOTE: This doesn't actually set the data in the texture. Only
|
|
717
822
|
# creates a placeholder texture that will be resized later on.
|
|
718
|
-
return tex_cls(data, interpolation=
|
|
823
|
+
return tex_cls(data, interpolation=texture_interpolation,
|
|
719
824
|
internalformat=texture_format,
|
|
720
825
|
format='luminance',
|
|
721
826
|
wrapping='clamp_to_edge')
|
|
@@ -752,11 +857,9 @@ class VolumeVisual(Visual):
|
|
|
752
857
|
self._texture.set_clim(clim)
|
|
753
858
|
|
|
754
859
|
# Apply to texture
|
|
755
|
-
if should_cast_to_f32(vol.dtype):
|
|
756
|
-
vol = vol.astype(np.float32)
|
|
757
860
|
self._texture.check_data_format(vol)
|
|
758
861
|
self._last_data = vol
|
|
759
|
-
self._texture.scale_and_set_data(vol, copy=copy)
|
|
862
|
+
self._texture.scale_and_set_data(vol, copy=copy)
|
|
760
863
|
self.shared_program['clim'] = self._texture.clim_normalized
|
|
761
864
|
self.shared_program['u_shape'] = (vol.shape[2], vol.shape[1],
|
|
762
865
|
vol.shape[0])
|
|
@@ -767,10 +870,6 @@ class VolumeVisual(Visual):
|
|
|
767
870
|
self._need_vertex_update = True
|
|
768
871
|
self._vol_shape = shape
|
|
769
872
|
|
|
770
|
-
@property
|
|
771
|
-
def interpolation_methods(self):
|
|
772
|
-
return self._interpolation_methods
|
|
773
|
-
|
|
774
873
|
@property
|
|
775
874
|
def rendering_methods(self):
|
|
776
875
|
return list(self._rendering_methods)
|
|
@@ -828,56 +927,87 @@ class VolumeVisual(Visual):
|
|
|
828
927
|
self.update()
|
|
829
928
|
|
|
830
929
|
@property
|
|
831
|
-
def
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
Current options are:
|
|
930
|
+
def interpolation_methods(self):
|
|
931
|
+
return self._interpolation_methods
|
|
835
932
|
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
data where additional interpolation does not make sense.
|
|
840
|
-
"""
|
|
933
|
+
@property
|
|
934
|
+
def interpolation(self):
|
|
935
|
+
"""Get interpolation algorithm name."""
|
|
841
936
|
return self._interpolation
|
|
842
937
|
|
|
843
938
|
@interpolation.setter
|
|
844
|
-
def interpolation(self,
|
|
845
|
-
if
|
|
846
|
-
raise ValueError(
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
self._interpolation = interp
|
|
852
|
-
self._texture.interpolation = self._interpolation
|
|
939
|
+
def interpolation(self, i):
|
|
940
|
+
if i not in self._interpolation_methods:
|
|
941
|
+
raise ValueError("interpolation must be one of %s" %
|
|
942
|
+
', '.join(self._interpolation_methods))
|
|
943
|
+
if self._interpolation != i:
|
|
944
|
+
self._interpolation = i
|
|
945
|
+
self._need_interpolation_update = True
|
|
853
946
|
self.update()
|
|
854
947
|
|
|
948
|
+
# The interpolation code could be transferred to a dedicated filter
|
|
949
|
+
# function in visuals/filters as discussed in #1051
|
|
950
|
+
def _build_interpolation(self):
|
|
951
|
+
"""Rebuild the _data_lookup_fn for different interpolations."""
|
|
952
|
+
interpolation = self._interpolation
|
|
953
|
+
# alias bicubic to cubic
|
|
954
|
+
if interpolation == 'bicubic':
|
|
955
|
+
warnings.warn(
|
|
956
|
+
"'bicubic' interpolation is Deprecated. Use 'cubic' instead.",
|
|
957
|
+
DeprecationWarning,
|
|
958
|
+
stacklevel=2,
|
|
959
|
+
)
|
|
960
|
+
interpolation = 'cubic'
|
|
961
|
+
self._data_lookup_fn = self._interpolation_fun[interpolation]
|
|
962
|
+
try:
|
|
963
|
+
self.shared_program.frag['get_data'] = self._data_lookup_fn
|
|
964
|
+
except Exception as e:
|
|
965
|
+
print(e)
|
|
966
|
+
|
|
967
|
+
# only 'linear' uses 'linear' texture interpolation
|
|
968
|
+
if interpolation == 'linear':
|
|
969
|
+
texture_interpolation = 'linear'
|
|
970
|
+
else:
|
|
971
|
+
# 'nearest' (and also 'linear') doesn't use spatial_filters.frag
|
|
972
|
+
# so u_kernel and shape setting is skipped
|
|
973
|
+
texture_interpolation = 'nearest'
|
|
974
|
+
if interpolation != 'nearest':
|
|
975
|
+
self.shared_program['u_kernel'] = self._kerneltex
|
|
976
|
+
self._data_lookup_fn['shape'] = self._last_data.shape[:3][::-1]
|
|
977
|
+
|
|
978
|
+
if self._texture.interpolation != texture_interpolation:
|
|
979
|
+
self._texture.interpolation = texture_interpolation
|
|
980
|
+
|
|
981
|
+
self._data_lookup_fn['texture'] = self._texture
|
|
982
|
+
|
|
983
|
+
self._need_interpolation_update = False
|
|
984
|
+
|
|
855
985
|
@staticmethod
|
|
856
986
|
@lru_cache(maxsize=10)
|
|
857
|
-
def
|
|
987
|
+
def _build_clipping_planes_glsl(n_planes: int) -> str:
|
|
858
988
|
"""Build the code snippet used to clip the volume based on self.clipping_planes."""
|
|
859
989
|
func_template = '''
|
|
860
990
|
float clip_planes(vec3 loc, vec3 vol_shape) {{
|
|
861
|
-
vec3 loc_transf = $clip_transform(vec4(loc, 1)).xyz;
|
|
862
|
-
float distance_from_clip =
|
|
991
|
+
vec3 loc_transf = $clip_transform(vec4(loc * vol_shape, 1)).xyz;
|
|
992
|
+
float distance_from_clip = 3.4e38; // max float
|
|
863
993
|
{clips};
|
|
864
994
|
return distance_from_clip;
|
|
865
995
|
}}
|
|
866
996
|
'''
|
|
867
997
|
# the vertex is considered clipped if on the "negative" side of the plane
|
|
868
998
|
clip_template = '''
|
|
869
|
-
vec3 relative_vec{idx} = loc_transf -
|
|
870
|
-
float distance_from_clip{idx} = dot(relative_vec{idx},
|
|
999
|
+
vec3 relative_vec{idx} = loc_transf - $clipping_plane_pos{idx};
|
|
1000
|
+
float distance_from_clip{idx} = dot(relative_vec{idx}, $clipping_plane_norm{idx});
|
|
871
1001
|
distance_from_clip = min(distance_from_clip{idx}, distance_from_clip);
|
|
872
1002
|
'''
|
|
873
1003
|
all_clips = []
|
|
874
1004
|
for idx in range(n_planes):
|
|
875
1005
|
all_clips.append(clip_template.format(idx=idx))
|
|
876
1006
|
formatted_code = func_template.format(clips=''.join(all_clips))
|
|
877
|
-
return
|
|
1007
|
+
return formatted_code
|
|
878
1008
|
|
|
879
1009
|
@property
|
|
880
|
-
def clipping_planes(self):
|
|
1010
|
+
def clipping_planes(self) -> np.ndarray:
|
|
881
1011
|
"""The set of planes used to clip the volume. Values on the negative side of the normal are discarded.
|
|
882
1012
|
|
|
883
1013
|
Each plane is defined by a position and a normal vector (magnitude is irrelevant). Shape: (n_planes, 2, 3).
|
|
@@ -895,12 +1025,12 @@ class VolumeVisual(Visual):
|
|
|
895
1025
|
return self._clipping_planes
|
|
896
1026
|
|
|
897
1027
|
@clipping_planes.setter
|
|
898
|
-
def clipping_planes(self, value):
|
|
1028
|
+
def clipping_planes(self, value: Optional[np.ndarray]):
|
|
899
1029
|
if value is None:
|
|
900
1030
|
value = np.empty([0, 2, 3])
|
|
901
1031
|
self._clipping_planes = value
|
|
902
1032
|
|
|
903
|
-
self._clip_func = self.
|
|
1033
|
+
self._clip_func = Function(self._build_clipping_planes_glsl(len(value)))
|
|
904
1034
|
self.shared_program.frag['clip_with_planes'] = self._clip_func
|
|
905
1035
|
|
|
906
1036
|
self._clip_func['clip_transform'] = self._clip_transform
|
|
@@ -910,7 +1040,7 @@ class VolumeVisual(Visual):
|
|
|
910
1040
|
self.update()
|
|
911
1041
|
|
|
912
1042
|
@property
|
|
913
|
-
def clipping_planes_coord_system(self):
|
|
1043
|
+
def clipping_planes_coord_system(self) -> str:
|
|
914
1044
|
"""
|
|
915
1045
|
Coordinate system used by the clipping planes (see visuals.transforms.transform_system.py)
|
|
916
1046
|
"""
|
|
@@ -962,17 +1092,19 @@ class VolumeVisual(Visual):
|
|
|
962
1092
|
(known_methods, method))
|
|
963
1093
|
self._method = method
|
|
964
1094
|
|
|
965
|
-
# $
|
|
1095
|
+
# $get_data needs to be unset and re-set, since it's present inside the snippets.
|
|
966
1096
|
# Program should probably be able to do this automatically
|
|
967
|
-
self.shared_program.frag['
|
|
1097
|
+
self.shared_program.frag['get_data'] = None
|
|
968
1098
|
self.shared_program.frag['raycasting_setup'] = self._raycasting_setup_snippet
|
|
969
1099
|
self.shared_program.frag['before_loop'] = self._before_loop_snippet
|
|
970
1100
|
self.shared_program.frag['in_loop'] = self._in_loop_snippet
|
|
971
1101
|
self.shared_program.frag['after_loop'] = self._after_loop_snippet
|
|
972
1102
|
self.shared_program.frag['sampler_type'] = self._texture.glsl_sampler_type
|
|
973
|
-
self.shared_program.frag['sample'] = self._texture.glsl_sample
|
|
974
1103
|
self.shared_program.frag['cmap'] = Function(self._cmap.glsl_map)
|
|
975
1104
|
self.shared_program['texture2D_LUT'] = self.cmap.texture_lut()
|
|
1105
|
+
self.shared_program['u_mip_cutoff'] = self._mip_cutoff
|
|
1106
|
+
self.shared_program['u_minip_cutoff'] = self._minip_cutoff
|
|
1107
|
+
self._need_interpolation_update = True
|
|
976
1108
|
self.update()
|
|
977
1109
|
|
|
978
1110
|
@property
|
|
@@ -1096,6 +1228,40 @@ class VolumeVisual(Visual):
|
|
|
1096
1228
|
self.shared_program['u_plane_thickness'] = value
|
|
1097
1229
|
self.update()
|
|
1098
1230
|
|
|
1231
|
+
@property
|
|
1232
|
+
def mip_cutoff(self):
|
|
1233
|
+
"""The lower cutoff value for `mip` and `attenuated_mip`.
|
|
1234
|
+
|
|
1235
|
+
When using the `mip` or `attenuated_mip` rendering methods, fragments
|
|
1236
|
+
with values below the cutoff will be discarded.
|
|
1237
|
+
"""
|
|
1238
|
+
return self._mip_cutoff
|
|
1239
|
+
|
|
1240
|
+
@mip_cutoff.setter
|
|
1241
|
+
def mip_cutoff(self, value):
|
|
1242
|
+
if value is None:
|
|
1243
|
+
value = np.finfo('float32').min
|
|
1244
|
+
self._mip_cutoff = float(value)
|
|
1245
|
+
self.shared_program['u_mip_cutoff'] = self._mip_cutoff
|
|
1246
|
+
self.update()
|
|
1247
|
+
|
|
1248
|
+
@property
|
|
1249
|
+
def minip_cutoff(self):
|
|
1250
|
+
"""The upper cutoff value for `minip`.
|
|
1251
|
+
|
|
1252
|
+
When using the `minip` rendering method, fragments
|
|
1253
|
+
with values above the cutoff will be discarded.
|
|
1254
|
+
"""
|
|
1255
|
+
return self._minip_cutoff
|
|
1256
|
+
|
|
1257
|
+
@minip_cutoff.setter
|
|
1258
|
+
def minip_cutoff(self, value):
|
|
1259
|
+
if value is None:
|
|
1260
|
+
value = np.finfo('float32').max
|
|
1261
|
+
self._minip_cutoff = float(value)
|
|
1262
|
+
self.shared_program['u_minip_cutoff'] = self._minip_cutoff
|
|
1263
|
+
self.update()
|
|
1264
|
+
|
|
1099
1265
|
def _create_vertex_data(self):
|
|
1100
1266
|
"""Create and set positions and texture coords from the given shape
|
|
1101
1267
|
|
|
@@ -1164,3 +1330,6 @@ class VolumeVisual(Visual):
|
|
|
1164
1330
|
def _prepare_draw(self, view):
|
|
1165
1331
|
if self._need_vertex_update:
|
|
1166
1332
|
self._create_vertex_data()
|
|
1333
|
+
|
|
1334
|
+
if self._need_interpolation_update:
|
|
1335
|
+
self._build_interpolation()
|