File structure checks - #35
Conversation
Added ability to filter BV-BRC genomes based on genome stat metadata, including CheckM completeness and CheckM contamination as defaults. Also enabled optional checking of genome length, CDS count, and GC content based on deviations from median distribution per bug. Updated genome downloader functions to use ftpes_download_one instead of old ftp_download_one, and moved a dot function helper to inside the ftpes_download_one loop to avoid issues with BiocParallel workers not understanding what that helper is.
Uncommenting a helper that was still needed for several calls. Fixing stuff I broke myself!
Updating to remove BiocParallel implementation that caused bugs. Patching back to prior Future implementation instead. CLI version of genome downloading appears to have issues that may be separate and requires further troubleshooting.
data_processing.R runDataProcessing() missing(ref_file_path) check ignores if the default path is used. Updated to accept the default path if a user doesn't specify rather than erroring at the end of the processing steps.
Updated default workers to 8 for CLI and FTP downloads, made two-try FTP download option the default, added a helper to purge broken file sets (i.e., if .faa is missing for an AccID but .fna and .gff download successfully).
ftpes --> ftps is_complete --> .is_complete
… consolidate on furrr
Cleaning up parallel implementation in data_curation.R, standardizing with furrr and dropping old duplicated within-loop helper function that was needed for earlier BiocParallel implementation. Standardized values for contamination and completeness across functions. Added a parameter in prepareGenomes to allow users to set number of workers. Reduced console clutter for final "out" print.
…to qc_filtering
…to qc_filtering
In data_curation.R, adding ability to export genome stats and QC metadata into human readable CSVs. `load_tables` allows you to run "example <- exportTables("data/Staphylococcus_aureus/Sau.duckdb", load_tables=TRUE)" and import the tables into R so you can play around with them directly. By default, it just saves the tables as exported CSVs. In data_processing.R, added a similar exporter that allows you to save out all the feature data and other DuckDB-connected tables, with the ability to output sequences too, and append AST phenotype data to the feature tables. Tables can be saved out as csv (default), tsv, Parquet, and xlsx. Added `writexl` to Suggests in DESCRIPTION. Fixed some reversions where I broke things in the PR this branch is based on. Will add those same fixes to that PR momentarily to keep things aligned.
Ran data processing workflow for Campylobacter coli. This can be unzipped and used as the test data for exportProcessedData() rather than having to run it all beforehand. Data curation step still requires that prepareGenomes() be run for a test bug, and Staphylococcus argenteus is a good use case for this.
Clarified a few names based on code review feedback.
An addendum to the problem.Positron:
RStudio:
|
Fix unreachable BV-BRC cache staleness check
.ensure_bvbrc_cache() only called .updateBVBRCdata() when the cache file/table was completely absent, so its max_age_days staleness check never ran once a cache existed. bvbrc_bac_data is one global table covering all of Bacteria, so this meant results silently depended on whenever *any* prior species query happened to build the cache, with no reproducibility across machines/sessions regardless of age.
Root-caused this from divergent prepareGenomes("Staphylococcus epidermidis") genome counts between two environments (872 vs 926 targets) that only converged (2211/2212) after a manual .updateBVBRCdata(max_age_days = 0).
Now .ensure_bvbrc_cache() always delegates to .updateBVBRCdata(),
which already has the correct fresh-vs-stale logic. max_age_days is
now a parameter on .ensure_bvbrc_cache() (default 30, unchanged) for
future tuning; left as-is since this is an internal function.
|
@epbrenner, I only retrieved 17 (of your 39) Sep genomes.
|
|
Providing a status update while I'm working on this: I'm now testing if inconsistency in the ordering of entries in the metadata download plus our initial parsing into tabular format might be leading to variation in the counts. Just auditing the freshly downloaded cache file yields different numbers of rows each time the download is repeated (e.g., 830,312 vs 836,173). |
|
Quick update on the GFFs of the 17 Sepi I downloaded: On the GFFs themselves — one real structural anomaly found among the 17: 1282.3129 is the clear structural outlier of the 17. Quantified across all genomes:
Every other genome has locus_tag on 96-99% of its CDS features (the small gaps are normal — pseudogenes/truncated genes). 1282.3129 has zero — not one of its 2563 CDS records carries a locus_tag attribute at all. Combined with the two other things I found on it earlier (no ##sequence-region headers, and self-referential placeholder contig IDs accn|1282.3129.con.0001 instead of a real NCBI accession like every other genome uses), this genome looks like it came through an older/different BV-BRC annotation pathway that never populated the standard GFF3 metadata fields. 1282.2719 is otherwise normal (2402/2490 locus_tags) — its only quirk is the missing sequence-region headers, which is probably cosmetic. locus_tag is exactly the kind of field a Prokka-oriented tool like Panaroo would lean on as a stable per-feature key when doing non-Prokka GFF cleanup (you had mentioned this: "a necessary step for non-Prokka GFFs"). A genome where that key is entirely absent is a strong candidate for hitting a degenerate fallback code path inside Panaroo's gene-validity filter. @epbrenner, maybe we can compare with 'good' Campy or S. argenteus? Should we look for this error in bad Klebs, too? |
|
The S. argenteus run finished completely — full pangenome output written (gene_presence_absence_roary.csv, final_graph.gml, etc.), total wall time ~2 minutes for 73 genomes. That's right in your normal "5-10 min for ≤400 genomes" range. From C: Notably, this isn't "more total searches" — Staph epi's ~10,460/9,641 searches over 17/16 genomes is actually a similar per-genome rate to argenteus's 37,278 over 73 genomes (~600 vs ~510 per genome). The difference is entirely in per-item rate: argenteus held steady at ~3 items/sec throughout; Staph epi collapsed toward zero after item ~8-12. So whatever's wrong makes individual searches pathologically expensive, not more numerous — consistent with fragmentation forcing expensive boundary-reconciliation work per gene, or (alternate theory still on the table) IS-element/transposase-driven near-identical-sequence ambiguity making individual alignment searches degenerate. Good next test: take the least fragmented Staph epi genomes (starting with 1282.2719 at 4 contigs) as a new mini-batch, and separately try adding one of the highly-fragmented ones (say Update: So: the batch without 904315.3 sailed past the exact step where every batch containing 904315.3 has permanently frozen (Test 1 still stuck at item 11, now ~15 minutes in). This is about as clean a confirmation as we're going to get — 904315.3 (144 contigs, the most fragmented Staph epidermidis genome in the set) looks like the real trigger, not 1282.3129's missing locus_tags, not a generic environment slowdown. |
|
Intermediate update from our conversation Neither metric (#contigs or #pseudogenes) singles out Pseudogene count doesn't correlate with contig count, and 904315.3 isn't the max: Boundary-truncation check (genes within 50bp of a contig edge, i.e. plausibly split/truncated by fragmentation): only 53 of 2501 features (2.1%) in 904315.3 are edge-adjacent. That's a small fraction, not an overwhelming majority — so it's not that most of its genes are boundary-split either. So your pseudogene theory isn't confirmed by raw counts, and my fragmentation theory is weaker than I made it sound too — 904315.3 is really only unusual in one respect (144 contigs, a genuine outlier vs. everyone else's ≤103), not in pseudogene load or boundary-truncation rate. This matches his broader intuition that "more contigs → faster" generally, since 904320.3 (87 contigs, most pseudogenes) is fine — 904315.3 may be an outlier for a reason specific to that extreme contig count rather than fragmentation-in-general or pseudogene load-in-general. |
|
@epbrenner @AbhirupaGhosh | Claude and Evan agree with each other! Root cause found: it's Panaroo's refind step, not a specific "bad" genome. Confirmed with two independent tests on different genome sets:
What we ruled out:
What we found: every batch we tested that degraded still contained 1282.2719. With --refind-mode off, Panaroo's --remove-invalid-genes step flagged 25 genes in the entire 17-genome run — all 25 from 1282.2719 alone — each with an internal premature stop codon (pseudogene-like truncations). No other genome in the batch contributed any. That matches your original instinct about pseudogenes: refinding tries to relocate/reconcile these truncated, ambiguous ORFs against the rest of the pangenome, and that reconciliation is where the cost explodes, not a generic "many contigs" or "missing metadata" property. Suggested next steps:
|
prepareGenomes(), retrieveMetadata(), etc. were out of sync with their actual current signatures (missing num_workers, max_checkm_contam, export_tables, etc. — all qc_filtering/export_tables-era additions that never got a devtools::document() run). devtools::document() call just caught the .Rd files up to match the real source. Net positive side effect, unrelated to the refind change itself, but worth knowing it's now folded into this same diff. Summary of what's done for the refind edit: 1. .processPanaroo() / .runPanaroo() / runPanaroo2Duckdb() / runDataProcessing() all now accept refind_mode/panaroo_refind_mode, threaded end-to-end, defaulting to "off". 2. Roxygen docs on all four, objective tone with the runtime caveat, noting the default is temporary pending a QC filter. 3. vignettes/intro.Rmd example call now shows panaroo_refind_mode = "off". 4. README.Rmd/README.md both get a new bullet explaining the default and how to restore normal behavior. 5. Package reinstalled and verified — all four functions report the correct default via formals(). Co-Authored-By: Evan Pierce Brenner <108823789+epbrenner@users.noreply.github.com>
Co-Authored-By: Evan Pierce Brenner <108823789+epbrenner@users.noreply.github.com>


The problem:
It has been a known issue that sometimes, pangenomes will fail to complete through
data_processing.R. This has prevented Klebsiella from running in prior attempts, and Abhirupa isolated the issue to a subset when the pangenome generation was run in batches and smaller pangenomes merged. Some genomes in one particular subset appeared to cause a failure, but we had no further diagnostic information. Because we have also seen inflation in feature matrix sizes as more genomes have been added, I originally sought to implement QC filtering to exclude contaminated or incomplete genomes. However, in my current testing using these QC filters, I have found another, smaller subset of genomes that do not complete in Panaroo.Details:
prepareGenomes(user_bacs = "Staphylococcus epidermidis", max_checkm_contam = 0)returns 39 genomes with AST data, a very small set. Panaroo fails to generate a pangenome after 1 hour of running. Logs stop printing when invalid genes are being removed from GFFs, a necessary step for non-Prokka GFFs. Despite this freeze, CPUs are locked at 400% use, indicating 4 cores are fully occupied during this entire time. Disk I/O is static, with no read and a constant small write, but no output is observed. RAM use is static and low. No hardware approaches its limits during the freeze. This occurs with our older Panaroo 1.5.3 container, and a much more recent 1.7.0 container, and after purging all Docker images and caches and rebuilding everything freshly, and when purging the package and installing everything freshly. Pangenomes run and complete in 5-10min for other bugs with 400 genomes or fewer.Approach:
In this PR, I am planning on subsetting this batch of 39 genomes down to identify an even smaller set that does not complete. Once I identify a few candidate GFFs that may be responsible, I will try to find atypical patterns in the way their gene headers are formatted that might lead to a CPU deadlock.