Skip to content

Commit 9e8ae1a

Browse files
authored
Merge pull request #9 from virtualcell/colab-test
Items needed ahead of colab demo
2 parents f3ae9f7 + 9ae4662 commit 9e8ae1a

File tree

18 files changed

+83294
-5994
lines changed

18 files changed

+83294
-5994
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
[![codecov](https://codecov.io/gh/virtualcell/pyvcell/branch/main/graph/badge.svg)](https://codecov.io/gh/virtualcell/pyvcell)
66
[![Commit activity](https://img.shields.io/github/commit-activity/m/virtualcell/pyvcell)](https://img.shields.io/github/commit-activity/m/virtualcell/pyvcell)
77
[![License](https://img.shields.io/github/license/virtualcell/pyvcell)](https://img.shields.io/github/license/virtualcell/pyvcell)
8+
[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/drive/101BPDYqu4_PupqmunT6Qhextks_VT-8X?usp=sharing)
89

910
This is the python wrapper for vcell modeling and simulation
1011

examples/combined_fvsolver_data.ipynb

Lines changed: 165 additions & 30 deletions
Large diffs are not rendered by default.

examples/fv_solver_workflow.ipynb

Lines changed: 82716 additions & 5691 deletions
Large diffs are not rendered by default.

examples/fv_solver_workflow.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
spatial_model = SbmlSpatialModel(filepath=model_fp)
1111
spatial_model.copy_parameters()
1212
simulation = SbmlSpatialSimulation(sbml_model=spatial_model)
13-
result = simulation.run(duration=5.0, output_time_step=0.1)
13+
result = simulation.run()
1414

15-
result.plotter.plot_slice_2d(time_index=3, channel_index=5, z_index=5)
16-
result.plotter.plot_slice_3d(time_index=3, channel_index=6)
15+
result.plotter.plot_slice_2d(time_index=3, channel_name="s0", z_index=5)
16+
result.plotter.plot_slice_3d(time_index=3, channel_id="s1")
1717
result.plotter.plot_concentrations()
1818
simulation.cleanup()

examples/fvsolver_demo.ipynb

Lines changed: 85 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
"metadata": {
55
"collapsed": false,
66
"ExecuteTime": {
7-
"end_time": "2025-01-28T17:18:48.500711Z",
8-
"start_time": "2025-01-28T17:18:48.492786Z"
7+
"end_time": "2025-02-26T15:04:10.692375Z",
8+
"start_time": "2025-02-26T15:04:10.684153Z"
99
}
1010
},
1111
"cell_type": "code",
@@ -51,8 +51,8 @@
5151
"metadata": {
5252
"collapsed": false,
5353
"ExecuteTime": {
54-
"end_time": "2025-01-28T17:18:56.180880Z",
55-
"start_time": "2025-01-28T17:18:55.115191Z"
54+
"end_time": "2025-02-26T15:04:14.578045Z",
55+
"start_time": "2025-02-26T15:04:12.294954Z"
5656
}
5757
},
5858
"id": "902c6d3a8dc70af",
@@ -127,22 +127,94 @@
127127
"sim file name is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/SimID_946368938_0_0002.sim\n",
128128
"[[[data:0.5]]]temporary directory used is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/\n",
129129
"sim file name is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/SimID_946368938_0_0003.sim\n",
130-
"[[[data:0.75]]]\n",
130+
"[[[data:0.75]]]temporary directory used is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/\n",
131+
"sim file name is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/SimID_946368938_0_0004.sim\n",
132+
"[[[data:1]]]temporary directory used is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/\n",
133+
"sim file name is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/SimID_946368938_0_0005.sim\n",
134+
"[[[data:1.25]]]temporary directory used is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/\n",
135+
"sim file name is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/SimID_946368938_0_0006.sim\n",
136+
"[[[data:1.5]]]temporary directory used is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/\n",
137+
"sim file name is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/SimID_946368938_0_0007.sim\n",
138+
"[[[data:1.75]]]temporary directory used is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/\n",
139+
"sim file name is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/SimID_946368938_0_0008.sim\n",
140+
"[[[data:2]]]temporary directory used is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/\n",
141+
"sim file name is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/SimID_946368938_0_0009.sim\n",
142+
"[[[data:2.25]]]temporary directory used is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/\n",
143+
"sim file name is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/SimID_946368938_0_0010.sim\n",
144+
"[[[data:2.5]]]temporary directory used is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/\n",
145+
"sim file name is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/SimID_946368938_0_0011.sim\n",
146+
"[[[data:2.75]]]temporary directory used is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/\n",
147+
"sim file name is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/SimID_946368938_0_0012.sim\n",
148+
"[[[data:3]]]temporary directory used is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/\n",
149+
"sim file name is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/SimID_946368938_0_0013.sim\n",
150+
"[[[data:3.25]]]temporary directory used is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/\n",
151+
"sim file name is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/SimID_946368938_0_0014.sim\n",
152+
"[[[data:3.5]]]temporary directory used is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/\n",
153+
"sim file name is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/SimID_946368938_0_0015.sim\n",
154+
"[[[data:3.75]]]temporary directory used is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/\n",
155+
"sim file name is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/SimID_946368938_0_0016.sim\n",
156+
"[[[data:4]]]temporary directory used is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/\n",
157+
"sim file name is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/SimID_946368938_0_0017.sim\n",
158+
"[[[data:4.25]]]temporary directory used is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/\n",
159+
"sim file name is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/SimID_946368938_0_0018.sim\n",
160+
"[[[data:4.5]]]temporary directory used is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/\n",
161+
"sim file name is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/SimID_946368938_0_0019.sim\n",
162+
"[[[data:4.75]]]temporary directory used is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/\n",
163+
"sim file name is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/SimID_946368938_0_0020.sim\n",
164+
"[[[data:5]]]temporary directory used is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/\n",
165+
"sim file name is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/SimID_946368938_0_0021.sim\n",
166+
"[[[data:5.25]]]temporary directory used is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/\n",
167+
"sim file name is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/SimID_946368938_0_0022.sim\n",
168+
"[[[data:5.5]]]temporary directory used is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/\n",
169+
"sim file name is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/SimID_946368938_0_0023.sim\n",
170+
"[[[data:5.75]]]temporary directory used is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/\n",
171+
"sim file name is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/SimID_946368938_0_0024.sim\n",
172+
"[[[data:6]]]temporary directory used is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/\n",
173+
"sim file name is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/SimID_946368938_0_0025.sim\n",
174+
"[[[data:6.25]]]temporary directory used is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/\n",
175+
"sim file name is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/SimID_946368938_0_0026.sim\n",
176+
"[[[data:6.5]]]temporary directory used is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/\n",
177+
"sim file name is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/SimID_946368938_0_0027.sim\n",
178+
"[[[data:6.75]]]temporary directory used is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/\n",
179+
"sim file name is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/SimID_946368938_0_0028.sim\n",
180+
"[[[data:7]]]temporary directory used is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/\n",
181+
"sim file name is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/SimID_946368938_0_0029.sim\n",
182+
"[[[data:7.25]]]temporary directory used is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/\n",
183+
"sim file name is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/SimID_946368938_0_0030.sim\n",
184+
"[[[data:7.5]]]temporary directory used is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/\n",
185+
"sim file name is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/SimID_946368938_0_0031.sim\n",
186+
"[[[data:7.75]]]temporary directory used is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/\n",
187+
"sim file name is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/SimID_946368938_0_0032.sim\n",
188+
"[[[data:8]]]temporary directory used is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/\n",
189+
"sim file name is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/SimID_946368938_0_0033.sim\n",
190+
"[[[data:8.25]]]temporary directory used is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/\n",
191+
"sim file name is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/SimID_946368938_0_0034.sim\n",
192+
"[[[data:8.5]]]temporary directory used is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/\n",
193+
"sim file name is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/SimID_946368938_0_0035.sim\n",
194+
"[[[data:8.75]]]temporary directory used is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/\n",
195+
"sim file name is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/SimID_946368938_0_0036.sim\n",
196+
"[[[data:9]]]temporary directory used is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/\n",
197+
"sim file name is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/SimID_946368938_0_0037.sim\n",
198+
"[[[data:9.25]]]temporary directory used is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/\n",
199+
"sim file name is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/SimID_946368938_0_0038.sim\n",
200+
"[[[data:9.5]]]temporary directory used is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/\n",
201+
"sim file name is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/SimID_946368938_0_0039.sim\n",
202+
"[[[data:9.75]]]\n",
131203
"Final Statistics.. \n",
132204
"\n",
133205
"lenrw = 396539 leniw = 50\n",
134206
"lenrwLS = 396496 leniwLS = 10\n",
135-
"nst = 63\n",
136-
"nfe = 76 nfeLS = 87\n",
137-
"nni = 72 nli = 87\n",
138-
"nsetups = 22 netf = 0\n",
139-
"npe = 2 nps = 147\n",
207+
"nst = 181\n",
208+
"nfe = 198 nfeLS = 214\n",
209+
"nni = 194 nli = 214\n",
210+
"nsetups = 94 netf = 0\n",
211+
"npe = 4 nps = 387\n",
140212
"ncfn = 0 ncfl = 0\n",
141-
"last step = 0.017509\n",
213+
"last step = 0.050000\n",
142214
"\n",
143215
"temporary directory used is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/\n",
144-
"sim file name is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/SimID_946368938_0_0004.sim\n",
145-
"[[[data:1]]][[[progress:100%]]]"
216+
"sim file name is /var/folders/yy/8crj8x7x5_3b86f0js6_0bn00000gr/T/SimID_946368938_0_0040.sim\n",
217+
"[[[data:10]]][[[progress:100%]]]"
146218
]
147219
}
148220
],

examples/test_output/mesh.mp4

-17.7 KB
Binary file not shown.

pyvcell/data_model/plotter.py

Lines changed: 54 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
import numpy as np
55
import zarr
66
from matplotlib import animation
7+
from matplotlib.collections import PathCollection
78

89
from pyvcell.data_model.var_types import NDArray2D
9-
from pyvcell.data_model.zarr_types import Channel
10+
from pyvcell.data_model.zarr_types import ChannelMetadata, ZarrMetadata
11+
from pyvcell.data_model.zarr_types import ChannelMetadata as Channel
1012
from pyvcell.simdata.mesh import CartesianMesh
1113
from pyvcell.simdata.postprocessing import PostProcessing, VariableInfo
1214
from pyvcell.utils import slice_dataset
@@ -17,6 +19,14 @@
1719

1820

1921
class Plotter:
22+
times: list[float]
23+
concentrations: NDArray2D
24+
channels: list[Channel]
25+
post_processing: PostProcessing
26+
zarr_dataset: Union[zarr.Group, zarr.Array]
27+
mesh: CartesianMesh
28+
metadata: ZarrMetadata
29+
2030
def __init__(
2131
self,
2232
times: list[float],
@@ -25,6 +35,7 @@ def __init__(
2535
post_processing: PostProcessing,
2636
zarr_dataset: Union[zarr.Group, zarr.Array],
2737
mesh: CartesianMesh,
38+
metadata: ZarrMetadata,
2839
) -> None:
2940
self.times = times
3041
self.num_timepoints = len(times)
@@ -33,6 +44,18 @@ def __init__(
3344
self.post_processing = post_processing
3445
self.zarr_dataset = zarr_dataset
3546
self.mesh = mesh
47+
self.metadata = metadata
48+
49+
def get_channel(self, label: str) -> ChannelMetadata:
50+
getter = filter(lambda c: c.label == label, self.channels)
51+
channel_data = next(getter, None)
52+
53+
if channel_data is None:
54+
raise ValueError(f"No channel found with label '{label}'")
55+
if next(getter, None) is not None:
56+
raise ValueError(f"More than one '{label}' channel found")
57+
58+
return channel_data
3659

3760
def plot_concentrations(self) -> None:
3861
t = self.times
@@ -45,19 +68,18 @@ def plot_concentrations(self) -> None:
4568
ax.grid()
4669
return plt.show()
4770

48-
def plot_slice_2d(self, time_index: int, channel_index: int, z_index: int) -> None:
49-
data_slice = slice_dataset(self.zarr_dataset, time_index, channel_index, z_index)
71+
def plot_slice_2d(self, time_index: int, channel_name: str, z_index: int) -> None:
72+
specified_channel = self.get_channel(channel_name)
73+
data_slice = slice_dataset(specified_channel, self.zarr_dataset, time_index, z_index)
5074

5175
t = self.zarr_dataset.attrs.asdict()["metadata"]["times"][time_index]
5276
channel_label = None
5377
channel_domain = None
5478

5579
for channel in self.channels:
56-
if channel.index == channel_index:
80+
if channel.index == specified_channel.index:
5781
channel_label = channel.label
5882
channel_domain = channel.domain_name
59-
# channel_label = self.channels[channel_index].label
60-
# channel_domain = self.channels[channel_index].domain_name
6183

6284
# z_coord = self.mesh.origin[2] + z_index * self.mesh.extent[2] / (self.mesh.size[2] - 1)
6385
title = f"{channel_label} (in {channel_domain}) at t={t}"
@@ -66,22 +88,31 @@ def plot_slice_2d(self, time_index: int, channel_index: int, z_index: int) -> No
6688
# Display the slice as an image
6789
plt.imshow(data_slice)
6890
plt.title(title)
69-
return plt.show()
91+
plt.show()
7092

71-
def plot_slice_3d(self, time_index: int, channel_index: int) -> None:
93+
def plot_slice_3d(self, time_index: int, channel_id: str) -> None:
7294
# Select a 3D volume for a single time point and channel, shape is (z, y, x)
73-
volume = self.zarr_dataset[time_index, channel_index, :, :, :]
95+
channel = self.get_channel(channel_id)
96+
volume = self.zarr_dataset[time_index, channel.index, :, :, :]
7497

7598
# Create a figure for 3D plotting
7699
fig = plt.figure()
77100
ax = fig.add_subplot(111, projection="3d")
78101

79102
# Define a mask to display the volume (use 'region_mask' channel)
80-
mask = np.copy(self.zarr_dataset[3, 0, :, :, :])
81-
z, y, x = np.where(mask == 1)
103+
mask = np.copy(self.zarr_dataset[time_index, 0, :, :, :])
104+
domain = channel.domain_name
82105

83-
# Get the intensity values for these points
84-
intensities = volume[z, y, x]
106+
if channel.domain_name == "all":
107+
z, y, x = np.where(mask > -1) # everywhere
108+
# Get the intensity values for these points
109+
intensities = volume[z, y, x]
110+
else:
111+
idx: set[int] = self.mesh.get_volume_region_ids(volume_domain_name=domain)
112+
region_func = lambda region_index: region_index in idx
113+
z, y, x = np.where(np.vectorize(region_func)(mask))
114+
# Get the intensity values for these points
115+
intensities = volume[z, y, x]
85116

86117
# Create a 3D scatter plot
87118
scatter = ax.scatter(x, y, z, c=intensities, cmap="viridis")
@@ -93,7 +124,9 @@ def plot_slice_3d(self, time_index: int, channel_index: int) -> None:
93124
ax.set_xlabel("X")
94125
ax.set_ylabel("Y")
95126
ax.set_zlabel("Z") # type: ignore[attr-defined]
96-
127+
t = self.times[time_index]
128+
title = f"{channel.label} (in {channel.domain_name}) at t={t}"
129+
plt.title(title)
97130
# Show the plot
98131
return plt.show()
99132

@@ -116,8 +149,7 @@ def get_3d_slice_animation(self, channel_index: int, interval: int = 200) -> ani
116149
interval (int): Time interval between frames in milliseconds.
117150
"""
118151
# Extract metadata and the number of time points
119-
channel_list = self.channels
120-
channel_domain = channel_list[channel_index - 5].domain_name
152+
channel: Channel = self.channels[channel_index]
121153
num_timepoints = self.num_timepoints
122154

123155
# Create a figure for 3D plotting
@@ -130,19 +162,19 @@ def get_3d_slice_animation(self, channel_index: int, interval: int = 200) -> ani
130162
ax.set_zlabel("Z") # type: ignore[attr-defined]
131163
sc = None
132164

133-
@no_type_check
134-
def update(frame: int):
165+
def update(frame: int) -> tuple[PathCollection]:
135166
"""Update function for animation"""
136-
# Define a mask to display the volume (use 'region_mask' channel)
137-
mask = np.copy(self.zarr_dataset[frame, 0, :, :, :])
138-
z, y, x = np.where(mask == 1)
167+
mask = np.copy(self.zarr_dataset[3, 0, :, :, :])
168+
print(f"Any mask: {np.any(mask)}")
139169

170+
z, y, x = np.where(mask > 0)
171+
print(f"got shapes: {z.shape}, {y.shape}, {x.shape}")
140172
volume = self.zarr_dataset[frame, channel_index, :, :, :]
141173
intensities = volume[z, y, x]
142174

143175
# Initialize the scatter plot with empty data
144176
scatter = ax.scatter(x, y, z, c=intensities, cmap="viridis")
145-
ax.set_title(f"Channel: {channel_domain}, Time Index: {frame}")
177+
ax.set_title(f"Channel: {channel.domain_name}, Time Index: {frame}")
146178
return (scatter,)
147179

148180
# Create the animation

0 commit comments

Comments
 (0)