Skip to content

Pna 2591 - Add adaptive_core_expansion and node_pls algorithms for cell denoising#352

Merged
ludvigla merged 44 commits into
devfrom
pna-2591
May 12, 2026
Merged

Pna 2591 - Add adaptive_core_expansion and node_pls algorithms for cell denoising#352
ludvigla merged 44 commits into
devfrom
pna-2591

Conversation

@ludvigla
Copy link
Copy Markdown
Contributor

@ludvigla ludvigla commented Apr 23, 2026

Description

This PR adds two new algorithms (described in detail in pixelatorR-internal) for graph denoising. Both tools take a Graph object as input, including a networkx graph and a count matrix, and outputs the same object with additional node metrics which can be used for downstream filtering.

  • adaptive_core_expansion: ACE utilizes topology-aware graph partitioning. It identifies a high-density k-core "seed" and iteratively expands it using transition probabilities to recruit peripheral nodes. The final partition is determined by maximizing the Bray-Curtis dissimilarity between the "core" and "periphery" layers.
  • node_pls: This method models the statistical relationship between node neighborhood abundance and graph coreness. By assigning each node calculated "denoising scores" we can apply targeted filtering.

This PR also adds additional options to the denoise cli, enabling ACE and PLS denoising.

Fixes: PNA-2591, and PNA-2671

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How Has This Been Tested?

Added tests in tests/common/test_adaptive_core_expansion.py and tests/common/test_node_pls.py.

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

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds two new graph-denoising algorithms (adaptive_core_expansion, node_pls) to operate on pixelator.common.graph.Graph, along with new tests covering basic behavior and error cases.

Changes:

  • Introduce adaptive_core_expansion graph partitioning algorithm (k-core seeded expansion + Bray–Curtis selection).
  • Introduce node_pls utilities for neighborhood-expanded node abundance matrices and PLS regression.
  • Add test suites for both algorithms.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 9 comments.

File Description
src/pixelator/common/graph/adaptive_core_expansion.py New adaptive k-core expansion partitioning implementation; sets partition node attribute.
src/pixelator/common/graph/node_pls.py New node-level PLS utilities for neighborhood abundance expansion, optional residualization, and model fitting.
tests/common/test_adaptive_core_expansion.py Adds tests validating expected partitions on a known PXL component + invalid input checks.
tests/common/test_node_pls.py Adds unit tests for residualization, neighborhood abundance matrix creation, and basic node_pls usage.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/pixelator/common/graph/adaptive_core_expansion.py Outdated
Comment thread src/pixelator/common/graph/adaptive_core_expansion.py Outdated
Comment thread src/pixelator/common/graph/node_pls.py
Comment thread src/pixelator/common/graph/adaptive_core_expansion.py
Comment thread src/pixelator/common/graph/adaptive_core_expansion.py Outdated
Comment thread src/pixelator/common/graph/adaptive_core_expansion.py
Comment thread src/pixelator/common/graph/node_pls.py
Comment thread src/pixelator/common/graph/node_pls.py
Comment thread src/pixelator/common/graph/adaptive_core_expansion.py Outdated
Comment thread src/pixelator/common/graph/adaptive_core_expansion.py Outdated
Comment thread src/pixelator/common/graph/adaptive_core_expansion.py Outdated
Copy link
Copy Markdown
Contributor

@maxkarlsson maxkarlsson left a comment

Choose a reason for hiding this comment

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

As far as I can see it looks good, but perhaps it would be best if we try to calculate som matrices and PLS models here and compare them with R?

Comment thread src/pixelator/common/graph/node_pls.py
Comment thread src/pixelator/common/graph/node_pls.py Outdated
Comment thread src/pixelator/common/graph/node_pls.py
@ptajvar ptajvar changed the title Pna 2591 Pna 2591 - Add adaptive_core_expansion and node_pls algorithms for cell denoising Apr 24, 2026
@ludvigla ludvigla requested a review from Copilot May 4, 2026 11:44
@ludvigla
Copy link
Copy Markdown
Contributor Author

ludvigla commented May 4, 2026

I have now added an additional parameter min_allowed_nodes_pct to adaptive_core_expansion (default 0.8). If the best partition (with highest Bray-Curtis score) includes a "high" core with less then min_allowed_nodes_pct, the partition with the highest Bray-Curtis score that falls above the min_allowed_nodes_pct threshold is used instead.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 11 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/pixelator/pna/analysis/denoise.py Outdated
Comment thread src/pixelator/pna/analysis/denoise.py
Comment thread src/pixelator/pna/analysis/denoise.py
Comment thread tests/common/test_adaptive_core_expansion.py Outdated
Comment thread src/pixelator/pna/cli/denoise.py
Comment thread src/pixelator/common/graph/adaptive_core_expansion.py Outdated
@ludvigla ludvigla requested a review from ptajvar May 6, 2026 07:38
@ludvigla ludvigla requested a review from maxkarlsson May 7, 2026 08:23
Copy link
Copy Markdown
Contributor

@maxkarlsson maxkarlsson left a comment

Choose a reason for hiding this comment

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

I had a look at the PLS stuff and I think it looks great! I tried to see if I can find any differences to the R implementation but I cannot 👍

ptajvar added 2 commits May 11, 2026 12:37
…ltiple denoise methods. Instead, we record number of nodes marked for removal by each method.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 13 out of 14 changed files in this pull request and generated 4 comments.

Comments suppressed due to low confidence (1)

src/pixelator/pna/analysis/denoise.py:423

  • denoise_one_core_layer() takes inflate_factor, but it is never forwarded to get_overexpressed_markers_in_one_core(). As a result, the CLI/analysis --inflate-factor/inflate_factor setting has no effect on one-core denoising in this PR. Pass inflate_factor=inflate_factor into get_overexpressed_markers_in_one_core (and consider adding a small unit/integration assertion that changing inflate_factor changes the sampled removals).
    markers_to_remove = get_overexpressed_markers_in_one_core(
        node_marker_counts=node_marker_counts,
        node_core_numbers=node_core_numbers,
        pval_significance_threshold=pval_significance_threshold,
    )
    one_core_layer = node_marker_counts[

Comment thread src/pixelator/common/graph/node_pls.py Outdated
Comment thread src/pixelator/pna/cli/denoise.py Outdated
Comment thread tests/pna/denoise/test_denoise.py Outdated
Comment thread tests/pna/denoise/test_denoise_cli.py Outdated
ptajvar and others added 3 commits May 11, 2026 15:17
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f97f8fb13790711f0df6ab1f654ece94c994377b740488b34f9ba929039fb1e3
oid sha256:9ea12d7216921248164d412b406aff86e5be0744bd87612b3ef19163e21b3e04
Copy link
Copy Markdown
Contributor

@ptajvar ptajvar May 11, 2026

Choose a reason for hiding this comment

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

What has changed here is the metadata pxl file metadata that switches panel name from proxiome-immuno-155 to proxiome-v1-immuno-155-v1.0

Copy link
Copy Markdown
Contributor

@johandahlberg johandahlberg left a comment

Choose a reason for hiding this comment

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

This looks great. I have focused the review on readability and pythonisity (if that is a word). I have made inline comments where I think updates might be useful, but there is nothing here that blocks merging.

A high level comment is that I think the adaptive_core_expansion function could benefit from being split into multiple smaller subfunctions to improve the testability, e.g. one for each conceptual stage:

  • find_k_core_seed
  • create_transition_probablity_matrix
  • select_largest_connected_component
  • find_partition (called once per binding_threhold)
  • select_best_parition_candidate

Great work the whole team of @ptajvar, @maxkarlsson and @ludvigla in getting this over the finishing line so quickly!

Comment thread src/pixelator/common/graph/adaptive_core_expansion.py Outdated
Comment thread src/pixelator/common/graph/adaptive_core_expansion.py Outdated
Comment thread src/pixelator/common/graph/adaptive_core_expansion.py Outdated
Comment thread src/pixelator/common/graph/adaptive_core_expansion.py Outdated
Comment thread src/pixelator/common/graph/adaptive_core_expansion.py
Comment on lines -47 to -48
number_of_disqualified_components: int | None
ratio_of_disqualified_components: float | None
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.

How come this is removed?

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.

A component would have been disqualified for having a k=1 layer that was larger than a given threshold. That only made sense for core-one denoising. Now that we multiple methods, a component may skip one denoising method but be denoised by another method. So instead of this, we store how many nodes are marked by each method to be removed from each component in the anndata.

Comment thread src/pixelator/pna/cli/denoise.py Outdated
Comment thread tests/common/test_adaptive_core_expansion.py Outdated
Comment thread tests/pna/denoise/test_denoise.py Outdated
Comment thread tests/pna/denoise/test_denoise_cli.py Outdated
@ludvigla ludvigla merged commit 9287501 into dev May 12, 2026
15 checks passed
@ludvigla ludvigla deleted the pna-2591 branch May 12, 2026 09:31
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.

5 participants