Skip to content

Atomic output files, exact CpG context for the non-converted filter, directional-library caveat - #97

Merged
semenko merged 1 commit into
mainfrom
tidy-ups
Nov 5, 2026
Merged

semenko merged 1 commit into
mainfrom
tidy-ups

Conversation

@semenko

@semenko semenko commented Nov 5, 2026

Copy link
Copy Markdown
Member

Summary

Three loose ends from the recent work.

1. Output files are written atomically

The matrix was saved to its final name first, and the fragment lengths and metadata were appended afterwards. A run interrupted in between left a file that loaded fine but was incomplete, and the next run skipped it because the output "already existed".

Each .methylation.npz is now assembled under a temporary name and renamed into place once complete. Consequences:

  • A failed or interrupted run leaves no output file, so re-running the same command redoes that BAM.
  • A failed --overwrite leaves the existing output untouched.
  • Overwriting now needs a writable directory, since the replacement is written beside the old file. Validation checks this up front.

2. --filter-non-converted: exact CpG context

The count of retained non-CpG cytosines decided CpG context from the neighbouring reference base, as reconstructed from the read. A methylated CpG cut by the end of a read has no visible neighbour, so it was counted as a retained non-CpG cytosine. The extractor already holds the CpG index, so context now comes from there.

Measured on real bwa-meth output, directional pairs, 10,018 reads:

Before After
Reads whose count equals a count made from the FASTA 9,908 10,018
Reads over by one 110 0

count_non_cpg_retained_reference() gains an optional cpg_positions argument. Called without it, it behaves as before.

3. Documentation: strand tags are only as good as the aligner

While checking the above I found that the upstream nf-core test data is a non-directional library. Bismark, in its default directional mode, aligned only the original-strand pairs. bwa-meth also aligned the complementary-strand pairs, but tags about 80% of them with a YD value that contradicts their bases:

Pairs in the upstream bwa-meth file Reads YD agrees with the bases
Also aligned by Bismark (directional) 10,018 all
bwa-meth only (complementary strands) 7,491 about 20%

For such pairs every CpG is called methylated whatever its true state. This is a limitation of aligning a non-directional library with bwa-meth, which assumes a directional one. It was equally true before the fragment-row change, and it cannot be detected from the tags. The bundled fixture is unaffected, because it keeps only fragments both aligners placed.

The README, CLAUDE.md and the fixture notes now state that bam2tensor trusts the strand tag, when that is safe, and what the MD tag can and cannot validate.

Tests

354 pass. New tests cover: failed and interrupted writes at each step, a failed overwrite, a re-run after a failed save, CpG context at both read ends for both strands, exact agreement with simulated truth for every read, and exact agreement with a FASTA-based count on real bwa-meth output.

nox: all 12 sessions pass locally.

…directional-library caveat

- Output: each .methylation.npz is assembled under a temporary name and
  renamed into place once the matrix, fragment lengths and metadata are all
  written. Previously the matrix was saved to its final name first, so an
  interrupted or failed run left a loadable but incomplete file that the
  next run skipped as already done. Overwriting now needs a writable
  directory (the replacement is written beside the old file), and a failed
  overwrite leaves the existing output intact.
- --filter-non-converted: CpG context for the retained-cytosine count now
  comes from the CpG index instead of the neighbouring base reconstructed
  from the read. A methylated CpG cut by the end of a read was being
  counted as a retained non-CpG cytosine (about 1% of reads were over by
  one). On real bwa-meth output the count now equals a count made from the
  FASTA for every read. count_non_cpg_retained_reference() gains an
  optional cpg_positions argument; without it, behaviour is unchanged.
- Docs: bam2tensor trusts the aligner's strand tag, which bwa-meth only
  gets right for directional libraries. The upstream nf-core bwa-meth test
  file turned out to contain non-directional pairs whose YD tag contradicts
  their bases; the README, CLAUDE.md and the fixture notes now say so, and
  describe what the MD tag can and cannot validate.
@semenko semenko added bug Something isn't working documentation Improvements or additions to documentation labels Nov 5, 2026
@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown

Coverage report

Click to see where and how coverage changed

FileStatementsMissingCoverageCoverage
(new stmts)
Lines missing
  src/bam2tensor
  __main__.py
  functions.py
Project Total  

This report was generated by python-coverage-comment-action

@semenko
semenko merged commit 19bfab7 into main Nov 5, 2026
8 checks passed
@semenko
semenko deleted the tidy-ups branch November 5, 2026 20:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant