Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
a632829
src/bam_file.hcpp: adding a function to pre-load the duplication counts
andrewdavidsmith Aug 11, 2026
325663a
src/fastq_bgzf_file.hcpp: adding a function to pre-load the duplicati…
andrewdavidsmith Aug 11, 2026
4cde64d
src/results_collector.hpp: adding a mode to initialize a results_coll…
andrewdavidsmith Aug 11, 2026
57967f5
src/original_duplicates.hcpp: adding sources to control initializatio…
andrewdavidsmith Aug 11, 2026
ca1b6ca
src/quality_score.hcpp: adding missing includes
andrewdavidsmith Aug 11, 2026
da86403
src/run_mode.hcpp: added a run mode setting for original dups
andrewdavidsmith Aug 11, 2026
2c49bff
src/duplication_results.hcpp: keeping most code that works for origin…
andrewdavidsmith Aug 11, 2026
e9c4556
src/results_summary.hcpp: removing things that depend both on the pre…
andrewdavidsmith Aug 11, 2026
4fdd8e4
CMakeLists.txt: adding object for the original_duplicates source
andrewdavidsmith Aug 11, 2026
ea9c6a1
src/results_collector.hpp: initialization in the init function based …
andrewdavidsmith Aug 11, 2026
3610e2a
src/falco_analyzer.hcpp: adding dups_init argument to analyze functio…
andrewdavidsmith Aug 11, 2026
4ec0ceb
src/results_collector.hpp: building original_dups directly into the a…
andrewdavidsmith Aug 11, 2026
a394f41
src/duplication_results.hcpp: removing preprocessor flags for origina…
andrewdavidsmith Aug 11, 2026
1401ba4
src/falco.cpp: adding code and cli options for user to specify origin…
andrewdavidsmith Aug 11, 2026
896c0fb
README.md: updates related to changes for original duplication results
andrewdavidsmith Aug 11, 2026
8e81614
data/test_scripts/orig_dups.sh: adding a test for original duplicatio…
andrewdavidsmith Aug 12, 2026
e44ea6c
README.md: update to readme
andrewdavidsmith Aug 12, 2026
6a667bd
cmake/tests.cmake: adding a test for original duplicates
andrewdavidsmith Aug 12, 2026
da18ce5
data/test_data/md5sum.txt: adding an output hash for original duplicates
andrewdavidsmith Aug 12, 2026
d6ac60e
src/bam_file.cpp: bugfix where revcomp wasn't taken for reverse stran…
andrewdavidsmith Aug 12, 2026
78eae39
src/duplication_results.hcpp: iwyu
andrewdavidsmith Aug 12, 2026
7d62ee1
src/falco_analyzer.hcpp: iwyu
andrewdavidsmith Aug 12, 2026
366156b
src/original_duplicates.hcpp: iwyu
andrewdavidsmith Aug 12, 2026
b206dc8
src/fastq_bgzf_file.hcpp: iwyu
andrewdavidsmith Aug 12, 2026
98d4c91
src/falco.cpp: clang-tidy not understanding a partial move-from
andrewdavidsmith Aug 12, 2026
bd988a4
src/duplication_results.hpp: type consistency for count_at_limit
andrewdavidsmith Aug 12, 2026
22431ba
src/original_duplicates.cpp: nolint for an empty loop variable
andrewdavidsmith Aug 12, 2026
bf92f14
CMakeLists.txt: adding a forgotten dependency
andrewdavidsmith Aug 11, 2026
eee0410
src/fastq_bgzf_file.cpp: changing the n_unique_multiplier to match th…
andrewdavidsmith Aug 12, 2026
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
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -199,13 +199,17 @@ add_library(run_mode OBJECT src/run_mode.cpp)
add_library(falco_config OBJECT src/falco_config.cpp)
target_link_libraries(falco_config PUBLIC run_mode)

add_library(original_duplicates OBJECT src/original_duplicates.cpp)
target_link_libraries(original_duplicates PUBLIC HTSLIB::HTSLIB)

add_executable(falco src/falco.cpp)
target_link_libraries(falco PUBLIC
tile_processor
contaminants
adapter_matcher
adapter_set
duplication_results
original_duplicates
kmer_counter
fastq_file
fastq_gz_file
Expand Down
27 changes: 3 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,9 @@ check large sequencing reads for common problems. Falco was rewritten for
version 2.0 in order to facilitate incoroprating new functionality moving
forward.

Output files and formats are the same: `summary.txt`, `fastqc_report.txt` and
`fastqc_report.html`

## Quick start

You will be able to find binaries for Linux and macOS with the releases. As of
2026-08-03, the falco v2.0 source has been merged into the main (master) branch
but the "release" might lag by a day or so.
You will be able to find binaries for Linux and macOS with the releases.

Example:
```
Expand Down Expand Up @@ -123,13 +118,6 @@ GitHub's macOS look

## Intended changes in Falco v2.0

Whether or not changes are "correct," they can be bad for users. If you have
many years of experience interpreting the output of FastQC or falco, that alone
is enough to give value to those results. Issues of correctness or accuracy
might not matter to many users. Below are intended changes. Anything else is
likely a bug and I'm happy to fix it. I'm also happy to reconsider these
intended changes.

### Tiles results

I found that the method for tile analysis is a bit unstable, and the tile grade
Expand Down Expand Up @@ -163,11 +151,8 @@ the same, the numbers differ dramatically. The motivation for the change is to
produce more useful output, and to soon build
[preseq](https://github.com/smithlabcode/preseq) into falco.

The original duplication analysis method is still implemented in falco v2.0, but
it must be turned on at build time by supplying `-DORIGINAL_DUPS=on` to
`cmake`. It's still there because I used a trivial implementation and helps me
debug. Here's how the old and new analyses differ, to the best of my
understanding. I'm happy to be corrected.
The original duplication analysis method is still implemented in falco v2.0, and
can be turned on with `--orig-dups`. Here's how the old and new analyses differ.

#### Original method
- 100,000 unique reads are hashed and counted (first 50nt of each read).
Expand All @@ -182,12 +167,6 @@ understanding. I'm happy to be corrected.
will appear as though they are randomly sampled due to fluctaions in thread
speed changing which 1M reads are hashed.

Building with `-DORIGINAL_DUPS=on` enables the original duplication analysis,
but it only gives the same results when run using one thread. The underlying
issues with this duplication analysis were part of the motivation for preseq, so
I'm reluctant to put effort towards improving how the original duplication
analysis is implemented.

## Citing falco

If falco was helpful for your research, you can cite us as follows:
Expand Down
1 change: 1 addition & 0 deletions cmake/tests.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ add_test(NAME "Tiles" COMMAND bash test_scripts/tiles.sh)
add_test(NAME "Kmers" COMMAND bash test_scripts/kmers.sh)
add_test(NAME "Groups" COMMAND bash test_scripts/groups.sh)
add_test(NAME "SAM input" COMMAND bash test_scripts/sam.sh)
add_test(NAME "Orig dups" COMMAND bash test_scripts/orig_dups.sh)
1 change: 1 addition & 0 deletions data/test_data/md5sum.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ ad21a5d9eb4cac3018c61fd7afdcbaf4 bam_mult_out/bam_2/fastqc_data.txt
4cf6ba5d87aa553a64ce5427c18ee85c bam_tiles_out/bam_1/fastqc_data.txt
ec81607e6c63f03e04c4c49588be816e groups_out/bam_1/fastqc_data.txt
a3a0045355760f0f1ec5b8ca4fc1753e sam_out/sam_1/fastqc_data.txt
4cf6ba5d87aa553a64ce5427c18ee85c bam_orig_dups_out/bam_1/fastqc_data.txt
19 changes: 19 additions & 0 deletions data/test_scripts/orig_dups.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: MIT

prog=./falco
infile=test_data/bam_1.bam
outdir=bam_orig_dups_out
if [[ -e "${infile}" ]]; then
${prog} --orig-dups -o ${outdir} ${infile}
x=$(md5sum --ignore-missing -c test_data/md5sum.txt | \
grep "${outdir}" | \
grep -c "OK$")
if [[ "${x}" != "1" ]]; then
exit 1;
fi
rm -r ${outdir}
else
echo "${infile} not found; skipping remaining tests";
exit 77;
fi
48 changes: 48 additions & 0 deletions src/bam_file.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

#include "bam_file.hpp"
#include "bamrec.hpp"
#include "duplication_results.hpp"
#include "falco_utils.hpp"
#include "falco_word.hpp"
#include "task_queue.hpp"

#include <htslib/hfile.h>
Expand Down Expand Up @@ -58,6 +60,52 @@ estimate_n_reads_bam(const std::string &filename)
return {estimate, total_read_len / n_reads, filesize};
}

[[nodiscard]] auto
init_dups(const std::string &filename,
const std::uint64_t n_unique) -> dups_map_t {
static constexpr auto complem = [](const auto x) {
return "TNGNNNCNNNNNNNNNNNNA"[x - 'A'];
};
static constexpr auto n_unique_multiplier = 10;
std::unique_ptr<htsFile, int (*)(htsFile *)> f(
hts_open(std::data(filename), "r"), &hts_close);
if (!f)
throw std::system_error(std::make_error_code(std::errc(errno)),
"failed to open file: " + filename);
std::unique_ptr<sam_hdr_t, void (*)(sam_hdr_t *)> h(sam_hdr_read(f.get()),
&sam_hdr_destroy);
if (!h)
throw std::system_error(std::make_error_code(std::errc(errno)),
"failed to read header: " + filename);
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access)
std::unique_ptr<bam1_t, void (*)(bam1_t *)> rec(bam_init1(), &bam_destroy1);

dups_map_t dups;
std::vector<char> buffer;
int r{};
std::uint64_t n_reads{};
const auto max_n_reads = n_unique_multiplier * n_unique;
while (n_reads++ < max_n_reads && std::size(dups) < n_unique &&
(r = sam_read1(f.get(), h.get(), rec.get())) >= 0) {
const auto l_qseq = rec->core.l_qseq;
const auto seq = bam_get_seq(rec.get());
if (std::ssize(buffer) < l_qseq)
buffer.resize(l_qseq);
for (auto i = 0; i < l_qseq; ++i)
buffer[i] = seq_nt16_str[bam_seqi(seq, i)];
if (bam_is_rev(rec.get())) {
std::reverse(std::begin(buffer), std::begin(buffer) + l_qseq);
std::transform(std::cbegin(buffer), std::cbegin(buffer) + l_qseq,
std::begin(buffer), complem);
}
++dups[falco_word(std::data(buffer), l_qseq)];
}
if (r < -1) // error
throw std::system_error(std::make_error_code(std::errc(errno)),
"error reading bam record from: " + filename);
return dups;
}

auto
bam_file::load_next(const std::int32_t file_id, //
task_queue &tq, //
Expand Down
5 changes: 5 additions & 0 deletions src/bam_file.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "bam_header.hpp"
#include "bgzf_block.hpp"
#include "bgzf_reader.hpp"
#include "duplication_results.hpp"

#include <atomic>
#include <cstdint>
Expand Down Expand Up @@ -119,6 +120,10 @@ class bam_file {
estimate_n_reads_bam(const std::string &filename)
-> std::tuple<std::uint64_t, std::uint64_t, std::int64_t>;

[[nodiscard]] auto
init_dups(const std::string &filename,
const std::uint64_t n_unique) -> dups_map_t;

inline auto
make_tasks(bam_file &reads_file, //
const std::int64_t n_threads, //
Expand Down
43 changes: 24 additions & 19 deletions src/duplication_results.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@
#include "html.hpp"
#include "run_mode.hpp"

#include "boost/boost_unordered.hpp"

#define FMT_HEADER_ONLY
#include "fmt/format.h"
#include "fmt/ranges.h"

#include "boost/boost_unordered.hpp"

#include <algorithm>
#include <array>
#include <cstdint>
Expand Down Expand Up @@ -107,6 +107,21 @@ duplication_results::get_overrepresented(const std::uint64_t n_reads) const
return ret;
}

auto
duplication_results::initialize(const run_mode &mode, const file_info &info,
const dups_init_t &dups_init) -> void {
read_skip =
info.n_reads_est < max_n_reads_total
? 0
: static_cast<std::int32_t>(info.n_reads_est / max_n_reads_total);
dups = dups_init.dups_zero;
count_at_limit = dups_init.count_at_limit;
if (!mode.do_dup_analysis()) {
// ADS: disabling dups analysis; does nothing for original dups
read_idx = std::numeric_limits<std::int64_t>::max();
}
}

auto
duplication_results::initialize(const run_mode &mode,
const file_info &info) -> void {
Expand All @@ -117,17 +132,13 @@ duplication_results::initialize(const run_mode &mode,
if (!mode.do_dup_analysis()) {
// ADS: disabling dups analysis
read_idx = std::numeric_limits<std::int64_t>::max();
// ADS: for ORIGINAL_DUPS this still does lots of work
}
}

auto
duplication_results::add_and_consume(duplication_results &rhs) -> void {
#ifndef ORIGINAL_DUPS
for (const auto &[k, v] : rhs.dups)
dups[k] += v;
#endif // ORIGINAL_DUPS
// ADS: ORIGINAL_DUPS mode just uses dups from one of two duplication_results
rhs.release();
}

Expand All @@ -137,9 +148,8 @@ get_grade_overrepresented(const std::uint64_t n_reads,
static constexpr auto label = "overrepresented";
const auto max_n_obs =
dr.dups.empty() ? 0LU : std::ranges::max(std::views::values(dr.dups));
return grader_set::get_grade(
label,
as_frac(max_n_obs, std::max(static_cast<std::uint64_t>(1), n_reads)));
return grader_set::get_grade(label,
as_frac(max_n_obs, std::max(1LU, n_reads)));
}

[[nodiscard]] auto
Expand Down Expand Up @@ -174,8 +184,6 @@ make_bins(const auto &breaks, const auto &hist) {
return binned;
}

#ifdef ORIGINAL_DUPS

// ADS: for original dups, from FastQC extrapolation of dup counts.
[[nodiscard]] auto
get_corrected_count(const std::uint64_t count_at_limit,
Expand Down Expand Up @@ -209,7 +217,8 @@ get_corrected_count(const std::uint64_t count_at_limit,
}

[[nodiscard]] auto
duplication_results::get_dups_summary() const -> dup_summary_t {
duplication_results::get_dups_summary(const std::uint64_t n_reads) const
-> dup_summary_t {
if (dups.empty())
return {};
const auto max_dup = std::ranges::max(std::views::values(dups));
Expand All @@ -218,22 +227,20 @@ duplication_results::get_dups_summary() const -> dup_summary_t {
++hist_dedup[n_copies];
for (auto [idx, val] : std::views::enumerate(hist_dedup))
val = static_cast<std::uint64_t>(
get_corrected_count(count_at_limit, read_idx, idx, val));
get_corrected_count(count_at_limit, n_reads, idx, val));
auto hist_mass =
std::views::transform(
std::views::enumerate(hist_dedup),
[](const auto x) { return std::get<0>(x) * std::get<1>(x); }) |
std::ranges::to<std::vector>();
return dup_summary_t{
max_dup,
get_n_counted_reads(), // number of counted reads
n_reads, // number of counted reads
std::move(hist_mass), // move to avoid copying when making tuple
std::move(hist_dedup), // move to avoid copying when making tuple
};
}

#else // NOT ORIGINAL_DUPS

[[nodiscard]] auto
duplication_results::get_dups_summary() const -> dup_summary_t {
if (dups.empty())
Expand All @@ -249,14 +256,12 @@ duplication_results::get_dups_summary() const -> dup_summary_t {
std::ranges::to<std::vector>();
return dup_summary_t{
max_dup,
get_n_counted_reads(), // total counted reads
get_n_counted_reads(), // number of counted reads
std::move(hist_mass), // move to avoid copying when making tuple
std::move(hist_dedup), // move to avoid copying when making tuple
};
}

#endif

[[nodiscard]] auto
get_grade_duplication(const dup_summary_t &summary) -> std::string {
// ADS: duplication grade is based on summary stat of estimated number of
Expand Down
Loading