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

sequana / sequana / 11808924106

17 Oct 2024 01:01PM UTC coverage: 73.623% (-1.2%) from 74.78%
11808924106

push

github

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

add somy, improved coverage

204 of 550 new or added lines in 19 files covered. (37.09%)

22 existing lines in 4 files now uncovered.

14009 of 19028 relevant lines covered (73.62%)

2.94 hits per line

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

76.63
/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"""
4✔
14
import argparse
4✔
15
import gc as garbage
4✔
16
import glob
4✔
17
import json
4✔
18
import os
4✔
19
import subprocess
4✔
20
import sys
4✔
21

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

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

34
from .utils import CONTEXT_SETTINGS
4✔
35

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

38

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

41

42
click.rich_click.OPTION_GROUPS = {
4✔
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):
4✔
87
    if value:
4✔
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
4✔
96

97

98
def download_genbank(ctx, param, value):
4✔
99
    if value:
4✔
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
4✔
107

108

109
@click.command(context_settings=CONTEXT_SETTINGS)
4✔
110
@click.option(
4✔
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(
4✔
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(
4✔
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(
4✔
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(
4✔
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)
4✔
156
@click.option(
4✔
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(
4✔
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(
4✔
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(
4✔
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(
4✔
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(
4✔
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(
4✔
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.")
4✔
214
@click.option(
4✔
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(
4✔
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(
4✔
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(
4✔
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(
4✔
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(
4✔
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(
4✔
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(
4✔
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(
4✔
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(
4✔
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(
4✔
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(
4✔
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
def main(**kwargs):
4✔
312
    """Welcome to SEQUANA -- Coverage standalone
313

314
        ----
315

316
       Sequana coverage compute running median on a coverage series extracted from one or
317
       several chromsomes and then identifies regions of interest (deleted regions or CNV
318
       regions).
319

320
       HTML report is created with coverage plot. A reference may be
321
       provided to plot the coverage versus GC content. An annotation file may be provided
322
       to annotate the found ROIs.
323

324
       Input files
325
       --------------
326

327
       The input file should be one of the following:
328

329
       - a tabulated file made of at least 3 columns.
330
         The first column being the chromosome name, the second is the position
331
         and the third column contains the coverage itself. An optional fourth column
332
         can be provided as an additional track shown in dedicated plots.
333
       - a BAM file that is converted automatically into a 3-column file as above.
334
         This file can be obtained before hand using various tools
335

336
             samtools depth -aa input.bam > output.bed
337

338
         or
339
              mosdepth -b1 TEMP input.bam; gunzip -c TEMP.regions.bed.gz | cut -f 1,3,4 > output.bed
340

341
       you can also use bedtools as follow:
342

343
              bedtools genomecov -d -ibam FILE.bam > output.bed
344

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

350
       Reference
351
       ----------
352

353
       If the reference is provided, an additional plot showing the coverage versus
354
       GC content is also shown.
355

356
           sequana_coverage --input-file file.bed --window-median 1001
357
           sequana_coverage --input-file file.bam --window-median 1001 -r <REFERENCE.fa>
358

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

362
       You can convert the BAM twice and combining the results. You can convert
363
       the BAM to a coverag plot as explained above. Then, create a second file
364
       using a quality filter
365

366
           samtools view -q 35  -o data.filtered.bam input.bam
367
           samtools depth data.filtered.bam input.bam -aa > test.bed
368
           sequana_coverage --input test.bed --show-html
369

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

372
           sequana_coverage --input-file input-bam --second-mapq 35
373

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

376
       Annotation
377
       ------------
378

379
       Note for multi chromosome and genbank features: for now, you will need to call
380
       sequana_coverage for each chromosome individually since we accept only one
381
       genbank as input parameter:
382

383
           sequana_coverage --input-file file.bed --genbank chrom1.gbk -c 1
384

385
       Large genomes:
386
       --------------
387

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

391
       CNV cases:
392
       --------------
393

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

398

399
    ----
400

401
    AUTHORS: Thomas Cokelaer, Dimitri Desvillechabrol
402
    Documentation: http://sequana.readthedocs.io
403
    Issues: http://github.com/sequana/sequana
404
    """
405
    options = AttrDict(**kwargs)
4✔
406

407
    logger.setLevel(options.logging_level)
4✔
408

409
    if options.annotation:
4✔
410
        assert os.path.exists(options.annotation), "%s does not exists" % options.annotation
4✔
411

412
    logger.info(f"Reading {options.input}. This may take time ")
4✔
413

414
    # Convert BAM to BED
415
    if options.input.endswith(".bam"):
4✔
NEW
416
        logger.info("Converting BAM into BED file with mosdepth")
×
417
        bedfile = options.input.replace(".bam", ".bed")
×
418

419
    if not options.input.endswith("bam") and (options.mapq or options.second_mapq):
4✔
NEW
420
        logger.error("--mapq and --second-map not used if input is not in BAM format")
×
NEW
421
        return sys.exit(1)
×
422

423
    if options.input.endswith(".bam") and options.bam2cov_method == "samtools":
4✔
NEW
424
        chrom = "" if not options.chromosome else f"-r {options.chromosome}"
×
425

NEW
426
        if options.second_mapq:
×
NEW
427
            shellcmd(f"samtools depth -aa -Q {options.mapq} {chrom} {options.input} > {bedfile}.1")
×
NEW
428
            shellcmd(f"samtools depth -aa -Q {options.second_mapq} {chrom} {options.input} > {bedfile}.2")
×
NEW
429
            shellcmd(f"paste <(cut -f 1,2,3 {options.input}.1 ) <( cut -f 3 {options.input}.2) > {bedfile}")
×
NEW
430
            shellcmd(f"rm -f {options.input}.[12]")
×
431

432
        else:
NEW
433
            shellcmd(f"{main_command} depth -aa {options.input} > {bedfile}")
×
434
    elif options.input.endswith(".bam") and options.bam2cov_method == "mosdepth":
4✔
NEW
435
        main_command = "mosdepth" if not options.chromosome else f"mosdepth -c {options.chromosome}"
×
436
        # -b1 for all bases
NEW
437
        if options.second_mapq:
×
438
            # decommposed because too complex for subprocess
NEW
439
            shellcmd(f"{main_command} -Q {options.mapq} -b1 lenny1 {options.input}")
×
NEW
440
            shellcmd(f"{main_command} -Q {options.second_mapq} -b1 lenny2 {options.input}")
×
NEW
441
            shellcmd(
×
442
                f"paste <(gunzip -c lenny1.regions.bed.gz | cut -f 1,3,4 ) <(gunzip -c lenny2.regions.bed.gz | cut -f 4 ) > {bedfile}"
443
            )
NEW
444
            shellcmd(f"rm -f lenny?.mosdepth*")
×
NEW
445
            shellcmd(f"rm -f lenny?.per-base*")
×
NEW
446
            shellcmd(f"rm -f lenny?.regions*")
×
447
        else:
448
            # here somehow the commands works out of the box on one line
NEW
449
            shellcmd(
×
450
                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*"
451
            )
452

453
    elif options.input.endswith(".bed"):
4✔
454
        bedfile = options.input
4✔
455
    else:
NEW
456
        logger.error("Input file must be a BAM or BED file")
×
NEW
457
        system.exit(1)
×
458

459
    # Set the thresholds
460
    if options.low_threshold is None:
4✔
461
        options.low_threshold = -options.threshold
×
462

463
    if options.high_threshold is None:
4✔
464
        options.high_threshold = options.threshold
×
465

466
    # Now we can create the instance of GenomeCoverage
467
    if "," in options.chromosome:
4✔
468
        chrom_list = options.chromosome.split(",")
×
469
    elif options.chromosome:
4✔
470
        chrom_list = [options.chromosome]
×
471
    else:
472
        chrom_list = []
4✔
473

474
    # initialisation (reading BED to get positions of chromosomes and chromosome names
475
    gc = SequanaCoverage(
4✔
476
        bedfile,
477
        options.annotation,
478
        options.low_threshold,
479
        options.high_threshold,
480
        options.double_threshold,
481
        options.double_threshold,
482
        chunksize=options.chunksize,
483
        chromosome_list=chrom_list,
484
        reference_file=options.reference,
485
        gc_window_size=options.w_gc,
486
        # force=True
487
    )
488

489
    # some information fo end-users,
490
    logger.info("There are %s chromosomes/contigs." % len(gc))
4✔
491
    for this in gc.chrom_names:
4✔
492
        end = gc.positions[this]["end"]
4✔
493
        start = gc.positions[this]["start"]
4✔
494
        data = (this, gc.positions[this]["start"], gc.positions[this]["end"], end - start)
4✔
495
        logger.info("    {} (starting pos: {}, ending pos: {}, length: {})".format(*data))
4✔
496

497
    # here we read chromosome by chromosome to save memory and analyse them.
498
    N = len(gc)
4✔
499
    for i, chrom in enumerate(gc.chrom_names[::-1]):
4✔
500
        logger.info(f"==================== analysing chrom/contig {i+1}/{N} ({chrom})")
4✔
501
        # since we read just one contig/chromosome, the chr_list contains
502
        # only one contig, so we access to it with index 0
503

504
        # Performs the computation and reporting for a given chromosome
505
        # This call performs the analysis, and creates the HTML page
506
        # if HTML is created, it fills the gc._html_list variable
507
        chrom_data = ChromosomeCov(gc, chrom, gc.thresholds, gc.chunksize)
4✔
508
        run_analysis(chrom_data, options)
4✔
509

510
        del chrom_data  # free memory for sure
4✔
511
        garbage.collect()
4✔
512

513
        # logging level seems to be reset to warning somewhere
514
        logger.setLevel(options.logging_level)
4✔
515

516
    # create a summary file for the HTML report.
517
    filenames = glob.glob(f"{options.output_directory}/*/sequana_summary_coverage.*json")
4✔
518
    BOCs = []
4✔
519
    DOCs = []
4✔
520
    names = []
4✔
521
    lengths = []
4✔
522
    for filename in filenames:
4✔
523
        data = json.loads(open(filename).read())["data"]
4✔
524
        BOCs.append(data["BOC"])
4✔
525
        lengths.append(data["length"])
4✔
526
        DOCs.append(data["DOC"])
4✔
527
        names.append(data["chrom_name"])
4✔
528
    df = pd.DataFrame({"DOC": DOCs, "BOC": BOCs, "name": names, "length": lengths})
4✔
529
    df = df.sort_values(by="name")
4✔
530
    df = df[["name", "length", "DOC", "BOC"]]
4✔
531
    df.to_csv(f"{options.output_directory}/summary.json", index=False)
4✔
532

533
    CoverageModule(gc)
4✔
534

535
    if options.skip_multiqc is False:
4✔
536
        logger.info("Creating multiqc report")
4✔
537
        pathtocfg = sequana_data("multiqc_config.yaml", "../multiqc/")
4✔
538
        cmd = f"multiqc . -m sequana_coverage -f -c {pathtocfg} "
4✔
539

540
        proc = subprocess.Popen(cmd.split(), cwd=options.output_directory)
4✔
541
        proc.wait()
4✔
542

543
    teardown(options.output_directory, "sequana_coverage")
4✔
544

545

546
def run_analysis(chrom, options):
4✔
547
    logger.info("Computing some metrics")
4✔
548

549
    if options.w_median > len(chrom) / 4:
4✔
550
        NW = int(len(chrom) / 4)
×
551
        if NW % 2 == 0:
×
552
            NW += 1
×
553
        logger.warning(
×
554
            "median window length is too long. \n"
555
            "    Setting the window length automatically to a fifth of\n"
556
            "    the chromosome length ({})".format(NW)
557
        )
558
    else:
559
        NW = options.w_median
4✔
560

561
    ######################### DEFINES OUTPUT DIR AND SAMPLE NAME  ###########
562
    config.output_dir = options.output_directory
4✔
563
    config.sample_name = os.path.basename(options.input).split(".")[0]
4✔
564

565
    directory = options.output_directory
4✔
566
    os.makedirs(directory, exist_ok=True)
4✔
567
    directory = f"{options.output_directory}/{chrom.chrom_name}"
4✔
568
    os.makedirs(directory, exist_ok=True)
4✔
569
    #########################################################################
570

571
    # compute the running median, zscore and ROIs for each chunk summarizing the
572
    # results in a ChromosomeCovMultiChunk instane
573
    logger.info("Using running median (w=%s)" % NW)
4✔
574
    logger.info("Number of mixture models %s " % options.k)
4✔
575
    results = chrom.run(
4✔
576
        NW, options.k, circular=options.circular, binning=options.binning, cnv_delta=options.cnv_clustering
577
    )
578
    chrom.plot_coverage(f"{directory}/coverage.png")
4✔
579

580
    if chrom.DOC < 8:
4✔
581
        logger.warning(
×
582
            "The depth of coverage is below 8. sequana_coverage is"
583
            " not optimised for such depth. You may want to "
584
            " increase the threshold to avoid too many false detections"
585
        )
586
    logger.info(chrom.__str__())
4✔
587

588
    # save summary and metrics
589
    summary = results.get_summary(caller="sequana_coverage")
4✔
590
    summary.to_json(f"{directory}/sequana_summary_coverage.json")
4✔
591

592
    mu = summary.data.get("fit_mu", 0)
4✔
593
    sigma = summary.data.get("fit_sigma", 0)
4✔
594
    pi = summary.data.get("pi", 0)
4✔
595

596
    logger.info(
4✔
597
        "Fitted central distribution (first chunk): mu=%s, sigma=%s, pi=%s"
598
        % (round(mu, 3), round(sigma, 3), round(pi, 3))
599
    )
600

601
    # some information about the ROIs found
602
    logger.info(
4✔
603
        "Searching for ROIs (threshold=[{},{}] ; double =[{},{}])".format(
604
            chrom.thresholds.low, chrom.thresholds.high, chrom.thresholds.low2, chrom.thresholds.high2
605
        )
606
    )
607
    ROIs = results.get_rois()  # results is a ChromosomeCovMultiChunk instance
4✔
608
    logger.info("Number of ROIs found: {}".format(len(ROIs.df)))
4✔
609
    logger.info("    - below average: {}".format(len(ROIs.get_low_rois())))
4✔
610
    logger.info("    - above average: {}".format(len(ROIs.get_high_rois())))
4✔
611

612
    # Create directory and save ROIs
613
    ROIs.df.to_csv(f"{directory}/rois.csv")
4✔
614

615
    logger.info(f"Creating report in {options.output_directory}. Please wait")
4✔
616

617
    if chrom._mode == "chunks":
4✔
618
        logger.warning("This chromosome is large. Plots in the HTML reports are skipped")
×
619

620
    ChromosomeCoverageModule(
4✔
621
        chrom,
622
        datatable=CoverageModule.init_roi_datatable(ROIs),
623
        options={"W": NW, "k": options.k, "ROIs": ROIs, "circular": options.circular},
624
        command=" ".join(["sequana_coverage"] + sys.argv[1:]),
625
    )
626

627

628
if __name__ == "__main__":  # pragma: no cover
629
    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