Summary
leech data prepare --pod5 <directory> is accepted, extracts zero chunks, exits 0, and reports a diagnostic that points at the wrong causes. A whole training corpus can silently come out empty.
Reproducer
import leech_core
# a real POD5 file works
leech_core.read_pod5_batch("run/pod5_pass/PAS67183_pass_..._13.pod5", ids)
# -> ok (5 of 5)
# the directory containing it does not
leech_core.read_pod5_batch("run/pod5_pass", ids)
# -> OSError: Failed to open POD5 run/pod5_pass: I/O error: No such device (os error 19)
Via the CLI:
leech data prepare --pod5 <dir> --bam aln.bam --output-dir out \
--motif CCAGGC --motif-offset 2 --motif-reference fasta \
--reference-fasta ref.fa --label uncharged --workers 8 --no-split --no-compress
Log (abridged) — 64,003 reads in, 0 chunks out, exit status 0:
WARNING - Rust batch failed, skipping: Failed to open POD5: I/O error: No such device (os error 19)
INFO - Progress: 60 batches, 60000 reads | 0 chunks extracted
...
INFO - Parallel processing complete: extracted 0 chunks from 64003 reads
WARNING - 0 chunks extracted from 64003 reads. Common causes: indels at motif site,
insufficient context, or MAPQ filtering (--min-mapq=0).
Stats: {'total_reads': 64003, 'reads_with_motif': 0, 'reads_without_motif': 64003, 'total_chunks': 0}
Every one of those "common causes" is wrong here, which is what made it expensive to chase — the BAM, motif and reference were all fine.
Cause
--pod5 is declared as
@click.option("--pod5", required=True, type=click.Path(exists=True, path_type=Path),
help="POD5 file with raw signal")
click.Path(exists=True) accepts directories, so a directory passes validation even though the help says "POD5 file". It reaches escapepod_signal::Reader::open (rust/src/pod5_io.rs:35, and the same call in inference_pipeline/{training,inference}.rs), which fails with ENODEV. preparation/parallel.py catches that per batch, logs it at WARNING, and continues, so every batch fails identically and the run still ends "successfully".
This is not a symlink problem — I initially suspected that and it is wrong. read_pod5_batch reads a symlinked POD5 fine; real / symlink / hardlink all succeed. Only the directory path fails.
Suggested fixes
Any one of these would have turned an afternoon into a second:
- Validate the input:
click.Path(exists=True, dir_okay=False, path_type=Path) on --pod5 (both sites: cli.py:50 and cli.py:1461). Cheapest fix.
- Or support directories, since a MinKNOW run is a directory of POD5s and passing one is the obvious thing to try — glob
*.pod5 and search across them. escapepod's Python iter_read_signals already does exactly this, so the behaviour would match.
- Do not let every batch fail silently. If N consecutive batches fail to open the POD5, or if 0 chunks are extracted from a non-zero number of reads, that should be an error and a non-zero exit, not a WARNING. Consider counting distinct failure reasons and surfacing the actual error text in the summary rather than a fixed list of guesses.
Happy to send a PR for (1) + (3) if that is the preferred shape.
Environment
- leech 0.3.2 (submodule
36097c6), leech_core built from rust/, HAS_RUST=True
- escapepod 0.5.1, Python 3.12, linux-64
- POD5s from RNA004 direct-RNA runs, basecalled with dorado 1.4.0
rna004_130bps_sup@v5.3.0 --emit-moves
Summary
leech data prepare --pod5 <directory>is accepted, extracts zero chunks, exits 0, and reports a diagnostic that points at the wrong causes. A whole training corpus can silently come out empty.Reproducer
Via the CLI:
Log (abridged) — 64,003 reads in, 0 chunks out, exit status 0:
Every one of those "common causes" is wrong here, which is what made it expensive to chase — the BAM, motif and reference were all fine.
Cause
--pod5is declared asclick.Path(exists=True)accepts directories, so a directory passes validation even though the help says "POD5 file". It reachesescapepod_signal::Reader::open(rust/src/pod5_io.rs:35, and the same call ininference_pipeline/{training,inference}.rs), which fails withENODEV.preparation/parallel.pycatches that per batch, logs it at WARNING, and continues, so every batch fails identically and the run still ends "successfully".This is not a symlink problem — I initially suspected that and it is wrong.
read_pod5_batchreads a symlinked POD5 fine; real / symlink / hardlink all succeed. Only the directory path fails.Suggested fixes
Any one of these would have turned an afternoon into a second:
click.Path(exists=True, dir_okay=False, path_type=Path)on--pod5(both sites:cli.py:50andcli.py:1461). Cheapest fix.*.pod5and search across them.escapepod's Pythoniter_read_signalsalready does exactly this, so the behaviour would match.Happy to send a PR for (1) + (3) if that is the preferred shape.
Environment
36097c6),leech_corebuilt fromrust/,HAS_RUST=Truerna004_130bps_sup@v5.3.0 --emit-moves