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

MikkelSchubert / adapterremoval / #103

18 Apr 2025 01:56PM UTC coverage: 66.959% (-0.2%) from 67.126%
#103

push

travis-ci

web-flow
include barcode orientation in HTML/JSON reports (#129)

This implements part of #68

0 of 48 new or added lines in 2 files covered. (0.0%)

3 existing lines in 2 files now uncovered.

9697 of 14482 relevant lines covered (66.96%)

3053.41 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
{
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

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

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

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

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

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

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

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

309
////////////////////////////////////////////////////////////////////////////////
310
// Processing
311

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

501
////////////////////////////////////////////////////////////////////////////////
502
// Analyses
503

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

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

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

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

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

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

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

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

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

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

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

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

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

581
////////////////////////////////////////////////////////////////////////////////
582
// Output
583

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

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

596
  return filenames;
×
597
}
×
598

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

607
  return out;
×
608
}
×
609

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

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

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

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

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

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

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

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

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

670
} // namespace
671

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

682
  std::ostringstream output;
×
683

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

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

700
  output << std::endl;
×
701

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

712
  return true;
×
713
}
714

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