Skip to content

Add cluster metrics viz - #11372

Merged
dbaku42 merged 70 commits into
nf-core:masterfrom
dbaku42:add-cluster-metrics-viz
May 24, 2026
Merged

Add cluster metrics viz#11372
dbaku42 merged 70 commits into
nf-core:masterfrom
dbaku42:add-cluster-metrics-viz

Conversation

@dbaku42

@dbaku42 dbaku42 commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Description

This PR adds two new modules:

  • cluster_metrics: Computes standard clustering quality metrics (Silhouette score, Calinski-Harabasz index, Davies-Bouldin index) and performs a k-sweep analysis.
  • cluster_viz: Generates 2D visualizations (PCA, UMAP, t-SNE) colored by cluster label and exports the coordinates as TSV files.

Both modules are designed to work together with the existing clustering module (which performs KMeans/DBSCAN on PLINK2 PCA results).

Features

  • Full support for conda via environment.yml
  • Comprehensive nf-test (normal + stub tests)
  • Clean output channels with proper naming
  • Optional plots (PNG) for visualization

Author

Related modules

  • Related to: #11337 (clustering)

This completes the core components of the snpclustering subworkflow.

@SPPearce

Copy link
Copy Markdown
Contributor

Um, you need to rebase these changes on the current master, as there was a bulk update (and you have a massive conflict ;) ).
Ping me when you have done that.

@dbaku42
dbaku42 force-pushed the add-cluster-metrics-viz branch from c038148 to 9e0f196 Compare April 30, 2026 12:29
@SPPearce

Copy link
Copy Markdown
Contributor

Hmm, this is an entirely custom set of modules?
This needs discussion as to where they live.
They could potentially be in custom/

@dbaku42

dbaku42 commented May 4, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @SPPearce, that makes sense.

I’ve moved both custom Python-based modules under modules/nf-core/custom/, updated the internal paths and tests, fixed the author typo, and refreshed the nf-test snapshots.

Both module tests now pass locally for cluster_metrics and cluster_viz.

@SPPearce SPPearce 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.

I think it'd need to be custom/clustermetrics and custom/clustervisualiation (no underscores allowed).
You'll need to use template directly, rather than referring to ${projectDir}/modules/nf-core/custom/cluster_metrics/templates/cluster_metrics.py.

@dbaku42

dbaku42 commented May 6, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @SPPearce — I renamed the custom modules to remove underscores and updated both modules to use ${moduleDir} instead of hardcoded ${projectDir} paths. I also updated the tests and snapshots accordingly.

@dbaku42
dbaku42 force-pushed the add-cluster-metrics-viz branch from 462fff8 to e4877e4 Compare May 6, 2026 22:17

@SPPearce SPPearce 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.

You can't use moduleDir at all in the main.nf, it won't work on cloud systems as the python script won't be there.
You need to use a template, like these modules.

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.

You could make the docker/singularity containers via Seqera Containers rather than needing a custom image in the nf-core quay.io.

@pinin4fjords pinin4fjords left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hi @dbaku42, thanks for the contribution. AI-assisted review (Claude, on behalf of @pinin4fjords). Some overlap with @SPPearce's earlier feedback. Beginner-friendly notes plus suggestion blocks you can apply directly.

The biggest fix is the templates/ mechanism. Right now your script: block runs python3 ${moduleDir}/templates/cluster_metrics.py, which is just running a normal Python script that happens to live in templates/. No templating is happening. This works on your laptop because ${moduleDir} resolves to a local path, but it breaks on cloud/HPC executors because that folder is not staged into the work directory the task runs in.

Nextflow's template directive does two things:

  1. Stages the file into the task work directory automatically (so it works on any executor).
  2. Runs the file through Groovy GString interpolation before staging, so ${features}, ${task.ext.prefix}, etc. get substituted into the Python source. This replaces argparse entirely.

Because the file goes through Groovy interpolation, a few Python source patterns need adjusting (\n -> \\n, r"\s+" -> r"\\s+", etc., I verified this locally). I have flagged the specific spots inline. See modules/nf-core/custom/tx2gene/templates/tx2gene.py for the canonical shape.

Two scope questions for discussion:

  • The *_selected.json "best k" output bakes a decision into the module, and the sweep refits KMeans regardless of what method produced the input labels. Could the module just emit the k-sweep table and let the consuming pipeline pick?
  • cluster_viz does PCA + UMAP + t-SNE + plotting + TSV export, and the PCA piece is essentially a re-plot of upstream PLINK eigenvec output. Consider dropping PCA plotting from this module and tightening to the embedding methods that genuinely belong together.

The clustervisualiation directory name looks like a typo for clustervisualization; worth fixing while you're renaming things.

Comment thread modules/nf-core/custom/clustermetrics/Dockerfile Outdated
Comment thread modules/nf-core/custom/clustermetrics/main.nf Outdated
Comment thread modules/nf-core/custom/clustermetrics/main.nf Outdated
Comment thread modules/nf-core/custom/clustermetrics/main.nf Outdated
Comment thread modules/nf-core/custom/clustermetrics/main.nf
Comment thread modules/nf-core/custom/clustervisualiation/templates/cluster_viz.py Outdated
Comment thread modules/nf-core/custom/clustervisualiation/templates/cluster_viz.py Outdated
Comment thread modules/nf-core/custom/clustervisualiation/templates/cluster_viz.py Outdated
Comment thread modules/nf-core/custom/clustervisualiation/templates/cluster_viz.py Outdated
Comment thread modules/nf-core/custom/clustervisualiation/tests/main.nf.test Outdated
dbaku42 and others added 6 commits May 8, 2026 13:06
Co-authored-by: Jonathan Manning <pininforthefjords@gmail.com>
Co-authored-by: Jonathan Manning <pininforthefjords@gmail.com>
Co-authored-by: Jonathan Manning <pininforthefjords@gmail.com>
Co-authored-by: Jonathan Manning <pininforthefjords@gmail.com>
…iz.py

Co-authored-by: Jonathan Manning <pininforthefjords@gmail.com>
…iz.py

Co-authored-by: Jonathan Manning <pininforthefjords@gmail.com>

@SPPearce SPPearce 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.

You seem to still have pcaclustering as well as plink2pcaclustering.
Actually, I'm wondering if this should just go into plink2/pcaclustering

Replace the PLINK2 .eigenvec parser with a plain pandas read of a TSV
that has a `sample_id` column plus numeric feature columns - the same
input contract `custom/clustermetrics` already uses. PLINK2 eigenvec
output is one supported source (drop FID, rename IID -> sample_id), not
the only one.

Also removes the duplicate `plink2pcaclustering/` directory left behind
by an in-flight rename.

clusters.csv / clustering_info.json md5s are byte-identical to those
recorded in the existing snapshot; the .snap file is unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@pinin4fjords

Copy link
Copy Markdown
Member

@SPPearce @dbaku42 - while looking at the pcaclustering vs plink2pcaclustering duplication, I sketched an alternative: drop the PLINK-aware parser entirely and accept the same generic sample_id + numeric-features TSV that clustermetrics already takes. PLINK eigenvec then becomes a 1-line awk upstream.

Verified locally that the existing snapshot stays byte-identical (proof in the PR body): dbaku42#1

Targeted at @dbaku42's branch as a proposal, not a request - if you'd rather go the other way and nest under plink2/pcaclustering, happy to close it. Just wanted to put the diff on the table.

AI-assisted (Claude, on behalf of @pinin4fjords).

…c-input

Make custom/pcaclustering accept generic sample_id TSV
@dbaku42

dbaku42 commented May 21, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @pinin4fjords! Keeping it general is a better approach. I've already merged your proposal.

@pinin4fjords

Copy link
Copy Markdown
Member

Thanks @pinin4fjords! Keeping it general is a better approach. I've already merged your proposal.

OK, lemme fix what's broken

pinin4fjords and others added 2 commits May 21, 2026 13:02
… fixture

The earlier generic-input refactor (PR nf-core#11372 / #1) pointed at
`popgen/clustering/test.tsv`, but the actual fixture in
nf-core/test-datasets (added in nf-core/test-datasets#2051) is named
`test_features.tsv`. CI was failing with "No such file or directory" on
all three pcaclustering tests as a result.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@pinin4fjords

Copy link
Copy Markdown
Member

Pushed 50d2883f to fix the CI failure on custom/pcaclustering - the test fixture in nf-core/test-datasets is named test_features.tsv, not test.tsv. My mistake in the earlier refactor PR (#11372 / dbaku42#1). One-line path fix on the three test cases. AI-assisted (Claude, on behalf of @pinin4fjords).

Use the first column of the features TSV as sample IDs regardless of its
header name, and treat all remaining columns as numeric features. The
required column name was a hidden assumption about input shape - this
relaxes it without adding inputs, so callers can feed in e.g. a
reformatted PLINK eigenvec (with `IID` or any other ID header) directly,
as long as the ID column is first.

Output schema (clusters.csv emits `sample_id,cluster`) is unchanged, so
downstream consumers (clustermetrics, clustervisualization) see the same
contract. Verified clusters.csv / clustering_info.json md5s remain
byte-identical to the snapshot against the existing test fixture.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@SPPearce SPPearce 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.

That seems much better.
Just to check, does it work if the first column (the sample id's) are all numeric?

Pin the first column to `dtype=str` at read time so pandas doesn't
type-infer it as int and strip leading zeros (e.g. "0001" -> 1).
Same output for the existing fixture (which has string IDs already);
snapshot md5s unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@pinin4fjords

Copy link
Copy Markdown
Member

@SPPearce: computer says yes. Plain integer IDs work fine, but I pushed f5edb634 to also preserve zero-padded ones (0001 etc.) - pandas was type-inferring the first column as int and stripping the padding. Pinning dtype={0: str} at read time fixes it without changing snapshot md5s against the existing fixture.

@dbaku42

dbaku42 commented May 22, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @pinin4fjords for your help! Regarding the test.tsv file, I already opened a PR nf-core/test-datasets#2073

@pinin4fjords

Copy link
Copy Markdown
Member

Well, tests are passing now. FYI I'm about to go on leave. You have my approval, I'll let @SPPearce give his final approval when he's ready.

@dbaku42
dbaku42 added this pull request to the merge queue May 24, 2026
Merged via the queue into nf-core:master with commit 2599d25 May 24, 2026
60 checks passed
@dbaku42
dbaku42 deleted the add-cluster-metrics-viz branch May 24, 2026 15:47
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.

3 participants