Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
79146bc
Merge pull request #44 from RaredonLab/main
RaredonLab Jun 1, 2026
d117dd6
feat: edge-file dropdown to flip between multiple edges.parquet (#46)
noahwang1991 Jul 14, 2026
cdad74f
Merge pull request #47 from noahwang1991/dev
noahwang1991 Jul 14, 2026
9fde747
feat: discover morphology images in subdirectories
msraredon Aug 4, 2026
700ba38
docs: align CLAUDE.md and README with shipped code; archive supersede…
msraredon Aug 4, 2026
5900569
perf: stream transcript and boundary queries through DuckDB
msraredon Aug 4, 2026
aa7ad54
docs: propose seqFISH (Spatial Genomics GenePS) support
msraredon Aug 4, 2026
d4406a9
test: golden-output guard; lift supplemental metadata to the base reader
msraredon Aug 5, 2026
6d0549c
feat: seqFISH (Spatial Genomics GenePS) support
msraredon Aug 5, 2026
8686f96
feat: supplemental edge metadata (edge-metadata/ folder)
msraredon Aug 6, 2026
2d0bc6c
chore: v0.6.0
msraredon Aug 6, 2026
8982971
Merge pull request #48 from msraredon/dev
msraredon Aug 6, 2026
ce2ef66
perf: spatial index cache for transcript and boundary queries
msraredon Aug 6, 2026
794ffad
feat: Visium HD support against real Space Ranger output
msraredon Aug 6, 2026
8a432fb
docs: correct the edges.parquet spec against the actual NICHESv2 writer
msraredon Aug 6, 2026
399fe5e
feat: per-platform NICHESv2 scripts, run against the demo datasets
msraredon Aug 6, 2026
a131416
chore: v0.7.0
msraredon Aug 6, 2026
7202c8a
Merge pull request #49 from msraredon/dev
msraredon Aug 6, 2026
d2fafd9
fix: CosMx and MERSCOPE against real public data
msraredon Aug 6, 2026
94a4009
fix: render datasets that ship no morphology image
msraredon Aug 6, 2026
ca15d99
chore: v0.7.1
msraredon Aug 6, 2026
b693261
Merge pull request #50 from msraredon/dev
msraredon Aug 6, 2026
b27ee5e
feat: metadata filtering (#45) and force-categorical toggle (#35)
msraredon Aug 6, 2026
56587f5
Merge pull request #51 from msraredon/dev
RaredonLab Aug 6, 2026
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: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ backend/__pycache__/
backend/app/**/__pycache__/
**/*.pyc
backend/.dzi_cache/
.tissueplex_cache/
**/.tissueplex_cache/
.env
.env.prod
.claude/
Expand Down
752 changes: 701 additions & 51 deletions CLAUDE.md

Large diffs are not rendered by default.

File renamed without changes.
File renamed without changes.
File renamed without changes.
17 changes: 17 additions & 0 deletions OBS/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# OBS — Obsolete planning documents

These documents describe earlier designs that have since been superseded by the shipped
implementation. They are kept for provenance and to explain why certain decisions were
made, **but they are not a specification and should not be used to guide new work.**

For current architecture, read `CLAUDE.md` at the repo root. For current data contracts
and deployment, read `docs/`.

| File | Written | Why it is obsolete |
|---|---|---|
| `PLAN.md` | 2026-05-01 | The original v1 plan, under the project's former name *ConnectivityExplorer*. Its edge schema (integer `lrm_id` 1–488, `strength`, `cell_id_source`/`cell_id_target`, Xenium pixel coordinates) was fully replaced by the NICHESv2 schema documented in `docs/data_format.md`. Still useful for the rationale behind storing connectivity as vector edges rather than 488 rasterized PNGs. |
| `PLAN_v2_2026-05-02.md` | 2026-05-02 | Status snapshot declaring v1 feature-complete. Its API reference lists the `/xenium/...` routes, which were replaced by the platform-agnostic `/spatial/...` router. Its state schema predates the `lrm_set` color mode and string-keyed `hiddenLrms`. Its P3 performance backlog is still partly relevant and has been carried into `CLAUDE.md`. |
| `EDGE_UI_PLAN.md` | 2026-05-02 | Design doc for the NICHESv2 edge-UI migration. Overtaken during implementation: it specifies **client-side** edge aggregation and a **server** round-trip for `lrm_set` coloring, and the shipped code does the opposite of both (server-side `/query-grouped`, client-side `lrm_set`). It also predates the tissue-graph layer, `edgeDensity`, `arrowStyle`, and `edgeColorClamp`. |

`NICHESv2_package_design.md` remains at the repo root: it documents the separate NICHESv2
R package rather than this codebase, and is still current for that package.
105 changes: 91 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
# TissuePlex

An interactive spatial transcriptomics viewer for exploring cell-cell communication from [NICHESv2](https://github.com/your-lab/NICHESv2) directly on the tissue image.
An interactive spatial transcriptomics viewer for exploring cell-cell communication from [NICHESv2](https://github.com/RaredonLab/NICHESv2) directly on the tissue image.

![TissuePlex demo](docs/demo.gif)

---

## What it does

Spatial transcriptomics platforms (Xenium, MERSCOPE, CosMx) produce high-resolution images with hundreds of genes measured per cell. NICHESv2 infers which cells are communicating and through which ligand-receptor mechanisms (LRMs). TissuePlex bridges those two outputs: it overlays the NICHESv2 communication graph on the tissue image and lets you explore it interactively.
Spatial transcriptomics platforms (Xenium, seqFISH, Visium HD, MERSCOPE, CosMx) produce high-resolution images with hundreds of genes measured per cell. NICHESv2 infers which cells are communicating and through which ligand-receptor mechanisms (LRMs). TissuePlex bridges those two outputs: it overlays the NICHESv2 communication graph on the tissue image and lets you explore it interactively.

**Key capabilities:**

- **Toggle individual LRMs in real time** — select any subset of 100s of ligand-receptor mechanisms and instantly see which cell pairs are communicating through them
- **Color edges by communication score or metadata** — visualize LRM set strength, cell type, or any custom column from your analysis as a continuous or categorical color scale
- **Click any edge for full detail** — inspect every active LRM for a given cell pair with their individual scores
- **Directed edges with arrowheads** — A→B and B→A are visually distinct; autocrine communication renders as rings
- **Multiple edge sets per dataset** — drop several `.parquet` files into an `edges/` folder and flip between scoring approaches on the same tissue without duplicating the image or cell data
- **Pan and zoom on high-resolution morphology images** — OME-TIFF tile pyramid with smooth zoom from whole-tissue to single-cell scale
- **Transcript dot overlay** — per-gene colored dots, filterable by gene species
- **Cell/spot segmentation** — polygon boundaries with color-by-gene-set or color-by-metadata
- **Region drawing and measurement tools** — annotate areas, export cell selections
- **Multi-channel morphology** — Xenium `morphology_focus/` channels are selectable alongside the top-level morphology image
- **Cross-platform metadata** — the `cell-metadata/` convention works the same way on every platform that supports it, so annotation workflows transfer between Xenium and seqFISH unchanged
- **Split-screen comparison** — two independently navigable panels sharing one set of layer controls, with a match-zoom button
- **Per-panel rotation** — rotate either panel to any angle to align tissue orientation
- **Transcript dot overlay** — per-gene colored dots, filterable by gene species, with hover tooltips
- **Cell/spot segmentation** — polygon boundaries with color-by-gene-set or color-by-metadata, and editable per-category colors
- **Metadata filtering** — restrict the view to a subset of cells or edges (a sample, a few cell types, a value range). Applied server-side before sampling, so a rare cluster renders at full density instead of being sampled away
- **Treat-as-categorical toggle** — integer-coded cluster IDs get a discrete editable palette rather than a viridis gradient, with the numeric order preserved in the legend
- **Region drawing and measurement tools** — annotate areas, export cell selections, save PNG screenshots
- **Supplemental metadata** — drop any CSV or parquet into a `cell-metadata/` folder to add custom color-by columns (clusters, pseudotime, etc.) without touching the original data
- **Multi-dataset support** — switch between datasets without restarting; each is auto-detected by platform

Expand All @@ -30,9 +37,15 @@ Spatial transcriptomics platforms (Xenium, MERSCOPE, CosMx) produce high-resolut
| Platform | Vendor | Morphology | Transcripts | Cell segments | Edges |
|---|---|:---:|:---:|:---:|:---:|
| **Xenium** | 10x Genomics | ✓ | ✓ | ✓ | ✓ |
| **seqFISH** | Spatial Genomics | ✓ | ✓ | ✓ | ✓ |
| **Visium HD** | 10x Genomics | ✓ | — | ✓ (bins) | ✓ |
| **MERSCOPE** | Vizgen | — | ✓ | — | ✓ |
| **CosMx** | Nanostring | — | ✓ | — | ✓ |

Xenium and seqFISH are the complete implementations. seqFISH means the commercial **Spatial Genomics GenePS** output, not the academic seqFISH/seqFISH+ method, which has no standard file layout; the current v2 layout is fully supported, and legacy v1 reads cells and transcripts but not boundaries.

The other readers cover cells, transcripts, and metadata coloring; boundary parsing is platform-specific and not yet implemented for them (MERSCOPE stores polygons in HDF5, CosMx in per-FOV label TIFFs). Visium HD renders each square bin as a polygon — so fill, colour-by, picking and region selection all work — but has no per-molecule transcript coordinates. Each reader declares what it supports via a capability flag, and the UI hides layers the platform cannot serve.

The edge connectivity layer (NICHESv2 output) works with any platform — it is platform-agnostic as long as cell barcodes match.

---
Expand All @@ -44,7 +57,7 @@ The edge connectivity layer (NICHESv2 output) works with any platform — it is
### Demo with sample data

```bash
git clone https://github.com/your-lab/TissuePlex.git
git clone https://github.com/RaredonLab/TissuePlex.git
cd TissuePlex
docker compose up --build
```
Expand All @@ -64,40 +77,66 @@ DATA_PATH=/absolute/path/to/your/datasets docker compose up --build
xenium_run_A/
experiment.xenium ← Xenium sentinel
morphology.ome.tif
morphology_focus/ ← optional; extra channels appear in the image picker
cells.parquet
transcripts.parquet
cell_boundaries.parquet
edges.parquet ← NICHESv2 output (optional)
edges/ ← optional; additional edge sets to flip between
raw_minimum.parquet
normalized_product.parquet

seqfish_run_B/ ← Spatial Genomics GenePS; one ROI per folder
Roi1_CellCoordinates.csv ← seqFISH sentinel
Roi1_CellxGene.csv
Roi1_TranscriptList.csv
Roi1_Boundaries.geojson
Roi1_DAPI.tiff
edges.parquet

merscope_run_B/
visium_hd_run_C/ ← Space Ranger outs/
binned_outputs/
square_008um/ ← Visium HD sentinel
filtered_feature_bc_matrix.h5
spatial/{tissue_positions.parquet, scalefactors_json.json, tissue_hires_image.png}
edges.parquet

merscope_run_D/
cell_by_gene.csv ← MERSCOPE sentinel
cell_metadata.csv
detected_transcripts.csv
edges.parquet

cosmx_run_C/
cosmx_run_E/
my_experiment_tx_file.csv ← CosMx sentinel
edges.parquet
```

If `edges.parquet` is absent the edge layers are hidden — all other layers work normally.
If `edges.parquet` is absent the edge layers are hidden — all other layers work normally. When a dataset has more than one edge source, a dropdown appears at the top of the Edge Data section; the selection applies to every open panel.

The first launch builds DZI tile pyramids from OME-TIFF morphology images. This takes ~30 seconds per dataset and is cached across restarts.

### Deploying to a server

[docs/cloud-deploy.md](docs/cloud-deploy.md) is a step-by-step DigitalOcean runbook (~$106–116/month) covering droplet setup, block storage for data, DNS, automatic TLS via Caddy, and data upload. Note that **access control is opt-in**: unless you enable Caddy's `basicauth`, anyone with the URL can view the data.

---

## NICHESv2 workflow

TissuePlex is designed as a downstream visualization step for [NICHESv2](https://github.com/your-lab/NICHESv2). After running NICHESv2 on your spatial dataset, export the connectivity object:
TissuePlex is designed as a downstream visualization step for [NICHESv2](https://github.com/RaredonLab/NICHESv2). After running NICHESv2 on your spatial dataset, export the connectivity object:

```r
# In R, after running NICHESv2:
export_for_TissuePlex(
export_to_TissuePlex(
niches_object,
output_path = "/your/datasets/xenium_run_A/edges.parquet"
output.path = "/your/datasets/xenium_run_A/edges.parquet",
celltype.col = "Type.6"
)
```

Working examples of the full pipeline — exporting Seurat metadata, running NICHESv2, and exporting the parquet — are in [`r/`](r/). Those scripts have hardcoded paths and are meant to be read and adapted, not run as-is.

Then launch TissuePlex — the edge layer will appear automatically.

The `edges.parquet` format is one row per **(directed edge) × (LRM)**. A→B and B→A are separate rows. Any additional columns in the file (cell types, scores, custom metadata) are automatically available as color-by options in the UI. See [docs/data_format.md](docs/data_format.md) for the full column specification.
Expand All @@ -123,7 +162,26 @@ Standard R export works out of the box:
write.csv(my_metadata, file.path(dataset_dir, "cell-metadata", "metadata.csv"))
```

Columns appear automatically in the **Cell Color** dropdown. Continuous columns get a gradient; string and low-cardinality integer columns get discrete colors.
Columns appear automatically in the **Cell Color** and **Cell Filter** dropdowns. Continuous columns get a gradient; string and low-cardinality integer columns get discrete colors. Use **treat as categorical** to override that guess either way — a Seurat cluster column with more than 30 levels still gets discrete colors, and a coded column you want as a gradient can have one.

## Supplemental edge metadata

The same idea for cell *pairs* — annotate edges without regenerating `edges.parquet` from R:

```
dataset_folder/
edges.parquet
edge-metadata/ ← create this directory
annotations.csv ← key column `edge` = "SendingCell|ReceivingCell"
```

```r
write.csv(annotations_df, file.path(dataset_dir, "edge-metadata", "annotations.csv"))
```

Columns appear automatically in the **edge color** dropdown, and show as an *Annotations* block when you click an edge. The folder sits beside the dataset rather than beside the edge file, so one set of annotations applies across every edge source — annotations describe cell pairs, which belong to the tissue rather than to one scoring run.

`sample_data/mouse_ileum_tiny` ships worked examples of both `cell-metadata/` and `edge-metadata/`.

---

Expand All @@ -138,9 +196,13 @@ DATA_ROOT=../sample_data uvicorn app.main:app --reload
# Frontend (React + Vite) — in a separate terminal
cd frontend
npm install
npm run dev # → http://localhost:3000, proxies /api → :8000
npm run dev # → http://localhost:5173, proxies /api → :8000
```

The dev server uses port 5173 so it does not collide with `docker compose`, which binds 3000 for the production frontend.

There is currently no automated test suite, CI, or linter — changes are verified by running the app.

---

## Architecture
Expand All @@ -154,6 +216,7 @@ FastAPI backend
/tiles — OME-TIFF → DZI tile pyramid (pyvips / tifffile fallback)
/spatial — transcripts, cell boundaries, cell metadata, gene expression
/edges — edge query, LRM catalogue, per-edge color values, edge detail
/layers — generic parquet layer serving
```

All data is served directly from parquet files via DuckDB — no database setup or import step. Tile pyramids are built on first access and cached.
Expand All @@ -169,6 +232,20 @@ The frontend automatically adapts its layer controls to the capabilities your re

---

## Roadmap

Tracked in [GitHub issues](https://github.com/RaredonLab/TissuePlex/issues).

[#45](https://github.com/RaredonLab/TissuePlex/issues/45) (select by metadata) and [#35](https://github.com/RaredonLab/TissuePlex/issues/35) (treat-as-categorical toggle) are both implemented. Natural follow-ups, neither yet built:

- Combining more than one filter at a time — today it is one column, so "cluster 4 *and* sample B" needs two passes
- Persisting the categorical choice and the palette across reloads
- Filtering transcripts, which needs a transcript→cell assignment that several platforms do not ship

Large datasets are handled by a spatial index built automatically on first access, alongside the tile pyramid: files over 64 MB are rewritten sorted by a spatial grid, which makes viewport queries roughly 27× faster on a 40M-row transcript file and also converts seqFISH CSV to parquet along the way. Set `SPATIAL_CACHE=0` to disable it.

---

## Citation

If you use TissuePlex in published work, please cite: *(preprint / paper link — coming soon)*
2 changes: 1 addition & 1 deletion backend/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from app.routers import tiles, spatial, edges, layers

APP_VERSION = "0.3.0"
APP_VERSION = "0.8.0"

app = FastAPI(title="TissuePlex API", version=APP_VERSION)

Expand Down
Loading
Loading