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

MikkelSchubert / adapterremoval / #111

30 Apr 2025 09:52AM UTC coverage: 67.045% (+0.06%) from 66.988%
#111

push

travis-ci

web-flow
fix misleading io error messages (#137)

In particular, errors without assosiated error codes would include
nonsensical error descriptions. The use of ios_base::failure is dropped,
since IO generally is not done using iostreams

2 of 8 new or added lines in 4 files covered. (25.0%)

9751 of 14544 relevant lines covered (67.04%)

3041.83 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 "errors.hpp"        // for io_error
9
#include "fastq.hpp"         // for ACGT, fastq, ACGT::values
10
#include "json.hpp"          // for json_dict, json_dict_ptr, json_list
11
#include "logging.hpp"       // for log_stream, error
12
#include "main.hpp"          // for NAME, VERSION
13
#include "managed_io.hpp"    // for managed_writer
14
#include "output.hpp"        // for sample_output_files
15
#include "reports.hpp"       // for write_json_report
16
#include "sequence_sets.hpp" // for adapter_set
17
#include "simd.hpp"          // for size_t
18
#include "statistics.hpp"    // for fastq_stats_ptr, trimming_statistics
19
#include "strutils.hpp"      // for string_vec, to_lower, indent_lines
20
#include "userconfig.hpp"    // for userconfig, output_files, output_sampl...
21
#include <cerrno>            // for errno
22
#include <cstring>           // for size_t, strerror
23
#include <memory>            // for __shared_ptr_access, shared_ptr, make_...
24
#include <string>            // for basic_string, string, operator+, char_...
25
#include <utility>           // for pair
26
#include <vector>            // for vector
27

28
namespace adapterremoval {
29

30
namespace {
31

32
////////////////////////////////////////////////////////////////////////////////
33
// Meta data
34

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

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

46
////////////////////////////////////////////////////////////////////////////////
47
// Summary statistics
48

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

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

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

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

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

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

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

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

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

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

124
////////////////////////////////////////////////////////////////////////////////
125
// Input
126

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

231
////////////////////////////////////////////////////////////////////////////////
232
// Demultiplexing
233

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

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

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

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

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

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

266
        const auto dict = barcode_list->inline_dict();
×
267
        dict->str("barcode1", it.barcode_1);
×
268
        dict->str("barcode2", it.barcode_2);
×
269

270
        switch (it.orientation) {
×
271
          case barcode_orientation::unspecified:
×
272
            dict->null("orientation");
×
273
            break;
×
274
          case barcode_orientation::forward:
×
275
            dict->str("orientation", "forward");
×
276
            break;
×
277
          case barcode_orientation::reverse:
×
278
            dict->str("orientation", "reverse");
×
279
            break;
×
280
          default:
×
281
            AR_FAIL("invalid barcode orientation");
×
282
        }
283

284
        dict->i64("reads", demux.samples.at(i).get(j));
×
285
      }
286

287
      const auto output = sample->dict("output");
×
288
      io_section(read_file::mate_1, "read1", stats.read_1, files)
×
289
        .write_to_if(output, true);
×
290

291
      io_section(read_file::mate_2, "read2", stats.read_2, files)
×
292
        .write_to_if(output, config.paired_ended_mode);
×
293

294
      io_section(read_file::singleton, "singleton", stats.singleton, files)
×
295
        .write_to_if(output,
×
296
                     config.paired_ended_mode && !demux_only &&
×
297
                       config.is_any_filtering_enabled());
×
298

299
      io_section(read_file::merged, "merged", stats.merged, files)
×
300
        .write_to_if(output, config.is_read_merging_enabled());
×
301

302
      io_section(read_file::discarded, "discarded", stats.discarded, files)
×
303
        .write_to_if(output, !demux_only && config.is_any_filtering_enabled());
×
304
    }
305
  } else {
×
306
    report.null("demultiplexing");
×
307
  }
308
}
309

310
////////////////////////////////////////////////////////////////////////////////
311
// Processing
312

313
//! The kind of action performed by a processing step
314
enum class feature_type
315
{
316
  //! Quality or complexity filtering
317
  filter,
318
  //! Read merging and error correction
319
  merge,
320
  //! Adapter, quality, or poly-X trimming
321
  trim,
322
};
323

324
std::string_view
325
feature_name(const feature_type action)
×
326
{
327
  switch (action) {
×
328
    case feature_type::filter:
×
329
      return "filter";
×
330
    case feature_type::merge:
×
331
      return "merge";
×
332
    case feature_type::trim:
×
333
      return "trim";
×
334
    default:
×
335
      AR_FAIL("invalid processing step type");
×
336
  }
337
}
338

339
//! Basic processing/filtering statistics
340
struct feature_stats
×
341
{
342
  //! Processing stage name
343
  std::string key;
344
  //! Whether or not this step is enabled by command-line options
345
  bool enabled;
346
  //! Number of reads/bases trimmed/filtered
347
  reads_and_bases count;
348
};
349

350
void
351
write_report_count(const json_list_ptr& json,
×
352
                   const feature_type action,
353
                   const feature_stats& it)
354
{
355
  if (it.enabled) {
×
356
    const auto dict = json->inline_dict();
×
357

358
    dict->str("step", it.key);
×
359
    dict->str("action", feature_name(action));
×
360
    dict->str("step", it.key);
×
361
    dict->u64("reads", it.count.reads());
×
362
    dict->u64("bases", it.count.bases());
×
363
  }
364
}
365

366
void
367
write_report_counts(const json_list_ptr& json,
×
368
                    const feature_type action,
369
                    const std::vector<feature_stats>& stats)
370
{
371
  for (const auto& it : stats) {
×
372
    write_report_count(json, action, it);
×
373
  }
374
}
375

376
void
377
write_report_poly_x(json_dict& json,
×
378
                    const std::string_view step,
379
                    const std::string_view nucleotides,
380
                    const indexed_count<ACGT>& reads,
381
                    const indexed_count<ACGT>& bases)
382
{
383
  json.str("step", step);
×
384
  json.str("action", "trim");
×
385
  json.i64("reads", reads.sum());
×
386
  json.i64("bases", bases.sum());
×
387

388
  const auto dict = json.dict("x");
×
389
  for (const auto nuc : nucleotides) {
×
390
    const auto nuc_stats = dict->inline_dict(std::string(1, to_lower(nuc)));
×
391

392
    nuc_stats->i64("reads", reads.get(nuc));
×
393
    nuc_stats->i64("bases", bases.get(nuc));
×
394
  }
395
}
396

397
void
398
write_report_processing(const userconfig& config,
×
399
                        json_dict_ptr report,
400
                        const statistics& stats)
401
{
402
  if (!config.is_adapter_trimming_enabled()) {
×
403
    report->null("processing");
×
404
    return;
×
405
  }
406

407
  trimming_statistics totals;
×
408
  for (const auto& it : stats.trimming) {
×
409
    totals += *it;
×
410
  }
411

412
  auto json = report->list("processing");
×
413
  write_report_count(json,
×
414
                     feature_type::trim,
415
                     { "terminal_pre",
416
                       config.is_terminal_base_pre_trimming_enabled(),
×
417
                       totals.terminal_pre_trimmed });
418

419
  if (config.is_poly_x_tail_pre_trimming_enabled()) {
×
420
    write_report_poly_x(*json->dict(),
×
421
                        "poly_x_pre",
422
                        config.pre_trim_poly_x,
×
423
                        totals.poly_x_pre_trimmed_reads,
424
                        totals.poly_x_pre_trimmed_bases);
425
  }
426

427
  {
×
428
    AR_REQUIRE(totals.adapter_trimmed_reads.size() ==
×
429
               totals.adapter_trimmed_bases.size());
430

431
    int64_t reads = 0;
432
    int64_t bases = 0;
433
    for (size_t i = 0; i < totals.adapter_trimmed_reads.size(); ++i) {
×
434
      reads += totals.adapter_trimmed_reads.get(i);
×
435
      bases += totals.adapter_trimmed_bases.get(i);
×
436
    }
437

438
    const auto dict = json->dict();
×
439

440
    dict->str("step", "adapters");
×
441
    dict->str("action", "trim");
×
442
    dict->u64("reads", reads);
×
443
    dict->u64("bases", bases);
×
444

445
    const auto adapters = config.samples.adapters().to_read_orientation();
×
446
    const auto adapter_list = dict->list("adapter_list");
×
447
    for (size_t i = 0; i < adapters.size(); ++i) {
×
448
      const auto adapter = adapter_list->inline_dict();
×
449

450
      adapter->str("adapter1", adapters.at(i).first);
×
451
      adapter->str("adapter2", adapters.at(i).second);
×
452
      adapter->i64("reads", totals.adapter_trimmed_reads.get(i));
×
453
      adapter->i64("bases", totals.adapter_trimmed_bases.get(i));
×
454
    }
455
  }
456

457
  write_report_count(
×
458
    json,
459
    feature_type::merge,
460
    { "merging", config.is_read_merging_enabled(), totals.reads_merged });
×
461

462
  write_report_count(json,
×
463
                     feature_type::trim,
464
                     { "terminal_post",
465
                       config.is_terminal_base_post_trimming_enabled(),
×
466
                       totals.terminal_post_trimmed });
467

468
  if (config.is_poly_x_tail_post_trimming_enabled()) {
×
469
    write_report_poly_x(*json->dict(),
×
470
                        "poly_x_post",
471
                        config.post_trim_poly_x,
×
472
                        totals.poly_x_post_trimmed_reads,
473
                        totals.poly_x_post_trimmed_bases);
474
  }
475

476
  write_report_count(json,
×
477
                     feature_type::trim,
478
                     { "low_quality",
479
                       config.is_low_quality_trimming_enabled(),
×
480
                       totals.low_quality_trimmed });
481

482
  // Filtering is (currently) performed after trimming
483
  write_report_counts(json,
×
484
                      feature_type::filter,
485
                      { { "min_length",
486
                          config.is_short_read_filtering_enabled(),
×
487
                          totals.filtered_min_length },
488
                        { "max_length",
489
                          config.is_long_read_filtering_enabled(),
×
490
                          totals.filtered_max_length },
491
                        { "ambiguous_bases",
492
                          config.is_ambiguous_base_filtering_enabled(),
×
493
                          totals.filtered_ambiguous },
494
                        { "mean_quality",
495
                          config.is_mean_quality_filtering_enabled(),
×
496
                          totals.filtered_mean_quality },
497
                        { "low_complexity",
498
                          config.is_low_complexity_filtering_enabled(),
×
499
                          totals.filtered_low_complexity } });
500
}
501

502
////////////////////////////////////////////////////////////////////////////////
503
// Analyses
504

505
void
506
write_report_duplication(json_dict_ptr json,
×
507
                         const std::string_view key,
508
                         const duplication_stats_ptr& stats)
509
{
510
  AR_REQUIRE(stats);
×
511
  auto duplication = json->dict(key);
×
512

513
  const auto summary = stats->summarize();
×
514
  duplication->str_vec("labels", summary.labels);
×
515
  duplication->f64_vec("unique_sequences", summary.unique_sequences);
×
516
  duplication->f64_vec("total_sequences", summary.total_sequences);
×
517
  duplication->f64("unique_frac", summary.unique_frac);
×
518
}
519

520
void
521
write_report_consensus_adapter(json_dict_ptr json,
×
522
                               const std::string_view key,
523
                               const consensus_adapter_stats& stats)
524
{
525
  auto dict = json->dict(key);
×
526

527
  const auto adapter = stats.summarize();
×
528
  dict->str("consensus", adapter.adapter().sequence());
×
529
  dict->str("qualities", adapter.adapter().qualities());
×
530

531
  auto kmer_dict = dict->dict("kmers");
×
532
  for (const auto& it : adapter.top_kmers()) {
×
533
    kmer_dict->i64(it.first, it.second);
×
534
  }
535
}
536

537
void
538
write_report_analyses(const userconfig& config,
×
539
                      json_dict_ptr json,
540
                      const statistics& stats)
541
{
542
  json = json->dict("analyses");
×
543

544
  if (config.report_duplication) {
×
545
    auto dict = json->dict("duplication");
×
546

547
    write_report_duplication(dict, "read1", stats.duplication_1);
×
548
    write_report_duplication(dict, "read2", stats.duplication_2);
×
549
  } else {
×
550
    json->null("duplication");
×
551
  }
552

553
  if (config.paired_ended_mode) {
×
554
    counts total_insert_sizes;
×
555
    for (const auto& it : stats.trimming) {
×
556
      total_insert_sizes += it->insert_sizes;
×
557
    }
558

559
    json->i64_vec("insert_sizes", total_insert_sizes);
×
560
  } else {
×
561
    json->null("insert_sizes");
×
562
  }
563

564
  if (stats.adapter_id) {
×
565
    auto consensus = json->dict("consensus_adapters");
×
566

567
    consensus->i64("aligned_pairs", stats.adapter_id->aligned_pairs);
×
568
    consensus->i64("pairs_with_adapters",
×
569
                   stats.adapter_id->pairs_with_adapters);
×
570

571
    write_report_consensus_adapter(consensus,
×
572
                                   "read1",
573
                                   stats.adapter_id->adapter1);
×
574
    write_report_consensus_adapter(consensus,
×
575
                                   "read2",
576
                                   stats.adapter_id->adapter2);
×
577
  } else {
×
578
    json->null("consensus_adapters");
×
579
  }
580
}
581

582
////////////////////////////////////////////////////////////////////////////////
583
// Output
584

585
string_vec
586
collect_files(const output_files& files, read_file rtype)
×
587
{
588
  string_vec filenames;
×
589

590
  for (const auto& sample_files : files.samples()) {
×
591
    const auto offset = sample_files.offset(rtype);
×
592
    if (offset != sample_output_files::disabled) {
×
593
      filenames.push_back(sample_files.filename(offset));
×
594
    }
595
  }
596

597
  return filenames;
×
598
}
×
599

600
string_vec
601
filter_output_file(output_file file)
×
602
{
603
  string_vec out;
×
604
  if (file.name != DEV_NULL) {
×
605
    out.emplace_back(file.name);
×
606
  }
607

608
  return out;
×
609
}
×
610

611
void
612
write_report_output(const userconfig& config,
×
613
                    json_dict& report,
614
                    const statistics& stats)
615
{
616
  if (config.run_type == ar_command::report_only) {
×
617
    report.null("output");
×
618
    return;
×
619
  }
620

621
  auto output_1 = std::make_shared<fastq_statistics>();
×
622
  auto output_2 = std::make_shared<fastq_statistics>();
×
623
  auto merged = std::make_shared<fastq_statistics>();
×
624
  auto singleton = std::make_shared<fastq_statistics>();
×
625
  auto discarded = std::make_shared<fastq_statistics>();
×
626

627
  for (const auto& it : stats.trimming) {
×
628
    *output_1 += *it->read_1;
×
629
    *output_2 += *it->read_2;
×
630
    *merged += *it->merged;
×
631
    *singleton += *it->singleton;
×
632
    *discarded += *it->discarded;
×
633
  }
634

635
  const auto out_files = config.get_output_filenames();
×
636
  const auto mate_1_files = collect_files(out_files, read_file::mate_1);
×
637
  const auto mate_2_files = collect_files(out_files, read_file::mate_2);
×
638
  const auto merged_files = collect_files(out_files, read_file::merged);
×
639
  const auto singleton_files = collect_files(out_files, read_file::singleton);
×
640
  const auto discarded_files = collect_files(out_files, read_file::discarded);
×
641

642
  const bool demux_only = config.run_type == ar_command::demultiplex_only;
×
643

644
  const auto output = report.dict("output");
×
645
  io_section("read1", output_1, mate_1_files).write_to_if(output, true);
×
646
  io_section("read2", output_2, mate_2_files)
×
647
    .write_to_if(output, config.paired_ended_mode);
×
648
  io_section("merged", merged, merged_files)
×
649
    .write_to_if(output, config.is_read_merging_enabled());
×
650

651
  io_section("unidentified1",
×
652
             stats.demultiplexing->unidentified_stats_1,
×
653
             filter_output_file(out_files.unidentified_1))
×
654
    .write_to_if(output, config.is_demultiplexing_enabled());
×
655
  io_section("unidentified2",
×
656
             stats.demultiplexing->unidentified_stats_2,
×
657
             filter_output_file(out_files.unidentified_2))
×
658
    .write_to_if(output,
×
659
                 config.is_demultiplexing_enabled() &&
×
660
                   config.paired_ended_mode);
×
661

662
  io_section("singleton", singleton, singleton_files)
×
663
    .write_to_if(output,
×
664
                 config.paired_ended_mode && !demux_only &&
×
665
                   config.is_any_filtering_enabled());
×
666

667
  io_section("discarded", discarded, discarded_files)
×
668
    .write_to_if(output, !demux_only && config.is_any_filtering_enabled());
×
669
}
670

671
} // namespace
672

673
bool
674
write_json_report(const userconfig& config,
×
675
                  const statistics& stats,
676
                  const std::string& filename)
677
{
678
  if (filename == DEV_NULL) {
×
679
    // User disabled the report
680
    return true;
681
  }
682

683
  std::ostringstream output;
×
684

685
  {
×
686
    json_dict_ptr report = std::make_shared<json_dict>();
×
687
    report->str("$schema",
×
688
                "https://MikkelSchubert.github.io/adapterremoval/schemas/" +
×
689
                  VERSION + ".json");
×
690

691
    write_report_meta(config, *report);
×
692
    write_report_summary(config, *report, stats);
×
693
    write_report_input(config, *report, stats);
×
694
    write_report_demultiplexing(config, *report, stats);
×
695
    write_report_processing(config, report, stats);
×
696
    write_report_analyses(config, report, stats);
×
697
    write_report_output(config, *report, stats);
×
698
    report->write(output);
×
699
  }
700

701
  output << std::endl;
×
702

703
  try {
×
704
    managed_writer writer{ filename };
×
705
    writer.write(output.str());
×
706
    writer.close();
×
NEW
707
  } catch (const io_error& error) {
×
708
    log::error() << "Error writing JSON report to '" << filename << "':\n"
×
709
                 << indent_lines(error.what());
×
710
    return false;
×
711
  }
×
712

713
  return true;
×
714
}
715

716
} // 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