diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b74c9b..a6f9d89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,18 @@ # 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 (thanks to @kerrycobb for initiating this). +* HTML report: for reads longer than 1000 bp, plots in the HTML will have + bases grouped to prevent generating very large, slow and unreadable HTML. + +## falco 2.0.1 (2026-08-12) Changes: * GC content distribution across reads now smoothing more similarly to falco v1 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 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/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)") diff --git a/src/html.cpp b/src/html.cpp index 6a11630..ec74c53 100644 --- a/src/html.cpp +++ b/src/html.cpp @@ -29,7 +29,8 @@ #include #include #include -#include // for std::get probably in fmt +#include // for std::get probably in fmt +#include // for pair #include [[nodiscard]] auto @@ -86,8 +87,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 +126,7 @@ Plotly.newPlot("gc_sequence_plot", [{}], {{ xaxis: {{title: "% GC"}}, -yaxis: {{title: "Density"}} +yaxis: {{title: "Density", automargin: true}} }}); )"; @@ -168,7 +169,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 +229,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 +273,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 +309,7 @@ Plotly.newPlot("quality_base_plot", [{}], {{ showlegend: false, -xaxis: {{title: "Base position"}}, +xaxis: {{title: "Base position", automargin: true}}, yaxis: {{title: "Phred quality", rangemode: "tozero"}}, }}); @@ -326,6 +327,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 +381,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) @@ -399,7 +401,7 @@ showscale: true, }}], {{ showlegend: false, -xaxis: {{title: "Base position"}}, +xaxis: {{title: "Base position", automargin: true}}, yaxis: {{title: "tile", type: "category"}}, }} )"""; @@ -425,6 +427,7 @@ yaxis: {{title: "tile", type: "category"}}, }; const auto z = std::views::transform(centered | std::views::values, format1); + assert(std::size(std::cbegin(centered)->second) == std::size(groups)); const auto grade = grades.grade(label); const auto title = grades.get_title(label); return fmt::format( @@ -439,8 +442,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"(