Skip to content

Commit

Permalink
Merge pull request #88 from kushalkolar/graphic-refactor
Browse files Browse the repository at this point in the history
graphics refactor, again
  • Loading branch information
kushalkolar committed Dec 25, 2022
2 parents 85a23f1 + 7a7797a commit cbff3f4
Show file tree
Hide file tree
Showing 15 changed files with 476 additions and 249 deletions.
30 changes: 16 additions & 14 deletions examples/gridplot.ipynb

Large diffs are not rendered by default.

202 changes: 123 additions & 79 deletions examples/simple.ipynb

Large diffs are not rendered by default.

61 changes: 18 additions & 43 deletions fastplotlib/graphics/_base.py
Original file line number Diff line number Diff line change
@@ -1,63 +1,33 @@
from typing import *

import pygfx
from pygfx import WorldObject
from pygfx.linalg import Vector3

from ..utils import get_colors
from .features import GraphicFeature, DataFeature, ColorFeature, PresentFeature
from .features import GraphicFeature, PresentFeature


class Graphic:
class BaseGraphic:
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")
super().__init_subclass__(**kwargs)


class Graphic(BaseGraphic):
def __init__(
self,
data,
colors: Any = False,
n_colors: int = None,
cmap: str = None,
alpha: float = 1.0,
name: str = None
):
"""
Parameters
----------
data: array-like
data to show in the graphic, must be float32.
Automatically converted to float32 for numpy arrays.
Tensorflow Tensors also work but this is not fully
tested and might not be supported in the future.
colors: Any
if ``False``, no color generation is performed, cmap is also ignored.
n_colors
cmap: str
name of colormap to use
alpha: float, optional
alpha value for the colors
name: str, optional
name this graphic, makes it indexable within plots
"""
# self.data = data.astype(np.float32)
self.data = DataFeature(parent=self, data=data, graphic_name=self.__class__.__name__)
self.colors = None

self.name = name

if n_colors is None:
n_colors = self.data.feature_data.shape[0]

if cmap is not None and colors is not False:
colors = get_colors(n_colors=n_colors, cmap=cmap, alpha=alpha)

if colors is not False:
self.colors = ColorFeature(parent=self, colors=colors, n_colors=n_colors, alpha=alpha)

# different from visible, toggles the Graphic presence in the Scene
# useful for bbox calculations to ignore these Graphics
self.present = PresentFeature(parent=self)

valid_features = ["visible"]
Expand All @@ -69,9 +39,14 @@ def __init__(
self._valid_features = tuple(valid_features)

@property
def world_object(self) -> pygfx.WorldObject:
def world_object(self) -> WorldObject:
return self._world_object

@property
def position(self) -> Vector3:
"""The position of the graphic"""
return self.world_object.position

@property
def interact_features(self) -> Tuple[str]:
"""The features for this ``Graphic`` that support interaction."""
Expand All @@ -87,7 +62,7 @@ def visible(self, v):
self.world_object.visible = v

@property
def children(self) -> pygfx.WorldObject:
def children(self) -> WorldObject:
return self.world_object.children

def __setattr__(self, key, value):
Expand Down
4 changes: 2 additions & 2 deletions fastplotlib/graphics/features/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from ._colors import ColorFeature
from ._data import DataFeature
from ._colors import ColorFeature, CmapFeature, ImageCmapFeature
from ._data import PointsDataFeature, ImageDataFeature
from ._present import PresentFeature
from ._base import GraphicFeature
20 changes: 17 additions & 3 deletions fastplotlib/graphics/features/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,22 @@ def _call_event_handlers(self, event_data: FeatureEvent):


def cleanup_slice(key: Union[int, slice], upper_bound) -> Union[slice, int]:
"""
If the key in an `int`, it just returns it. Otherwise,
it parses it and removes the `None` vals and replaces
them with corresponding values that can be used to
create a `range`, get `len` etc.
Parameters
----------
key
upper_bound
Returns
-------
"""
if isinstance(key, int):
return key

Expand Down Expand Up @@ -157,7 +173,7 @@ def _upper_bound(self) -> int:
return self.feature_data.shape[0]

def _update_range_indices(self, key):
"""Currently used by colors and data"""
"""Currently used by colors and positions data"""
key = cleanup_slice(key, self._upper_bound)

if isinstance(key, int):
Expand All @@ -178,5 +194,3 @@ def _update_range_indices(self, key):
self._buffer.update_range(ix, size=1)
else:
raise TypeError("must pass int or slice to update range")


64 changes: 61 additions & 3 deletions fastplotlib/graphics/features/_colors.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import numpy as np

from ._base import GraphicFeatureIndexable, cleanup_slice, FeatureEvent
from ._base import GraphicFeature, GraphicFeatureIndexable, cleanup_slice, FeatureEvent
from ...utils import get_colors, get_cmap_texture
from pygfx import Color


Expand All @@ -15,7 +16,7 @@ def __getitem__(self, item):
def __repr__(self):
return repr(self._buffer.data)

def __init__(self, parent, colors, n_colors, alpha: float = 1.0):
def __init__(self, parent, colors, n_colors: int, alpha: float = 1.0):
"""
ColorFeature
Expand All @@ -27,7 +28,12 @@ def __init__(self, parent, colors, n_colors, alpha: float = 1.0):
specify colors as a single human readable string, RGBA array,
or an iterable of strings or RGBA arrays
n_colors: number of colors to hold, if passing in a single str or single RGBA array
n_colors: int
number of colors to hold, if passing in a single str or single RGBA array
alpha: float
alpha value for the colors
"""
# if provided as a numpy array of str
if isinstance(colors, np.ndarray):
Expand Down Expand Up @@ -185,3 +191,55 @@ def _feature_changed(self, key, new_data):
event_data = FeatureEvent(type="color-changed", pick_info=pick_info)

self._call_event_handlers(event_data)


class CmapFeature(ColorFeature):
"""
Indexable colormap feature, mostly wraps colors and just provides a way to set colormaps.
"""
def __init__(self, parent, colors):
super(ColorFeature, self).__init__(parent, colors)

def __setitem__(self, key, value):
key = cleanup_slice(key, self._upper_bound)
if not isinstance(key, slice):
raise TypeError("Cannot set cmap on single indices, must pass a slice object or "
"set it on the entire data.")

n_colors = len(range(key.start, key.stop, key.step))

colors = get_colors(n_colors, cmap=value)
super(CmapFeature, self).__setitem__(key, colors)


class ImageCmapFeature(GraphicFeature):
"""
Colormap for ImageGraphic
"""
def __init__(self, parent, cmap: str):
cmap_texture_view = get_cmap_texture(cmap)
super(ImageCmapFeature, self).__init__(parent, cmap_texture_view)
self.name = cmap

def _set(self, cmap_name: str):
self._parent.world_object.material.map.texture.data[:] = get_colors(256, cmap_name)
self._parent.world_object.material.map.texture.update_range((0, 0, 0), size=(256, 1, 1))
self.name = cmap_name

self._feature_changed(key=None, new_data=self.name)

def __repr__(self):
return repr(self.name)

def _feature_changed(self, key, new_data):
# this is a non-indexable feature so key=None

pick_info = {
"index": None,
"world_object": self._parent.world_object,
"new_data": new_data
}

event_data = FeatureEvent(type="cmap-changed", pick_info=pick_info)

self._call_event_handlers(event_data)

0 comments on commit cbff3f4

Please sign in to comment.