Skip to content

data prepare: a directory passed to --pod5 yields 0 chunks and exit 0, with a misleading diagnostic #166

Description

@jayhesselberth

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:

  1. 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.
  2. 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.
  3. 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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions