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

sequana / sequana / 16568275773

28 Jul 2025 11:51AM UTC coverage: 69.115% (-0.6%) from 69.722%
16568275773

push

github

web-flow
Merge pull request #870 from cokelaer/dev

add threshold parameter in G4 module

1 of 2 new or added lines in 1 file covered. (50.0%)

725 existing lines in 17 files now uncovered.

14389 of 20819 relevant lines covered (69.11%)

2.07 hits per line

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

76.22
/sequana/scripts/coverage.py
1
#
2
#  This file is part of Sequana software
3
#
4
#  Copyright (c) 2016-2022 - Sequana Development Team
5
#
6
#  Distributed under the terms of the 3-clause BSD license.
7
#  The full license is in the LICENSE file, distributed with this software.
8
#
9
#  website: https://github.com/sequana/sequana
10
#  documentation: http://sequana.readthedocs.io
11
#
12
##############################################################################
13
""".. rubric:: Standalone application dedicated to coverage"""
3✔
14
import argparse
3✔
15
import gc as garbage
3✔
16
import glob
3✔
17
import json
3✔
18
import os
3✔
19
import subprocess
3✔
20
import sys
3✔
21

22
import colorlog
3✔
23
import rich_click as click
3✔
24
from easydev import AttrDict, shellcmd
3✔
25

26
from sequana import sequana_data
3✔
27
from sequana import version as sequana_version
3✔
28
from sequana.bedtools import ChromosomeCov, SequanaCoverage
3✔
29
from sequana.lazy import pandas as pd
3✔
30
from sequana.modules_report.coverage import ChromosomeCoverageModule, CoverageModule
3✔
31
from sequana.scripts.common import teardown
3✔
32
from sequana.utils import config
3✔
33

34
from .utils import CONTEXT_SETTINGS
3✔
35

36
logger = colorlog.getLogger(__name__)
3✔
37

38

39
CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])
3✔
40

41

42
click.rich_click.OPTION_GROUPS = {
3✔
43
    "sequana_coverage": [
44
        {
45
            "name": "Required",
46
            "options": ["--input-file"],
47
        },
48
        {
49
            "name": "Detection Algorithm Options",
50
            "options": [
51
                "--circular",
52
                "--mixture-models",
53
                "--window-median",
54
                "--window-gc",
55
                "--low-threshold",
56
                "--high-threshold",
57
                "--threshold",
58
                "--cnv-clustering",
59
                "--clustering-parameter",
60
            ],
61
        },
62
        {
63
            "name": "Modifiers",
64
            "options": ["--annotation-file", "--reference-file", "--chromosome", "--chunk-size", "--binning"],
65
        },
66
        {
67
            "name": "Download utilities",
68
            "options": ["--download-reference", "--download-genbank", "--database"],
69
        },
70
        {
71
            "name": "Output files",
72
            "options": ["--no-multiqc", "--output-directory"],
73
        },
74
        {
75
            "name": "Input files",
76
            "options": ["--bam2cov-method", "--mapq", "--second-mapq"],
77
        },
78
        {
79
            "name": "Behaviour",
80
            "options": ["--version", "--level", "--debug-level", "--help"],
81
        },
82
    ],
83
}
84

85

86
def download_reference(ctx, param, value):
3✔
87
    if value:
3✔
88
        database = ctx.params.get("database", "ENA")
×
89
        click.echo(f"Downloading reference {value} from {database}...", nl=False)
×
90
        from bioservices.apps import download_fasta as df
×
91

92
        df.download_fasta(value, method="ENA")
×
93
        click.echo("done")
×
94
        sys.exit(0)
×
95
    return value
3✔
96

97

98
def download_genbank(ctx, param, value):
3✔
99
    if value:
3✔
100
        click.echo(f"Downloading genbank {value}...", nl=False)
×
101
        from sequana.snpeff import download_fasta_and_genbank
×
102

103
        download_fasta_and_genbank(value, value, genbank=True, fasta=False)
×
104
        click.echo("done")
×
105
        sys.exit(0)
×
106
    return value
3✔
107

108

109
@click.command(context_settings=CONTEXT_SETTINGS)
3✔
110
@click.option(
3✔
111
    "--database",
112
    "database",
113
    default="ENA",
114
    show_default=True,
115
    type=click.Choice(["ENA", "EUtils"]),
116
    is_eager=True,
117
    # expose_value=False,
118
    help="Download the reference from one of these database (default ENA)",
119
)
120
@click.option(
3✔
121
    "--download-reference",
122
    "download_reference",
123
    default=None,
124
    type=click.STRING,
125
    callback=download_reference,
126
    is_eager=True,
127
    # expose_value=False,
128
    help="Provide accession number to download a reference from NCBI/ENA",
129
)
130
@click.option(
3✔
131
    "--download-genbank",
132
    "download_genbank",
133
    default=None,
134
    type=click.STRING,
135
    callback=download_genbank,
136
    is_eager=True,
137
    help="Provide accession number to download annotation from NCBI/ENA",
138
)
139
@click.option(
3✔
140
    "-i",
141
    "--input-file",
142
    "input",
143
    type=click.Path(),
144
    required=True,
145
    help="""Input file in BED or BAM format. If a BAM file is provided, it will be converted locally to a BED file using genomecov, which must be installed.""",
146
)
147
@click.option(
3✔
148
    "-a",
149
    "--annotation-file",
150
    "annotation",
151
    type=click.STRING,
152
    default=None,
153
    help="a valid gff3 or genbank annotation. Recommended for prokaryotes or small eukaryotes only.",
154
)
155
@click.version_option(sequana_version)
3✔
156
@click.option(
3✔
157
    "-o",
158
    "--circular",
159
    is_flag=True,
160
    help="""If the DNA of the organism is circular (typically
161
            viruses or bacteria), set to True""",
162
)
163
@click.option(
3✔
164
    "-c",
165
    "--chromosome",
166
    "chromosome",
167
    type=click.STRING,
168
    default="",
169
    show_default=True,
170
    help="By default all chromosomes are analysed. You may want to analyse only one" " by using this parameter",
171
)
172
@click.option(
3✔
173
    "-k",
174
    "--mixture-models",
175
    "k",
176
    type=click.INT,
177
    help="Number of mixture models to use (default 2, although if sequencing depth is below 8, k is set to 1 automatically). To ignore that behaviour set k to the required value",
178
    default=2,
179
    show_default=True,
180
)
181
@click.option(
3✔
182
    "--debug-level",
183
    "logging_level",
184
    default="INFO",
185
    show_default=True,
186
    type=click.Choice(["DEBUG", "INFO", "WARNING", "CRITICAL", "ERROR"]),
187
    help="set to DEBUG, INFO, WARNING, CRITICAL, ERROR",
188
)
189
@click.option(
3✔
190
    "--level",
191
    "logging_level",
192
    default="INFO",
193
    show_default=True,
194
    type=click.Choice(["DEBUG", "INFO", "WARNING", "CRITICAL", "ERROR"]),
195
    help="set to DEBUG, INFO, WARNING, CRITICAL, ERROR",
196
)
197
@click.option(
3✔
198
    "-g",
199
    "--window-gc",
200
    "w_gc",
201
    type=click.INT,
202
    default=101,
203
    show_default=True,
204
    help="Length of the running window to compute the GC content",
205
)
206
@click.option(
3✔
207
    "--no-multiqc",
208
    "skip_multiqc",
209
    is_flag=True,
210
    show_default=True,
211
    help="Do not create any multiqc HTML page.",
212
)
213
@click.option("--output-directory", "output_directory", default="report", help="name of the output (report) directory.")
3✔
214
@click.option(
3✔
215
    "-r",
216
    "--reference-file",
217
    "reference",
218
    type=click.STRING,
219
    default=None,
220
    help="If available, you can provide a reference (ENA/NCBI). It must have the same length as the one used to create the BAM or BED file. If provided, it is used to create the coverage versus GC content image. Recommended for prokaryotes or small eukaryotes only.",
221
)
222
@click.option(
3✔
223
    "-w",
224
    "--window-median",
225
    "w_median",
226
    type=click.IntRange(200),
227
    help="Length of the running median window (default 20,001, recommended for bacteria).  For short genome (below 100000 bases), we set this parameter to one fifth of the genome length. minimal value is 125 (one fourth of 500bp that should be minimal length of contigs)",
228
    default=20001,
229
    show_default=True,
230
)
231
@click.option(
3✔
232
    "-L",
233
    "--low-threshold",
234
    "low_threshold",
235
    default=-4,
236
    show_default=True,
237
    type=click.FloatRange(max=0),
238
    help=("lower threshold (zscore) of the confidence interval. " "Overwrite value given by --threshold/-T"),
239
)
240
@click.option(
3✔
241
    "-H",
242
    "--high-threshold",
243
    "high_threshold",
244
    default=4,
245
    type=click.FloatRange(min=0),
246
    show_default=True,
247
    help=("higher threshold (zscore) of the confidence interval. " "Overwrite value given by --threshold/-T"),
248
)
249
@click.option(
3✔
250
    "-T",
251
    "--threshold",
252
    "threshold",
253
    default=4,
254
    type=click.FLOAT,
255
    show_default=True,
256
    help="set lower and higher thresholds of the confidence interval. ",
257
)
258
@click.option(
3✔
259
    "-C",
260
    "--clustering-parameter",
261
    "double_threshold",
262
    default=0.5,
263
    show_default=True,
264
    type=click.FLOAT,
265
    help="set lower and higher double threshold parameter (in [0,1]). Do not use value close to zero. Ideally, around 0.5. lower value will tend to cluster more than higher value",
266
)
267
@click.option(
3✔
268
    "-s",
269
    "--chunk-size",
270
    "chunksize",
271
    type=click.IntRange(1000000),  # 1e6
272
    default=10000000,  # 1e7
273
    show_default=True,
274
    help="Length of the chunk to be used for the analysis. ",
275
)
276
@click.option(
3✔
277
    "-B",
278
    "--binning",
279
    "binning",
280
    type=click.IntRange(2),
281
    default=None,
282
    help="merge consecutive (non overlapping) data points, taking the mean. This is useful for large genome (e.g. human). This allows a faster computation, especially for CNV detection were only large windows are of interest. For instance, using a binning of 50 or 100 allows the human genome to be analysed.",
283
)
284
@click.option(
3✔
285
    "--cnv-clustering",
286
    "cnv_clustering",
287
    default=-1,
288
    type=click.INT,
289
    help="Two consecutive ROIs are merged when their distance in bases is below this parameter. If set to -1, not used. ",
290
    show_default=True,
291
)
292
@click.option(
3✔
293
    "--bam2cov-method",
294
    type=click.Choice(["samtools", "mosdepth"]),
295
    default="mosdepth",
296
    help="method used internally to convert BAM into BED file",
297
    show_default=True,
298
)
299
@click.option(
3✔
300
    "--mapq",
301
    default=0,
302
    help="mapping quality threshold. reads with a quality less than this value are ignored",
303
    show_default=True,
304
)
305
@click.option(
3✔
306
    "--second-mapq",
307
    default=0,
308
    help="mapping quality threshold of the second optional coverage track. reads with a quality less than this value are ignored. ",
309
    show_default=True,
310
)
311
@click.option(
3✔
312
    "-F",
313
    "--flag",
314
    default=1796,
315
    help="exclude reads with any of the bits in FLAG set. to ignore, supp, use -F 3844",
316
    show_default=True,
317
)
318
def main(**kwargs):
3✔
319
    """Welcome to SEQUANA -- Coverage standalone
320

321
        ----
322

323
       Sequana coverage compute running median on a coverage series extracted from one or
324
       several chromsomes and then identifies regions of interest (deleted regions or CNV
325
       regions).
326

327
       HTML report is created with coverage plot. A reference may be
328
       provided to plot the coverage versus GC content. An annotation file may be provided
329
       to annotate the found ROIs.
330

331
       Input files
332
       --------------
333

334
       The input file should be one of the following:
335

336
       - a tabulated file made of at least 3 columns.
337
         The first column being the chromosome name, the second is the position
338
         and the third column contains the coverage itself. An optional fourth column
339
         can be provided as an additional track shown in dedicated plots.
340
       - a BAM file that is converted automatically into a 3-column file as above.
341
         This file can be obtained before hand using various tools
342

343
             samtools depth -aa input.bam > output.bed
344

345
         or
346
              mosdepth -b1 TEMP input.bam; gunzip -c TEMP.regions.bed.gz | cut -f 1,3,4 > output.bed
347

348
       you can also use bedtools as follow:
349

350
              bedtools genomecov -d -ibam FILE.bam > output.bed
351

352
       mosdepth and samtools software give the same results; mosdepth being slighly faster.
353
       genomecov is slower and overestimated repeated or low quality mapping regions.
354
       Note also that by default, with samtools depths are clipped at 8000 by default.
355
       mosdepth method is therefore the default method since v0.18
356

357
       Reference
358
       ----------
359

360
       If the reference is provided, an additional plot showing the coverage versus
361
       GC content is also shown.
362

363
           sequana_coverage --input-file file.bed --window-median 1001
364
           sequana_coverage --input-file file.bam --window-median 1001 -r <REFERENCE.fa>
365

366
       An other interesting option is to provide a BED file with 4 columns. The
367
       fourth column being another coverage data created with e.g. a quality filter.
368

369
       You can convert the BAM twice and combining the results. You can convert
370
       the BAM to a coverag plot as explained above. Then, create a second file
371
       using a quality filter
372

373
           samtools view -q 35  -o data.filtered.bam input.bam
374
           samtools depth data.filtered.bam input.bam -aa > test.bed
375
           sequana_coverage --input test.bed --show-html
376

377
       Since versi0n v0.18, you can also do it on the fly as follows:
378

379
           sequana_coverage --input-file input-bam --second-mapq 35
380

381
       Note that only the first file (column 3) will be used for statistical analysis.
382

383
       Annotation
384
       ------------
385

386
       Note for multi chromosome and genbank features: for now, you will need to call
387
       sequana_coverage for each chromosome individually since we accept only one
388
       genbank as input parameter:
389

390
           sequana_coverage --input-file file.bed --genbank chrom1.gbk -c 1
391

392
       Large genomes:
393
       --------------
394

395
       If your input data is large and does not fit into memory, use the --binning BIN
396
       options to average data into bin of BIN values.
397

398
       CNV cases:
399
       --------------
400

401
       By default, sequana_coverage identify events as small as 1 bin. For the CNV
402
       detection case, you may want to cluster events. the --cnv-merging DELTA option
403
       merges consecutives events whose distance is smaller that DELTA
404

405

406
    ----
407

408
    AUTHORS: Thomas Cokelaer, Dimitri Desvillechabrol
409
    Documentation: http://sequana.readthedocs.io
410
    Issues: http://github.com/sequana/sequana
411
    """
412
    options = AttrDict(**kwargs)
3✔
413

414
    logger.setLevel(options.logging_level)
3✔
415

416
    if options.annotation:
3✔
417
        assert os.path.exists(options.annotation), "%s does not exists" % options.annotation
3✔
418

419
    logger.info(f"Reading {options.input}. This may take time ")
3✔
420

421
    # Convert BAM to BED
422
    if options.input.endswith(".bam"):
3✔
UNCOV
423
        logger.info("Converting BAM into BED file with mosdepth")
×
424
        bedfile = options.input.replace(".bam", ".bed")
×
425

426
    if not options.input.endswith("bam") and (options.mapq or options.second_mapq):
3✔
427
        logger.error("--mapq and --second-map not used if input is not in BAM format")
×
428
        return sys.exit(1)
×
429

430
    if options.input.endswith(".bam") and options.bam2cov_method == "samtools":
3✔
UNCOV
431
        chrom = "" if not options.chromosome else f"-r {options.chromosome}"
×
432

433
        if options.second_mapq:
×
UNCOV
434
            shellcmd(f"samtools depth -aa -Q {options.mapq} {chrom} {options.input} > {bedfile}.1")
×
435
            shellcmd(f"samtools depth -aa -Q {options.second_mapq} {chrom} {options.input} > {bedfile}.2")
×
UNCOV
436
            shellcmd(f"paste <(cut -f 1,2,3 {options.input}.1 ) <( cut -f 3 {options.input}.2) > {bedfile}")
×
437
            shellcmd(f"rm -f {options.input}.[12]")
×
438

439
        else:
440
            shellcmd(f"{main_command} depth -aa {options.input} > {bedfile}")
×
441
    elif options.input.endswith(".bam") and options.bam2cov_method == "mosdepth":
3✔
UNCOV
442
        main_command = "mosdepth" if not options.chromosome else f"mosdepth -c {options.chromosome}"
×
UNCOV
443
        main_command += f" -F {options.flag}"
×
444

445
        # -b1 for all bases
446
        if options.second_mapq:
×
447
            # decommposed because too complex for subprocess
UNCOV
448
            shellcmd(f"{main_command} -Q {options.mapq} -b1 lenny1 {options.input}")
×
449
            shellcmd(f"{main_command} -Q {options.second_mapq} -b1 lenny2 {options.input}")
×
UNCOV
450
            shellcmd(
×
451
                f"bash -c 'paste <(gunzip -c lenny1.regions.bed.gz | cut -f 1,3,4 ) <(gunzip -c lenny2.regions.bed.gz | cut -f 4 ) > {bedfile}'"
452
            )
UNCOV
453
            shellcmd(f"rm -f lenny?.mosdepth*")
×
UNCOV
454
            shellcmd(f"rm -f lenny?.per-base*")
×
UNCOV
455
            shellcmd(f"rm -f lenny?.regions*")
×
456
        else:
457
            # here somehow the commands works out of the box on one line
UNCOV
458
            shellcmd(
×
459
                f"{main_command} -Q {options.mapq} -b1 lenny {options.input} && gunzip -c lenny.regions.bed.gz | cut -f 1,3,4 > {bedfile} && rm -f lenny.mosdepth*"
460
            )
461

462
    elif options.input.endswith(".bed"):
3✔
463
        bedfile = options.input
3✔
464
    else:
UNCOV
465
        logger.error("Input file must be a BAM or BED file")
×
UNCOV
466
        system.exit(1)
×
467

468
    # Set the thresholds
469
    if options.low_threshold is None:
3✔
470
        options.low_threshold = -options.threshold
×
471

472
    if options.high_threshold is None:
3✔
UNCOV
473
        options.high_threshold = options.threshold
×
474

475
    # Now we can create the instance of GenomeCoverage
476
    if "," in options.chromosome:
3✔
UNCOV
477
        chrom_list = options.chromosome.split(",")
×
478
    elif options.chromosome:
3✔
UNCOV
479
        chrom_list = [options.chromosome]
×
480
    else:
481
        chrom_list = []
3✔
482

483
    # initialisation (reading BED to get positions of chromosomes and chromosome names
484
    gc = SequanaCoverage(
3✔
485
        bedfile,
486
        options.annotation,
487
        options.low_threshold,
488
        options.high_threshold,
489
        options.double_threshold,
490
        options.double_threshold,
491
        chunksize=options.chunksize,
492
        chromosome_list=chrom_list,
493
        reference_file=options.reference,
494
        gc_window_size=options.w_gc,
495
        # force=True
496
    )
497

498
    # some information fo end-users,
499
    logger.info("There are %s chromosomes/contigs." % len(gc))
3✔
500
    for this in gc.chrom_names:
3✔
501
        end = gc.positions[this]["end"]
3✔
502
        start = gc.positions[this]["start"]
3✔
503
        data = (this, gc.positions[this]["start"], gc.positions[this]["end"], end - start)
3✔
504
        logger.info("    {} (starting pos: {}, ending pos: {}, length: {})".format(*data))
3✔
505

506
    # here we read chromosome by chromosome to save memory and analyse them.
507
    N = len(gc)
3✔
508
    for i, chrom in enumerate(gc.chrom_names[::-1]):
3✔
509
        logger.info(f"==================== analysing chrom/contig {i+1}/{N} ({chrom})")
3✔
510
        # since we read just one contig/chromosome, the chr_list contains
511
        # only one contig, so we access to it with index 0
512

513
        # Performs the computation and reporting for a given chromosome
514
        # This call performs the analysis, and creates the HTML page
515
        # if HTML is created, it fills the gc._html_list variable
516
        chrom_data = ChromosomeCov(gc, chrom, gc.thresholds, gc.chunksize)
3✔
517
        run_analysis(chrom_data, options)
3✔
518

519
        del chrom_data  # free memory for sure
3✔
520
        garbage.collect()
3✔
521

522
        # logging level seems to be reset to warning somewhere
523
        logger.setLevel(options.logging_level)
3✔
524

525
    # create a summary file for the HTML report.
526
    filenames = glob.glob(f"{options.output_directory}/*/sequana_summary_coverage.*json")
3✔
527
    BOCs = []
3✔
528
    DOCs = []
3✔
529
    names = []
3✔
530
    lengths = []
3✔
531
    for filename in filenames:
3✔
532
        data = json.loads(open(filename).read())["data"]
3✔
533
        BOCs.append(data["BOC"])
3✔
534
        lengths.append(data["length"])
3✔
535
        DOCs.append(data["DOC"])
3✔
536
        names.append(data["chrom_name"])
3✔
537
    df = pd.DataFrame({"DOC": DOCs, "BOC": BOCs, "name": names, "length": lengths})
3✔
538
    df = df.sort_values(by="name")
3✔
539
    df = df[["name", "length", "DOC", "BOC"]]
3✔
540
    df.to_csv(f"{options.output_directory}/summary.json", index=False)
3✔
541

542
    CoverageModule(gc)
3✔
543

544
    if options.skip_multiqc is False:
3✔
545
        logger.info("Creating multiqc report")
3✔
546
        pathtocfg = sequana_data("multiqc_config.yaml", "../multiqc/")
3✔
547
        cmd = f"multiqc . -m sequana_coverage -f -c {pathtocfg} "
3✔
548

549
        proc = subprocess.Popen(cmd.split(), cwd=options.output_directory)
3✔
550
        proc.wait()
3✔
551

552
    teardown(options.output_directory, "sequana_coverage")
3✔
553

554

555
def run_analysis(chrom, options):
3✔
556

557
    if options.w_median > len(chrom) / 4:
3✔
UNCOV
558
        NW = int(len(chrom) / 4)
×
UNCOV
559
        if NW % 2 == 0:
×
UNCOV
560
            NW += 1
×
UNCOV
561
        logger.warning(
×
562
            "median window length is too long. \n"
563
            "    Setting the window length automatically to a fifth of\n"
564
            "    the chromosome length ({})".format(NW)
565
        )
566
    else:
567
        NW = options.w_median
3✔
568

569
    ######################### DEFINES OUTPUT DIR AND SAMPLE NAME  ###########
570
    config.output_dir = options.output_directory
3✔
571
    config.sample_name = os.path.basename(options.input).split(".")[0]
3✔
572

573
    directory = options.output_directory
3✔
574
    os.makedirs(directory, exist_ok=True)
3✔
575
    directory = f"{options.output_directory}/{chrom.chrom_name}"
3✔
576
    os.makedirs(directory, exist_ok=True)
3✔
577
    #########################################################################
578

579
    # compute the running median, zscore and ROIs for each chunk summarizing the
580
    # results in a ChromosomeCovMultiChunk instane
581
    logger.info("Using running median (w=%s)" % NW)
3✔
582
    logger.info("Number of mixture models %s " % options.k)
3✔
583
    results = chrom.run(
3✔
584
        NW, options.k, circular=options.circular, binning=options.binning, cnv_delta=options.cnv_clustering
585
    )
586
    chrom.plot_coverage(f"{directory}/coverage.png")
3✔
587

588
    if chrom.DOC < 8:
3✔
UNCOV
589
        logger.warning(
×
590
            "The depth of coverage is below 8. sequana_coverage is"
591
            " not optimised for such depth. You may want to "
592
            " increase the threshold to avoid too many false detections"
593
        )
594
    logger.info(chrom.__str__())
3✔
595

596
    # save summary and metrics
597
    summary = results.get_summary(caller="sequana_coverage")
3✔
598
    summary.to_json(f"{directory}/sequana_summary_coverage.json")
3✔
599

600
    mu = summary.data.get("fit_mu", 0)
3✔
601
    sigma = summary.data.get("fit_sigma", 0)
3✔
602
    pi = summary.data.get("pi", 0)
3✔
603

604
    logger.info(
3✔
605
        "Fitted central distribution (first chunk): mu=%s, sigma=%s, pi=%s"
606
        % (round(mu, 3), round(sigma, 3), round(pi, 3))
607
    )
608

609
    # some information about the ROIs found
610
    logger.info(
3✔
611
        "Searching for ROIs (threshold=[{},{}] ; double =[{},{}])".format(
612
            chrom.thresholds.low, chrom.thresholds.high, chrom.thresholds.low2, chrom.thresholds.high2
613
        )
614
    )
615
    ROIs = results.get_rois()  # results is a ChromosomeCovMultiChunk instance
3✔
616
    logger.info("Number of ROIs found: {}".format(len(ROIs.df)))
3✔
617
    logger.info("    - below average: {}".format(len(ROIs.get_low_rois())))
3✔
618
    logger.info("    - above average: {}".format(len(ROIs.get_high_rois())))
3✔
619

620
    # Create directory and save ROIs
621
    ROIs.df.to_csv(f"{directory}/rois.csv")
3✔
622

623
    logger.info(f"Creating report in {options.output_directory}. Please wait")
3✔
624

625
    if chrom._mode == "chunks":
3✔
UNCOV
626
        logger.warning("This chromosome is large. Plots in the HTML reports are skipped")
×
627

628
    ChromosomeCoverageModule(
3✔
629
        chrom,
630
        datatable=CoverageModule.init_roi_datatable(ROIs),
631
        options={"W": NW, "k": options.k, "ROIs": ROIs, "circular": options.circular},
632
        command=" ".join(["sequana_coverage"] + sys.argv[1:]),
633
    )
634

635

636
if __name__ == "__main__":  # pragma: no cover
637
    main()
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