Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

add ImageVolumeGraphic #791

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
Loading
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions 1 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 2 examples/image_volume/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Image Volume Examples
=====================
83 changes: 83 additions & 0 deletions 83 examples/image_volume/image_volume_4d.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""
Volume movie
============

"""

# test_example = false
# sphinx_gallery_pygfx_docs = 'screenshot'

import numpy as np
from scipy.ndimage import gaussian_filter
import fastplotlib as fpl
from tqdm import tqdm


def gen_data(p=1, noise=.5, T=256, framerate=30, firerate=2.,):
if p == 2:
gamma = np.array([1.5, -.55])
elif p == 1:
gamma = np.array([.9])
else:
raise
dims = (128, 128, 30) # size of image
sig = (4, 4, 2) # neurons size
bkgrd = 10
N = 150 # number of neurons
np.random.seed(0)
centers = np.asarray([[np.random.randint(s, x - s)
for x, s in zip(dims, sig)] for i in range(N)])
Y = np.zeros((T,) + dims, dtype=np.float32)
trueSpikes = np.random.rand(N, T) < firerate / float(framerate)
trueSpikes[:, 0] = 0
truth = trueSpikes.astype(np.float32)
for i in tqdm(range(2, T)):
if p == 2:
truth[:, i] += gamma[0] * truth[:, i - 1] + gamma[1] * truth[:, i - 2]
else:
truth[:, i] += gamma[0] * truth[:, i - 1]
for i in tqdm(range(N)):
Y[:, centers[i, 0], centers[i, 1], centers[i, 2]] = truth[i]
tmp = np.zeros(dims)
tmp[tuple(np.array(dims)//2)] = 1.
print("gaussing filtering")
z = np.linalg.norm(gaussian_filter(tmp, sig).ravel())

print("finishing")
Y = bkgrd + noise * np.random.randn(*Y.shape) + 10 * gaussian_filter(Y, (0,) + sig) / z

return Y


voldata = gen_data()

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

vmin, vmax = fpl.utils.quick_min_max(voldata)

volume = fig[0, 0].add_image_volume(voldata[0], vmin=vmin, vmax=vmax, interpolation="linear", cmap="gnuplot2")

hlut = fpl.HistogramLUTTool(voldata, volume)

fig[0, 0].docks["right"].size = 100
fig[0, 0].docks["right"].controller.enabled = False
fig[0, 0].docks["right"].add_graphic(hlut)
fig[0, 0].docks["right"].auto_scale(maintain_aspect=False)

fig.show()


i = 0
def update():
global i

volume.data = voldata[i]

i += 1
if i == voldata.shape[0]:
i = 0


fig.add_animations(update)

fpl.loop.run()
24 changes: 24 additions & 0 deletions 24 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 1 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 2 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 7 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
101 changes: 82 additions & 19 deletions 101 fastplotlib/graphics/features/_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,13 @@
)


# 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 +33,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 +69,39 @@ 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
)
self._buffer: np.ndarray[pygfx.Texture] = np.empty(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 +134,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 +153,17 @@ 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 +177,36 @@ 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
10 changes: 5 additions & 5 deletions 10 fastplotlib/graphics/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,10 @@ def __init__(
| shape must be ``[n_rows, n_cols]``, ``[n_rows, n_cols, 3]`` for RGB or ``[n_rows, n_cols, 4]`` for RGBA

vmin: int, optional
minimum value for color scaling, calculated from data if not provided
minimum value for color scaling, estimated from data if not provided

vmax: int, optional
maximum value for color scaling, calculated from data if not provided
maximum value for color scaling, estimated from data if not provided

cmap: str, optional, default "plasma"
colormap to use to display the data
Expand All @@ -129,8 +129,8 @@ def __init__(

world_object = pygfx.Group()

# texture array that manages the textures on the GPU for displaying this image
self._data = TextureArray(data, isolated_buffer=isolated_buffer)
# texture array that manages the multiple textures on the GPU that represent this image
self._data = TextureArray(data, dim=2, isolated_buffer=isolated_buffer)

if (vmin is None) or (vmax is None):
vmin, vmax = quick_min_max(data)
Expand Down Expand Up @@ -165,7 +165,7 @@ def __init__(
)

# iterate through each texture chunk and create
# an _ImageTIle, offset the tile using the data indices
# an _ImageTile, offset the tile using the data indices
for texture, chunk_index, data_slice in self._data:

# create an ImageTile using the texture for this chunk
Expand Down
Loading
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.