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

cokelaer / sequana / 7117302237

06 Dec 2023 04:23PM UTC coverage: 75.482% (+1.8%) from 73.729%
7117302237

push

github

cokelaer
Update version

13709 of 18162 relevant lines covered (75.48%)

2.26 hits per line

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

84.65
/sequana/modules_report/coverage.py
1
# coding: utf-8
2
#
3
#  This file is part of Sequana software
4
#
5
#  Copyright (c) 2016 - Sequana Development Team
6
#
7
#  File author(s):
8
#      Dimitri Desvillechabrol <dimitri.desvillechabrol@pasteur.fr>,
9
#          <d.desvillechabrol@gmail.com>
10
#
11
#  Distributed under the terms of the 3-clause BSD license.
12
#  The full license is in the LICENSE file, distributed with this software.
13
#
14
#  website: https://github.com/sequana/sequana
15
#  documentation: http://sequana.readthedocs.io
16
#
17
##############################################################################
18
"""Module to write coverage report"""
3✔
19
import os
3✔
20

21
import colorlog
3✔
22
import pandas as pd
3✔
23

24
from sequana import bedtools
3✔
25
from sequana.modules_report.base_module import SequanaBaseModule
3✔
26
from sequana.modules_report.summary import SummaryModule
3✔
27
from sequana.plots.canvasjs_linegraph import CanvasJSLineGraph
3✔
28
from sequana.utils import config
3✔
29
from sequana.utils.datatables_js import DataTable, DataTableFunction
3✔
30

31
logger = colorlog.getLogger(__name__)
3✔
32

33

34
__all__ = ["CoverageModule", "ChromosomeCoverageModule"]
3✔
35

36

37
class CoverageModule(SequanaBaseModule):
3✔
38
    """Write HTML report of coverage analysis. This class takes either a
39
    :class:`SequanaCoverage` instances or a csv file where analysis are stored.
40
    """
41

42
    def __init__(self, data, region_window=200000):
3✔
43
        """.. rubric:: constructor
44

45
        :param data: it can be a csv filename created by sequana_coverage or a
46
            :class:`bedtools.SequanaCoverage` object.
47
        :param region_window:
48
        """
49
        super().__init__()
3✔
50
        self.region_window = region_window
3✔
51

52
        if isinstance(data, bedtools.SequanaCoverage):
3✔
53
            self.bed = data
3✔
54
        else:
55
            raise TypeError
×
56

57
        if len(self.bed._html_list) == 0:
3✔
58
            try:
×
59
                html_list = self.create_chromosome_reports()
×
60
            except TypeError:
×
61
                msg = (
×
62
                    "Data must be either a csv file or a :class:`SequanaCoverage` " "instance where zscore is computed."
63
                )
64
                raise TypeError(msg)
×
65
        else:
66
            html_list = sorted(list(self.bed._html_list))
3✔
67

68
        self.title = f"Main coverage report ({config.sample_name})"
3✔
69
        self.intro = (
3✔
70
            f"<p>Report the coverage of your sample ({config.sample_name}) to check the "
71
            "quality of your mapping and to highlight regions of "
72
            "interest (under and over covered).</p>"
73
        )
74

75
        # set the chromosome tables
76
        self.sections = list()
3✔
77

78
        self.create_chromosome_table(html_list)
3✔
79

80
        # and create final HTML
81
        self.create_html("sequana_coverage.html")
3✔
82

83
    def create_chromosome_table(self, html_list):
3✔
84
        """Create table with links to chromosome reports"""
85
        df = pd.DataFrame(
3✔
86
            [
87
                [
88
                    chrom,
89
                    self.bed._basic_stats[chrom]["length"],
90
                    self.bed._basic_stats[chrom]["DOC"],
91
                    self.bed._basic_stats[chrom]["CV"],
92
                    page,
93
                ]
94
                for chrom, page in zip(self.bed.chrom_names, html_list)
95
            ],
96
            columns=["chromosome", "size", "mean_coverage", "coef_variation", "link"],
97
        )
98
        datatable = DataTable(df, "chrom")
3✔
99
        datatable.datatable.datatable_options = {
3✔
100
            "pageLength": 15,
101
            "dom": "Bfrtip",
102
            "buttons": ["copy", "csv"],
103
        }
104
        datatable.datatable.set_links_to_column("link", "chromosome")
3✔
105
        js = datatable.create_javascript_function()
3✔
106
        html_table = datatable.create_datatable(float_format="%.3g")
3✔
107
        self.sections.append(
3✔
108
            {
109
                "name": "Chromosomes",
110
                "anchor": "chromosomes",
111
                "content": "<p>Link to coverage analysis report for each chromosome. "
112
                "Size, mean coverage and coefficient of variation are reported"
113
                " in the table below.</p>\n{0}\n{1}".format(js, html_table),
114
            }
115
        )
116

117
    def create_chromosome_reports(self):
3✔
118
        """Create HTML report for each chromosome present in data."""
119
        # We choose the first chromosome, to build a common javascript object
120
        datatable_js = CoverageModule.init_roi_datatable(self.bed[0])
×
121
        chrom_output_dir = config.output_dir
×
122
        if not os.path.exists(chrom_output_dir):
×
123
            os.makedirs(chrom_output_dir)
×
124

125
        page_list = []
×
126
        for chrom in self.bed:
×
127
            logger.info(f"Creating coverage report {chrom.chrom_name}")
×
128
            chrom_report = ChromosomeCoverageModule(chrom, datatable_js, region_window=self.region_window)
×
129
            page_list.append(chrom_report.html_page)
×
130
        return page_list
×
131

132
    # a static method because we need it in the coverage standalone
133
    # to initiate the datatables
134
    def init_roi_datatable(rois):
3✔
135
        """Initiate :class:`DataTableFunction` to create table to link each
136
        row with sub HTML report. All table will have the same appearance.
137
        We can therefore initialise the roi once for all.
138

139
        :param rois: can be a ROIs from ChromosomeCov instance or a simple
140
            dataframe
141
        """
142
        # computed
143
        try:
3✔
144
            df = rois.df.copy()
3✔
145
        except:
3✔
146
            df = rois.copy()
3✔
147
        df["link"] = ""
3✔
148
        # set datatable options
149
        datatable_js = DataTableFunction(df, "roi")
3✔
150
        if "start" in df.columns:
3✔
151
            datatable_js.set_links_to_column("link", "start")
3✔
152
        if "end" in df.columns:
3✔
153
            datatable_js.set_links_to_column("link", "end")
3✔
154
        datatable_js.datatable_options = {
3✔
155
            "scrollX": "true",
156
            "pageLength": 15,
157
            "scrollCollapse": "true",
158
            "dom": "Bfrtip",
159
            "buttons": ["copy", "csv"],
160
        }
161
        return datatable_js
3✔
162

163

164
class ChromosomeCoverageModule(SequanaBaseModule):
3✔
165
    """Write HTML report of coverage analysis for each chromosome. It is
166
    created by CoverageModule.
167
    """
168

169
    def __init__(self, chromosome, datatable, region_window=200000, options=None, command="", skip_html=False):
3✔
170
        """
171

172
        :param chromosome:
173
        :param datatable:
174
        :param directory:
175
        :param int region_window: length of the sub coverage plot
176
        :param options: should contain "W", "k", "circular"
177

178
        """
179
        super().__init__()
3✔
180
        self.region_window = region_window
3✔
181

182
        directory = chromosome.chrom_name
3✔
183
        # to define where are css and js
184
        if directory in {None, "."}:
3✔
185
            self.path = ""
×
186
            directory = "."
×
187
        else:
188
            self.path = "../"
3✔
189

190
        self.chromosome = chromosome
3✔
191
        self.datatable = datatable
3✔
192
        self.command = command
3✔
193
        self.command += "\nSequana version: {}".format(config.version)
3✔
194
        self.title = "Coverage analysis of chromosome {0}".format(self.chromosome.chrom_name)
3✔
195

196
        self.intro = "<p>The genome coverage analysis of the chromosome " "<b>{0}</b>.</p>".format(
3✔
197
            self.chromosome.chrom_name
198
        )
199
        self.create_report_content(directory, options=options)
3✔
200

201
        if skip_html:
3✔
202
            return
×
203

204
        self.html_page = "{0}{1}{2}.cov.html".format(directory, os.sep, self.chromosome.chrom_name)
3✔
205
        self.create_html(self.html_page)
3✔
206

207
        # inform the main coverage instance that HTML is ready
208
        self.chromosome.bed._html_list = self.chromosome.bed._html_list.union([self.html_page])
3✔
209

210
    def create_report_content(self, directory, options=None):
3✔
211
        """Generate the sections list to fill the HTML report."""
212
        self.sections = list()
3✔
213

214
        if self.chromosome._mode == "memory":
3✔
215
            # nothing to do, all computations should be already available
216
            # and in memory
217
            rois = self.chromosome.rois
3✔
218
        elif self.chromosome._mode == "chunks":
×
219
            # we need to reset the data to the first chunk
220
            # and compute the median and zscore. So, first, we save the entire
221
            # data set
222
            # self.chromosome.reset()
223
            # self.chromosome.running_median(options['W'], circular=options['circular'])
224
            # self.chromosome.compute_zscore(options['k'])
225
            # We mus set the ROI manually
226
            rois = options["ROIs"]
×
227

228
        if self.chromosome.DOC > 0:
3✔
229
            self.coverage_plot()
3✔
230
            if self.chromosome._mode == "memory":
3✔
231
                links = self.subcoverage(rois, directory)
3✔
232
            else:
233
                links = None
×
234
        else:
235
            links = None
×
236
        self.basic_stats()
3✔
237

238
        if self.chromosome.DOC:
3✔
239
            self.regions_of_interest(rois, links)
3✔
240
            self.coverage_barplot()
3✔
241
            if "gc" in self.chromosome.df.columns:
3✔
242
                self.gc_vs_coverage()
3✔
243
            self.normalized_coverage()
3✔
244
            self.zscore_distribution()
3✔
245
        self.add_command()
3✔
246

247
    def add_command(self):
3✔
248
        self.sections.append(
3✔
249
            {
250
                "name": "Command",
251
                "anchor": "command",
252
                "content": ("<p>Command used: <pre>{}</pre>.</p>".format(self.command)),
253
            }
254
        )
255

256
    def _add_large_data_section(self, name, anchor):
3✔
257
        self.sections.append(
×
258
            {
259
                "name": name,
260
                "anchor": anchor,
261
                "content": ("<p>Large data sets (--chunk-size argument " "used), skipped plotting.</p>"),
262
            }
263
        )
264

265
    def coverage_plot(self):
3✔
266
        """Coverage section."""
267
        if self.chromosome._mode == "chunks":
3✔
268
            self._add_large_data_section("Coverage", "coverage")
×
269
            return
×
270

271
        image = self.create_embedded_png(self.chromosome.plot_coverage, input_arg="filename")
3✔
272

273
        self.sections.append(
3✔
274
            {
275
                "name": "Coverage",
276
                "anchor": "coverage",
277
                "content": "<p>The following figures shows the per-base coverage along the"
278
                " reference genome (black line). The blue line indicates the "
279
                "running median. From the normalised coverage, we estimate "
280
                "z-scores on a per-base level. The red lines indicates the "
281
                "z-scores at plus or minus N standard deviations, where N is "
282
                "chosen by the user. (default:4). Only a million point are "
283
                "shown. This may explain some visual discrepancies with. </p>\n{0}".format(image),
284
            }
285
        )
286

287
    def coverage_barplot(self):
3✔
288
        """Coverage barplots section."""
289
        if self.chromosome._mode == "chunks":
3✔
290
            self._add_large_data_section("Coverage histogram", "cov_barplot")
×
291
            return
×
292

293
        image1 = self.create_embedded_png(self.chromosome.plot_hist_coverage, input_arg="filename", style="width:45%")
3✔
294
        image2 = self.create_embedded_png(
3✔
295
            self.chromosome.plot_hist_coverage,
296
            input_arg="filename",
297
            style="width:45%",
298
            logx=False,
299
        )
300
        self.sections.append(
3✔
301
            {
302
                "name": "Coverage histogram",
303
                "anchor": "cov_barplot",
304
                "content": "<p>The following figures contain the histogram of the genome "
305
                "coverage. The X and Y axis being in log scale in the left panel"
306
                "while only the Y axis is in log scale in the right panel.</p>\n"
307
                "{0}\n{1}".format(image1, image2),
308
            }
309
        )
310

311
    def subcoverage(self, rois, directory):
3✔
312
        """Create subcoverage reports to have access to a zoomable line plot.
313

314
        :params rois:
315
        :param directory: directory name for the chromsome
316

317
        This method create sub reports for each region of 200,000 bases (can be
318
        changed). Usually, it starts at position 0 so reports will be stored
319
        in e.g. for a genome of 2,300,000 bases::
320

321
            chromosome_name/chromosome_name_0_200000.html
322
            chromosome_name/chromosome_name_200000_400000.html
323
            ...
324
            ...
325
            chromosome_name/chromosome_name_2000000_2200000.html
326
            chromosome_name/chromosome_name_2200000_2300000.html
327

328
        Note that if the BED file positions does not start at zero, then
329
        names will take care of that.
330

331
        """
332
        # an aliases
333
        W = self.region_window
3✔
334
        name = self.chromosome.chrom_name
3✔
335
        chrom = self.chromosome
3✔
336
        N = len(self.chromosome)
3✔
337

338
        # position does not always start at position zero
339
        shift = self.chromosome.df.pos.iloc[0]
3✔
340
        maxpos = self.chromosome.df.pos.iloc[-1]
3✔
341

342
        # create directory
343

344
        chrom_output_dir = os.sep.join([config.output_dir, str(directory), "subplots"])
3✔
345
        if not os.path.exists(chrom_output_dir):
3✔
346
            os.makedirs(chrom_output_dir)
3✔
347

348
        # create the combobox to link toward different sub coverage
349
        # Here, we should (1) get the length of the data and (2)
350
        # figure out the boundary. Indeed, you can imagine a BED file
351
        # that does not start at position zero, but from POS1>0 to POS2
352

353
        links = ["subplots/{0}_{1}_{2}.html".format(name, i, min(i + W, maxpos)) for i in range(shift, shift + N, W)]
3✔
354
        intra_links = ("{0}_{1}_{2}.html".format(name, i, min(i + W, maxpos)) for i in range(shift, shift + N, W))
3✔
355

356
        combobox = self.create_combobox(links, "sub", True)
3✔
357
        combobox_intra = self.create_combobox(intra_links, "sub", False)
3✔
358
        datatable = self._init_datatable_function(rois)
3✔
359

360
        # break the chromosome as pieces of 200,000 bp
361
        for i in range(shift, shift + N, W):
3✔
362
            SubCoverageModule(chrom, rois, combobox_intra, datatable, i, min(i + W, maxpos), directory)
3✔
363

364
        self.sections.append({"name": "Subcoverage", "anchor": "subcoverage", "content": combobox})
3✔
365
        return links
3✔
366

367
    def _init_datatable_function(self, rois):
3✔
368
        """Initiate :class:`DataTableFunction` to create table to link each
369
        row with sub HTML report. All table will have the same appearance. So,
370
        let's initiate its only once.
371
        """
372
        datatable_js = DataTableFunction(rois.df, "roi")
3✔
373
        datatable_js.datatable_options = {
3✔
374
            "scrollX": "true",
375
            "pageLength": 15,
376
            "scrollCollapse": "true",
377
            "dom": "Bfrtip",
378
            "buttons": ["copy", "csv"],
379
        }
380
        return datatable_js
3✔
381

382
    def basic_stats(self):
3✔
383
        """Basics statistics section."""
384
        li = "<li><b>{0}</b> ({1}): {2:.2f}</li>"
3✔
385
        stats = self.chromosome.get_stats()
3✔
386

387
        description = {
3✔
388
            "BOC": "breadth of coverage: the proportion (in %s) " + " of the genome covered by at least one read.",
389
            "CV": "the coefficient of variation.",
390
            "DOC": "the sequencing depth (Depth of Coverage), that is the " + "average the genome coverage.",
391
            "MAD": "median of the absolute median deviation defined as median(|X-median(X)|).",
392
            "Median": "Median of the coverage.",
393
            "STD": "standard deviation.",
394
            "GC": "GC content (%)",
395
        }
396

397
        data = [
3✔
398
            li.format(key, description[key], stats[key])
399
            for key in ["BOC", "CV", "DOC", "MAD", "Median", "STD", "GC"]
400
            if key in stats.keys()
401
        ]
402

403
        stats = "<ul>{0}</ul>".format("\n".join(data))
3✔
404
        self.sections.append(
3✔
405
            {
406
                "name": "Basic stats",
407
                "anchor": "basic_stats",
408
                "content": "<p>Here are some basic statistics about the " "genome coverage.</p>\n{0}".format(stats),
409
            }
410
        )
411

412
    def regions_of_interest(self, rois, links):
3✔
413
        """Region of interest section."""
414

415
        def connect_link(x):
3✔
416
            for link in links:
3✔
417
                _, x1, x2 = link.rsplit(os.sep)[1].rstrip(".html").rsplit("_", 2)
3✔
418
                x1 = int(x1)
3✔
419
                x2 = int(x2)
3✔
420
                if x >= x1 and x <= x2:
3✔
421
                    return link
3✔
422
            # for the case where the data is fully stored in memory, we must
423
            # find all events !
424
            if self.chromosome._mode == "memory" and self.chromosome.binning == 1:
×
425
                raise Exception("{} position not in the range of reports".format(x))
×
426

427
        if links:
3✔
428
            links_list = [connect_link(n) for n in rois.df["start"]]
3✔
429
        else:
430
            links_list = [None for n in rois.df["start"]]
×
431

432
        rois.df["link"] = links_list
3✔
433
        # create datatable
434
        low_roi = rois.get_low_rois()
3✔
435
        high_roi = rois.get_high_rois()
3✔
436

437
        datatable = CoverageModule.init_roi_datatable(low_roi)
3✔
438

439
        datatable.set_links_to_column("link", "chr")
3✔
440

441
        js = datatable.create_javascript_function()
3✔
442
        lroi = DataTable(low_roi, "lroi", datatable)
3✔
443
        hroi = DataTable(high_roi, "hroi", datatable)
3✔
444
        html_low_roi = lroi.create_datatable(float_format="%.3g")
3✔
445
        html_high_roi = hroi.create_datatable(float_format="%.3g")
3✔
446
        rois.df.drop("link", axis=1, inplace=True)
3✔
447
        roi_paragraph = (
3✔
448
            "<p>Regions with a z-score {0}er than {1:.2f} and at "
449
            "least one base with a z-score {0}er than {2:.2f} are detected."
450
            "There are {3} {0} regions of interest."
451
            "</p>"
452
        )
453
        low_paragraph = roi_paragraph.format(
3✔
454
            "low",
455
            self.chromosome.thresholds.low2,
456
            self.chromosome.thresholds.low,
457
            len(low_roi),
458
        )
459
        high_paragraph = roi_paragraph.format(
3✔
460
            "high",
461
            self.chromosome.thresholds.high2,
462
            self.chromosome.thresholds.high,
463
            len(high_roi),
464
        )
465

466
        self.sections.append(
3✔
467
            {
468
                "name": "Regions Of Interest (ROI)",
469
                "anchor": "roi",
470
                "content": "{4}\n"
471
                "<p>The following tables give regions of "
472
                "interest detected by sequana. Here are the definitions of the "
473
                "columns:</p>\n"
474
                "<ul><li>mean_cov: the average of coverage</li>\n"
475
                "<li>mean_rm: the average of running median</li>\n"
476
                "<li>mean_zscore: the average of zscore</li>\n"
477
                "<li>max_zscore: the higher zscore contains in the region</li>"
478
                "</ul>\n"
479
                "<h3>Low coverage region</h3>\n{0}\n{1}\n"
480
                "<h3>High coverage region</h3>\n{2}\n{3}\n".format(
481
                    low_paragraph, html_low_roi, high_paragraph, html_high_roi, js
482
                ),
483
            }
484
        )
485

486
    def gc_vs_coverage(self):
3✔
487
        """3 dimensional plot of GC content versus coverage."""
488
        image = self.create_embedded_png(self.chromosome.plot_gc_vs_coverage, input_arg="filename")
3✔
489
        corr = self.chromosome.get_gc_correlation()
3✔
490
        self.sections.append(
3✔
491
            {
492
                "name": "Coverage vs GC content",
493
                "anchor": "cov_vs_gc",
494
                "content": "<p>The correlation coefficient between the coverage and GC "
495
                "content is <b>{0:.3g}</b> with a window size of {1}bp.</p>\n"
496
                "{2}\n"
497
                "<p>Note: the correlation coefficient has to be between -1.0 "
498
                "and 1.0. A coefficient of 0 means no correlation, while a "
499
                "coefficient of -1 or 1 means an existing "
500
                "correlation between GC and Coverage</p>".format(corr, self.chromosome.bed.gc_window_size, image),
501
            }
502
        )
503

504
    def normalized_coverage(self):
3✔
505
        """Barplot of normalized coverage section."""
506
        if self.chromosome._mode == "chunks":
3✔
507
            self._add_large_data_section("Normalised coverage", "normalised_coverage")
×
508
            return
×
509

510
        image = self.create_embedded_png(self.chromosome.plot_hist_normalized_coverage, input_arg="filename")
3✔
511
        self.sections.append(
3✔
512
            {
513
                "name": "Normalised coverage",
514
                "anchor": "normalised_coverage",
515
                "content": "<p>Distribution of the normalised coverage with predicted "
516
                "Gaussian. The red line should be followed the trend of the "
517
                "barplot.</p>\n{0}".format(image),
518
            }
519
        )
520

521
    def zscore_distribution(self):
3✔
522
        """Barplot of zscore distribution section."""
523
        if self.chromosome._mode == "chunks":
3✔
524
            self._add_large_data_section("Z-Score distribution", "zscore_distribution")
×
525
            return
×
526

527
        image = self.create_embedded_png(self.chromosome.plot_hist_zscore, input_arg="filename")
3✔
528
        self.sections.append(
3✔
529
            {
530
                "name": "Z-Score distribution",
531
                "anchor": "zscore_distribution",
532
                "content": "<p>Distribution of the z-score (normalised coverage); You "
533
                "should see a Gaussian distribution centered around 0. The "
534
                "estimated parameters are mu={0:.2f} and sigma={1:.2f}.</p>\n"
535
                "{2}".format(
536
                    self.chromosome.best_gaussian["mu"],
537
                    self.chromosome.best_gaussian["sigma"],
538
                    image,
539
                ),
540
            }
541
        )
542

543

544
class SubCoverageModule(SequanaBaseModule):
3✔
545
    """Write HTML report of subsection of chromosome with a javascript
546
    coverage plot.
547
    """
548

549
    def __init__(self, chromosome, rois, combobox, datatable, start, stop, directory):
3✔
550
        super().__init__()
3✔
551

552
        if directory == ".":
3✔
553
            self.path = "../"
×
554
        else:
555
            self.path = "../../"
3✔
556

557
        self.chromosome = chromosome
3✔
558
        self.rois = rois
3✔
559
        self.combobox = combobox
3✔
560
        self.datatable = datatable
3✔
561

562
        self.start = start
3✔
563
        self.stop = stop
3✔
564
        self.title = "Coverage analysis of chromosome {0}<br>" "positions {1} and {2}".format(
3✔
565
            self.chromosome.chrom_name, start, stop
566
        )
567
        self.create_report_content()
3✔
568
        self.create_html(
3✔
569
            "{0}{4}subplots{4}{1}_{2}_{3}.html".format(directory, self.chromosome.chrom_name, start, stop, os.sep)
570
        )
571

572
    def create_report_content(self):
3✔
573
        """Generate the sections list to fill the HTML report."""
574
        self.sections = list()
3✔
575

576
        self.canvasjs_line_plot()
3✔
577
        self.regions_of_interest()
3✔
578

579
    def canvasjs_line_plot(self):
3✔
580
        """Create the CanvasJS line plot section."""
581
        # set column of interest and create the csv
582
        x_col = "pos"
3✔
583
        y_col = ("cov", "mapq0", "gc")
3✔
584
        columns = self.chromosome.df.columns
3✔
585
        y_col = [n for n in y_col if n in columns]
3✔
586
        csv = self.chromosome.to_csv(
3✔
587
            start=self.start,
588
            stop=self.stop,
589
            columns=[x_col] + y_col,
590
            index=False,
591
            float_format="%.3g",
592
        )
593

594
        # create CanvasJS stuff
595
        cjs = CanvasJSLineGraph(csv, "cov", x_col, y_col)
3✔
596
        # set options
597
        cjs.set_options({"zoomEnabled": "true", "zoomType": "x", "exportEnabled": "true"})
3✔
598
        # set title
599
        cjs.set_title("Genome Coverage")
3✔
600
        # set legend
601
        cjs.set_legend(
3✔
602
            {
603
                "verticalAlign": "bottom",
604
                "horizontalAlign": "center",
605
                "cursor": "pointer",
606
            },
607
            hide_on_click=True,
608
        )
609
        # set axis
610
        cjs.set_axis_x(
3✔
611
            {
612
                "title": "Position (bp)",
613
                "labelAngle": 30,
614
                "minimum": self.start,
615
                "maximum": self.stop,
616
            }
617
        )
618
        cjs.set_axis_y({"title": "Coverage (Count)"})
3✔
619
        cjs.set_axis_y2(
3✔
620
            {
621
                "title": "GC content (ratio)",
622
                "minimum": 0,
623
                "maximum": 1,
624
                "lineColor": "#FFC425",
625
                "titleFontColor": "#FFC425",
626
                "labelFontColor": "#FFC425",
627
            }
628
        )
629
        # set datas
630
        cjs.set_data(
3✔
631
            index=0,
632
            data_dict={
633
                "type": "line",
634
                "name": "Coverage",
635
                "showInLegend": "true",
636
                "color": "#5BC0DE",
637
                "lineColor": "#5BC0DE",
638
            },
639
        )
640
        try:
3✔
641
            i = y_col.index("mapq0")
3✔
642
            cjs.set_data(
×
643
                index=i,
644
                data_dict={
645
                    "type": "line",
646
                    "name": "Coverage 2",
647
                    "showInLegend": "true",
648
                    "color": "#D9534F",
649
                    "lineColor": "#D9534F",
650
                },
651
            )
652
        except ValueError:
3✔
653
            pass
3✔
654
        try:
3✔
655
            i = y_col.index("gc")
3✔
656
            cjs.set_data(
3✔
657
                index=i,
658
                data_dict={
659
                    "type": "line",
660
                    "axisYType": "secondary",
661
                    "name": "GC content",
662
                    "showInLegend": "true",
663
                    "color": "#FFC425",
664
                    "lineColor": "#FFC425",
665
                },
666
            )
667
        except ValueError:
×
668
            pass
×
669
        # create canvasJS
670
        html_cjs = cjs.create_canvasjs()
3✔
671
        self.sections.append(
3✔
672
            {
673
                "name": "Interactive coverage plot",
674
                "anchor": "iplot",
675
                "content": ("{0}{1}\n".format(self.combobox, html_cjs)),
676
            }
677
        )
678

679
    def regions_of_interest(self):
3✔
680
        """Region of interest section."""
681
        subseq = [self.start, self.stop]
3✔
682

683
        low_roi = self.rois.get_low_rois(subseq)
3✔
684
        high_roi = self.rois.get_high_rois(subseq)
3✔
685
        js = self.datatable.create_javascript_function()
3✔
686
        lroi = DataTable(low_roi, "lroi", self.datatable)
3✔
687
        hroi = DataTable(high_roi, "hroi", self.datatable)
3✔
688
        html_low_roi = lroi.create_datatable(float_format="%.3g")
3✔
689
        html_high_roi = hroi.create_datatable(float_format="%.3g")
3✔
690
        roi_paragraph = (
3✔
691
            "<p>Regions with a z-score {0}er than {1:.2f} and at "
692
            "least one base with a z-score {0}er than {2:.2f} are detected as "
693
            "{0} coverage region. Thus, there are {3} {0} coverage regions "
694
            "between the position {4} and the position {5}</p>"
695
        )
696
        low_paragraph = roi_paragraph.format(
3✔
697
            "low",
698
            self.chromosome.thresholds.low2,
699
            self.chromosome.thresholds.low,
700
            len(low_roi),
701
            self.start,
702
            self.stop,
703
        )
704
        high_paragraph = roi_paragraph.format(
3✔
705
            "high",
706
            self.chromosome.thresholds.high2,
707
            self.chromosome.thresholds.high,
708
            len(high_roi),
709
            self.start,
710
            self.stop,
711
        )
712

713
        self.sections.append(
3✔
714
            {
715
                "name": "Regions Of Interest (ROI)",
716
                "anchor": "roi",
717
                "content": "{4}\n"
718
                "<p>Running median is the median computed along the genome "
719
                "using a sliding window. The following tables give regions of "
720
                "interest detected by sequana. Here are some definition of the "
721
                "table's columns:</p>\n"
722
                "<ul><li><b>mean_cov</b>: the average of coverage</li>\n"
723
                "<li><b>mean_rm</b>: the average of running median</li>\n"
724
                "<li><b>mean_zscore</b>: the average of zscore</li>\n"
725
                "<li><b>max_zscore</b>: the higher zscore contains in the "
726
                "region</li>\n"
727
                "<li><b>log2_ratio</b>:log2(mean_cov/mean_rm)</li></ul>\n"
728
                "<h3>Low coverage region</h3>\n{0}\n{1}\n"
729
                "<h3>High coverage region</h3>\n{2}\n{3}\n".format(
730
                    low_paragraph, html_low_roi, high_paragraph, html_high_roi, js
731
                ),
732
            }
733
        )
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