Skip to content

feat: add PNA cell:cell conjugate segmentation - #467

Open
elhb wants to merge 6 commits into
devfrom
segment
Open

elhb wants to merge 6 commits into
devfrom
segment

Conversation

@elhb

@elhb elhb commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Description

This PR adds the public Python APIs for PNA cell:cell conjugate segmentation, matching the pixelatorR workflow:

  1. cc_protein_weights — rank-2 NMF protein weights for two labeled populations (the w matrix).
  2. distance_from_node_set — integer hop distance from a set of seed nodes on a PNAGraph.
  3. partition_counts — protein counts summed by node partition (cell type / interface / other).
  4. segment_cell — classify conjugate-graph nodes into the two cell types, with an optional interface.

They live in pixelator.pna.analysis.segmentation and are re-exported from pixelator.pna.analysis. Input for graph steps is a PNAGraph (the same object you get from dataset.edgelist().iterator()); weights are fit on AnnData (dataset.adata() with population labels in obs).

cc_protein_weights currently implements mode="cell_abundance" (the default, and the path used by segment_cell and the R tutorial). mode="k_neighborhood" raises NotImplementedError. Projection of w onto neighborhood profiles is non-negative least squares via sklearn (LinearRegression(positive=True)), without pixelatorR’s RcppML L1 = 0.2. That difference is documented; see the comparison below.

Collects the already-reviewed pieces from #458, #459, #460, and #464.

Type of change

  • New feature (non-breaking change which adds functionality)
  • This change requires a documentation update

How Has This Been Tested?

CI (pytest). Shape, column names, error paths, and behavioral smoke.

  • Synthetic two-community conjugate PNAGraph (exclusive T/B markers, two crossing edges, a disconnected island): core compartments are stable, crossing nodes become interface when detection is on, the island is other under LCC filtering.
  • Shared 5-cell PBMC .pxl (same cells as pixelatorR minimal_pna_pxl_file()): the functions run on a real graph. That file is not a biological conjugate; R’s own tests only check that labels fall in {Mono, CD4T, interface, other}.

Comparison with pixelatorR (not in CI). The R test-segment_cell.R case was run in both languages on the same 5-cell PXL, same component (2708240b908e2eba), same population labels (Mono vs CD4T), and the same NMF seed (7331). Python node ids are integer UMIs; R names have a -umi1/-umi2 suffix — those were stripped before scoring.

Two comparisons:

  1. Independent path — each language fits w, then runs segment_cell. This is what a scientist would do in each language.
  2. Shared weights — Python segment_cell is given the R w matrix. This isolates the graph/NNLS steps from NMF.

Results on 37,665 nodes:

segment_cell setting Independent w Python using R’s w
default 99.83% (63 disagree) 100%
detect_interface=False 99.84% (62) 100%
k_interface_expansion=4 99.84% (61) 100%
keep largest component 99.84% (62) 100%
min_comp_size=1 99.88% (46) 99.997% (1 node)

NMF weights: 103 proteins in both languages, identical top-8 markers per population, cosine similarity 1.000 on both columns.

With shared w, labels match exactly except one node when every connected component of size ≥ 1 is kept (2-means threshold RNG). Independent-path disagreements are almost all R other vs Python CD4T — small fragments around the LCC cutoff, not core compartment swaps.

image

A separate planted T/B conjugate (learned weights → segment_cellpartition_counts) recovered cores and the interface at 100% against the planted labels. That graph is the conjugate-correctness check; the 5-cell file is the R-parity smoke check.

PR checklist:

  • This comment contains a description of changes (with reason).
  • I have performed a self-review of my own code
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • If a new tool or package is included, I have updated dependencies in pyproject.toml and cited it properly
  • I have checked my code and documentation and corrected any misspellings
  • I have documented any significant changes to the code in CHANGELOG.md

Note

Medium Risk
New analysis-only surface area with no pipeline changes, but segment_cell/cc_protein_weights drive scientific labels on conjugate graphs and can diverge slightly from R at boundaries due to NNLS differences.

Overview
Adds public Python APIs for PNA cell:cell conjugate segmentation, aligned with pixelatorR, under pixelator.pna.analysis.segmentation and re-exported from pixelator.pna.analysis.

cc_protein_weights fits rank-2 NMF on whole-cell protein abundance from AnnData (two labeled populations; mode="cell_abundance" only; k_neighborhood raises NotImplementedError) and returns the w matrix used downstream. segment_cell annotates a conjugate PNAGraph in place with a compartment attribute (two cell-type names, optional interface, or other) via k-hop neighborhood profiles, non-negative projection of w, optional spatial smoothing, connected-component filtering, and interface detection. distance_from_node_set writes multi-source BFS hop distances to distance_from_seed. partition_counts collapses node marker counts by partition vector or node attribute.

Docs (CHANGELOG.md, docs/api/overview.rst) list the new entry points. Tests cover synthetic conjugates, R-parity checks on a shared 5-cell PBMC fixture, and validation/error paths. Python’s segment_cell projection omits pixelatorR’s RcppML L1 penalty, so boundary labels can differ slightly from R when weights are fit independently.

Reviewed by Cursor Bugbot for commit cdb1c12. Bugbot is set up for automated code reviews on this repo. Configure here.

elhb and others added 6 commits September 11, 2026 08:43
* feat: add segment_cell for conjugate graph node classification

Port pixelatorR segment_cell onto PNAGraph so two cell types (and an optional interface) can be labeled from cc_protein_weights.

Co-authored-by: Cursor <cursoragent@cursor.com>
@elhb elhb changed the title Segment component feat: add PNA cell:cell conjugate segmentation Sep 17, 2026
@elhb
elhb marked this pull request as ready for review September 17, 2026 11:43
@elhb
elhb requested a review from ludvigla September 22, 2026 06:53

@ludvigla ludvigla left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! I have no additional comments.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants