Refresh the custom/orfcollapse fixture for the positional orf_class vocabulary - #2193
Merged
FelixKrueger merged 3 commits intoAug 3, 2026
Conversation
…class vocabulary nf-core/riboseq's custom/orfmerge no longer emits the length-derived `smORF` orf_class: a short ORF keeps its positional class and carries a separate `is_smorf` length flag. The catalogue also gained an `orf_type_native` column holding each caller's own ORF-type label. This fixture still carried `orf_class = smORF` and lacked both new columns, so custom/orfcollapse aborted on it -- the module now rejects an orf_class value it does not know rather than silently dropping those rows from its MultiQC table. The two identical-peptide rows keep the same class so the same row still survives the fold; the BED12, ORF-to-gene map, peptide FASTA and cluster TSV are unchanged.
…class vocabulary nf-core/riboseq's custom/orfmerge no longer emits the length-derived `smORF` orf_class: a short ORF keeps its positional class and carries a separate `is_smorf` length flag. The catalogue also gained an `orf_type_native` column holding each caller's own ORF-type label. This fixture still carried `orf_class = smORF` and lacked both new columns, so custom/orfcollapse aborted on it -- the module now rejects an orf_class value it does not know rather than silently dropping those rows from its MultiQC table. The two identical-peptide rows keep the same class so the same row still survives the fold; the BED12, ORF-to-gene map, peptide FASTA and cluster TSV are unchanged.
…class vocabulary nf-core/riboseq's custom/orfmerge no longer emits the length-derived `smORF` orf_class: a short ORF keeps its positional class and carries a separate `is_smorf` length flag. The catalogue also gained an `orf_type_native` column holding each caller's own ORF-type label. This fixture still carried `orf_class = smORF` and lacked both new columns, so custom/orfcollapse aborted on it -- the module now rejects an orf_class value it does not know rather than silently dropping those rows from its MultiQC table. The two identical-peptide rows keep the same class so the same row still survives the fold; the BED12, ORF-to-gene map, peptide FASTA and cluster TSV are unchanged.
FelixKrueger
added a commit
to FelixKrueger/nf-core-modules
that referenced
this pull request
Aug 3, 2026
custom/orfnormalise overwrote orf_class with "smORF" for every ORF at or under 100 aa, so a short uORF, a short downstream ORF, a short truncated CDS variant and a short novel intergenic ORF were indistinguishable in exactly the size range microprotein work cares about. Because custom/orfmerge keys its clustering strategy on orf_class and custom/orfcollapse scoped its peptide dedup to that value, the length override also decided how an ORF merged across callers. orf_class is now purely positional, and length moves to a separate is_smorf flag driven by a new --smorf-max-aa (default 100, reproducing the previous membership exactly). The vocabulary gains uoORF, doORF and intORF, which the callers were already reporting: RiboCode and ribotricer emit Overlap_uORF/Overlap_dORF, RiboCode and Ribo-TISH emit internal, PRICE emits uoORF and iORF. ORF-type tokens are now matched exactly rather than by substring, which is what previously folded overlap_uORF into uORF. orfmerge partitions by clustering strategy with orf_class absent from every grouping key, because callers disagree on class for the same ORF -- Ribo-TISH reports 5'UTR for both uORFs and CDS-overlapping uORFs -- and keying on class emits one row per disagreeing caller. Reciprocal overlap is measured on summed exon-block intersection rather than the outer span, which for a spliced ORF is mostly intron, and linkage is complete so a chain of partial overlaps cannot fold distinct ORFs together. Unknown classes and unassigned rows now abort. orfcollapse derives its eligible set from aa_length plus --smorf-max-aa rather than a propagated flag, and prefers the more specific class when a peptide cluster spans several, so an annotated CDS is not deleted by a longer novel ORF sharing its peptide. Adds orf_type_native, carrying each caller's own label through to the catalogue so every harmonisation decision stays auditable. Requires the refreshed orfcollapse fixture from nf-core/test-datasets#2193.
LouisLeNezet
pushed a commit
to LouisLeNezet/modules
that referenced
this pull request
Aug 6, 2026
…-core#12498) * Decouple orf_class from ORF length across the custom/orf* modules custom/orfnormalise overwrote orf_class with "smORF" for every ORF at or under 100 aa, so a short uORF, a short downstream ORF, a short truncated CDS variant and a short novel intergenic ORF were indistinguishable in exactly the size range microprotein work cares about. Because custom/orfmerge keys its clustering strategy on orf_class and custom/orfcollapse scoped its peptide dedup to that value, the length override also decided how an ORF merged across callers. orf_class is now purely positional, and length moves to a separate is_smorf flag driven by a new --smorf-max-aa (default 100, reproducing the previous membership exactly). The vocabulary gains uoORF, doORF and intORF, which the callers were already reporting: RiboCode and ribotricer emit Overlap_uORF/Overlap_dORF, RiboCode and Ribo-TISH emit internal, PRICE emits uoORF and iORF. ORF-type tokens are now matched exactly rather than by substring, which is what previously folded overlap_uORF into uORF. orfmerge partitions by clustering strategy with orf_class absent from every grouping key, because callers disagree on class for the same ORF -- Ribo-TISH reports 5'UTR for both uORFs and CDS-overlapping uORFs -- and keying on class emits one row per disagreeing caller. Reciprocal overlap is measured on summed exon-block intersection rather than the outer span, which for a spliced ORF is mostly intron, and linkage is complete so a chain of partial overlaps cannot fold distinct ORFs together. Unknown classes and unassigned rows now abort. orfcollapse derives its eligible set from aa_length plus --smorf-max-aa rather than a propagated flag, and prefers the more specific class when a peptide cluster spans several, so an annotated CDS is not deleted by a longer novel ORF sharing its peptide. Adds orf_type_native, carrying each caller's own label through to the catalogue so every harmonisation decision stays auditable. Requires the refreshed orfcollapse fixture from nf-core/test-datasets#2193. * Address the nf-core#12498 review: cross-strategy merging and flag-driven collapse Class selects the clustering strategy in orfmerge, so two callers agreeing on an ORF's exact structure but disagreeing on its class were partitioned into different strategies and could never merge -- the de-merge this PR set out to remove, surviving one level down. Rows sharing an identical exon structure are now grouped first and clustered as a single proxy chosen by class specificity, with the members restored afterwards. Output is byte-identical on every existing fixture, so a synthetic test pins the new behaviour. orfcollapse takes eligibility from the catalogue's is_smorf rather than re-deriving it, and requires the column to be present and 0/1. --smorf-max-aa is kept solely to re-derive the flag and abort when the two disagree, so a threshold passed to one process and not the other fails loudly instead of silently narrowing the collapse scope. This keeps the flag and the length on a row in agreement, which deriving is_smorf from the row's own aa_length needs. orfnormalise aborts on an unmapped ORF-type token instead of counting it: the token falls through to `other` and selects the wrong merge strategy. Explicit `other` mappings are unaffected, since they report as matched. Also corrects two shipped docs against the code: orfcollapse's representative is chosen by class specificity before length, and orfmerge's reciprocal overlap is measured on summed exon-block intersection, not the outer genomic span. The two one-off orfnormalise test configs are replaced by a shared tests/nextflow.config exposing params.module_args. * Validate ORF classes on the input rows, not the merge proxies The exact-structure pre-grouping moved the unknown-class guard onto proxies. representative() ranks an unknown class last, so at a shared structure a known class always won the proxy and the unknown one escaped the guard entirely, then write_catalogue re-emitted the row under the known class -- the silent swallow the guard exists to prevent. Validate the input rows before grouping. Also states orfnormalise's fail-fast behaviour in meta.yml: an unmatched token aborts before outputs are written, so a non-zero unmapped_orf_type can never appear in the provenance line.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The
custom/orfcollapsefixture still carriesorf_class = smORF, which nf-core/riboseq's ORF catalogue no longer produces — a short ORF now keeps its positional class (uORF,dORF,canonical_cds, …) and a separateis_smorfcolumn carries the length flag. The catalogue also gained anorf_type_nativecolumn holding each caller's own ORF-type label. This updatescohort.catalogue.tsvto that schema, so the three small ORFs becomeuORF/uORF/dORFand both new columns are present. The BED12, ORF-to-gene map, peptide FASTA and cluster TSV are unchanged, and the module test still collapses the same two identical-peptide rows onto the same survivor.AI-assisted detail — keep or bin as you see fit
Needed because
custom/orfcollapsenow aborts on anorf_classvalue it does not recognise, rather than silently omitting those rows from its per-class MultiQC table. WithsmORFgone from the module's vocabulary, the current fixture fails the guard.Row-level changes to
cohort.catalogue.tsv:is_smorforf_type_nativeorf_00000001smORFuORF5'UTRorf_00000002smORFuORFuORForf_00000003canonical_cdscanonical_cdsAnnotatedorf_00000004smORFdORF3'UTRorf_00000001andorf_00000002keep the same class on purpose. They are the identical-peptide pair the fixture exists to exercise, and orfcollapse prefers the more specific class when a peptide cluster spans several — so giving them different classes would change which row survives the fold and break the test'sorf_00000001survivor assertions.cohort.catalogue.mqc.tsvis regenerated over the module's current class order. It is not consumed by any test, but leavingsmORFin it would misinform the next reader.Verified by running the module template against the updated fixture: all thirteen assertions in
custom/orfcollapse'smain.nf.testhold — 4 rows collapse to 3,orf_00000002folds intoorf_00000001, the survivor unions both callers and both samples, the ORF-to-gene map remapsg5/t5onto the survivor, and the peptide FASTA keeps only the survivor. The survivor'sorf_type_nativebecomes5'UTR,uORF, the union of the folded members' labels.The corresponding pipeline change is not yet open as a PR; this fixture update has to land first, since the module test cannot go green without it.