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

MikkelSchubert / adapterremoval / #45

20 Sep 2024 06:49PM UTC coverage: 26.244% (-49.2%) from 75.443%
#45

push

travis-ci

web-flow
attempt to fix coveralls run

2458 of 9366 relevant lines covered (26.24%)

4362.23 hits per line

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

0.0
/src/reports_json.cpp
1
/*************************************************************************\
2
 * AdapterRemoval - cleaning next-generation sequencing reads            *
3
 *                                                                       *
4
 * Copyright (C) 2011 by Stinus Lindgreen - stinus@binf.ku.dk            *
5
 * Copyright (C) 2014 by Mikkel Schubert - mikkelsch@gmail.com           *
6
 *                                                                       *
7
 * This program is free software: you can redistribute it and/or modify  *
8
 * it under the terms of the GNU General Public License as published by  *
9
 * the Free Software Foundation, either version 3 of the License, or     *
10
 * (at your option) any later version.                                   *
11
 *                                                                       *
12
 * This program is distributed in the hope that it will be useful,       *
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of        *
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
15
 * GNU General Public License for more details.                          *
16
 *                                                                       *
17
 * You should have received a copy of the GNU General Public License     *
18
 * along with this program.  If not, see <http://www.gnu.org/licenses/>. *
19
\*************************************************************************/
20
#include "adapter_id.hpp"    // for consensus_adapter_stats
21
#include "commontypes.hpp"   // for read_type, read_type::mate_1, read_typ...
22
#include "counts.hpp"        // for counts, counts_tmpl, indexed_count
23
#include "debug.hpp"         // for AR_REQUIRE
24
#include "fastq.hpp"         // for ACGT, fastq, ACGT::values
25
#include "json.hpp"          // for json_dict, json_dict_ptr, json_list
26
#include "logging.hpp"       // for log_stream, error
27
#include "main.hpp"          // for NAME, VERSION
28
#include "managed_io.hpp"    // for managed_writer
29
#include "output.hpp"        // for sample_output_files
30
#include "reports.hpp"       // for write_json_report
31
#include "sequence_sets.hpp" // for adapter_set
32
#include "simd.hpp"          // for size_t
33
#include "statistics.hpp"    // for fastq_stats_ptr, trimming_statistics
34
#include "strutils.hpp"      // for string_vec, to_lower, indent_lines
35
#include "userconfig.hpp"    // for userconfig, output_files, output_sampl...
36
#include <cerrno>            // for errno
37
#include <cstring>           // for size_t, strerror
38
#include <memory>            // for __shared_ptr_access, shared_ptr, make_...
39
#include <string>            // for basic_string, string, operator+, char_...
40
#include <utility>           // for pair
41
#include <vector>            // for vector
42

43
namespace adapterremoval {
44

45
namespace {
46

47
////////////////////////////////////////////////////////////////////////////////
48
// Meta data
49

50
void
51
write_report_meta(const userconfig& config, json_dict& report)
×
52
{
53
  const auto meta = report.dict("meta");
×
54

55
  meta->str("version", NAME + " " + VERSION);
×
56
  meta->str_vec("command", config.args);
×
57
  meta->f64("runtime", config.runtime());
×
58
  meta->str("timestamp", userconfig::start_time);
×
59
}
60

61
////////////////////////////////////////////////////////////////////////////////
62
// Summary statistics
63

64
void
65
write_report_summary_stats(const json_dict_ptr& json,
×
66
                           const std::vector<fastq_stats_ptr>& stats)
67
{
68
  size_t n_reads = 0;
×
69
  size_t n_reads_s = 0;
×
70
  size_t n_bases = 0;
×
71
  size_t n_a = 0;
×
72
  size_t n_c = 0;
×
73
  size_t n_g = 0;
×
74
  size_t n_t = 0;
×
75
  size_t n_n = 0;
×
76
  size_t n_q20 = 0;
×
77
  size_t n_q30 = 0;
×
78

79
  for (const auto& it : stats) {
×
80
    n_reads += it->number_of_output_reads();
×
81
    n_reads_s += it->number_of_sampled_reads();
×
82
    n_bases += it->length_dist().product();
×
83
    // The following stats are all (potentially) based on a subset of reads
84
    n_a += it->nucleotides_pos('A').sum();
×
85
    n_c += it->nucleotides_pos('C').sum();
×
86
    n_g += it->nucleotides_pos('G').sum();
×
87
    n_t += it->nucleotides_pos('T').sum();
×
88
    n_n += it->nucleotides_pos('N').sum();
×
89
    n_q20 += it->quality_dist().sum(20);
×
90
    n_q30 += it->quality_dist().sum(30);
×
91
  }
92

93
  const auto n_bases_s = n_a + n_c + n_g + n_t + n_n;
×
94

95
  json->u64("reads", n_reads);
×
96
  json->u64("bases", n_bases);
×
97
  json->f64("mean_length",
×
98
            static_cast<double>(n_bases) / static_cast<double>(n_reads));
99
  json->u64("reads_sampled", n_reads_s);
×
100
  json->f64("q20_rate",
×
101
            static_cast<double>(n_q20) / static_cast<double>(n_bases_s));
102
  json->f64("q30_rate",
×
103
            static_cast<double>(n_q30) / static_cast<double>(n_bases_s));
104
  json->f64("uncalled_rate",
×
105
            static_cast<double>(n_n) / static_cast<double>(n_bases_s));
106
  json->f64("gc_content",
×
107
            static_cast<double>(n_g + n_c) / static_cast<double>(n_bases_s));
108
}
109

110
void
111
write_report_summary(const userconfig& config,
×
112
                     json_dict& report,
113
                     const statistics& stats)
114
{
115
  const auto summary = report.dict("summary");
×
116

117
  write_report_summary_stats(summary->dict("input"),
×
118
                             { stats.input_1, stats.input_2 });
×
119

120
  if (config.run_type == ar_command::report_only) {
×
121
    summary->null("output");
×
122
  } else {
123
    const auto output = summary->dict("output");
×
124

125
    std::vector<fastq_stats_ptr> passed;
×
126
    for (const auto& it : stats.trimming) {
×
127
      passed.push_back(it->read_1);
×
128
      passed.push_back(it->read_2);
×
129
      passed.push_back(it->merged);
×
130
      passed.push_back(it->singleton);
×
131

132
      // Discarded reads are excluded, even if saved
133
    }
134

135
    write_report_summary_stats(summary->dict("output"), passed);
×
136
  }
137
}
138

139
////////////////////////////////////////////////////////////////////////////////
140
// Input
141

142
/** Helper struct used to simplify writing of multiple io sections. */
143
struct io_section
144
{
145
  io_section(read_type rtype,
×
146
             std::string name,
147
             fastq_stats_ptr stats,
148
             const sample_output_files& sample_files)
149
    : io_section(std::move(name), std::move(stats), {})
×
150
  {
151
    const auto offset = sample_files.offset(rtype);
×
152
    if (offset != sample_output_files::disabled) {
×
153
      m_filenames.push_back(sample_files.filename(offset));
×
154
    }
155
  }
156

157
  io_section(std::string name, fastq_stats_ptr stats, string_vec filenames)
×
158
    : m_stats(std::move(stats))
×
159
    , m_name(std::move(name))
×
160
    , m_filenames(std::move(filenames))
×
161
  {
162
  }
163

164
  void write_to_if(const json_dict_ptr& json, bool enabled = true) const
×
165
  {
166
    if (!enabled) {
×
167
      json->null(m_name);
×
168
      return;
×
169
    }
170

171
    const auto section = json->dict(m_name);
×
172
    if (m_filenames.empty()) {
×
173
      section->null("filenames");
×
174
    } else {
175
      section->str_vec("filenames", m_filenames);
×
176
    }
177

178
    section->u64("input_reads", m_stats->number_of_input_reads());
×
179
    section->u64("output_reads", m_stats->number_of_output_reads());
×
180
    section->u64("reads_sampled", m_stats->number_of_sampled_reads());
×
181
    section->i64_vec("lengths", m_stats->length_dist());
×
182

183
    if (m_stats->length_dist().product()) {
×
184
      const auto total_bases = m_stats->nucleotides_pos();
×
185
      const auto total_quality = m_stats->qualities_pos();
×
186

187
      {
×
188
        const auto quality_curves = section->dict("quality_curves");
×
189

190
        for (const auto nuc : ACGT::values) {
×
191
          const auto nucleotides = m_stats->nucleotides_pos(nuc);
×
192
          const auto quality = m_stats->qualities_pos(nuc);
×
193

194
          quality_curves->f64_vec(std::string(1, to_lower(nuc)),
×
195
                                  quality / nucleotides);
×
196
        }
197

198
        quality_curves->f64_vec("mean", total_quality / total_bases);
×
199
      }
200

201
      {
×
202
        const auto content_curves = section->dict("content_curves");
×
203

204
        for (const auto nuc : ACGTN::values) {
×
205
          const auto bases = m_stats->nucleotides_pos(nuc);
×
206

207
          // FIXME: Should be raw counts instead of fractions
208
          content_curves->f64_vec(std::string(1, to_lower(nuc)),
×
209
                                  bases / total_bases);
×
210
        }
211
      }
212

213
      const auto quality_dist = m_stats->quality_dist().trim();
×
214
      section->i64_vec("quality_scores", quality_dist);
×
215
      section->f64_vec("gc_content", m_stats->gc_content());
×
216
    } else {
×
217
      section->null("quality_curves");
×
218
      section->null("content_curves");
×
219
      section->null("quality_scores");
×
220
      section->null("gc_content");
×
221
    }
222
  }
223

224
private:
225
  const fastq_stats_ptr m_stats;
226
  //! Name of the section
227
  const std::string m_name;
228
  //! Filenames (if any) generated for this file type
229
  string_vec m_filenames;
230
};
231

232
void
233
write_report_input(const userconfig& config,
×
234
                   json_dict& report,
235
                   const statistics& stats)
236
{
237
  const auto input = report.dict("input");
×
238
  const auto mate_2_filenames =
×
239
    config.interleaved_input ? config.input_files_1 : config.input_files_2;
×
240

241
  io_section("read1", stats.input_1, config.input_files_1).write_to_if(input);
×
242
  io_section("read2", stats.input_2, mate_2_filenames)
×
243
    .write_to_if(input, config.paired_ended_mode);
×
244
}
245

246
////////////////////////////////////////////////////////////////////////////////
247
// Demultiplexing
248

249
void
250
write_report_demultiplexing(const userconfig& config,
×
251
                            json_dict& report,
252
                            const statistics& sample_stats)
253
{
254
  const bool demux_only = config.run_type == ar_command::demultiplex_only;
×
255
  const auto out_files = config.get_output_filenames();
×
256

257
  if (config.is_demultiplexing_enabled()) {
×
258
    const auto demultiplexing = report.dict("demultiplexing");
×
259

260
    const auto& demux = *sample_stats.demultiplexing;
×
261
    size_t assigned_reads = 0;
×
262
    for (size_t it : demux.samples) {
×
263
      assigned_reads += it;
×
264
    }
265

266
    demultiplexing->u64("assigned_reads", assigned_reads);
×
267
    demultiplexing->u64("ambiguous_reads", demux.ambiguous);
×
268
    demultiplexing->u64("unassigned_reads", demux.unidentified);
×
269

270
    const auto samples = demultiplexing->dict("samples");
×
271
    for (size_t i = 0; i < demux.samples.size(); ++i) {
×
272
      const auto sample = samples->dict(config.samples.at(i).name());
×
273
      const auto& stats = *sample_stats.trimming.at(i);
×
274
      const auto& files = out_files.get_sample(i);
×
275

276
      // TODO: Remove once per barcode read counts have been implemented
277
      sample->u64("reads", demux.samples.at(i));
×
278

279
      const auto barcodes = sample->list("barcodes");
×
280
      for (const auto& it : config.samples.at(i)) {
×
281
        const auto dict = barcodes->inline_dict();
×
282
        dict->str("barcode1", it.barcode_1);
×
283
        dict->str("barcode2", it.barcode_2);
×
284
        // TODO: Per barcode-pair read counts
285
      }
286

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

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

294
      io_section(read_type::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_type::merged, "merged", stats.merged, files)
×
300
        .write_to_if(output, config.is_read_merging_enabled());
×
301

302
      io_section(read_type::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 fastq_stats_ptr& stats)
509
{
510
  const auto dup_stats = stats->duplication();
×
511

512
  if (dup_stats) {
×
513
    auto duplication = json->dict(key);
×
514

515
    const auto summary = dup_stats->summarize();
×
516
    duplication->str_vec("labels", summary.labels);
×
517
    duplication->f64_vec("unique_sequences", summary.unique_sequences);
×
518
    duplication->f64_vec("total_sequences", summary.total_sequences);
×
519
    duplication->f64("unique_frac", summary.unique_frac);
×
520
  } else {
×
521
    json->null(key);
×
522
  }
523
}
524

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

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

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

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

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

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

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

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

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

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

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

585
////////////////////////////////////////////////////////////////////////////////
586
// Output
587

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

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

600
  return filenames;
×
601
}
×
602

603
void
604
write_report_output(const userconfig& config,
×
605
                    json_dict& report,
606
                    const statistics& stats)
607
{
608
  if (config.run_type == ar_command::report_only) {
×
609
    report.null("output");
×
610
    return;
×
611
  }
612

613
  auto output_1 = std::make_shared<fastq_statistics>();
×
614
  auto output_2 = std::make_shared<fastq_statistics>();
×
615
  auto merged = std::make_shared<fastq_statistics>();
×
616
  auto singleton = std::make_shared<fastq_statistics>();
×
617
  auto discarded = std::make_shared<fastq_statistics>();
×
618

619
  for (const auto& it : stats.trimming) {
×
620
    *output_1 += *it->read_1;
×
621
    *output_2 += *it->read_2;
×
622
    *merged += *it->merged;
×
623
    *singleton += *it->singleton;
×
624
    *discarded += *it->discarded;
×
625
  }
626

627
  const auto out_files = config.get_output_filenames();
×
628
  const auto mate_1_files = collect_files(out_files, read_type::mate_1);
×
629
  const auto mate_2_files = collect_files(out_files, read_type::mate_2);
×
630
  const auto merged_files = collect_files(out_files, read_type::merged);
×
631
  const auto singleton_files = collect_files(out_files, read_type::singleton);
×
632
  const auto discarded_files = collect_files(out_files, read_type::discarded);
×
633

634
  const bool demux_only = config.run_type == ar_command::demultiplex_only;
×
635

636
  const auto output = report.dict("output");
×
637
  io_section("read1", output_1, mate_1_files).write_to_if(output, true);
×
638
  io_section("read2", output_2, mate_2_files)
×
639
    .write_to_if(output, config.paired_ended_mode);
×
640
  io_section("merged", merged, merged_files)
×
641
    .write_to_if(output, config.is_read_merging_enabled());
×
642

643
  io_section("unidentified1",
×
644
             stats.demultiplexing->unidentified_stats_1,
×
645
             { out_files.unidentified_1.name })
×
646
    .write_to_if(output, config.is_demultiplexing_enabled());
×
647
  io_section("unidentified2",
×
648
             stats.demultiplexing->unidentified_stats_2,
×
649
             { out_files.unidentified_2.name })
×
650
    .write_to_if(
×
651
      output, config.is_demultiplexing_enabled() && config.paired_ended_mode);
×
652

653
  io_section("singleton", singleton, singleton_files)
×
654
    .write_to_if(output,
×
655
                 config.paired_ended_mode && !demux_only &&
×
656
                   config.is_any_filtering_enabled());
×
657

658
  io_section("discarded", discarded, discarded_files)
×
659
    .write_to_if(output, !demux_only && config.is_any_filtering_enabled());
×
660
}
661

662
} // namespace
663

664
bool
665
write_json_report(const userconfig& config,
×
666
                  const statistics& stats,
667
                  const std::string& filename)
668
{
669
  if (filename == DEV_NULL) {
×
670
    // User disabled the report
671
    return true;
672
  }
673

674
  std::ostringstream output;
×
675

676
  {
×
677
    json_dict_ptr report = std::make_shared<json_dict>();
×
678
    report->str("$schema",
×
679
                "https://MikkelSchubert.github.io/adapterremoval/schemas/" +
×
680
                  VERSION + ".json");
×
681

682
    write_report_meta(config, *report);
×
683
    write_report_summary(config, *report, stats);
×
684
    write_report_input(config, *report, stats);
×
685
    write_report_demultiplexing(config, *report, stats);
×
686
    write_report_processing(config, report, stats);
×
687
    write_report_analyses(config, report, stats);
×
688
    write_report_output(config, *report, stats);
×
689
    report->write(output);
×
690
  }
691

692
  output << std::endl;
×
693

694
  try {
×
695
    managed_writer writer{ filename };
×
696
    writer.write(output.str());
×
697
    writer.close();
×
698
  } catch (const std::ios_base::failure& error) {
×
699
    log::error() << "Error writing JSON report to '" << filename << "':\n"
×
700
                 << indent_lines(error.what());
×
701
    return false;
×
702
  }
×
703

704
  return true;
×
705
}
706

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