using CairoMakie
CairoMakie.activate!()

3D Tumor Spheroid

Tumour spheroids grown in suspension are a classical in vitro model for avascular solid tumours. They recapitulate key features of in vivo tumour biology: a proliferating outer shell, a quiescent mid-zone, and a necrotic core — all driven by oxygen and nutrient gradients. Simulating spheroid growth in 3D is essential because the geometry of diffusion-limited growth is qualitatively different from its 2D projection.

Potts.jl uses the exact same API in 3D as in 2D: the only change is the topology type and grid dimensionality. This tutorial demonstrates that generality on a 50 × 50 × 50 lattice.

Packages

Zarr is imported alongside PottsToolkit so we can use ZarrBackend to write the 3D time-series to disk. 3D simulations produce large arrays (50³ × n_frames × 4 bytes ≈ tens of MB per run), making on-disk backends preferable to the default in-memory MemoryBackend.

using PottsToolkit
using MakiePotts
using Zarr
Precompiling packages...
    530.8 ms  ✓ ChunkCodecCore
    656.2 ms  ✓ URIs
    449.4 ms  ✓ SimpleBufferStream
    493.5 ms  ✓ InputBuffers
    556.4 ms  ✓ LRUCache
    484.7 ms  ✓ BitFlags
    776.3 ms  ✓ ConcurrentUtilities
    730.0 ms  ✓ CodecInflate64
    484.6 ms  ✓ DateTimes64
    620.7 ms  ✓ ExceptionUnwrapping
    758.2 ms  ✓ Lz4_jll
    749.4 ms  ✓ MbedTLS_jll
    613.4 ms  ✓ ChunkCodecLibZstd
    547.5 ms  ✓ ChunkCodecLibZlib
    495.4 ms  ✓ LRUCache → SerializationExt
   1199.8 ms  ✓ DiskArrays
   2035.4 ms  ✓ OpenSSL
    705.1 ms  ✓ Blosc_jll
   1116.6 ms  ✓ MbedTLS
    577.5 ms  ✓ Blosc
   6582.2 ms  ✓ ZipArchives
  18080.6 ms  ✓ HTTP
   2398.3 ms  ✓ Zarr
  23 dependencies successfully precompiled in 30 seconds. 46 already precompiled.
Precompiling packages...
    581.2 ms  ✓ FilePaths → FilePathsURIsExt
  1 dependency successfully precompiled in 1 seconds. 12 already precompiled.
Precompiling packages...
   1215.5 ms  ✓ FileIO → HTTPExt
  1 dependency successfully precompiled in 1 seconds. 46 already precompiled.
Precompiling packages...
   1505.2 ms  ✓ CorePotts → CorePottsZarrExt
  1 dependency successfully precompiled in 2 seconds. 151 already precompiled.

Cell Type

Tumor = CellType(:Tumor)
Medium = CellType(:Medium, is_background = true)

Energy Model

VolumeComponent and AdhesionComponent are sufficient to produce compact spheroidal growth morphology. In 3D, a target volume of 65 lattice sites corresponds roughly to a sphere of radius 2.5 voxels. J(Tumor,Medium) is set high to ensure tight packing: tumour cells prefer contact with each other over the surrounding medium, consistent with the elevated E-cadherin expression observed in early-stage carcinomas.

sys = PottsSystem(
    cell_types = [Medium, Tumor],
    penalties = [
        VolumeComponent(Tumor => (λ = 5.0f0, target = 65)),
        AdhesionComponent(
            (Tumor, Tumor) => 2.0f0,
            (Tumor, Medium) => 18.0f0
        )
    ]
)

Problem — 3D Lattice

Passing a 3-element tuple (50, 50, 50) as the grid size automatically constructs a 3D lattice. VonNeumannTopology{3}() gives 6-connectivity (the six face-adjacent voxels), which is the 3D analogue of the 4-connected 2D neighbourhood and the appropriate choice for isotropic diffusion-limited problems.

To accurately model a spheroid, we seed a single dense tumour mass in the centre of the grid using HypersphereLayout, rather than randomly distributing cells.

prob = PottsProblem(
    sys,
    HypersphereLayout(Tumor, (25, 25, 25), 10),
    (50, 50, 50);
    tspan = (0, 400),
    topology = VonNeumannTopology{3}()
)

alg = CheckerboardMetropolis(T = 2.0f0, sweeps_per_step = 10)

Solving with On-Disk Storage

ZarrBackend writes each saved frame as a compressed chunk in a Zarr store. The store path is relative to the current working directory. Zarr supports random access, so you can load any frame without reading the full file. Pass the backend to solve alongside saveat.

sol = solve(prob, alg;
    saveat = 5,
    backend = ZarrBackend("05_tumor_spheroid.zarr")
)

Visualising a Z-Slice

MakiePotts's record_potts can display a 2D cross-section of a 3D grid. The slice keyword (when provided) selects a fixed index along the last dimension (z-axis), so the movie shows the equatorial plane of the spheroid — the same view used in confocal microscopy z-stack projections. Here we take the central slice at z = 25.

record_potts(
    "05_tumor_spheroid_3d.mp4",
    sol;
    slice = 25,
    metrics = [
        "N Cells" => u -> u.N_cells[],
        "Mean Volume" => u -> begin
            n = u.N_cells[]
            n > 0 ? sum(Array(u.cell_data.volumes)[1:n]) / n : 0.0
        end
    ],
    framerate = 20,
    resolution = (1200, 700)
)

This page was generated using Literate.jl.