Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
python-version: ["3.10", "3.11", "3.12", "3.13"]

steps:
- uses: actions/checkout@v4
Expand Down
86 changes: 40 additions & 46 deletions docs/tutorials/11_physicell_integration.marimo.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,22 +60,16 @@ def _():

@app.cell
def _():
from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from pathlib import Path

from spatialtissuepy import SpatialTissueData
from spatialtissuepy.summary import SpatialSummary, StatisticsPanel
from spatialtissuepy.synthetic.physicell import (
PhysiCellSimulation,
PhysiCellTimeStep,
read_physicell_timestep,
discover_physicell_timesteps,
is_alive,
is_dead,
)
from spatialtissuepy.summary import StatisticsPanel, SpatialSummary
from spatialtissuepy.viz import plot_spatial_scatter

np.random.seed(42)
return (
Expand Down Expand Up @@ -149,7 +143,7 @@ def _(Path, PhysiCellSimulation):
print(f"Number of timesteps: {sim.n_timesteps}")
print(f"Time range: {sim.times[0]:.0f} - {sim.times[-1]:.0f} minutes")
print(f"Time range: {sim.times[0]/60:.1f} - {sim.times[-1]/60:.1f} hours")
print(f"\nCell type mapping:")
print("\nCell type mapping:")
for cell_id, cell_name in sim.cell_type_mapping.items():
print(f" {cell_id}: {cell_name}")
return (sim,)
Expand Down Expand Up @@ -177,7 +171,7 @@ def _(sim):
def _(sim):
# Load the first timestep
ts_initial = sim.get_timestep(0)
print(f"Initial timestep:")
print("Initial timestep:")
print(f" Time: {ts_initial.time:.0f} min ({ts_initial.time/60:.1f} hours)")
print(f" Live cells: {ts_initial.n_cells}")
print(f" Dead cells: {ts_initial.n_dead_cells}")
Expand All @@ -192,7 +186,7 @@ def _(sim):

# Load the final timestep
ts_final = sim.get_timestep(sim.n_timesteps-1)
print(f"\nFinal timestep:")
print("\nFinal timestep:")
print(f" Time: {ts_final.time:.0f} min ({ts_final.time/60:.1f} hours)")
print(f" Live cells: {ts_final.n_cells}")
print(f" Dead cells: {ts_final.n_dead_cells}")
Expand All @@ -216,7 +210,7 @@ def _(ts_final):
# Convert to SpatialTissueData
spatial_data = ts_final.to_spatial_data()

print(f"SpatialTissueData object:")
print("SpatialTissueData object:")
print(f" Coordinates shape: {spatial_data.coordinates.shape}")
print(f" Cell types: {spatial_data.cell_types_unique}")
print(f" Has markers: {spatial_data.markers is not None}")
Expand Down Expand Up @@ -333,7 +327,7 @@ def _():
time_hours = counts_df['time'] / 60

# Plot tumor cells
ax1.plot(time_hours, counts_df['n_malignant_epithelial_cell'],
ax1.plot(time_hours, counts_df['n_malignant_epithelial_cell'],
color=cell_colors['malignant_epithelial_cell'], linewidth=2, label='Tumor')
ax1.set_xlabel('Time (hours)')
ax1.set_ylabel('Cell Count')
Expand All @@ -352,7 +346,7 @@ def _():

for col, ct, label in immune_types:
if col in counts_df.columns:
ax2.plot(time_hours, counts_df[col],
ax2.plot(time_hours, counts_df[col],
color=cell_colors[ct], linewidth=2, label=label)

ax2.set_xlabel('Time (hours)')
Expand All @@ -374,31 +368,31 @@ def _():
# Plot macrophage polarization ratio (M1 vs M2)
with plt.style.context('seaborn-v0_8'):
fig2, ax3 = plt.subplots(figsize=(10, 5))

time_h = counts_df['time'] / 60
m1_counts = counts_df['n_M1_macrophage'].values
m2_counts = counts_df['n_M2_macrophage'].values

# Avoid division by zero
total_polarized = m1_counts + m2_counts
m1_ratio = m1_counts / (total_polarized + 1e-6) # M1 / (M1 + M2)

ax3.plot(time_h, m1_ratio, 'g-', linewidth=2, label='M1 Ratio')
ax3.axhline(0.5, color='gray', linestyle='--', alpha=0.5, label='Equal M1/M2')
ax3.fill_between(time_h, m1_ratio, 0.5,
where=(m1_ratio > 0.5), alpha=0.3, color='green',
ax3.fill_between(time_h, m1_ratio, 0.5,
where=(m1_ratio > 0.5), alpha=0.3, color='green',
label='Pro-inflammatory')
ax3.fill_between(time_h, m1_ratio, 0.5,
ax3.fill_between(time_h, m1_ratio, 0.5,
where=(m1_ratio < 0.5), alpha=0.3, color='purple',
label='Anti-inflammatory')

ax3.set_xlabel('Time (hours)')
ax3.set_ylabel('M1 / (M1 + M2) Ratio')
ax3.set_title('Macrophage Polarization Over Time')
ax3.set_ylim(0, 1)
ax3.legend(loc='upper right')
ax3.grid(alpha=0.3)

plt.tight_layout()
plt.show()
plt.close()
Expand Down Expand Up @@ -429,32 +423,32 @@ def _(SpatialSummary, StatisticsPanel, np, pd, sim):
sample_hours = np.arange(0, 120, 10)
sample_indices = []

def _():
def _():
for target_hours in sample_hours:
target_min = target_hours * 60
idx = np.argmin(np.abs(sim.times - target_min))
if idx not in sample_indices: # Avoid duplicates
sample_indices.append(idx)

print(f"Computing spatial statistics for {len(sample_indices)} timesteps...")

# Compute statistics over time
metrics_over_time = []

for i, idx in enumerate(sample_indices):
ts = sim.get_timestep(idx)
tissue = ts.to_spatial_data()

summary = SpatialSummary(tissue, panel)
results = summary.to_dict()
results['time'] = ts.time
results['time_hours'] = ts.time / 60
results['time_index'] = ts.time_index
metrics_over_time.append(results)

if (i + 1) % 5 == 0:
print(f" Processed {i + 1}/{len(sample_indices)} timesteps")

metrics_df = pd.DataFrame(metrics_over_time)
print("\nTracked metrics:")
print(metrics_df.columns.tolist())
Expand All @@ -471,25 +465,25 @@ def _():
with plt.style.context('seaborn-v0_8'):
# Plot spatial metrics over time
fig3, axes3 = plt.subplots(1, 2, figsize=(12, 5))

# Mean nearest neighbor distance
ax_nn = axes3[0]
ax_nn.plot(metrics_df['time_hours'], metrics_df['mean_nnd'],
ax_nn.plot(metrics_df['time_hours'], metrics_df['mean_nnd'],
'b-o', linewidth=2, markersize=4)
ax_nn.set_xlabel('Time (hours)')
ax_nn.set_ylabel('Mean NN Distance (μm)')
ax_nn.set_title('Cell Density Over Time\n(Lower = More Dense)')
ax_nn.grid(alpha=0.3)

# Total cell count trend
ax_total = axes3[1]
ax_total.plot(metrics_df['time_hours'], metrics_df['n_cells'],
ax_total.plot(metrics_df['time_hours'], metrics_df['n_cells'],
'g-o', linewidth=2, markersize=4)
ax_total.set_xlabel('Time (hours)')
ax_total.set_ylabel('Total Live Cells')
ax_total.set_title('Total Cell Population')
ax_total.grid(alpha=0.3)

plt.tight_layout()
plt.show()
plt.close()
Expand Down Expand Up @@ -541,11 +535,11 @@ def compute_mean_distance_to_tumor(tissue):
def _():
# Compute tumor proximity over time
tumor_proximity = []

for idx in sample_indices:
ts = sim.get_timestep(idx)
tissue = ts.to_spatial_data()

row = {
'time_hours': ts.time / 60,
'n_tumor': np.sum(tissue.cell_types == 'malignant_epithelial_cell')
Expand All @@ -564,22 +558,22 @@ def _(cell_colors, pd, plt, tumor_proximity):
def _():
with plt.style.context('seaborn-v0_8'):
fig4, ax4 = plt.subplots(figsize=(12, 6))

immune_proximity_cols = [c for c in proximity_df.columns if '_to_tumor' in c]

for col in immune_proximity_cols:
cell_type = col.replace('_to_tumor', '')
color = cell_colors.get(cell_type, '#999999')
label = cell_type.replace('_macrophage', ' Mac').replace('_cell', '')
ax4.plot(proximity_df['time_hours'], proximity_df[col],
ax4.plot(proximity_df['time_hours'], proximity_df[col],
color=color, linewidth=2, marker='o', markersize=4, label=label)

ax4.set_xlabel('Time (hours)')
ax4.set_ylabel('Mean Distance to Nearest Tumor Cell (μm)')
ax4.set_title('Immune Cell Proximity to Tumor Over Time')
ax4.legend(loc='upper right')
ax4.grid(alpha=0.3)

plt.tight_layout()
plt.show()
plt.close()
Expand Down Expand Up @@ -617,16 +611,16 @@ def _():
idx = np.argmin(np.abs(sim.times - target_min))
ts = sim.get_timestep(idx)
tissue = ts.to_spatial_data()

summary = SpatialSummary(tissue, full_panel)
results = summary.to_dict()
results['time_hours'] = ts.time / 60
results['timepoint'] = f"t={target_hours}h"
key_results.append(results)

key_df = pd.DataFrame(key_results)
key_df = key_df.set_index('timepoint')

# Display key metrics
display_cols = ['time_hours', 'n_cells', 'mean_nn_distance']#, 'shannon_entropy']
display_cols = [c for c in display_cols if c in key_df.columns]
Expand Down
Loading
Loading