K3D-tools / K3D-jupyter

K3D lets you create 3D plots backed by WebGL with high-level API (surfaces, isosurfaces, voxels, mesh, cloud points, vtk objects, volume renderer, colormaps, etc). The primary aim of K3D-jupyter is to be easy for use as stand alone package like matplotlib, but also to allow interoperation with existing libraries as VTK.
MIT License
916 stars 123 forks source link

voxel alignment should be user-controllable #453

Open kretes opened 3 weeks ago

kretes commented 3 weeks ago

Description

Currently k3d is rendering Sparse volume as voxels that are centered around the center of the voxel coordinate. This should at least be up to the user to display Sparse with voxel being aligned to a corner.

Here is what I get for a dummy data that I intend to be plotted as the same volume:

from chunky3d import Sparse
from chunky3d.k3d_connector import get_k3d_obj
import k3d
import numpy as np
sp1 = Sparse((3,3,3), spacing=(1,1,1))
sp01 = Sparse((30,30,30), spacing=(0.1,0.1,0.1))
sp1[1, 1, 1] = 1
sp01[10:20,10:20,10:20] = 1

plot with:

vox1 = get_k3d_obj(sp1)
vox01 = get_k3d_obj(sp01)
vox1.name = "1"
vox1.color_map = k3d.nice_colors[0]
vox1.outlines=False
vox01.name = "01"
vox01.color_map = k3d.nice_colors[1]
vox01.outlines=False

plot = k3d.plot()
plot += vox1
plot += vox01
plot

and I get:

image

This comes from the get_voxel_bounds method from Sparse: https://github.com/K3D-tools/chunky3d/blob/d902a5f1338e0b3b612b8be6fef91310a4520fb2/chunky3d/chunky.py#L1177

sp1.get_voxels_bounds(), sp01.get_voxels_bounds()
# ([-0.5, 2.5, -0.5, 2.5, -0.5, 2.5], [-0.05, 2.95, -0.05, 2.95, -0.05, 2.95])

If I implement a method that aligns voxel corner with the origin:

def get_sparse_bounds_with_voxel_corner_in_origin(sparse):
    return [
        sparse.origin[0],
        sparse.origin[0] + sparse.spacing[0] * (sparse._shape[2]),
        sparse.origin[1],
        sparse.origin[1] + sparse.spacing[1] * (sparse._shape[1]),
        sparse.origin[2],
        sparse.origin[2] + sparse.spacing[2] * (sparse._shape[0]),
    ]

get_sparse_bounds_with_voxel_corner_in_origin(sp1), get_sparse_bounds_with_voxel_corner_in_origin(sp01)
# ([0, 3, 0, 3, 0, 3], [0, 3.0, 0, 3.0, 0, 3.0])

And use it when plotting:

def get_k3d_obj_voxel_corner_in_origin(sparse):
    return k3d.voxels_group(
        np.array(sparse.shape, dtype=np.uint32)[::-1],
        voxels_group=sparse.get_k3d_voxels_group_dict(),
        bounds=get_sparse_bounds_with_voxel_corner_in_origin(sparse), # not voxel_bounds
        # bounds=sparse.get_voxels_bounds(),
        outlines=False,
        color_map=k3d.nice_colors[0:-2] * 50,
    )

vox1 = get_k3d_obj_voxel_corner_in_origin(sp1)
vox1.name = "1"
vox1.opacity = 0.5
vox1.color_map = k3d.nice_colors[0]
vox01 = get_k3d_obj_voxel_corner_in_origin(sp01)
vox01.name = "01"
vox01.opacity = 0.5
vox01.color_map = k3d.nice_colors[1]

plot = k3d.plot()
plot += vox1
plot += vox01
plot

I get what I intend to:

image

So the issue is about the ability to pick the way data should be interpreted - I think that this should be achievable by configuring k3d to do so, without modification of the k3d codebase.

artur-trzesiok commented 2 days ago

Hi @kretes !

K3D is consistent here. Please look at https://k3d-jupyter.org/reference/factory.voxels_group.html

By default, the voxels are a grid inscribed in the -0.5 < x, y, z < 0.5 cube regardless of the passed voxel array shape, like aspect ratio.

That is true for k3d.voxel, k3d.surface k3d.marching_cubes etc. So it is consistent. Object with "sampling" by default are in world-space cube -0.5 to 0.5. To keep aspect ratio we can pass bounds / model_matrix. So from my point of view your case is more related to chunky3d. @tgandor can you look at it? To be specific - at implementation of https://github.com/K3D-tools/chunky3d/blob/d902a5f1338e0b3b612b8be6fef91310a4520fb2/chunky3d/chunky.py#L1177

artur-trzesiok commented 2 days ago

@tgandor get_k3d_obj by default use get_voxels_bounds instead of get_bounds. That confused @kretes i think.

And I agree that helper (get_k3d_obj) didnt provide any control over it.