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

MikkelSchubert / adapterremoval / #117

25 May 2025 03:01PM UTC coverage: 66.932% (-0.07%) from 67.006%
#117

push

travis-ci

web-flow
iwyu and reduce build-time inter-dependencies (#144)

26 of 145 new or added lines in 20 files covered. (17.93%)

89 existing lines in 5 files now uncovered.

9738 of 14549 relevant lines covered (66.93%)

3041.19 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 "fastq_enc.hpp"     // for ACGT, ACGTN
11
#include "json.hpp"          // for json_dict, json_dict_ptr, json_list
12
#include "logging.hpp"       // for log_stream, error
13
#include "main.hpp"          // for NAME, VERSION
14
#include "managed_io.hpp"    // for managed_writer
15
#include "output.hpp"        // for sample_output_files
16
#include "reports.hpp"       // for write_json_report
17
#include "sequence.hpp"      // for dna_sequence
18
#include "sequence_sets.hpp" // for adapter_set
19
#include "statistics.hpp"    // for fastq_stats_ptr, trimming_statistics
20
#include "strutils.hpp"      // for string_vec, to_lower, indent_lines
21
#include "userconfig.hpp"    // for userconfig, output_files, output_sampl...
22
#include <array>             // for array
23
#include <cstdint>           // for int64_t
24
#include <cstring>           // for size_t, strerror
25
#include <memory>            // for __shared_ptr_access, shared_ptr, make_...
26
#include <sstream>           // for basic_ostringstream, basic_ostream, bas...
27
#include <string>            // for basic_string, string, operator+, char_...
28
#include <string_view>       // for string_view, operator!=, operator==
29
#include <utility>           // for pair
30
#include <vector>            // for vector
31

32
namespace adapterremoval {
33

34
namespace {
35

36
////////////////////////////////////////////////////////////////////////////////
37
// Meta data
38

39
void
40
write_report_meta(const userconfig& config, json_dict& report)
×
41
{
42
  const auto meta = report.dict("meta");
×
43

44
  meta->str("version", NAME + " " + VERSION);
×
45
  meta->str_vec("command", config.args);
×
46
  meta->f64("runtime", config.runtime());
×
47
  meta->str("timestamp", userconfig::start_time);
×
48
}
49

50
////////////////////////////////////////////////////////////////////////////////
51
// Summary statistics
52

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

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

82
  const auto n_bases_s = n_a + n_c + n_g + n_t + n_n;
×
83

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

99
void
100
write_report_summary(const userconfig& config,
×
101
                     json_dict& report,
102
                     const statistics& stats)
103
{
104
  const auto summary = report.dict("summary");
×
105

106
  write_report_summary_stats(summary->dict("input"),
×
107
                             { stats.input_1, stats.input_2 });
×
108

109
  if (config.run_type == ar_command::report_only) {
×
110
    summary->null("output");
×
111
  } else {
112
    const auto output = summary->dict("output");
×
113

114
    std::vector<fastq_stats_ptr> passed;
×
115
    for (const auto& it : stats.trimming) {
×
116
      passed.push_back(it->read_1);
×
117
      passed.push_back(it->read_2);
×
118
      passed.push_back(it->merged);
×
119
      passed.push_back(it->singleton);
×
120

121
      // Discarded reads are excluded, even if saved
122
    }
123

124
    write_report_summary_stats(summary->dict("output"), passed);
×
125
  }
126
}
127

128
////////////////////////////////////////////////////////////////////////////////
129
// Input
130

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

146
  io_section(std::string name, fastq_stats_ptr stats, string_vec filenames)
×
147
    : m_stats(std::move(stats))
×
148
    , m_name(std::move(name))
×
149
    , m_filenames(std::move(filenames))
×
150
  {
151
  }
152

153
  void write_to_if(const json_dict_ptr& json, bool enabled = true) const
×
154
  {
155
    if (!enabled) {
×
156
      json->null(m_name);
×
157
      return;
×
158
    }
159

160
    const auto section = json->dict(m_name);
×
161
    if (m_filenames.empty()) {
×
162
      section->null("filenames");
×
163
    } else {
164
      section->str_vec("filenames", m_filenames);
×
165
    }
166

167
    section->u64("input_reads", m_stats->number_of_input_reads());
×
168
    section->u64("output_reads", m_stats->number_of_output_reads());
×
169
    section->u64("reads_sampled", m_stats->number_of_sampled_reads());
×
170
    section->i64_vec("lengths", m_stats->length_dist());
×
171

172
    if (m_stats->length_dist().product()) {
×
173
      const auto total_bases = m_stats->nucleotides_pos();
×
174
      const auto total_quality = m_stats->qualities_pos();
×
175

176
      {
×
177
        const auto quality_curves = section->dict("quality_curves");
×
178

179
        for (const auto nuc : ACGT::values) {
×
180
          const auto nucleotides = m_stats->nucleotides_pos(nuc);
×
181
          const auto quality = m_stats->qualities_pos(nuc);
×
182

183
          quality_curves->f64_vec(std::string(1, to_lower(nuc)),
×
184
                                  quality / nucleotides);
×
185
        }
186

187
        quality_curves->f64_vec("mean", total_quality / total_bases);
×
188
      }
189

190
      {
×
191
        const auto content_curves = section->dict("content_curves");
×
192

193
        for (const auto nuc : ACGTN::values) {
×
194
          const auto bases = m_stats->nucleotides_pos(nuc);
×
195

196
          // FIXME: Should be raw counts instead of fractions
197
          content_curves->f64_vec(std::string(1, to_lower(nuc)),
×
198
                                  bases / total_bases);
×
199
        }
200
      }
201

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

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

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

230
  io_section("read1", stats.input_1, config.input_files_1).write_to_if(input);
×
231
  io_section("read2", stats.input_2, mate_2_filenames)
×
232
    .write_to_if(input, config.paired_ended_mode);
×
233
}
234

235
////////////////////////////////////////////////////////////////////////////////
236
// Demultiplexing
237

238
void
239
write_report_demultiplexing(const userconfig& config,
×
240
                            json_dict& report,
241
                            const statistics& sample_stats)
242
{
243
  const bool demux_only = config.run_type == ar_command::demultiplex_only;
×
244
  const auto out_files = config.get_output_filenames();
×
245

246
  if (config.is_demultiplexing_enabled()) {
×
247
    const auto demultiplexing = report.dict("demultiplexing");
×
248

249
    const auto& demux = *sample_stats.demultiplexing;
×
250
    size_t assigned_reads = 0;
×
251
    for (const auto& it : demux.samples) {
×
252
      assigned_reads += it.sum();
×
253
    }
254

255
    demultiplexing->u64("assigned_reads", assigned_reads);
×
256
    demultiplexing->u64("ambiguous_reads", demux.ambiguous);
×
257
    demultiplexing->u64("unassigned_reads", demux.unidentified);
×
258

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

NEW
265
      const auto& barcodes = config.samples->at(i);
×
266
      const auto barcode_list = sample->list("barcodes");
×
267
      for (size_t j = 0; j < barcodes.size(); ++j) {
×
268
        const auto it = barcodes.at(j);
×
269

270
        const auto dict = barcode_list->inline_dict();
×
271
        dict->str("barcode1", it.barcode_1);
×
272
        dict->str("barcode2", it.barcode_2);
×
273

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

288
        dict->i64("reads", demux.samples.at(i).get(j));
×
289
      }
290

291
      const auto output = sample->dict("output");
×
292
      io_section(read_file::mate_1, "read1", stats.read_1, files)
×
293
        .write_to_if(output, true);
×
294

295
      io_section(read_file::mate_2, "read2", stats.read_2, files)
×
296
        .write_to_if(output, config.paired_ended_mode);
×
297

298
      io_section(read_file::singleton, "singleton", stats.singleton, files)
×
299
        .write_to_if(output,
×
300
                     config.paired_ended_mode && !demux_only &&
×
301
                       config.is_any_filtering_enabled());
×
302

303
      io_section(read_file::merged, "merged", stats.merged, files)
×
304
        .write_to_if(output, config.is_read_merging_enabled());
×
305

306
      io_section(read_file::discarded, "discarded", stats.discarded, files)
×
307
        .write_to_if(output, !demux_only && config.is_any_filtering_enabled());
×
308
    }
309
  } else {
×
310
    report.null("demultiplexing");
×
311
  }
312
}
313

314
////////////////////////////////////////////////////////////////////////////////
315
// Processing
316

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

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

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

354
void
355
write_report_count(const json_list_ptr& json,
×
356
                   const feature_type action,
357
                   const feature_stats& it)
358
{
359
  if (it.enabled) {
×
360
    const auto dict = json->inline_dict();
×
361

362
    dict->str("step", it.key);
×
363
    dict->str("action", feature_name(action));
×
364
    dict->str("step", it.key);
×
365
    dict->u64("reads", it.count.reads());
×
366
    dict->u64("bases", it.count.bases());
×
367
  }
368
}
369

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

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

392
  const auto dict = json.dict("x");
×
393
  for (const auto nuc : nucleotides) {
×
394
    const auto nuc_stats = dict->inline_dict(std::string(1, to_lower(nuc)));
×
395

396
    nuc_stats->i64("reads", reads.get(nuc));
×
397
    nuc_stats->i64("bases", bases.get(nuc));
×
398
  }
399
}
400

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

411
  trimming_statistics totals;
×
412
  for (const auto& it : stats.trimming) {
×
413
    totals += *it;
×
414
  }
415

416
  auto json = report->list("processing");
×
417
  write_report_count(json,
×
418
                     feature_type::trim,
419
                     { "terminal_pre",
420
                       config.is_terminal_base_pre_trimming_enabled(),
×
421
                       totals.terminal_pre_trimmed });
422

423
  if (config.is_poly_x_tail_pre_trimming_enabled()) {
×
424
    write_report_poly_x(*json->dict(),
×
425
                        "poly_x_pre",
426
                        config.pre_trim_poly_x,
×
427
                        totals.poly_x_pre_trimmed_reads,
428
                        totals.poly_x_pre_trimmed_bases);
429
  }
430

431
  {
×
432
    AR_REQUIRE(totals.adapter_trimmed_reads.size() ==
×
433
               totals.adapter_trimmed_bases.size());
434

435
    int64_t reads = 0;
436
    int64_t bases = 0;
437
    for (size_t i = 0; i < totals.adapter_trimmed_reads.size(); ++i) {
×
438
      reads += totals.adapter_trimmed_reads.get(i);
×
439
      bases += totals.adapter_trimmed_bases.get(i);
×
440
    }
441

442
    const auto dict = json->dict();
×
443

444
    dict->str("step", "adapters");
×
445
    dict->str("action", "trim");
×
446
    dict->u64("reads", reads);
×
447
    dict->u64("bases", bases);
×
448

NEW
449
    const auto adapters = config.samples->adapters().to_read_orientation();
×
450
    const auto adapter_list = dict->list("adapter_list");
×
451
    for (size_t i = 0; i < adapters.size(); ++i) {
×
452
      const auto adapter = adapter_list->inline_dict();
×
453

454
      adapter->str("adapter1", adapters.at(i).first);
×
455
      adapter->str("adapter2", adapters.at(i).second);
×
456
      adapter->i64("reads", totals.adapter_trimmed_reads.get(i));
×
457
      adapter->i64("bases", totals.adapter_trimmed_bases.get(i));
×
458
    }
459
  }
460

461
  write_report_count(
×
462
    json,
463
    feature_type::merge,
464
    { "merging", config.is_read_merging_enabled(), totals.reads_merged });
×
465

466
  write_report_count(json,
×
467
                     feature_type::trim,
468
                     { "terminal_post",
469
                       config.is_terminal_base_post_trimming_enabled(),
×
470
                       totals.terminal_post_trimmed });
471

472
  if (config.is_poly_x_tail_post_trimming_enabled()) {
×
473
    write_report_poly_x(*json->dict(),
×
474
                        "poly_x_post",
475
                        config.post_trim_poly_x,
×
476
                        totals.poly_x_post_trimmed_reads,
477
                        totals.poly_x_post_trimmed_bases);
478
  }
479

480
  write_report_count(json,
×
481
                     feature_type::trim,
482
                     { "low_quality",
483
                       config.is_low_quality_trimming_enabled(),
×
484
                       totals.low_quality_trimmed });
485

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

506
////////////////////////////////////////////////////////////////////////////////
507
// Analyses
508

509
void
510
write_report_duplication(json_dict_ptr json,
×
511
                         const std::string_view key,
512
                         const duplication_stats_ptr& stats)
513
{
514
  AR_REQUIRE(stats);
×
515
  auto duplication = json->dict(key);
×
516

517
  const auto summary = stats->summarize();
×
518
  duplication->str_vec("labels", summary.labels);
×
519
  duplication->f64_vec("unique_sequences", summary.unique_sequences);
×
520
  duplication->f64_vec("total_sequences", summary.total_sequences);
×
521
  duplication->f64("unique_frac", summary.unique_frac);
×
522
}
523

524
void
525
write_report_consensus_adapter(json_dict_ptr json,
×
526
                               const std::string_view key,
527
                               const consensus_adapter_stats& stats)
528
{
529
  auto dict = json->dict(key);
×
530

531
  const auto adapter = stats.summarize();
×
532
  dict->str("consensus", adapter.adapter().sequence());
×
533
  dict->str("qualities", adapter.adapter().qualities());
×
534

535
  auto kmer_dict = dict->dict("kmers");
×
536
  for (const auto& it : adapter.top_kmers()) {
×
537
    kmer_dict->i64(it.first, it.second);
×
538
  }
539
}
540

541
void
542
write_report_analyses(const userconfig& config,
×
543
                      json_dict_ptr json,
544
                      const statistics& stats)
545
{
546
  json = json->dict("analyses");
×
547

548
  if (config.report_duplication) {
×
549
    auto dict = json->dict("duplication");
×
550

551
    write_report_duplication(dict, "read1", stats.duplication_1);
×
552
    write_report_duplication(dict, "read2", stats.duplication_2);
×
553
  } else {
×
554
    json->null("duplication");
×
555
  }
556

557
  if (config.paired_ended_mode) {
×
558
    counts total_insert_sizes;
×
559
    for (const auto& it : stats.trimming) {
×
560
      total_insert_sizes += it->insert_sizes;
×
561
    }
562

563
    json->i64_vec("insert_sizes", total_insert_sizes);
×
564
  } else {
×
565
    json->null("insert_sizes");
×
566
  }
567

568
  if (stats.adapter_id) {
×
569
    auto consensus = json->dict("consensus_adapters");
×
570

571
    consensus->i64("aligned_pairs", stats.adapter_id->aligned_pairs);
×
572
    consensus->i64("pairs_with_adapters",
×
573
                   stats.adapter_id->pairs_with_adapters);
×
574

575
    write_report_consensus_adapter(consensus,
×
576
                                   "read1",
577
                                   stats.adapter_id->adapter1);
×
578
    write_report_consensus_adapter(consensus,
×
579
                                   "read2",
580
                                   stats.adapter_id->adapter2);
×
581
  } else {
×
582
    json->null("consensus_adapters");
×
583
  }
584
}
585

586
////////////////////////////////////////////////////////////////////////////////
587
// Output
588

589
string_vec
590
collect_files(const output_files& files, read_file rtype)
×
591
{
592
  string_vec filenames;
×
593

594
  for (const auto& sample_files : files.samples()) {
×
595
    const auto offset = sample_files.offset(rtype);
×
596
    if (offset != sample_output_files::disabled) {
×
597
      filenames.push_back(sample_files.filename(offset));
×
598
    }
599
  }
600

601
  return filenames;
×
602
}
×
603

604
string_vec
605
filter_output_file(output_file file)
×
606
{
607
  string_vec out;
×
608
  if (file.name != DEV_NULL) {
×
609
    out.emplace_back(file.name);
×
610
  }
611

612
  return out;
×
613
}
×
614

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

625
  auto output_1 = std::make_shared<fastq_statistics>();
×
626
  auto output_2 = std::make_shared<fastq_statistics>();
×
627
  auto merged = std::make_shared<fastq_statistics>();
×
628
  auto singleton = std::make_shared<fastq_statistics>();
×
629
  auto discarded = std::make_shared<fastq_statistics>();
×
630

631
  for (const auto& it : stats.trimming) {
×
632
    *output_1 += *it->read_1;
×
633
    *output_2 += *it->read_2;
×
634
    *merged += *it->merged;
×
635
    *singleton += *it->singleton;
×
636
    *discarded += *it->discarded;
×
637
  }
638

639
  const auto out_files = config.get_output_filenames();
×
640
  const auto mate_1_files = collect_files(out_files, read_file::mate_1);
×
641
  const auto mate_2_files = collect_files(out_files, read_file::mate_2);
×
642
  const auto merged_files = collect_files(out_files, read_file::merged);
×
643
  const auto singleton_files = collect_files(out_files, read_file::singleton);
×
644
  const auto discarded_files = collect_files(out_files, read_file::discarded);
×
645

646
  const bool demux_only = config.run_type == ar_command::demultiplex_only;
×
647

648
  const auto output = report.dict("output");
×
649
  io_section("read1", output_1, mate_1_files).write_to_if(output, true);
×
650
  io_section("read2", output_2, mate_2_files)
×
651
    .write_to_if(output, config.paired_ended_mode);
×
652
  io_section("merged", merged, merged_files)
×
653
    .write_to_if(output, config.is_read_merging_enabled());
×
654

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

666
  io_section("singleton", singleton, singleton_files)
×
667
    .write_to_if(output,
×
668
                 config.paired_ended_mode && !demux_only &&
×
669
                   config.is_any_filtering_enabled());
×
670

671
  io_section("discarded", discarded, discarded_files)
×
672
    .write_to_if(output, !demux_only && config.is_any_filtering_enabled());
×
673
}
674

675
} // namespace
676

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

687
  std::ostringstream output;
×
688

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

695
    write_report_meta(config, *report);
×
696
    write_report_summary(config, *report, stats);
×
697
    write_report_input(config, *report, stats);
×
698
    write_report_demultiplexing(config, *report, stats);
×
699
    write_report_processing(config, report, stats);
×
700
    write_report_analyses(config, report, stats);
×
701
    write_report_output(config, *report, stats);
×
702
    report->write(output);
×
703
  }
704

705
  output << std::endl;
×
706

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

717
  return true;
×
718
}
719

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

© 2025 Coveralls, Inc