From a9387a20892ee7385eea902a697a4d0c8c479b6a Mon Sep 17 00:00:00 2001 From: Andrew D Smith Date: Wed, 9 Sep 2026 11:34:50 -0700 Subject: [PATCH 01/11] CMakeLists.txt and data/test_data/md5sum.txt: updating version number and reflecting this in test output file hashes --- CMakeLists.txt | 2 +- data/test_data/md5sum.txt | 34 +++++++++++++++++----------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 67251f3..03a5955 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ # $ cmake --version cmake_minimum_required(VERSION 3.30) project(falco - VERSION 2.0.1 + VERSION 2.0.2 DESCRIPTION "A tool to assess the quality of sequence read data" HOMEPAGE_URL https://github.com/smithlabcode/falco LANGUAGES CXX diff --git a/data/test_data/md5sum.txt b/data/test_data/md5sum.txt index dd86140..76372d5 100644 --- a/data/test_data/md5sum.txt +++ b/data/test_data/md5sum.txt @@ -1,17 +1,17 @@ -f59cf622d7d36b9d2f1170e6d63438ab bam_mult_out/bam_1/fastqc_data.txt -6318172912a6b7f9d348ce7b0b0618ed bam_mult_out/bam_2/fastqc_data.txt -f59cf622d7d36b9d2f1170e6d63438ab bam_orig_dups_out/bam_1/fastqc_data.txt -f59cf622d7d36b9d2f1170e6d63438ab bam_out/bam_1/fastqc_data.txt -f59cf622d7d36b9d2f1170e6d63438ab bam_tiles_out/bam_1/fastqc_data.txt -c2d51938c568b3c224efe9134baddb03 fastq_bgzf_mult_out/fastq_bgzip_1/fastqc_data.txt -eb09f6189fbb1519d87d3c349d0be279 fastq_bgzf_mult_out/fastq_bgzip_2/fastqc_data.txt -c2d51938c568b3c224efe9134baddb03 fastq_bgzf_out/fastq_bgzip_1/fastqc_data.txt -c2d51938c568b3c224efe9134baddb03 fastq_bgzf_threads_out/fastq_bgzip_1/fastqc_data.txt -5d74ecab5efbdb35e775e969411cbda4 fastq_gz_out/fastq_gz_1/fastqc_data.txt -be637ed4a4ebf2f1a64edf30ef1f8bb8 fastq_plain_out/fastq_1/fastqc_data.txt -bc8fd6e40a0ca55cb00634bc306a896c groups_out/bam_1/fastqc_data.txt -a1952ae366bd7c7207f40db833b1d16b sam_out/sam_1/fastqc_data.txt -3fe18e2ee85e3912ede8c57559bf9f88 preseq_out/bam_1/preseq_hist.txt -045639456fbe4a81b5de2789ceb95ddd bam_kmers_out/bam_1/fastqc_data.txt -a1aa02211d63f507646a9e12122f0226 sam_stdin_out/sam_stdin/fastqc_data.txt -c61e37d2c8d64b6950a5dad300e5fd60 fastq_stdin_out/fastq_stdin/fastqc_data.txt +64cd41bd26bfd42e234fc3799bf245e8 bam_tiles_out/bam_1/fastqc_data.txt +ef8c02aec6745200ffef3355981e8f04 bam_kmers_out/bam_1/fastqc_data.txt +dc3aeeffd19458cec93b93d0a8c61bee fastq_gz_out/fastq_gz_1/fastqc_data.txt +55ce06b400bf48023a45d05aa997539d groups_out/bam_1/fastqc_data.txt +e1493086c5ddaedf20ec61f8c7a80ba4 fastq_stdin_out/fastq_stdin/fastqc_data.txt +64cd41bd26bfd42e234fc3799bf245e8 preseq_out/bam_1/fastqc_data.txt +1fa22a239dc71e99d0b4387c20a146e7 sam_out/sam_1/fastqc_data.txt +64cd41bd26bfd42e234fc3799bf245e8 bam_out/bam_1/fastqc_data.txt +7c047f91119eee2922c293c7881be1ff fastq_bgzf_threads_out/fastq_bgzip_1/fastqc_data.txt +244963d5c9c1dbcf609d879408611de8 fastq_bgzf_mult_out/fastq_bgzip_2/fastqc_data.txt +7c047f91119eee2922c293c7881be1ff fastq_bgzf_mult_out/fastq_bgzip_1/fastqc_data.txt +64cd41bd26bfd42e234fc3799bf245e8 bam_orig_dups_out/bam_1/fastqc_data.txt +64cd41bd26bfd42e234fc3799bf245e8 bam_mult_out/bam_1/fastqc_data.txt +a5107edbcf8b22e443e0e2940a189ce2 bam_mult_out/bam_2/fastqc_data.txt +7c047f91119eee2922c293c7881be1ff fastq_bgzf_out/fastq_bgzip_1/fastqc_data.txt +b8bee551c09cdfd885a76cc231c90cb6 sam_stdin_out/sam_stdin/fastqc_data.txt +1e726d61c68092876e40fca069b788b2 fastq_plain_out/fastq_1/fastqc_data.txt From c02ff6bbd658c9120017d676305e805890189363 Mon Sep 17 00:00:00 2001 From: Andrew D Smith Date: Wed, 9 Sep 2026 11:35:07 -0700 Subject: [PATCH 02/11] CHANGELOG.md: update for v2.0.2 --- CHANGELOG.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b74c9b..b2f628e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,16 @@ # falco changelog -## falco 2.0.1 (2026-08-03) +## falco 2.0.2 (2026-09-09) + +Changes: +* Input for preseq: falco can output a file (`outdir/preseq_hist.txt`) that + can be directly input to preseq for library complexity analysis. +* Building on macOS: code updates for easier building using Apple Clang on + macOS. +* Standard input: Falco can now accept piped or redirected input from stdin in + either SAM or FASTQ format. + +## falco 2.0.1 (2026-08-12) Changes: * GC content distribution across reads now smoothing more similarly to falco v1 From 91358668f03e193f7f0de7cf46de6e79c14f3255 Mon Sep 17 00:00:00 2001 From: Andrew D Smith Date: Wed, 9 Sep 2026 11:42:22 -0700 Subject: [PATCH 03/11] CHANGELOG.md: adding shout out kerrycobb --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2f628e..4c29e50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ Changes: * Building on macOS: code updates for easier building using Apple Clang on macOS. * Standard input: Falco can now accept piped or redirected input from stdin in - either SAM or FASTQ format. + either SAM or FASTQ format (thanks to @kerrycobb for initiating this). ## falco 2.0.1 (2026-08-12) From cec44a145cc85fa9cba7df770fb72c43206bb92c Mon Sep 17 00:00:00 2001 From: Andrew D Smith Date: Wed, 9 Sep 2026 12:30:22 -0700 Subject: [PATCH 04/11] Fixing issues with axis tics overlapping labels in html just by specifying automargin for the plots --- src/adapter_matcher.cpp | 2 +- src/html.cpp | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/adapter_matcher.cpp b/src/adapter_matcher.cpp index 822e9e5..bc9552c 100644 --- a/src/adapter_matcher.cpp +++ b/src/adapter_matcher.cpp @@ -121,7 +121,7 @@ adapter_matcher::html(const std::uint64_t n_reads, Plotly.newPlot("adapters_plot", {}, {{ -xaxis: {{title: "Base position"}}, +xaxis: {{title: "Base position", automargin: true}}, yaxis: {{title: "% sequences with adapter before position"}}, }}); )"; diff --git a/src/html.cpp b/src/html.cpp index 6a11630..3adcc25 100644 --- a/src/html.cpp +++ b/src/html.cpp @@ -86,8 +86,8 @@ sequence_length_html(const std::vector &lengths, Plotly.newPlot("length_plot", [{}], {{ -xaxis: {{title: "Sequence length"}}, -yaxis: {{title: "Number of sequences"}}, +xaxis: {{title: "Sequence length", automargin: true}}, +yaxis: {{title: "Number of sequences", automargin: true}}, }}); )"; @@ -125,7 +125,7 @@ Plotly.newPlot("gc_sequence_plot", [{}], {{ xaxis: {{title: "% GC"}}, -yaxis: {{title: "Density"}} +yaxis: {{title: "Density", automargin: true}} }}); )"; @@ -168,7 +168,7 @@ sequence_html(const std::vector &nucs, Plotly.newPlot("sequence_plot", [{}], {{ -xaxis: {{title: "Base position"}}, +xaxis: {{title: "Base position", automargin: true}}, yaxis: {{title: "Per base sequence content", range: [0, 100]}}, }}); @@ -228,7 +228,7 @@ line: {{color: "red"}}, name: "Fraction of N reads per base" }}], {{ -xaxis: {{title: "Base position"}}, +xaxis: {{title: "Base position", automargin: true}}, yaxis: {{title: "% N", range: [0, 100]}}, }} ); @@ -272,7 +272,7 @@ line: {{color: "red"}}, name: "Sequence quality distribution" }}], {{ -xaxis: {{title: "Phred quality", range: [0, 40]}}, +xaxis: {{title: "Phred quality", range: [0, 40], automargin: true}}, yaxis: {{title: "Density", rangemode: "tozero"}}, }}); @@ -308,7 +308,7 @@ Plotly.newPlot("quality_base_plot", [{}], {{ showlegend: false, -xaxis: {{title: "Base position"}}, +xaxis: {{title: "Base position", automargin: true}}, yaxis: {{title: "Phred quality", rangemode: "tozero"}}, }}); @@ -399,7 +399,7 @@ showscale: true, }}], {{ showlegend: false, -xaxis: {{title: "Base position"}}, +xaxis: {{title: "Base position", automargin: true}}, yaxis: {{title: "tile", type: "category"}}, }} )"""; From 62b0b8a7ee2af962edb6670c3b29ba10e9605d23 Mon Sep 17 00:00:00 2001 From: Andrew D Smith Date: Wed, 9 Sep 2026 16:03:32 -0700 Subject: [PATCH 05/11] src/falco.cpp: adding explanation about the --groups flag and the grouping in HTML files for long read data --- src/falco.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/falco.cpp b/src/falco.cpp index a9ddea3..ff3d504 100644 --- a/src/falco.cpp +++ b/src/falco.cpp @@ -38,6 +38,12 @@ Output files will be created in "results/SRX081761". Reading from stdin disables tile analysis. To re-enable it, specify either 4 or 6 to indicate position of the tile id in read names. Example: "--stdin fq:4" +Group bases in the output: +$ falco --groups -o results SRX081761_1.fastq +Output is summarized in base position groups after the first 9 bp. As in FastQC, +grouping is done before grading and therefore impacts grades. If reads exceed +1000 bp grouping is done automatically in HTML files without impacting grades. + Default configuration files can be found here: {} Use these as templates. Copy and modify them to customize your analysis. @@ -116,7 +122,7 @@ write_output( for (const auto [result, info, outdir] : std::views::zip(results, infos, outdirs)) { const auto outdir_path = std::filesystem::path{outdir}; - const auto summary = results_summary(std::move(result), mode, info); + auto summary = results_summary(std::move(result), mode, info); write_file(outdir_path / report_filename, summary.get_report()); write_file(outdir_path / html_filename, summary.get_html()); write_file(outdir_path / summary_filename, summary.get_summary()); @@ -396,7 +402,7 @@ main(int argc, char *argv[]) { infiles_opt->expected(1); }, "Read from stdin assuming given format (see help)") - ->option_text("fq|sam[:{4,6}]") + ->option_text("fq|sam[:N]") ->delimiter(':') ->allow_extra_args(false) ->type_size(1, 2) @@ -419,7 +425,8 @@ main(int argc, char *argv[]) { }, "Use original duplication mode (enables --dups)") ->excludes(preseq_opt) ->option_text(" "); - app.add_flag("--groups", do_groups, "Group base positions in output") + app.add_flag("--groups", do_groups, + "Group base positions in output (see help)") ->option_text(" "); app.add_flag("--tiles,!--no-tiles", do_tiles, "Toggle per-tile quality analysis (default: on)") From 3cca2e4d7e29fa8a02fe6e58ae2a896d0c4e8d0e Mon Sep 17 00:00:00 2001 From: Andrew D Smith Date: Wed, 9 Sep 2026 16:04:27 -0700 Subject: [PATCH 06/11] src/results_summary.hcpp: within get_html, if reads are longer than a cutoff (now 1000 bp) the positions will be grouped for generating the html which avoids making html pages that are too large and slow for browsers --- src/results_summary.cpp | 15 ++++++++++++++- src/results_summary.hpp | 2 +- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/results_summary.cpp b/src/results_summary.cpp index be4d724..4ae3ccc 100644 --- a/src/results_summary.cpp +++ b/src/results_summary.cpp @@ -19,8 +19,16 @@ #include #include +#ifndef NDEBUG +static bool applied_groups{false}; +#endif + auto results_summary::apply_groups() -> void { +#ifndef NDEBUG + assert(!applied_groups); + applied_groups = true; +#endif groups = get_default_base_groups(max_read_len, mode.do_groups()); if (mode.do_groups()) { apply_base_groups(groups, base_counts); @@ -172,7 +180,7 @@ results_summary::get_report() const -> std::string { } [[nodiscard]] auto -results_summary::get_html() const -> std::string { +results_summary::get_html() -> std::string { const auto basic_stats = basic_stats_html(info, n_reads, min_read_len, max_read_len, median_read_len, total_gc, total_bases, grades); @@ -180,6 +188,11 @@ results_summary::get_html() const -> std::string { {"basic_stats", basic_stats}, }; + if (!mode.do_groups() && max_read_len > html_readlen_cutoff) { + mode.set_do_groups(1); + apply_groups(); + } + if (mode.do_adap()) sections.emplace("adapter", am.html(n_reads, max_read_len, groups, grades)); diff --git a/src/results_summary.hpp b/src/results_summary.hpp index d5a5930..2694f2b 100644 --- a/src/results_summary.hpp +++ b/src/results_summary.hpp @@ -80,7 +80,7 @@ struct results_summary { get_report() const -> std::string; [[nodiscard]] auto - get_html() const -> std::string; + get_html() -> std::string; [[nodiscard]] auto get_summary() const -> std::string; From a338e89f15b754cf7a946e76031eb5ac080b9825 Mon Sep 17 00:00:00 2001 From: Andrew D Smith Date: Wed, 9 Sep 2026 16:05:15 -0700 Subject: [PATCH 07/11] src/html.hcpp: added a constant (now 1000) for max read len before the html report needs to have bases grouped and added several assertions --- src/html.cpp | 10 ++++++---- src/html.hpp | 2 ++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/html.cpp b/src/html.cpp index 3adcc25..89d9c78 100644 --- a/src/html.cpp +++ b/src/html.cpp @@ -326,6 +326,7 @@ yaxis: {{title: "Phred quality", rangemode: "tozero"}}, return "yellow"; return "green"; }; + assert(std::size(qual) == std::size(groups)); std::vector lines; for (const auto [idx, q] : falco::views::enumerate(qual)) { const auto fq = five_quants(q); @@ -379,8 +380,8 @@ basic_stats_html(const file_info &info, const std::uint64_t n_reads, [[nodiscard]] auto tile_html(const tile_processor::tiles_centered_t ¢ered, - const std::vector &groups, const file_grades &grades) - -> std::string { + const std::vector &groups, + const file_grades &grades) -> std::string { static constexpr auto label = "tile"; static constexpr auto n_quants = 20.0; // ADS: ??? (-10: red, 0: light blue, +10: dark blue) @@ -425,6 +426,7 @@ yaxis: {{title: "tile", type: "category"}}, }; const auto z = std::views::transform(centered | std::views::values, format1); + assert(std::size(centered) == std::size(groups)); const auto grade = grades.grade(label); const auto title = grades.get_title(label); return fmt::format( @@ -439,8 +441,8 @@ yaxis: {{title: "tile", type: "category"}}, } [[nodiscard]] auto -kmer_html(const std::vector &results, const file_grades &grades) - -> std::string { +kmer_html(const std::vector &results, + const file_grades &grades) -> std::string { static constexpr auto label = "kmer"; static constexpr auto plot_format = R"(