Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
2eeea6e
add volume graphic, basic stuff works
kushalkolar Apr 12, 2025
874cd3c
remove a useless file I accidentally added
kushalkolar Apr 12, 2025
b2b9b10
black
kushalkolar Apr 12, 2025
6feec31
add volume movie
kushalkolar Apr 12, 2025
ac76f6a
linear interpolation is better
kushalkolar Apr 13, 2025
1e61b64
merge conflict
kushalkolar Jun 4, 2025
4e616bb
cmap shown for volume image in hlut tooL
kushalkolar Jun 4, 2025
1ed2794
volume render modes work
kushalkolar Jun 4, 2025
b0f26ff
example works
kushalkolar Jun 4, 2025
9d059c7
some updates to ImageGraphic for sharing buffesr
kushalkolar Jun 5, 2025
d27cd31
update volume and add examples
kushalkolar Jun 5, 2025
98a09f7
update example
kushalkolar Jun 5, 2025
61be715
update add_graphics mixin
kushalkolar Jun 5, 2025
b6e29cd
bah
kushalkolar Jun 5, 2025
36d5abb
separate TextureArrayVolume for volumes, fix indexing for tiling volumes
kushalkolar Aug 17, 2025
e3229e0
remove dim kwarg
kushalkolar Aug 17, 2025
f88d2af
update interpolation
kushalkolar Aug 17, 2025
3c865f9
tests for TextureArrayVolume WIP
kushalkolar Aug 17, 2025
312108b
remove print
kushalkolar Aug 17, 2025
df92287
fix example
kushalkolar Sep 26, 2025
76a92f3
add example
kushalkolar Sep 26, 2025
3c9a6d8
modify lfs pointer see if this works
kushalkolar Sep 27, 2025
d2618c6
merge conflict
kushalkolar Sep 27, 2025
a95627d
better alpha handling
kushalkolar Sep 27, 2025
b3af4a5
add example to tests
kushalkolar Sep 27, 2025
ae501b6
correct docs
kushalkolar Sep 27, 2025
a20e4fb
add zarr multi channel example
kushalkolar Sep 27, 2025
568785b
remove debug stuf
kushalkolar Sep 27, 2025
23e0475
volume graphic tests
kushalkolar Sep 28, 2025
6c1234b
fixes
kushalkolar Sep 28, 2025
041787e
volume image graphci features
kushalkolar Sep 28, 2025
00d20c5
allow setting only vmin or vmax for images
kushalkolar Sep 28, 2025
f6203f4
black
kushalkolar Sep 28, 2025
177a126
update example
kushalkolar Sep 28, 2025
278d76b
update graphic methods mixin
kushalkolar Sep 28, 2025
25562b6
fix
kushalkolar Sep 28, 2025
7bac111
upate example
kushalkolar Sep 28, 2025
26dfd05
better way to detect images
kushalkolar Sep 28, 2025
599c6dd
texture array for volume image is 3D
kushalkolar Sep 28, 2025
e9b7db9
fix test
kushalkolar Sep 28, 2025
8698c3e
rename to figure so pygfx gallery scraper picks it up
kushalkolar Sep 28, 2025
e23bf44
allow longer time for actions, I guess volumes take a while
kushalkolar Sep 28, 2025
5095612
rename
kushalkolar Sep 28, 2025
c2f2923
update screenshots
kushalkolar Sep 28, 2025
0566f1b
for some reason using black via python gives different results than b…
kushalkolar Sep 28, 2025
5de4a23
add ImageVolume api docs
kushalkolar Sep 28, 2025
9b57677
replace image_widget_grid yet again...
kushalkolar Sep 28, 2025
78b4e96
forgot to commit event tables diff
kushalkolar Sep 28, 2025
616a223
hlut tool can manage multiple ImageGraphics or ImageVolumeGraphics
kushalkolar Sep 30, 2025
c8e44f6
remove property that's no longer part of the API
kushalkolar Sep 30, 2025
e9c9a7a
docs
kushalkolar Sep 30, 2025
39344c3
docstring fix
kushalkolar Sep 30, 2025
dbe5056
better text on hlut
kushalkolar Sep 30, 2025
8ff0f7d
black
kushalkolar Sep 30, 2025
d92b24a
update api docs
kushalkolar Sep 30, 2025
201532a
update CI timeouts because of new downloaded volume image
kushalkolar Oct 1, 2025
ce4570b
update screenshots because hlut line is more gray
kushalkolar Oct 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
add volume graphic, basic stuff works
  • Loading branch information
kushalkolar committed Apr 12, 2025
commit 2eeea6ec450f1f41a0b3217c34dc33bc71761db2
1 change: 1 addition & 0 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"subsection_order": ExplicitOrder(
[
"../../examples/image",
"../../examples/image_volume",
"../../examples/heatmap",
"../../examples/image_widget",
"../../examples/gridplot",
Expand Down
2 changes: 2 additions & 0 deletions examples/image_volume/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Image Volume Examples
=====================
24 changes: 24 additions & 0 deletions examples/image_volume/image_volume_ray.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""
Volume Ray mode
===============

View a volume, uses the fly controller by default so you can fly around the scene using WASD keys and the mouse:
https://docs.pygfx.org/stable/_autosummary/controllers/pygfx.controllers.FlyController.html#pygfx.controllers.FlyController
"""

# test_example = false
# sphinx_gallery_pygfx_docs = 'screenshot'

import numpy as np
import fastplotlib as fpl
import imageio.v3 as iio

voldata = iio.imread("imageio:stent.npz").astype(np.float32)

fig = fpl.Figure(cameras="3d", size=(700, 560))

fig[0, 0].add_image_volume(voldata)

fig.show()

fpl.loop.run()
1 change: 1 addition & 0 deletions examples/tests/testutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
# examples live in themed sub-folders
example_globs = [
"image/*.py",
"image_volume/*.py",
"image_widget/*.py",
"heatmap/*.py",
"scatter/*.py",
Expand Down
2 changes: 2 additions & 0 deletions fastplotlib/graphics/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from .line import LineGraphic
from .scatter import ScatterGraphic
from .image import ImageGraphic
from .image_volume import ImageVolumeGraphic
from .text import TextGraphic
from .line_collection import LineCollection, LineStack

Expand All @@ -10,6 +11,7 @@
"LineGraphic",
"ScatterGraphic",
"ImageGraphic",
"ImageVolumeGraphic",
"TextGraphic",
"LineCollection",
"LineStack",
Expand Down
7 changes: 0 additions & 7 deletions fastplotlib/graphics/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,6 @@ class Graphic:
_features: dict[str, type] = dict()

def __init_subclass__(cls, **kwargs):
# set the type of the graphic in lower case like "image", "line_collection", etc.
cls.type = (
cls.__name__.lower()
.replace("graphic", "")
.replace("collection", "_collection")
.replace("stack", "_stack")
)

# set of all features
cls._features = {
Expand Down
85 changes: 68 additions & 17 deletions fastplotlib/graphics/features/_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,12 @@
)


# manages an array of 8192x8192 Textures representing chunks of an image
class TextureArray(GraphicFeature):
"""
Manages an array of Textures representing chunks of an image.

Creates multiple pygfx.Texture objects based on the GPU's max texture dimension limit.
"""
event_info_spec = [
{
"dict key": "key",
Expand All @@ -28,13 +32,30 @@ class TextureArray(GraphicFeature):
},
]

def __init__(self, data, isolated_buffer: bool = True):
def __init__(self, data, dim: int, isolated_buffer: bool = True):
"""

Parameters
----------
dim: int, 2 | 3
whether the data array represents a 2D or 3D texture

"""
if dim not in (2, 3):
raise ValueError("`dim` must be 2 | 3")

self._dim = dim

super().__init__()

data = self._fix_data(data)

shared = pygfx.renderers.wgpu.get_shared()
self._texture_limit_2d = shared.device.limits["max-texture-dimension-2d"]

if self._dim == 2:
self._texture_size_limit = shared.device.limits["max-texture-dimension-2d"]
else:
self._texture_size_limit = shared.device.limits["max-texture-dimension-3d"]

if isolated_buffer:
# useful if data is read-only, example: memmaps
Expand All @@ -47,26 +68,38 @@ def __init__(self, data, isolated_buffer: bool = True):
# data start indices for each Texture
self._row_indices = np.arange(
0,
ceil(self.value.shape[0] / self._texture_limit_2d) * self._texture_limit_2d,
self._texture_limit_2d,
ceil(self.value.shape[0] / self._texture_size_limit) * self._texture_size_limit,
self._texture_size_limit,
)
self._col_indices = np.arange(
0,
ceil(self.value.shape[1] / self._texture_limit_2d) * self._texture_limit_2d,
self._texture_limit_2d,
ceil(self.value.shape[1] / self._texture_size_limit) * self._texture_size_limit,
self._texture_size_limit,
)

shape = [self.row_indices.size, self.col_indices.size]

if self._dim == 3:
self._zdim_indices = np.arange(
0,
ceil(self.value.shape[2] / self._texture_size_limit) * self._texture_size_limit,
self._texture_size_limit,
)
shape += [self.zdim_indices.size]
else:
self._zdim_indices = np.empty(0)

# buffer will be an array of textures
self._buffer: np.ndarray[pygfx.Texture] = np.empty(
shape=(self.row_indices.size, self.col_indices.size), dtype=object
shape=shape, dtype=object
)

self._iter = None

# iterate through each chunk of passed `data`
# create a pygfx.Texture from this chunk
for _, buffer_index, data_slice in self:
texture = pygfx.Texture(self.value[data_slice], dim=2)
texture = pygfx.Texture(self.value[data_slice], dim=self._dim)

self.buffer[buffer_index] = texture

Expand Down Expand Up @@ -99,6 +132,10 @@ def col_indices(self) -> np.ndarray:
"""
return self._col_indices

@property
def zdim_indices(self) -> np.ndarray:
return self._zdim_indices

@property
def shared(self) -> int:
return self._shared
Expand All @@ -114,7 +151,11 @@ def _fix_data(self, data):
return data.astype(np.float32)

def __iter__(self):
self._iter = product(enumerate(self.row_indices), enumerate(self.col_indices))
if self._dim == 2:
self._iter = product(enumerate(self.row_indices), enumerate(self.col_indices))
elif self._dim == 3:
self._iter = product(enumerate(self.row_indices), enumerate(self.col_indices), enumerate(self.zdim_indices))

return self

def __next__(self) -> tuple[pygfx.Texture, tuple[int, int], tuple[slice, slice]]:
Expand All @@ -128,22 +169,32 @@ def __next__(self) -> tuple[pygfx.Texture, tuple[int, int], tuple[slice, slice]]
| tuple[int, int]: chunk index, i.e corresponding index of ``self.buffer`` array
| tuple[slice, slice]: data slice of big array in this chunk and Texture
"""
(chunk_row, data_row_start), (chunk_col, data_col_start) = next(self._iter)
if self._dim == 2:
(chunk_row, data_row_start), (chunk_col, data_col_start) = next(self._iter)
elif self._dim == 3:
(chunk_row, data_row_start), (chunk_col, data_col_start), (chunk_z, data_z_start) = next(self._iter)

# indices for to self.buffer for this chunk
chunk_index = (chunk_row, chunk_col)
chunk_index = [chunk_row, chunk_col]

if self._dim == 3:
chunk_index += [chunk_z]

# stop indices of big data array for this chunk
row_stop = min(self.value.shape[0], data_row_start + self._texture_limit_2d)
col_stop = min(self.value.shape[1], data_col_start + self._texture_limit_2d)
row_stop = min(self.value.shape[0], data_row_start + self._texture_size_limit)
col_stop = min(self.value.shape[1], data_col_start + self._texture_size_limit)
if self._dim == 3:
z_stop = min(self.value.shape[2], data_z_start + self._texture_size_limit)

# row and column slices that slice the data for this chunk from the big data array
data_slice = (slice(data_row_start, row_stop), slice(data_col_start, col_stop))
data_slice = [slice(data_row_start, row_stop), slice(data_col_start, col_stop)]
if self._dim == 3:
data_slice += [slice(data_z_start, z_stop)]

# texture for this chunk
texture = self.buffer[chunk_index]
texture = self.buffer[tuple(chunk_index)]

return texture, chunk_index, data_slice
return texture, chunk_index, tuple(data_slice)

def __getitem__(self, item):
return self.value[item]
Expand Down
Loading