cellier 0.0.2__py3-none-any.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.
- cellier/__init__.py +10 -0
- cellier/convenience/__init__.py +4 -0
- cellier/convenience/qt/__init__.py +1 -0
- cellier/convenience/qt/qt_viewer.py +43 -0
- cellier/events/__init__.py +5 -0
- cellier/events/_event_bus.py +25 -0
- cellier/events/_visual.py +175 -0
- cellier/gui/__init__.py +1 -0
- cellier/gui/constants.py +9 -0
- cellier/gui/qt/__init__.py +1 -0
- cellier/gui/qt/utils.py +36 -0
- cellier/models/__init__.py +1 -0
- cellier/models/data_manager.py +45 -0
- cellier/models/data_stores/__init__.py +6 -0
- cellier/models/data_stores/base_data_store.py +36 -0
- cellier/models/data_stores/lines.py +84 -0
- cellier/models/data_stores/points.py +84 -0
- cellier/models/scene/__init__.py +15 -0
- cellier/models/scene/cameras.py +106 -0
- cellier/models/scene/canvas.py +22 -0
- cellier/models/scene/dims_manager.py +106 -0
- cellier/models/scene/scene.py +49 -0
- cellier/models/viewer.py +43 -0
- cellier/models/visuals/__init__.py +11 -0
- cellier/models/visuals/base.py +43 -0
- cellier/models/visuals/lines.py +63 -0
- cellier/models/visuals/points.py +57 -0
- cellier/py.typed +5 -0
- cellier/qt_canvas.py +14 -0
- cellier/render/__init__.py +5 -0
- cellier/render/_render_manager.py +284 -0
- cellier/render/cameras.py +26 -0
- cellier/render/constants.py +8 -0
- cellier/render/lines.py +101 -0
- cellier/render/points.py +110 -0
- cellier/render/utils.py +20 -0
- cellier/slicer/__init__.py +12 -0
- cellier/slicer/data_slice.py +170 -0
- cellier/slicer/slicer.py +104 -0
- cellier/slicer/transforms.py +169 -0
- cellier/slicer/utils.py +33 -0
- cellier/slicer/world_slice.py +203 -0
- cellier/types.py +14 -0
- cellier/util/__init__.py +1 -0
- cellier/util/chunk.py +407 -0
- cellier/util/geometry.py +202 -0
- cellier/v1/__init__.py +10 -0
- cellier/v1/convenience/__init__.py +4 -0
- cellier/v1/convenience/qt/__init__.py +1 -0
- cellier/v1/convenience/qt/qt_viewer.py +43 -0
- cellier/v1/gui/__init__.py +1 -0
- cellier/v1/gui/constants.py +9 -0
- cellier/v1/gui/qt/__init__.py +1 -0
- cellier/v1/gui/qt/utils.py +36 -0
- cellier/v1/models/__init__.py +1 -0
- cellier/v1/models/data_manager.py +73 -0
- cellier/v1/models/data_stores/__init__.py +1 -0
- cellier/v1/models/data_stores/base_data_store.py +36 -0
- cellier/v1/models/data_stores/image.py +298 -0
- cellier/v1/models/data_stores/mesh.py +81 -0
- cellier/v1/models/data_stores/points.py +119 -0
- cellier/v1/models/data_streams/__init__.py +1 -0
- cellier/v1/models/data_streams/base_data_stream.py +39 -0
- cellier/v1/models/data_streams/image.py +80 -0
- cellier/v1/models/data_streams/mesh.py +43 -0
- cellier/v1/models/data_streams/points.py +46 -0
- cellier/v1/models/nodes/__init__.py +1 -0
- cellier/v1/models/nodes/base_node.py +21 -0
- cellier/v1/models/nodes/image_node.py +89 -0
- cellier/v1/models/nodes/mesh_node.py +96 -0
- cellier/v1/models/nodes/points_node.py +54 -0
- cellier/v1/models/scene/__init__.py +15 -0
- cellier/v1/models/scene/cameras.py +106 -0
- cellier/v1/models/scene/canvas.py +22 -0
- cellier/v1/models/scene/dims_manager.py +106 -0
- cellier/v1/models/scene/scene.py +57 -0
- cellier/v1/models/viewer.py +42 -0
- cellier/v1/py.typed +5 -0
- cellier/v1/qt_canvas.py +14 -0
- cellier/v1/render/__init__.py +1 -0
- cellier/v1/render/cameras.py +26 -0
- cellier/v1/render/image.py +279 -0
- cellier/v1/render/mesh.py +107 -0
- cellier/v1/render/points.py +115 -0
- cellier/v1/render/render_manager.py +237 -0
- cellier/v1/render/utils.py +27 -0
- cellier/v1/slicer/__init__.py +6 -0
- cellier/v1/slicer/data_slice.py +150 -0
- cellier/v1/slicer/slicer.py +182 -0
- cellier/v1/slicer/transforms.py +169 -0
- cellier/v1/slicer/utils.py +33 -0
- cellier/v1/slicer/world_slice.py +203 -0
- cellier/v1/util/__init__.py +1 -0
- cellier/v1/util/chunk.py +407 -0
- cellier/v1/util/geometry.py +202 -0
- cellier/v1/viewer_controller.py +266 -0
- cellier/viewer_controller.py +348 -0
- cellier-0.0.2.dist-info/METADATA +46 -0
- cellier-0.0.2.dist-info/RECORD +101 -0
- cellier-0.0.2.dist-info/WHEEL +4 -0
- cellier-0.0.2.dist-info/licenses/LICENSE +28 -0
cellier/__init__.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""A workshop for rendering cellular models."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
__version__ = version("cellier")
|
|
7
|
+
except PackageNotFoundError:
|
|
8
|
+
__version__ = "uninstalled"
|
|
9
|
+
__author__ = "Kevin Yamauchi"
|
|
10
|
+
__email__ = "kevin.yamauchi@gmail.com"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Convenience module for making Qt GUIs."""
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Qt widget for the viewer."""
|
|
2
|
+
|
|
3
|
+
from typing import Dict
|
|
4
|
+
|
|
5
|
+
from qtpy.QtWidgets import QVBoxLayout, QWidget
|
|
6
|
+
from wgpu.gui.qt import WgpuCanvas
|
|
7
|
+
|
|
8
|
+
from cellier.models.viewer import ViewerModel
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class QtViewer(QWidget):
|
|
12
|
+
"""Qt widget for the viewer.
|
|
13
|
+
|
|
14
|
+
This contains all the canvases and the dim sliders.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, viewer_model: ViewerModel, parent=None) -> None:
|
|
18
|
+
super().__init__(parent=parent)
|
|
19
|
+
|
|
20
|
+
# make the canvas widgets
|
|
21
|
+
canvas_widgets = {}
|
|
22
|
+
for scene_model in viewer_model.scenes.scenes:
|
|
23
|
+
for canvas_model in scene_model.canvases:
|
|
24
|
+
canvas_widgets[canvas_model.id] = WgpuCanvas(parent=self)
|
|
25
|
+
self._canvases = canvas_widgets
|
|
26
|
+
|
|
27
|
+
# make the layout
|
|
28
|
+
self.setLayout(QVBoxLayout())
|
|
29
|
+
for canvas_widget in self._canvases.values():
|
|
30
|
+
# add the canvas widgets
|
|
31
|
+
self.layout().addWidget(canvas_widget)
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def canvases(self) -> Dict[str, WgpuCanvas]:
|
|
35
|
+
"""The canvas widgets.
|
|
36
|
+
|
|
37
|
+
Returns
|
|
38
|
+
-------
|
|
39
|
+
Dict[str, WgpuCanvas]
|
|
40
|
+
Dictionary where the keys are the canvas IDs and
|
|
41
|
+
the values are the canvas widgets.
|
|
42
|
+
"""
|
|
43
|
+
return self._canvases
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Class to manage connecting events between the model and the view."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from cellier.events._visual import VisualEventBus
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class EventBus:
|
|
11
|
+
"""Class to manage connecting events between the model and the view.
|
|
12
|
+
|
|
13
|
+
There are three types of events:
|
|
14
|
+
- visual: communicate changes to the visual model state.
|
|
15
|
+
- visual_controls: communicate changes to the visual gui state.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
# the signals for each visual model
|
|
20
|
+
self._visual_bus = VisualEventBus()
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def visual(self) -> VisualEventBus:
|
|
24
|
+
"""Return the visual events."""
|
|
25
|
+
return self._visual_bus
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"""Class to manage connecting events between the visual model and the view."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Any, Callable
|
|
5
|
+
|
|
6
|
+
from psygnal import EmissionInfo, Signal, SignalInstance
|
|
7
|
+
|
|
8
|
+
from cellier.types import VisualType
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class VisualEventBus:
|
|
14
|
+
"""Class to manage connecting events for the visual models.
|
|
15
|
+
|
|
16
|
+
There are three types of events:
|
|
17
|
+
- visual: communicate changes to the visual model state.
|
|
18
|
+
- visual_controls: communicate changes to the visual gui state.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self):
|
|
22
|
+
# the signals for each visual model that has been registered
|
|
23
|
+
self._visual_model_signals: dict[str, SignalInstance] = {}
|
|
24
|
+
|
|
25
|
+
# the signals for each visual control that has been registered
|
|
26
|
+
self._visual_control_signals: dict[str, SignalInstance] = {}
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def model_signals(self) -> dict[str, SignalInstance]:
|
|
30
|
+
"""Return the signals for each registered visual model.
|
|
31
|
+
|
|
32
|
+
The dictionary key is the visual model ID and the value is the SignalInstance.
|
|
33
|
+
"""
|
|
34
|
+
return self._visual_model_signals
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def control_signals(self) -> dict[str, SignalInstance]:
|
|
38
|
+
"""Return the signals for each registered visual control.
|
|
39
|
+
|
|
40
|
+
The dictionary key is the visual model ID and the value is
|
|
41
|
+
the SignalInstance.
|
|
42
|
+
"""
|
|
43
|
+
return self._visual_control_signals
|
|
44
|
+
|
|
45
|
+
def register_visual(self, visual: VisualType):
|
|
46
|
+
"""Register a visual with the event bus.
|
|
47
|
+
|
|
48
|
+
This will create a signal on the event bus that will be
|
|
49
|
+
emitted when the visual model updates. Other components (e.g., GUI)
|
|
50
|
+
can register to this signal to be notified of changes via the
|
|
51
|
+
subscribe_to_visual() method.
|
|
52
|
+
|
|
53
|
+
Parameters
|
|
54
|
+
----------
|
|
55
|
+
visual : VisualType
|
|
56
|
+
The visual model to register.
|
|
57
|
+
"""
|
|
58
|
+
if visual.id in self.model_signals:
|
|
59
|
+
logging.info(f"Visual {visual.id} is already registered.")
|
|
60
|
+
return
|
|
61
|
+
# connect all events to the visual model update handler
|
|
62
|
+
visual.events.all.connect(self._on_visual_model_update)
|
|
63
|
+
|
|
64
|
+
# initialize the visual model callbacks
|
|
65
|
+
self.model_signals[visual.id] = SignalInstance(
|
|
66
|
+
name=visual.id, check_nargs_on_connect=False, check_types_on_connect=False
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def subscribe_to_visual(self, visual_id: str, callback: Callable):
|
|
70
|
+
"""Subscribe to an event on a visual model.
|
|
71
|
+
|
|
72
|
+
Parameters
|
|
73
|
+
----------
|
|
74
|
+
visual_id : str
|
|
75
|
+
The ID of the visual model to subscribe to.
|
|
76
|
+
callback : Callable
|
|
77
|
+
The callback to call when the visual model updates.
|
|
78
|
+
"""
|
|
79
|
+
try:
|
|
80
|
+
visual_model_signal = self.model_signals[visual_id]
|
|
81
|
+
except KeyError:
|
|
82
|
+
raise ValueError(f"Visual {visual_id} is not registered.") from None
|
|
83
|
+
|
|
84
|
+
# connect the visual model signal
|
|
85
|
+
visual_model_signal.connect(callback)
|
|
86
|
+
|
|
87
|
+
def register_controls(self, visual_id: str, signal: SignalInstance):
|
|
88
|
+
"""Register a visual control with the event bus.
|
|
89
|
+
|
|
90
|
+
This creates a signal on the event bus that will be emitted when
|
|
91
|
+
the visual control updates. Visual models can subscribe to this
|
|
92
|
+
signal to be notified of changes.
|
|
93
|
+
|
|
94
|
+
Parameters
|
|
95
|
+
----------
|
|
96
|
+
visual_id : str
|
|
97
|
+
The ID of the visual model to register the control for.
|
|
98
|
+
signal : SignalInstance
|
|
99
|
+
The signal to register.
|
|
100
|
+
"""
|
|
101
|
+
if visual_id not in self.control_signals:
|
|
102
|
+
# If we haven't registered this visual_id yet, create the signal
|
|
103
|
+
self.control_signals[visual_id] = SignalInstance(
|
|
104
|
+
name=visual_id,
|
|
105
|
+
check_nargs_on_connect=False,
|
|
106
|
+
check_types_on_connect=False,
|
|
107
|
+
)
|
|
108
|
+
# connect the visual control signal
|
|
109
|
+
signal.connect(self._on_visual_control_update)
|
|
110
|
+
|
|
111
|
+
def subscribe_to_controls(self, visual_id: str, callback: Callable):
|
|
112
|
+
"""Subscribe to the event emitted when the visual controls are updated."""
|
|
113
|
+
try:
|
|
114
|
+
visual_control_signal = self.control_signals[visual_id]
|
|
115
|
+
except KeyError:
|
|
116
|
+
raise ValueError(f"Visual {visual_id} is not registered.") from None
|
|
117
|
+
|
|
118
|
+
# connect the visual control signal
|
|
119
|
+
visual_control_signal.connect(callback)
|
|
120
|
+
|
|
121
|
+
def _on_visual_model_update(self, event: EmissionInfo):
|
|
122
|
+
"""Handle a visual model update event.
|
|
123
|
+
|
|
124
|
+
This emits a dictionary containing the visual id and
|
|
125
|
+
the updated property values.
|
|
126
|
+
"""
|
|
127
|
+
# get the sender
|
|
128
|
+
emitter_object = Signal.sender()
|
|
129
|
+
|
|
130
|
+
try:
|
|
131
|
+
signal = self.model_signals[emitter_object.id]
|
|
132
|
+
except KeyError:
|
|
133
|
+
logger.debug(
|
|
134
|
+
f"EventBus received event from visual model {emitter_object.id},"
|
|
135
|
+
"but the model is not registered"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# dictionary with the updated state
|
|
139
|
+
property_name = event.signal.name
|
|
140
|
+
property_value = event.args[0]
|
|
141
|
+
new_state = {"id": emitter_object.id, property_name: property_value}
|
|
142
|
+
signal.emit(new_state)
|
|
143
|
+
|
|
144
|
+
def _on_visual_control_update(self, event: Any | None = None):
|
|
145
|
+
"""Handle a visual control update event.
|
|
146
|
+
|
|
147
|
+
The update dictionary must include the following keys:
|
|
148
|
+
- id: the id of the visual model
|
|
149
|
+
- controls_update_callback: the visual control's update callback that
|
|
150
|
+
should be blocked when updating the visual model.
|
|
151
|
+
this prevents the update from bouncing back
|
|
152
|
+
to the GUI.
|
|
153
|
+
"""
|
|
154
|
+
visual_id = event["id"]
|
|
155
|
+
try:
|
|
156
|
+
control_signal = self.control_signals[visual_id]
|
|
157
|
+
except KeyError:
|
|
158
|
+
logger.debug(
|
|
159
|
+
f"EventBus received event from visual model {visual_id},"
|
|
160
|
+
"but the model is not registered"
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
callback_to_block = event.pop("controls_update_callback")
|
|
164
|
+
|
|
165
|
+
# update the visual model
|
|
166
|
+
if visual_id in self._visual_control_signals:
|
|
167
|
+
# block the gui update callback so the signal doesn't bounce back
|
|
168
|
+
visual_signal = self.model_signals[visual_id]
|
|
169
|
+
|
|
170
|
+
# emit the event and temporarily disconnect the callback
|
|
171
|
+
visual_signal.disconnect(callback_to_block, missing_ok=True)
|
|
172
|
+
control_signal.emit(event)
|
|
173
|
+
visual_signal.connect(callback_to_block)
|
|
174
|
+
else:
|
|
175
|
+
control_signal.emit(event)
|
cellier/gui/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""GUI related functions."""
|
cellier/gui/constants.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Qt GUI related functions."""
|
cellier/gui/qt/utils.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Utility functions for generating Qt widgets."""
|
|
2
|
+
|
|
3
|
+
from typing import Dict, Optional
|
|
4
|
+
|
|
5
|
+
from qtpy.QtWidgets import QWidget
|
|
6
|
+
from wgpu.gui.qt import WgpuCanvas
|
|
7
|
+
|
|
8
|
+
from cellier.models.viewer import ViewerModel
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def construct_qt_canvases_from_model(
|
|
12
|
+
viewer_model: ViewerModel, parent: Optional[QWidget] = None
|
|
13
|
+
) -> Dict[str, WgpuCanvas]:
|
|
14
|
+
"""Construct Qt PyGFX canvas widgets from the viewer model.
|
|
15
|
+
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
viewer_model : ViewerModel
|
|
19
|
+
The viewer model to construct the Qt canvases from.
|
|
20
|
+
|
|
21
|
+
parent : Optional[QWidget]
|
|
22
|
+
The parent for the constructed Qt canvases.
|
|
23
|
+
Default value is None.
|
|
24
|
+
|
|
25
|
+
Returns
|
|
26
|
+
-------
|
|
27
|
+
Dict[str, WgpuCanvas]
|
|
28
|
+
A dictionary where the canvas IDs are the keys and
|
|
29
|
+
The constructed Qt canvases are the values.
|
|
30
|
+
"""
|
|
31
|
+
canvas_widgets = {}
|
|
32
|
+
for scene_model in viewer_model.scenes.scenes.values():
|
|
33
|
+
for canvas_model in scene_model.canvases.values():
|
|
34
|
+
canvas_widgets[canvas_model.id] = WgpuCanvas(parent=parent)
|
|
35
|
+
|
|
36
|
+
return canvas_widgets
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Models to express the viewer state."""
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Class to hold all of the data stores and streams."""
|
|
2
|
+
|
|
3
|
+
from typing import Dict, Union
|
|
4
|
+
|
|
5
|
+
from psygnal import EventedModel
|
|
6
|
+
from pydantic import Field
|
|
7
|
+
from typing_extensions import Annotated
|
|
8
|
+
|
|
9
|
+
from cellier.models.data_stores.points import PointsMemoryStore
|
|
10
|
+
|
|
11
|
+
# types for discrimitive unions
|
|
12
|
+
DataStoreType = Annotated[
|
|
13
|
+
Union[PointsMemoryStore,],
|
|
14
|
+
Field(discriminator="store_type"),
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DataManager(EventedModel):
|
|
19
|
+
"""Class to model all data_stores in the viewer.
|
|
20
|
+
|
|
21
|
+
todo: add discrimitive union
|
|
22
|
+
|
|
23
|
+
Attributes
|
|
24
|
+
----------
|
|
25
|
+
stores : Dict[str, DataStoreType]
|
|
26
|
+
The data stores in the viewer.
|
|
27
|
+
The key to the store is the data store id.
|
|
28
|
+
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
stores: Dict[str, DataStoreType]
|
|
32
|
+
|
|
33
|
+
def add_data_store(self, data_store: DataStoreType):
|
|
34
|
+
"""Add a data store to the viewer.
|
|
35
|
+
|
|
36
|
+
Parameters
|
|
37
|
+
----------
|
|
38
|
+
data_store : DataStoreType
|
|
39
|
+
The data store to add to the viewer.
|
|
40
|
+
|
|
41
|
+
"""
|
|
42
|
+
self.stores[data_store.id] = data_store
|
|
43
|
+
|
|
44
|
+
# emit event to signal that the data has been updated
|
|
45
|
+
self.events.stores.emit()
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Base Data Store classes."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from uuid import uuid4
|
|
5
|
+
|
|
6
|
+
from psygnal import EventedModel
|
|
7
|
+
from pydantic import Field
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BaseDataStore(EventedModel):
|
|
11
|
+
"""The base class for all DataStores.
|
|
12
|
+
|
|
13
|
+
Parameters
|
|
14
|
+
----------
|
|
15
|
+
id : str
|
|
16
|
+
The unique identifier for the data store.
|
|
17
|
+
The default value is a UUID4 generated hex string.
|
|
18
|
+
|
|
19
|
+
Attributes
|
|
20
|
+
----------
|
|
21
|
+
id : str
|
|
22
|
+
The unique identifier for the data store.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
# store a UUID to identify this specific scene.
|
|
26
|
+
id: str = Field(default_factory=lambda: uuid4().hex)
|
|
27
|
+
name: str = "data store"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(frozen=True)
|
|
31
|
+
class DataStoreSlice:
|
|
32
|
+
"""Base class for all data store slices."""
|
|
33
|
+
|
|
34
|
+
scene_id: str
|
|
35
|
+
visual_id: str
|
|
36
|
+
resolution_level: int
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Classes for lines DataStores."""
|
|
2
|
+
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from pydantic import ConfigDict, field_serializer, field_validator
|
|
7
|
+
from pydantic_core.core_schema import ValidationInfo
|
|
8
|
+
|
|
9
|
+
from cellier.models.data_stores.base_data_store import BaseDataStore
|
|
10
|
+
from cellier.slicer.data_slice import DataSliceRequest, RenderedLinesDataSlice
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BaseLinesDataStore(BaseDataStore):
|
|
14
|
+
"""The base class for all lines data_stores.
|
|
15
|
+
|
|
16
|
+
todo: properly set up. this shouldn't specify ndarrays.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
coordinates: np.ndarray
|
|
20
|
+
|
|
21
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
22
|
+
|
|
23
|
+
@field_validator("coordinates", mode="before")
|
|
24
|
+
@classmethod
|
|
25
|
+
def coerce_to_ndarray_float32(cls, v: str, info: ValidationInfo):
|
|
26
|
+
"""Coerce to a float32 numpy array."""
|
|
27
|
+
if not isinstance(v, np.ndarray):
|
|
28
|
+
v = np.asarray(v, dtype=np.float32)
|
|
29
|
+
return v.astype(np.float32)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class LinesMemoryStore(BaseLinesDataStore):
|
|
33
|
+
"""Point data_stores store for arrays stored in memory."""
|
|
34
|
+
|
|
35
|
+
# this is used for a discriminated union
|
|
36
|
+
store_type: Literal["lines_memory"] = "lines_memory"
|
|
37
|
+
|
|
38
|
+
@field_serializer("coordinates")
|
|
39
|
+
def serialize_ndarray(self, array: np.ndarray, _info) -> list:
|
|
40
|
+
"""Coerce numpy arrays into lists for serialization."""
|
|
41
|
+
return array.tolist()
|
|
42
|
+
|
|
43
|
+
def get_slice(self, slice_data: DataSliceRequest) -> RenderedLinesDataSlice:
|
|
44
|
+
"""Get the data required to render a slice of the mesh.
|
|
45
|
+
|
|
46
|
+
todo: generalize to oblique slicing
|
|
47
|
+
"""
|
|
48
|
+
displayed_dimensions = list(slice_data.world_slice.displayed_dimensions)
|
|
49
|
+
points_ndim = self.coordinates.shape[1]
|
|
50
|
+
|
|
51
|
+
# get a mask for the not displayed dimensions
|
|
52
|
+
not_displayed_mask = np.ones((points_ndim,), dtype=bool)
|
|
53
|
+
not_displayed_mask[displayed_dimensions] = False
|
|
54
|
+
|
|
55
|
+
# get the range to include
|
|
56
|
+
point = np.asarray(slice_data.world_slice.point)
|
|
57
|
+
margin_negative = np.asarray(slice_data.world_slice.margin_negative)
|
|
58
|
+
margin_positive = np.asarray(slice_data.world_slice.margin_positive)
|
|
59
|
+
low = point - margin_negative
|
|
60
|
+
high = point + margin_positive
|
|
61
|
+
|
|
62
|
+
# get the components of the range from the not displayed dimensions
|
|
63
|
+
not_displayed_low = low[not_displayed_mask]
|
|
64
|
+
not_displayed_high = high[not_displayed_mask]
|
|
65
|
+
|
|
66
|
+
# find the coordinates inside the slice
|
|
67
|
+
not_displayed_coordinates = self.coordinates[:, not_displayed_mask]
|
|
68
|
+
|
|
69
|
+
inside_slice_mask = np.all(
|
|
70
|
+
(not_displayed_coordinates >= not_displayed_low)
|
|
71
|
+
& (not_displayed_coordinates <= not_displayed_high),
|
|
72
|
+
axis=1,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
in_slice_coordinates = np.atleast_2d(
|
|
76
|
+
self.coordinates[inside_slice_mask, :][:, displayed_dimensions]
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
return RenderedLinesDataSlice(
|
|
80
|
+
scene_id=slice_data.scene_id,
|
|
81
|
+
visual_id=slice_data.visual_id,
|
|
82
|
+
resolution_level=slice_data.resolution_level,
|
|
83
|
+
coordinates=in_slice_coordinates,
|
|
84
|
+
)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Classes for Point DataStores."""
|
|
2
|
+
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from pydantic import ConfigDict, field_serializer, field_validator
|
|
7
|
+
from pydantic_core.core_schema import ValidationInfo
|
|
8
|
+
|
|
9
|
+
from cellier.models.data_stores.base_data_store import BaseDataStore
|
|
10
|
+
from cellier.slicer.data_slice import DataSliceRequest, RenderedPointsDataSlice
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BasePointsDataStore(BaseDataStore):
|
|
14
|
+
"""The base class for all point data_stores.
|
|
15
|
+
|
|
16
|
+
todo: properly set up. this shouldn't specify ndarrays.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
coordinates: np.ndarray
|
|
20
|
+
|
|
21
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
22
|
+
|
|
23
|
+
@field_validator("coordinates", mode="before")
|
|
24
|
+
@classmethod
|
|
25
|
+
def coerce_to_ndarray_float32(cls, v: str, info: ValidationInfo):
|
|
26
|
+
"""Coerce to a float32 numpy array."""
|
|
27
|
+
if not isinstance(v, np.ndarray):
|
|
28
|
+
v = np.asarray(v, dtype=np.float32)
|
|
29
|
+
return v.astype(np.float32)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class PointsMemoryStore(BasePointsDataStore):
|
|
33
|
+
"""Point data_stores store for arrays stored in memory."""
|
|
34
|
+
|
|
35
|
+
# this is used for a discriminated union
|
|
36
|
+
store_type: Literal["points_memory"] = "points_memory"
|
|
37
|
+
|
|
38
|
+
@field_serializer("coordinates")
|
|
39
|
+
def serialize_ndarray(self, array: np.ndarray, _info) -> list:
|
|
40
|
+
"""Coerce numpy arrays into lists for serialization."""
|
|
41
|
+
return array.tolist()
|
|
42
|
+
|
|
43
|
+
def get_slice(self, slice_data: DataSliceRequest) -> RenderedPointsDataSlice:
|
|
44
|
+
"""Get the data required to render a slice of the mesh.
|
|
45
|
+
|
|
46
|
+
todo: generalize to oblique slicing
|
|
47
|
+
"""
|
|
48
|
+
displayed_dimensions = list(slice_data.world_slice.displayed_dimensions)
|
|
49
|
+
points_ndim = self.coordinates.shape[1]
|
|
50
|
+
|
|
51
|
+
# get a mask for the not displayed dimensions
|
|
52
|
+
not_displayed_mask = np.ones((points_ndim,), dtype=bool)
|
|
53
|
+
not_displayed_mask[displayed_dimensions] = False
|
|
54
|
+
|
|
55
|
+
# get the range to include
|
|
56
|
+
point = np.asarray(slice_data.world_slice.point)
|
|
57
|
+
margin_negative = np.asarray(slice_data.world_slice.margin_negative)
|
|
58
|
+
margin_positive = np.asarray(slice_data.world_slice.margin_positive)
|
|
59
|
+
low = point - margin_negative
|
|
60
|
+
high = point + margin_positive
|
|
61
|
+
|
|
62
|
+
# get the components of the range from the not displayed dimensions
|
|
63
|
+
not_displayed_low = low[not_displayed_mask]
|
|
64
|
+
not_displayed_high = high[not_displayed_mask]
|
|
65
|
+
|
|
66
|
+
# find the coordinates inside the slice
|
|
67
|
+
not_displayed_coordinates = self.coordinates[:, not_displayed_mask]
|
|
68
|
+
|
|
69
|
+
inside_slice_mask = np.all(
|
|
70
|
+
(not_displayed_coordinates >= not_displayed_low)
|
|
71
|
+
& (not_displayed_coordinates <= not_displayed_high),
|
|
72
|
+
axis=1,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
in_slice_coordinates = np.atleast_2d(
|
|
76
|
+
self.coordinates[inside_slice_mask, :][:, displayed_dimensions]
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
return RenderedPointsDataSlice(
|
|
80
|
+
scene_id=slice_data.scene_id,
|
|
81
|
+
visual_id=slice_data.visual_id,
|
|
82
|
+
resolution_level=slice_data.resolution_level,
|
|
83
|
+
coordinates=in_slice_coordinates,
|
|
84
|
+
)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Models for the scene objects."""
|
|
2
|
+
|
|
3
|
+
from cellier.models.scene.cameras import OrthographicCamera, PerspectiveCamera
|
|
4
|
+
from cellier.models.scene.canvas import Canvas
|
|
5
|
+
from cellier.models.scene.dims_manager import CoordinateSystem, DimsManager
|
|
6
|
+
from cellier.models.scene.scene import Scene
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"Canvas",
|
|
10
|
+
"CoordinateSystem",
|
|
11
|
+
"DimsManager",
|
|
12
|
+
"OrthographicCamera",
|
|
13
|
+
"PerspectiveCamera",
|
|
14
|
+
"Scene",
|
|
15
|
+
]
|