Skip to content

CellChat — cell–cell communication

Precomputed CellChat outputs served as an interactive viewer: a source × target pathway heatmap, a sortable ligand-receptor table, and an optional cross-group delta view. Tool-callable from the copilot when both modules are enabled.

extras_key cellchat
config_key cellchat
install pip install 'stellar-atlas[cellchat]'
frontend tab CellChat

Enable

modules:
  cellchat:
    enabled: true
    source_dir: data/external/cellchat   # relative to project root

Input format

Four parquet files under source_dir/. STELLAR enforces the columns below and ignores extras — pass-through columns are preserved verbatim in the parquet copies but are not surfaced by the routes.

cellchat_groups.parquet — one row per CellChat run group

column type required notes
group_id string yes Unique slug — short, URL-safe (e.g. subtype:t1).
label string yes Human-readable name shown in the SPA picker.
n_cells int64 no Cell count the CellChat object was built from.

A "group" is one cohort whose signalling you ran in R. STELLAR is agnostic to what defines the cohort — you might key by subtype, by sex, by condition, by donor. Use whatever slug makes sense.

cellchat_celltypes.parquet — which cell types each group exercised

column type required
group_id string yes
cell_type string yes

cellchat_pathway_strength.parquet — pathway × source × target strength

column type required
group_id string yes
pathway string yes
source string yes
target string yes
prob float32 yes

The prob value is the communication probability CellChat assigned to that edge for that pathway. Drive the source × target heatmap directly from this table.

cellchat_lr.parquet — ligand-receptor hits

column type required
group_id string yes
pathway string yes
ligand string yes
receptor string yes
source string yes
target string yes
prob float32 yes
padj float64 yes

Producing the input from a CellChat .rds

CellChat is an R-only package — STELLAR does not depend on it directly and stays Python-pure. Run the R extractor below once per atlas, preferably as part of your existing R pipeline.

Where to put this

Save as extract_cellchat.R next to your .rds files; STELLAR intentionally doesn't ship the script (you'll want to tweak the field mapping for your run anyway).

# extract_cellchat.R
# Convert a list of CellChat objects (one per group) into the four
# parquet files STELLAR's cellchat module reads.
#
#   Rscript extract_cellchat.R \
#       --out  data/external/cellchat \
#       --rds  ccs_by_group.rds        # named list of CellChat objects

suppressPackageStartupMessages({
  library(CellChat)
  library(arrow)
  library(dplyr)
  library(tibble)
})

args <- commandArgs(trailingOnly = TRUE)
out  <- args[which(args == "--out") + 1]
rds  <- args[which(args == "--rds") + 1]
dir.create(out, recursive = TRUE, showWarnings = FALSE)

ccs <- readRDS(rds)   # list(group_id = CellChat-object, ...)

groups_rows     <- list()
celltype_rows   <- list()
pathway_rows    <- list()
lr_rows         <- list()

for (gid in names(ccs)) {
  cc <- ccs[[gid]]
  cell_types <- levels(cc@idents)

  groups_rows[[gid]] <- tibble(
    group_id = gid,
    label    = gid,
    n_cells  = ncol(cc@data)
  )

  celltype_rows[[gid]] <- tibble(
    group_id  = gid,
    cell_type = cell_types
  )

  prob_arr <- cc@netP$prob
  for (pwy in dimnames(prob_arr)[[3]]) {
    m <- prob_arr[, , pwy]
    df <- as.data.frame.table(m, stringsAsFactors = FALSE)
    colnames(df) <- c("source", "target", "prob")
    df <- df[df$prob > 0, , drop = FALSE]
    if (nrow(df)) {
      pathway_rows[[paste(gid, pwy, sep = "::")]] <- tibble(
        group_id = gid,
        pathway  = pwy,
        source   = df$source,
        target   = df$target,
        prob     = as.numeric(df$prob)
      )
    }
  }

  lr <- subsetCommunication(cc)
  if (nrow(lr)) {
    lr_rows[[gid]] <- tibble(
      group_id = gid,
      pathway  = lr$pathway_name,
      ligand   = lr$ligand,
      receptor = lr$receptor,
      source   = as.character(lr$source),
      target   = as.character(lr$target),
      prob     = as.numeric(lr$prob),
      padj     = as.numeric(lr$pval)
    )
  }
}

write_parquet(bind_rows(groups_rows),   file.path(out, "cellchat_groups.parquet"))
write_parquet(bind_rows(celltype_rows), file.path(out, "cellchat_celltypes.parquet"))
write_parquet(bind_rows(pathway_rows),  file.path(out, "cellchat_pathway_strength.parquet"))
write_parquet(bind_rows(lr_rows),       file.path(out, "cellchat_lr.parquet"))

message("wrote 4 parquet files to ", out)

If your pval is uncorrected, run p.adjust() per group/pathway before emitting the padj column.

API surface

route what
GET /api/cellchat/groups list groups + cell-type counts
GET /api/cellchat/pathways/{group_id} pathways with summed prob
POST /api/cellchat/network body {group_id, pathway?, top_n?} — Arrow IPC of (source, target, pathway, prob)
POST /api/cellchat/lr body {group_id, pathway, top_n?, padj_max?} — Arrow IPC of the L-R table
POST /api/cellchat/compare body {group_a, group_b, pathway?, top_n?} — Arrow IPC of (pathway, source, target, prob_a, prob_b, delta)

Example calls

List every CellChat group + its cell types:

curl -s http://localhost:18901/api/cellchat/groups | python -m json.tool
# {"groups": [{"group_id": "condition:control",
#              "label":    "Control",
#              "n_cells":  12450,
#              "cell_types": ["T", "B", "Neuron", ...]}, ...]}

Top signaling pathways for one group:

curl -s 'http://localhost:18901/api/cellchat/pathways/condition:control?top_n=10' \
     | python -m json.tool
# {"pathways": [{"pathway":    "TGFb",
#                "total_prob": 4.17,
#                "n_pairs":    36}, ...]}

Network for one group + pathway (Arrow IPC binary):

curl -sX POST http://localhost:18901/api/cellchat/network \
     -H 'content-type: application/json' \
     -d '{"group_id": "condition:control",
          "pathway":  "TGFb",
          "top_n":    50}' \
     -o /tmp/cc_network.arrow

Copilot tools

When both cellchat and copilot are enabled the module contributes three tools to the Claude agent loop.

list_cellchat_groups

{
  "name": "list_cellchat_groups",
  "description": "List every CellChat run group, with the cell types each group exercised.",
  "input_schema": {"type": "object", "properties": {}, "required": []}
}

get_cellchat_pathways

{
  "name": "get_cellchat_pathways",
  "description": "List signaling pathways for one group, aggregated across source × target cell-type pairs.",
  "input_schema": {
    "type": "object",
    "properties": {
      "group_id": {"type": "string"},
      "top_n":    {"type": "integer", "default": 25}
    },
    "required": ["group_id"]
  }
}

compare_cellchat_groups

{
  "name": "compare_cellchat_groups",
  "description": "Compare two groups' signaling — returns the largest source × target × pathway deltas (prob_a - prob_b).",
  "input_schema": {
    "type": "object",
    "properties": {
      "group_id_a": {"type": "string"},
      "group_id_b": {"type": "string"},
      "pathway":    {"type": "string"},
      "top_n":      {"type": "integer", "default": 25}
    },
    "required": ["group_id_a", "group_id_b"]
  }
}

System prompt fragment

CellChat signaling is precomputed per group (a 'group' is one cohort: subtype / sex / condition slice). Discover group_ids with list_cellchat_groups, then drill down with get_cellchat_pathways or compare_cellchat_groups. Probabilities are CellChat's communication probability.

Implementation at stellar/modules/cellchat/__init__.py — see Extending for the pattern.

Frontend tab

The CellChat tab appears in the SPA nav when this module is enabled: group picker (radio buttons) → pathway dropdown → source × target heatmap of prob → sortable L-R table beneath. Toggle "compare with another group" to swap the main heatmap for a prob_a − prob_b delta heatmap.