• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

MikkelSchubert / adapterremoval / #95

13 Apr 2025 06:28PM UTC coverage: 30.749%. Remained the same
#95

push

travis-ci

web-flow
rename enums read_type and fastq_flags (#121)

To clarifies usage of two commonly used enums, `read_type` was renamed
to `read_file`, since it is used to specify input and output file
types, while `fastq_flags` was renamed to `read_type` since it is used
to differentiate between different pre/post-processed read types

0 of 66 new or added lines in 6 files covered. (0.0%)

4 existing lines in 1 file now uncovered.

3157 of 10267 relevant lines covered (30.75%)

3943.28 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/reports_json.cpp
1
// SPDX-License-Identifier: GPL-3.0-or-later
2
// SPDX-FileCopyrightText: 2011 Stinus Lindgreen <stinus@binf.ku.dk>
3
// SPDX-FileCopyrightText: 2014 Mikkel Schubert <mikkelsch@gmail.com>
4
#include "adapter_id.hpp"    // for consensus_adapter_stats
5
#include "commontypes.hpp"   // for read_file, read_file::mate_1, read_typ...
6
#include "counts.hpp"        // for counts, counts_tmpl, indexed_count
7
#include "debug.hpp"         // for AR_REQUIRE
8
#include "fastq.hpp"         // for ACGT, fastq, ACGT::values
9
#include "json.hpp"          // for json_dict, json_dict_ptr, json_list
10
#include "logging.hpp"       // for log_stream, error
11
#include "main.hpp"          // for NAME, VERSION
12
#include "managed_io.hpp"    // for managed_writer
13
#include "output.hpp"        // for sample_output_files
14
#include "reports.hpp"       // for write_json_report
15
#include "sequence_sets.hpp" // for adapter_set
16
#include "simd.hpp"          // for size_t
17
#include "statistics.hpp"    // for fastq_stats_ptr, trimming_statistics
18
#include "strutils.hpp"      // for string_vec, to_lower, indent_lines
19
#include "userconfig.hpp"    // for userconfig, output_files, output_sampl...
20
#include <cerrno>            // for errno
21
#include <cstring>           // for size_t, strerror
22
#include <memory>            // for __shared_ptr_access, shared_ptr, make_...
23
#include <string>            // for basic_string, string, operator+, char_...
24
#include <utility>           // for pair
25
#include <vector>            // for vector
26

27
namespace adapterremoval {
28

29
namespace {
30

31
////////////////////////////////////////////////////////////////////////////////
32
// Meta data
33

34
void
35
write_report_meta(const userconfig& config, json_dict& report)
×
36
{
37
  const auto meta = report.dict("meta");
×
38

39
  meta->str("version", NAME + " " + VERSION);
×
40
  meta->str_vec("command", config.args);
×
41
  meta->f64("runtime", config.runtime());
×
42
  meta->str("timestamp", userconfig::start_time);
×
43
}
44

45
////////////////////////////////////////////////////////////////////////////////
46
// Summary statistics
47

48
void
49
write_report_summary_stats(const json_dict_ptr& json,
×
50
                           const std::vector<fastq_stats_ptr>& stats)
51
{
52
  size_t n_reads = 0;
×
53
  size_t n_reads_s = 0;
×
54
  size_t n_bases = 0;
×
55
  size_t n_a = 0;
×
56
  size_t n_c = 0;
×
57
  size_t n_g = 0;
×
58
  size_t n_t = 0;
×
59
  size_t n_n = 0;
×
60
  size_t n_q20 = 0;
×
61
  size_t n_q30 = 0;
×
62

63
  for (const auto& it : stats) {
×
64
    n_reads += it->number_of_output_reads();
×
65
    n_reads_s += it->number_of_sampled_reads();
×
66
    n_bases += it->length_dist().product();
×
67
    // The following stats are all (potentially) based on a subset of reads
68
    n_a += it->nucleotides_pos('A').sum();
×
69
    n_c += it->nucleotides_pos('C').sum();
×
70
    n_g += it->nucleotides_pos('G').sum();
×
71
    n_t += it->nucleotides_pos('T').sum();
×
72
    n_n += it->nucleotides_pos('N').sum();
×
73
    n_q20 += it->quality_dist().sum(20);
×
74
    n_q30 += it->quality_dist().sum(30);
×
75
  }
76

77
  const auto n_bases_s = n_a + n_c + n_g + n_t + n_n;
×
78

79
  json->u64("reads", n_reads);
×
80
  json->u64("bases", n_bases);
×
81
  json->f64("mean_length",
×
82
            static_cast<double>(n_bases) / static_cast<double>(n_reads));
83
  json->u64("reads_sampled", n_reads_s);
×
84
  json->f64("q20_rate",
×
85
            static_cast<double>(n_q20) / static_cast<double>(n_bases_s));
86
  json->f64("q30_rate",
×
87
            static_cast<double>(n_q30) / static_cast<double>(n_bases_s));
88
  json->f64("uncalled_rate",
×
89
            static_cast<double>(n_n) / static_cast<double>(n_bases_s));
90
  json->f64("gc_content",
×
91
            static_cast<double>(n_g + n_c) / static_cast<double>(n_bases_s));
92
}
93

94
void
95
write_report_summary(const userconfig& config,
×
96
                     json_dict& report,
97
                     const statistics& stats)
98
{
99
  const auto summary = report.dict("summary");
×
100

101
  write_report_summary_stats(summary->dict("input"),
×
102
                             { stats.input_1, stats.input_2 });
×
103

104
  if (config.run_type == ar_command::report_only) {
×
105
    summary->null("output");
×
106
  } else {
107
    const auto output = summary->dict("output");
×
108

109
    std::vector<fastq_stats_ptr> passed;
×
110
    for (const auto& it : stats.trimming) {
×
111
      passed.push_back(it->read_1);
×
112
      passed.push_back(it->read_2);
×
113
      passed.push_back(it->merged);
×
114
      passed.push_back(it->singleton);
×
115

116
      // Discarded reads are excluded, even if saved
117
    }
118

119
    write_report_summary_stats(summary->dict("output"), passed);
×
120
  }
121
}
122

123
////////////////////////////////////////////////////////////////////////////////
124
// Input
125

126
/** Helper struct used to simplify writing of multiple io sections. */
127
struct io_section
128
{
NEW
129
  io_section(read_file rtype,
×
130
             std::string name,
131
             fastq_stats_ptr stats,
132
             const sample_output_files& sample_files)
133
    : io_section(std::move(name), std::move(stats), {})
×
134
  {
135
    const auto offset = sample_files.offset(rtype);
×
136
    if (offset != sample_output_files::disabled) {
×
137
      m_filenames.push_back(sample_files.filename(offset));
×
138
    }
139
  }
140

141
  io_section(std::string name, fastq_stats_ptr stats, string_vec filenames)
×
142
    : m_stats(std::move(stats))
×
143
    , m_name(std::move(name))
×
144
    , m_filenames(std::move(filenames))
×
145
  {
146
  }
147

148
  void write_to_if(const json_dict_ptr& json, bool enabled = true) const
×
149
  {
150
    if (!enabled) {
×
151
      json->null(m_name);
×
152
      return;
×
153
    }
154

155
    const auto section = json->dict(m_name);
×
156
    if (m_filenames.empty()) {
×
157
      section->null("filenames");
×
158
    } else {
159
      section->str_vec("filenames", m_filenames);
×
160
    }
161

162
    section->u64("input_reads", m_stats->number_of_input_reads());
×
163
    section->u64("output_reads", m_stats->number_of_output_reads());
×
164
    section->u64("reads_sampled", m_stats->number_of_sampled_reads());
×
165
    section->i64_vec("lengths", m_stats->length_dist());
×
166

167
    if (m_stats->length_dist().product()) {
×
168
      const auto total_bases = m_stats->nucleotides_pos();
×
169
      const auto total_quality = m_stats->qualities_pos();
×
170

171
      {
×
172
        const auto quality_curves = section->dict("quality_curves");
×
173

174
        for (const auto nuc : ACGT::values) {
×
175
          const auto nucleotides = m_stats->nucleotides_pos(nuc);
×
176
          const auto quality = m_stats->qualities_pos(nuc);
×
177

178
          quality_curves->f64_vec(std::string(1, to_lower(nuc)),
×
179
                                  quality / nucleotides);
×
180
        }
181

182
        quality_curves->f64_vec("mean", total_quality / total_bases);
×
183
      }
184

185
      {
×
186
        const auto content_curves = section->dict("content_curves");
×
187

188
        for (const auto nuc : ACGTN::values) {
×
189
          const auto bases = m_stats->nucleotides_pos(nuc);
×
190

191
          // FIXME: Should be raw counts instead of fractions
192
          content_curves->f64_vec(std::string(1, to_lower(nuc)),
×
193
                                  bases / total_bases);
×
194
        }
195
      }
196

197
      const auto quality_dist = m_stats->quality_dist().trim();
×
198
      section->i64_vec("quality_scores", quality_dist);
×
199
      section->f64_vec("gc_content", m_stats->gc_content());
×
200
    } else {
×
201
      section->null("quality_curves");
×
202
      section->null("content_curves");
×
203
      section->null("quality_scores");
×
204
      section->null("gc_content");
×
205
    }
206
  }
207

208
private:
209
  const fastq_stats_ptr m_stats;
210
  //! Name of the section
211
  const std::string m_name;
212
  //! Filenames (if any) generated for this file type
213
  string_vec m_filenames;
214
};
215

216
void
217
write_report_input(const userconfig& config,
×
218
                   json_dict& report,
219
                   const statistics& stats)
220
{
221
  const auto input = report.dict("input");
×
222
  const auto mate_2_filenames =
×
223
    config.interleaved_input ? config.input_files_1 : config.input_files_2;
×
224

225
  io_section("read1", stats.input_1, config.input_files_1).write_to_if(input);
×
226
  io_section("read2", stats.input_2, mate_2_filenames)
×
227
    .write_to_if(input, config.paired_ended_mode);
×
228
}
229

230
////////////////////////////////////////////////////////////////////////////////
231
// Demultiplexing
232

233
void
234
write_report_demultiplexing(const userconfig& config,
×
235
                            json_dict& report,
236
                            const statistics& sample_stats)
237
{
238
  const bool demux_only = config.run_type == ar_command::demultiplex_only;
×
239
  const auto out_files = config.get_output_filenames();
×
240

241
  if (config.is_demultiplexing_enabled()) {
×
242
    const auto demultiplexing = report.dict("demultiplexing");
×
243

244
    const auto& demux = *sample_stats.demultiplexing;
×
245
    size_t assigned_reads = 0;
×
246
    for (const auto& it : demux.samples) {
×
247
      assigned_reads += it.sum();
×
248
    }
249

250
    demultiplexing->u64("assigned_reads", assigned_reads);
×
251
    demultiplexing->u64("ambiguous_reads", demux.ambiguous);
×
252
    demultiplexing->u64("unassigned_reads", demux.unidentified);
×
253

254
    const auto samples = demultiplexing->dict("samples");
×
255
    for (size_t i = 0; i < demux.samples.size(); ++i) {
×
256
      const auto sample = samples->dict(config.samples.at(i).name());
×
257
      const auto& stats = *sample_stats.trimming.at(i);
×
258
      const auto& files = out_files.get_sample(i);
×
259

260
      const auto& barcodes = config.samples.at(i);
×
261
      const auto barcode_list = sample->list("barcodes");
×
262
      for (size_t j = 0; j < barcodes.size(); ++j) {
×
263
        const auto it = barcodes.at(j);
×
264

265
        const auto dict = barcode_list->inline_dict();
×
266
        dict->str("barcode1", it.barcode_1);
×
267
        dict->str("barcode2", it.barcode_2);
×
268
        dict->i64("reads", demux.samples.at(i).get(j));
×
269
      }
270

271
      const auto output = sample->dict("output");
×
NEW
272
      io_section(read_file::mate_1, "read1", stats.read_1, files)
×
273
        .write_to_if(output, true);
×
274

NEW
275
      io_section(read_file::mate_2, "read2", stats.read_2, files)
×
276
        .write_to_if(output, config.paired_ended_mode);
×
277

NEW
278
      io_section(read_file::singleton, "singleton", stats.singleton, files)
×
279
        .write_to_if(output,
×
280
                     config.paired_ended_mode && !demux_only &&
×
281
                       config.is_any_filtering_enabled());
×
282

NEW
283
      io_section(read_file::merged, "merged", stats.merged, files)
×
284
        .write_to_if(output, config.is_read_merging_enabled());
×
285

NEW
286
      io_section(read_file::discarded, "discarded", stats.discarded, files)
×
287
        .write_to_if(output, !demux_only && config.is_any_filtering_enabled());
×
288
    }
289
  } else {
×
290
    report.null("demultiplexing");
×
291
  }
292
}
293

294
////////////////////////////////////////////////////////////////////////////////
295
// Processing
296

297
//! The kind of action performed by a processing step
298
enum class feature_type
299
{
300
  //! Quality or complexity filtering
301
  filter,
302
  //! Read merging and error correction
303
  merge,
304
  //! Adapter, quality, or poly-X trimming
305
  trim,
306
};
307

308
std::string_view
309
feature_name(const feature_type action)
×
310
{
311
  switch (action) {
×
312
    case feature_type::filter:
×
313
      return "filter";
×
314
    case feature_type::merge:
×
315
      return "merge";
×
316
    case feature_type::trim:
×
317
      return "trim";
×
318
    default:
×
319
      AR_FAIL("invalid processing step type");
×
320
  }
321
}
322

323
//! Basic processing/filtering statistics
324
struct feature_stats
×
325
{
326
  //! Processing stage name
327
  std::string key;
328
  //! Whether or not this step is enabled by command-line options
329
  bool enabled;
330
  //! Number of reads/bases trimmed/filtered
331
  reads_and_bases count;
332
};
333

334
void
335
write_report_count(const json_list_ptr& json,
×
336
                   const feature_type action,
337
                   const feature_stats& it)
338
{
339
  if (it.enabled) {
×
340
    const auto dict = json->inline_dict();
×
341

342
    dict->str("step", it.key);
×
343
    dict->str("action", feature_name(action));
×
344
    dict->str("step", it.key);
×
345
    dict->u64("reads", it.count.reads());
×
346
    dict->u64("bases", it.count.bases());
×
347
  }
348
}
349

350
void
351
write_report_counts(const json_list_ptr& json,
×
352
                    const feature_type action,
353
                    const std::vector<feature_stats>& stats)
354
{
355
  for (const auto& it : stats) {
×
356
    write_report_count(json, action, it);
×
357
  }
358
}
359

360
void
361
write_report_poly_x(json_dict& json,
×
362
                    const std::string_view step,
363
                    const std::string_view nucleotides,
364
                    const indexed_count<ACGT>& reads,
365
                    const indexed_count<ACGT>& bases)
366
{
367
  json.str("step", step);
×
368
  json.str("action", "trim");
×
369
  json.i64("reads", reads.sum());
×
370
  json.i64("bases", bases.sum());
×
371

372
  const auto dict = json.dict("x");
×
373
  for (const auto nuc : nucleotides) {
×
374
    const auto nuc_stats = dict->inline_dict(std::string(1, to_lower(nuc)));
×
375

376
    nuc_stats->i64("reads", reads.get(nuc));
×
377
    nuc_stats->i64("bases", bases.get(nuc));
×
378
  }
379
}
380

381
void
382
write_report_processing(const userconfig& config,
×
383
                        json_dict_ptr report,
384
                        const statistics& stats)
385
{
386
  if (!config.is_adapter_trimming_enabled()) {
×
387
    report->null("processing");
×
388
    return;
×
389
  }
390

391
  trimming_statistics totals;
×
392
  for (const auto& it : stats.trimming) {
×
393
    totals += *it;
×
394
  }
395

396
  auto json = report->list("processing");
×
397
  write_report_count(json,
×
398
                     feature_type::trim,
399
                     { "terminal_pre",
400
                       config.is_terminal_base_pre_trimming_enabled(),
×
401
                       totals.terminal_pre_trimmed });
402

403
  if (config.is_poly_x_tail_pre_trimming_enabled()) {
×
404
    write_report_poly_x(*json->dict(),
×
405
                        "poly_x_pre",
406
                        config.pre_trim_poly_x,
×
407
                        totals.poly_x_pre_trimmed_reads,
408
                        totals.poly_x_pre_trimmed_bases);
409
  }
410

411
  {
×
412
    AR_REQUIRE(totals.adapter_trimmed_reads.size() ==
×
413
               totals.adapter_trimmed_bases.size());
414

415
    int64_t reads = 0;
416
    int64_t bases = 0;
417
    for (size_t i = 0; i < totals.adapter_trimmed_reads.size(); ++i) {
×
418
      reads += totals.adapter_trimmed_reads.get(i);
×
419
      bases += totals.adapter_trimmed_bases.get(i);
×
420
    }
421

422
    const auto dict = json->dict();
×
423

424
    dict->str("step", "adapters");
×
425
    dict->str("action", "trim");
×
426
    dict->u64("reads", reads);
×
427
    dict->u64("bases", bases);
×
428

429
    const auto adapters = config.samples.adapters().to_read_orientation();
×
430
    const auto adapter_list = dict->list("adapter_list");
×
431
    for (size_t i = 0; i < adapters.size(); ++i) {
×
432
      const auto adapter = adapter_list->inline_dict();
×
433

434
      adapter->str("adapter1", adapters.at(i).first);
×
435
      adapter->str("adapter2", adapters.at(i).second);
×
436
      adapter->i64("reads", totals.adapter_trimmed_reads.get(i));
×
437
      adapter->i64("bases", totals.adapter_trimmed_bases.get(i));
×
438
    }
439
  }
440

441
  write_report_count(
×
442
    json,
443
    feature_type::merge,
444
    { "merging", config.is_read_merging_enabled(), totals.reads_merged });
×
445

446
  write_report_count(json,
×
447
                     feature_type::trim,
448
                     { "terminal_post",
449
                       config.is_terminal_base_post_trimming_enabled(),
×
450
                       totals.terminal_post_trimmed });
451

452
  if (config.is_poly_x_tail_post_trimming_enabled()) {
×
453
    write_report_poly_x(*json->dict(),
×
454
                        "poly_x_post",
455
                        config.post_trim_poly_x,
×
456
                        totals.poly_x_post_trimmed_reads,
457
                        totals.poly_x_post_trimmed_bases);
458
  }
459

460
  write_report_count(json,
×
461
                     feature_type::trim,
462
                     { "low_quality",
463
                       config.is_low_quality_trimming_enabled(),
×
464
                       totals.low_quality_trimmed });
465

466
  // Filtering is (currently) performed after trimming
467
  write_report_counts(json,
×
468
                      feature_type::filter,
469
                      { { "min_length",
470
                          config.is_short_read_filtering_enabled(),
×
471
                          totals.filtered_min_length },
472
                        { "max_length",
473
                          config.is_long_read_filtering_enabled(),
×
474
                          totals.filtered_max_length },
475
                        { "ambiguous_bases",
476
                          config.is_ambiguous_base_filtering_enabled(),
×
477
                          totals.filtered_ambiguous },
478
                        { "mean_quality",
479
                          config.is_mean_quality_filtering_enabled(),
×
480
                          totals.filtered_mean_quality },
481
                        { "low_complexity",
482
                          config.is_low_complexity_filtering_enabled(),
×
483
                          totals.filtered_low_complexity } });
484
}
485

486
////////////////////////////////////////////////////////////////////////////////
487
// Analyses
488

489
void
490
write_report_duplication(json_dict_ptr json,
×
491
                         const std::string_view key,
492
                         const duplication_stats_ptr& stats)
493
{
494
  AR_REQUIRE(stats);
×
495
  auto duplication = json->dict(key);
×
496

497
  const auto summary = stats->summarize();
×
498
  duplication->str_vec("labels", summary.labels);
×
499
  duplication->f64_vec("unique_sequences", summary.unique_sequences);
×
500
  duplication->f64_vec("total_sequences", summary.total_sequences);
×
501
  duplication->f64("unique_frac", summary.unique_frac);
×
502
}
503

504
void
505
write_report_consensus_adapter(json_dict_ptr json,
×
506
                               const std::string_view key,
507
                               const consensus_adapter_stats& stats)
508
{
509
  auto dict = json->dict(key);
×
510

511
  const auto adapter = stats.summarize();
×
512
  dict->str("consensus", adapter.adapter().sequence());
×
513
  dict->str("qualities", adapter.adapter().qualities());
×
514

515
  auto kmer_dict = dict->dict("kmers");
×
516
  for (const auto& it : adapter.top_kmers()) {
×
517
    kmer_dict->i64(it.first, it.second);
×
518
  }
519
}
520

521
void
522
write_report_analyses(const userconfig& config,
×
523
                      json_dict_ptr json,
524
                      const statistics& stats)
525
{
526
  json = json->dict("analyses");
×
527

528
  if (config.report_duplication) {
×
529
    auto dict = json->dict("duplication");
×
530

531
    write_report_duplication(dict, "read1", stats.duplication_1);
×
532
    write_report_duplication(dict, "read2", stats.duplication_2);
×
533
  } else {
×
534
    json->null("duplication");
×
535
  }
536

537
  if (config.paired_ended_mode) {
×
538
    counts total_insert_sizes;
×
539
    for (const auto& it : stats.trimming) {
×
540
      total_insert_sizes += it->insert_sizes;
×
541
    }
542

543
    json->i64_vec("insert_sizes", total_insert_sizes);
×
544
  } else {
×
545
    json->null("insert_sizes");
×
546
  }
547

548
  if (stats.adapter_id) {
×
549
    auto consensus = json->dict("consensus_adapters");
×
550

551
    consensus->i64("aligned_pairs", stats.adapter_id->aligned_pairs);
×
552
    consensus->i64("pairs_with_adapters",
×
553
                   stats.adapter_id->pairs_with_adapters);
×
554

555
    write_report_consensus_adapter(consensus,
×
556
                                   "read1",
557
                                   stats.adapter_id->adapter1);
×
558
    write_report_consensus_adapter(consensus,
×
559
                                   "read2",
560
                                   stats.adapter_id->adapter2);
×
561
  } else {
×
562
    json->null("consensus_adapters");
×
563
  }
564
}
565

566
////////////////////////////////////////////////////////////////////////////////
567
// Output
568

569
string_vec
NEW
570
collect_files(const output_files& files, read_file rtype)
×
571
{
572
  string_vec filenames;
×
573

574
  for (const auto& sample_files : files.samples()) {
×
575
    const auto offset = sample_files.offset(rtype);
×
576
    if (offset != sample_output_files::disabled) {
×
577
      filenames.push_back(sample_files.filename(offset));
×
578
    }
579
  }
580

581
  return filenames;
×
582
}
×
583

584
string_vec
585
filter_output_file(output_file file)
×
586
{
587
  string_vec out;
×
588
  if (file.name != DEV_NULL) {
×
589
    out.emplace_back(file.name);
×
590
  }
591

592
  return out;
×
593
}
×
594

595
void
596
write_report_output(const userconfig& config,
×
597
                    json_dict& report,
598
                    const statistics& stats)
599
{
600
  if (config.run_type == ar_command::report_only) {
×
601
    report.null("output");
×
602
    return;
×
603
  }
604

605
  auto output_1 = std::make_shared<fastq_statistics>();
×
606
  auto output_2 = std::make_shared<fastq_statistics>();
×
607
  auto merged = std::make_shared<fastq_statistics>();
×
608
  auto singleton = std::make_shared<fastq_statistics>();
×
609
  auto discarded = std::make_shared<fastq_statistics>();
×
610

611
  for (const auto& it : stats.trimming) {
×
612
    *output_1 += *it->read_1;
×
613
    *output_2 += *it->read_2;
×
614
    *merged += *it->merged;
×
615
    *singleton += *it->singleton;
×
616
    *discarded += *it->discarded;
×
617
  }
618

619
  const auto out_files = config.get_output_filenames();
×
NEW
620
  const auto mate_1_files = collect_files(out_files, read_file::mate_1);
×
NEW
621
  const auto mate_2_files = collect_files(out_files, read_file::mate_2);
×
NEW
622
  const auto merged_files = collect_files(out_files, read_file::merged);
×
NEW
623
  const auto singleton_files = collect_files(out_files, read_file::singleton);
×
NEW
624
  const auto discarded_files = collect_files(out_files, read_file::discarded);
×
625

626
  const bool demux_only = config.run_type == ar_command::demultiplex_only;
×
627

628
  const auto output = report.dict("output");
×
629
  io_section("read1", output_1, mate_1_files).write_to_if(output, true);
×
630
  io_section("read2", output_2, mate_2_files)
×
631
    .write_to_if(output, config.paired_ended_mode);
×
632
  io_section("merged", merged, merged_files)
×
633
    .write_to_if(output, config.is_read_merging_enabled());
×
634

635
  io_section("unidentified1",
×
636
             stats.demultiplexing->unidentified_stats_1,
×
637
             filter_output_file(out_files.unidentified_1))
×
638
    .write_to_if(output, config.is_demultiplexing_enabled());
×
639
  io_section("unidentified2",
×
640
             stats.demultiplexing->unidentified_stats_2,
×
641
             filter_output_file(out_files.unidentified_2))
×
642
    .write_to_if(output,
×
643
                 config.is_demultiplexing_enabled() &&
×
644
                   config.paired_ended_mode);
×
645

646
  io_section("singleton", singleton, singleton_files)
×
647
    .write_to_if(output,
×
648
                 config.paired_ended_mode && !demux_only &&
×
649
                   config.is_any_filtering_enabled());
×
650

651
  io_section("discarded", discarded, discarded_files)
×
652
    .write_to_if(output, !demux_only && config.is_any_filtering_enabled());
×
653
}
654

655
} // namespace
656

657
bool
658
write_json_report(const userconfig& config,
×
659
                  const statistics& stats,
660
                  const std::string& filename)
661
{
662
  if (filename == DEV_NULL) {
×
663
    // User disabled the report
664
    return true;
665
  }
666

667
  std::ostringstream output;
×
668

669
  {
×
670
    json_dict_ptr report = std::make_shared<json_dict>();
×
671
    report->str("$schema",
×
672
                "https://MikkelSchubert.github.io/adapterremoval/schemas/" +
×
673
                  VERSION + ".json");
×
674

675
    write_report_meta(config, *report);
×
676
    write_report_summary(config, *report, stats);
×
677
    write_report_input(config, *report, stats);
×
678
    write_report_demultiplexing(config, *report, stats);
×
679
    write_report_processing(config, report, stats);
×
680
    write_report_analyses(config, report, stats);
×
681
    write_report_output(config, *report, stats);
×
682
    report->write(output);
×
683
  }
684

685
  output << std::endl;
×
686

687
  try {
×
688
    managed_writer writer{ filename };
×
689
    writer.write(output.str());
×
690
    writer.close();
×
691
  } catch (const std::ios_base::failure& error) {
×
692
    log::error() << "Error writing JSON report to '" << filename << "':\n"
×
693
                 << indent_lines(error.what());
×
694
    return false;
×
695
  }
×
696

697
  return true;
×
698
}
699

700
} // namespace adapterremoval
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc