Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
34 changes: 17 additions & 17 deletions data/test_data/md5sum.txt
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion src/adapter_matcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"}},
}});
</script>)";
Expand Down
13 changes: 10 additions & 3 deletions src/falco.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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)
Expand All @@ -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)")
Expand Down
29 changes: 16 additions & 13 deletions src/html.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
#include <span>
#include <stdexcept>
#include <string>
#include <tuple> // for std::get probably in fmt
#include <tuple> // for std::get probably in fmt
#include <utility> // for pair
#include <vector>

[[nodiscard]] auto
Expand Down Expand Up @@ -86,8 +87,8 @@ sequence_length_html(const std::vector<std::uint64_t> &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}},
}});
</script>
)";
Expand Down Expand Up @@ -125,7 +126,7 @@ Plotly.newPlot("gc_sequence_plot",
[{}],
{{
xaxis: {{title: "% GC"}},
yaxis: {{title: "Density"}}
yaxis: {{title: "Density", automargin: true}}
}});
</script>
)";
Expand Down Expand Up @@ -168,7 +169,7 @@ sequence_html(const std::vector<falco::nuc_array> &nucs,
Plotly.newPlot("sequence_plot",
[{}],
{{
xaxis: {{title: "Base position"}},
xaxis: {{title: "Base position", automargin: true}},
yaxis: {{title: "Per base sequence content", range: [0, 100]}},
}});
</script>
Expand Down Expand Up @@ -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]}},
}}
);</script>
Expand Down Expand Up @@ -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"}},
}});
</script>
Expand Down Expand Up @@ -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"}},
}});
</script>
Expand All @@ -326,6 +327,7 @@ yaxis: {{title: "Phred quality", rangemode: "tozero"}},
return "yellow";
return "green";
};
assert(std::size(qual) == std::size(groups));
std::vector<std::string> lines;
for (const auto [idx, q] : falco::views::enumerate(qual)) {
const auto fq = five_quants(q);
Expand Down Expand Up @@ -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 &centered,
const std::vector<base_group_t> &groups, const file_grades &grades)
-> std::string {
const std::vector<base_group_t> &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)
Expand All @@ -399,7 +401,7 @@ showscale: true,
}}],
{{
showlegend: false,
xaxis: {{title: "Base position"}},
xaxis: {{title: "Base position", automargin: true}},
yaxis: {{title: "tile", type: "category"}},
}}
)""";
Expand All @@ -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(
Expand All @@ -439,8 +442,8 @@ yaxis: {{title: "tile", type: "category"}},
}

[[nodiscard]] auto
kmer_html(const std::vector<kmer_result> &results, const file_grades &grades)
-> std::string {
kmer_html(const std::vector<kmer_result> &results,
const file_grades &grades) -> std::string {
static constexpr auto label = "kmer";
static constexpr auto plot_format = R"(<div id="kmer_plot"></div>
<script>Plotly.newPlot("kmer_plot",
Expand Down
2 changes: 2 additions & 0 deletions src/html.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ struct file_grades;
struct kmer_result;
struct file_info;

static constexpr auto html_readlen_cutoff = 1000;

// grade / section_label / section_title / grade / text
static constexpr auto html_module_fmt =
R"(<div class="module">
Expand Down
11 changes: 10 additions & 1 deletion src/results_summary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -172,14 +172,23 @@ 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);
auto sections = std::unordered_map<std::string, std::string>{
{"basic_stats", basic_stats},
};

if (!mode.do_groups() && max_read_len > html_readlen_cutoff) {
mode.set_do_groups(1);
apply_groups();
// ADS: this below is not good. The issues related to different groupings
// for the grading and report, vs. the HTML, need to be handled better
// overall.
centered = tp.get_centered();
}

if (mode.do_adap())
sections.emplace("adapter", am.html(n_reads, max_read_len, groups, grades));

Expand Down
2 changes: 1 addition & 1 deletion src/results_summary.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down