├── tests ├── nim.cfg ├── test_transcript.nim └── test_translate.nim ├── package.json ├── .gitignore ├── CHANGES.md ├── seqcover.nimble ├── src ├── seqcoverpkg │ ├── seqcover_html.nim │ ├── typeenum.nim │ ├── background.nim │ ├── seqcover.html │ ├── transcript.js │ ├── transcript.nim │ ├── utils.nim │ └── seqcover.js └── seqcover.nim ├── LICENSE ├── .github └── workflows │ └── main.yaml ├── README.md └── test ├── transcript-test.js └── data.js /tests/nim.cfg: -------------------------------------------------------------------------------- 1 | path = "$projectPath/../src" 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "test": "mocha" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | o.js 2 | tests/test_translate 3 | tests/test_transcript 4 | src/seqcover 5 | src/seqcoverpkg/background 6 | src/seqcoverpkg/transcript 7 | src/seqcoverpkg/typeenum 8 | src/seqcoverpkg/utils 9 | seqcover_report.html 10 | 11 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | v0.0.5 2 | ====== 3 | + handle more chrMT namings 4 | + allow extending introns 5 | + fix bug where sample names were converted to dates 6 | 7 | v0.0.4 8 | ============ 9 | + plot aesthetics (#23) 10 | + add sub-command `seqcover save-transcripts` 11 | 12 | v0.0.3 13 | ====== 14 | + fix bug for overlapping exons (#21) 15 | + prefer "22" over "22_KI270879v1_alt" 16 | + fix bug for no exons (#22 and thanks @oyvindbusk for reporting this and other issues) 17 | + report e.g. "exon 1 / 6" when hovering. 18 | + show genomic position next to gene 19 | -------------------------------------------------------------------------------- /seqcover.nimble: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | version = "0.0.5" 4 | author = "Brent Pedersen" 5 | description = "find depth support for DUP/DEL/CNV calls that use PE/SR" 6 | license = "MIT" 7 | 8 | 9 | # Dependencies 10 | 11 | requires "https://github.com/brentp/d4-nim >= 0.0.1", "argparse >= 0.7.0 & < 1.0", "binaryheap", "hts" 12 | srcDir = "src" 13 | installExt = @["nim"] 14 | 15 | bin = @["seqcover"] 16 | 17 | 18 | skipDirs = @["tests"] 19 | 20 | 21 | #task test, "run the tests": 22 | # exec "nim c --lineDir:on --debuginfo -r --threads:on tests/all" 23 | -------------------------------------------------------------------------------- /src/seqcoverpkg/seqcover_html.nim: -------------------------------------------------------------------------------- 1 | import strutils 2 | import ./utils 3 | import json 4 | 5 | const TOKEN = "SEQCOVER_JAVASCRIPT" 6 | 7 | const html = staticRead("./seqcover.html") 8 | const seqcover_js = staticRead("./seqcover.js") 9 | const transcript_js = staticRead("./transcript.js") 10 | 11 | 12 | proc write_html*(path:string, plot_data:seq[GenePlotData]) = 13 | 14 | 15 | var pieces = html.split(TOKEN) 16 | 17 | pieces = @[pieces[0], "", pieces[1]] 18 | 19 | var fh:File 20 | if not fh.open(path, fmWrite): 21 | quit "could not open file:" & path 22 | discard 23 | 24 | #pieces[3] = $(%(plot_data)) 25 | for p in pieces: 26 | if p == "XX": 27 | fh.write("var plot_data = ") 28 | fh.write_line($(%(plot_data))) 29 | else: 30 | fh.write_line(p) 31 | 32 | fh.close 33 | 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Brent Pedersen, Joe Brown, Mike Cormier 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/seqcoverpkg/typeenum.nim: -------------------------------------------------------------------------------- 1 | import d4 2 | import hts 3 | import strutils 4 | 5 | type CoverType* {.pure.} = enum 6 | D4 7 | BED 8 | 9 | type Cover* = object 10 | case kind:CoverType 11 | of CoverType.D4: 12 | d4*: D4 13 | of CoverType.BED: 14 | bed*: BGZI 15 | 16 | type iv = object 17 | start:int32 18 | stop:int32 19 | depth:int32 20 | 21 | proc open_dp*(path:string): Cover = 22 | if path.endsWith(".d4"): 23 | result = Cover(kind:CoverType.D4) 24 | doAssert result.d4.open(path), "seqcover: error opening d4 file:" & path 25 | else: 26 | result = Cover(kind:CoverType.BED) 27 | doAssert result.bed.open(path), "seqcover: error opening bed.gz file:" & path 28 | 29 | proc close*(c:var Cover) = 30 | case c.kind 31 | of CoverType.D4: 32 | c.d4.close() 33 | of CoverType.BED: 34 | discard c.bed.close 35 | 36 | proc parse(s:string): iv {.noInit.} = 37 | var i = -1 38 | for tok in s.split('\t', maxsplit=4): 39 | i.inc 40 | if i == 0: continue 41 | if i == 1: 42 | result.start = parseInt(tok).int32 43 | if i == 2: 44 | result.stop = parseInt(tok).int32 45 | if i == 3: 46 | result.depth = parseInt(tok).int32 47 | break 48 | 49 | proc values*(c:var Cover, chrom:string, start:uint32, values: var seq[int32]) = 50 | case c.kind 51 | of CoverType.D4: 52 | c.d4.values(chrom, start, values) 53 | of CoverType.BED: 54 | zeroMem(values[0].addr, values.len * sizeof(int32)) 55 | for s in c.bed.query(chrom, start.int64, start.int64 + values.len.int64): 56 | let iv = s.parse 57 | for p in max(0, iv.start - start.int32)..<(min(values.len.int32, iv.stop)): 58 | values[p] = iv.depth 59 | 60 | proc chromosomes*(c:var Cover, fai:Fai): OrderedTableRef[string, uint32] = 61 | case c.kind 62 | of CoverType.D4: 63 | result = c.d4.chromosomes 64 | of CoverType.BED: 65 | result = newOrderedTable[string, uint32]() 66 | if fai == nil: 67 | raise newException(OSError, "--fasta index required when .bed.gz files are given") 68 | for i in 0..> $GITHUB_ENV 30 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 31 | - uses: actions/checkout@v2 32 | - uses: actions/setup-node@v2-beta 33 | with: 34 | node-version: '12' 35 | 36 | - uses: iffy/install-nim@v3 37 | with: 38 | version: ${{ matrix.version }} 39 | 40 | - uses: actions-rs/toolchain@v1 41 | with: 42 | toolchain: stable 43 | - uses: actions-rs/cargo@v1 44 | 45 | - name : install htslib 46 | run: | 47 | sudo apt-get update 48 | sudo apt-get install git llvm curl wget libcurl4-openssl-dev autoconf 49 | wget https://github.com/samtools/htslib/archive/1.11.tar.gz 50 | tar xzf 1.11.tar.gz 51 | cd htslib-1.11/ 52 | autoheader && autoconf && ./configure --disable-libcurl 53 | sudo make -j4 install 54 | 55 | #export HTSLIB=system 56 | #sudo ln -s /usr/bin/gcc /usr/bin/musl-gcc 57 | # rustup target add x86_64-unknown-linux-musl 58 | - name : install d4 59 | run: | 60 | git clone https://github.com/38/d4-format 61 | cd d4-format 62 | cargo build --release --all #--target=x86_64-unknown-linux-musl 63 | ls target/release 64 | sudo cp target/release/libd4binding.* /usr/local/lib 65 | sudo cp ./d4binding/include/d4.h /usr/local/include/ 66 | 67 | # Runs a single command using the runners shell 68 | - name: test nim stuff 69 | run: | 70 | export LD_LIBRARY_PATH=/usr/local/lib/ 71 | nimble build -y 72 | nimble test 73 | 74 | # Runs a set of commands using the runners shell 75 | - name: test javascript stuff 76 | run: | 77 | npm install mocha 78 | npm test 79 | 80 | 81 | -------------------------------------------------------------------------------- /tests/test_transcript.nim: -------------------------------------------------------------------------------- 1 | import unittest 2 | import seqcoverpkg/transcript 3 | import seqcoverpkg/utils 4 | import hts/fai 5 | import seqcoverpkg/typeenum 6 | import tables 7 | import json 8 | 9 | suite "transcript suite": 10 | test "simple plot exon coords": 11 | 12 | #exon_plot_coords*(tr:Transcript, dps:TableRef[string, D4], extend:uint32=10): tuple[x:seq[uint32], depths: TableRef[string, seq[int32]], g: seq[uint32]] = 13 | var tr = Transcript(txStart: 10, txEnd: 100, cdsStart: 15, cdsEnd: 95, position: @[[15, 25], [85, 91]]) 14 | var dps = newTable[string, Cover]() 15 | var bgs = newTable[string, Cover]() 16 | var fa:Fai 17 | var res = tr.exon_plot_coords(dps, bgs, 10, 10, fa) 18 | check res.x.len == res.g.len 19 | for name, depth in res.depths.pairs: 20 | check res.x.len == depth.len 21 | 22 | test "translate": 23 | 24 | var u = Transcript(txstart:55, cdsstart:85, position: @[[85, 96], [122, 137]], cdsend: 137, txend: 152) 25 | var o = u #Transcript(txstart:55, cdsstart:85, position: @[[85, 96], [122, 137]], cdsend: 137, txend: 152) 26 | 27 | 28 | var t = u.translate(o, 10, 100) 29 | check t.position == @[[40, 51], [77, 92]] 30 | 31 | check t.txstart == 10 32 | check t.txend == 107 33 | 34 | o = Transcript(txstart:55, cdsstart:85, position: @[[89, 94], [132, 137]], cdsend: 137, txend: 152) 35 | t = u.translate(o, 10, 100) 36 | check t.position == @[[44, 49], [87, 92]] 37 | check t.txstart == 10 38 | check t.txend == 107 39 | 40 | test "spanning exon": 41 | var g = Gene(symbol: "HNRNPA1", description: "heterogeneous nuclear ribonucleoprotein A1", transcripts: @[ 42 | Transcript(cdsstart: 74591, cdsend: 78097, chr: "12", position: @[[78041, 78101], [78332, 80871]], strand: 1, transcript: "NM_002136", txstart: 74509, txend: 80871), 43 | Transcript(cdsstart: 80871, cdsend: 80871, chr: "12", position: @[[77595, 77751], [78041, 78101], [80445, 80516], [80736, 80871]], strand: 1, transcript: "NR_135167", txstart: 74509, txend: 80871) 44 | #Transcript(cdsstart: 80871, cdsend: 80871, chr: "12", position: @[[77595, 77751], [78041, 78101], [80445, 80516], [80736, 80871]], strand: 1, transcript: "NR_135167", txstart: 74509, txend: 80871) 45 | ]) 46 | 47 | var u = g.transcripts.union 48 | 49 | var tr = u.translate(g.transcripts[0], 10, 100) 50 | 51 | check tr == Transcript(cdsstart: 92, cdsend: 3428, chr: "12", position: @[[3372, 3432], [3552, 6091]], strand: 1, transcript: "NM_002136", txstart: 10, txend: 6091) 52 | 53 | test "no exons": 54 | 55 | var g = Gene(symbol: "CCDC39", description: "coiled-coil domain containing 39", transcripts: @[Transcript(cdsstart: 180614920, cdsend: 180679380, chr: "3", position: @[[180614007, 180615077], [180616280, 180616363], [180616515, 180616695], [180616825, 180616966], [180619258, 180619365], [180619810, 180619970], [180631468, 180631592], [180641992, 180642201], [180644119, 180644257], [180647078, 180647243], [180648164, 180648359], [180651400, 180651533], [180652162, 180652266], [180654761, 180654953], [180659451, 180659580], [180659676, 180659769], [180660569, 180660728], [180661860, 180662007], [180663866, 180663986], [180679290, 180679489]], strand: -1, transcript: "NM_181426", txstart: 180614007, txend: 180679489)]) 56 | var u = g.transcripts.union 57 | 58 | for t in g.transcripts: 59 | var v = u.translate(t, 10, 100) 60 | check v.`chr` == g.transcripts[0].`chr` 61 | 62 | -------------------------------------------------------------------------------- /tests/test_translate.nim: -------------------------------------------------------------------------------- 1 | import unittest 2 | import json 3 | import tables 4 | import hts/fai 5 | import seqcoverpkg/transcript 6 | import seqcoverpkg/typeenum 7 | import seqcoverpkg/utils 8 | 9 | var extend = 10 10 | var max_gap = 100 11 | 12 | suite "translate suite": 13 | test "bug case": 14 | var u = Transcript(cdsstart: 21505, cdsend: 31930, chr: "X", position: @[[19450, 21772], [24664, 24871], [25019, 25152]], strand: -1, transcript: "union", txstart: 19450, txend: 35554) 15 | var t = Transcript(cdsstart: 21505, cdsend: 31930, chr: "X", position: @[[19450, 21772], [24664, 24871], [25019, 25152]], strand: -1, transcript: "NM_002641", txstart: 19450, txend: 35554) 16 | var tr = u.translate(t, extend.uint32, max_gap.uint32) 17 | check tr.position == @[[10, 2332], [2452, 2659], [2779, 2912]] 18 | 19 | 20 | test "offset first exon": 21 | var u = Transcript(cdsstart: 21505, cdsend: 31930, chr: "X", position: @[[19450, 21772], [24664, 24871], [25019, 25152], [25913, 26046], [31215, 31992], [35500, 35554]], strand: -1, transcript: "union", txstart: 19450, txend: 35554) 22 | 23 | var l = u.find_offset(u.position[0][1], extend, max_gap) 24 | check l == 2322 25 | var r = u.find_offset(u.position[1][0], extend, max_gap) 26 | check r == l + 2 * extend + max_gap 27 | 28 | test "weird kcnq2": 29 | 30 | var o = Transcript(cdsstart: 33744, cdsend: 33900, chr: "20", position: @[[33677, 33903]], strand: -1, transcript: "NM_172109", txstart: 33677, txend: 72655) 31 | var u = Transcript(cdsstart: 06643, cdsend: 72463, chr: "20", position: 32 | @[[207, 7375], 33 | [8412, 8536], 34 | [13449, 13581], 35 | [14087, 14193], 36 | [14902, 15126], 37 | [19618, 19672], 38 | [24176, 24206], 39 | [28366, 28435], 40 | [31339, 31369], 41 | [33677, 33903], 42 | [38624, 38720], 43 | [39597, 39708], 44 | [42405, 42531], 45 | [44658, 44834], [45237, 45364], [46746, 46837], [72167, 72655]], strand: -1, transcript: "union", txstart: 207, txend: 72655) 46 | 47 | var ot = u.translate(o, extend.uint32, max_gap.uint32) 48 | var ut = u.translate(u, extend.uint32, max_gap.uint32) 49 | 50 | check ot.position[0] == ut.position[9] 51 | check ot.position[0][1] == ot.cdsend + 3 52 | check ot.position[0][0] - ot.cdsstart == o.position[0][0] - o.cdsstart 53 | 54 | test "translation doesn't change exon length and adjusts intron length correctly": 55 | 56 | var u = Transcript(cdsstart: 9551, cdsend: 26688, chr: "20", position: @[[8675, 9033], [9497, 9617], [13168, 13226], [13257, 13383], [13984, 14058], [15280, 15312], [18516, 18632]], strand: 1, transcript: "union", txstart: 8675, txend: 27449) 57 | 58 | var ut = u.translate(u, extend.uint32, max_gap.uint32) 59 | 60 | for i, e in ut.position: 61 | check e[1] - e[0] == u.position[i][1] - u.position[i][0] 62 | 63 | if i > 0: 64 | var l = ut.position[i-1] 65 | var r = e 66 | check r[0] - l[1] == min(2 * extend + max_gap, u.position[i][0] - u.position[i - 1][1]) 67 | 68 | test "translation matches depth coords": 69 | var u = Transcript(cdsstart: 9551, cdsend: 26688, chr: "20", position: @[[8675, 9033], [9497, 9617], [13168, 13226], [13257, 13383], [13984, 14058], [15280, 15312], [18516, 18632]], strand: 1, transcript: "union", txstart: 8675, txend: 27449) 70 | var ut = u.translate(u, extend.uint32, max_gap.uint32) 71 | 72 | var g = Gene(transcripts: @[u]) 73 | var fa:Fai 74 | 75 | #stderr.write_line u 76 | #stderr.write_line ut 77 | var t = newTable[string, Cover]() 78 | var b = newTable[string, Cover]() 79 | #var gpt = ut.exon_plot_coords(t, extend.uint32, max_gap.uint32) 80 | var gpt = @[g.plot_data(t, b, extend.uint32, max_gap.uint32, fa)] 81 | check gpt[0].transcripts[0].transcript == "union" 82 | #echo $(%gpt) 83 | 84 | 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [](https://github.com/brentp/seqcover/actions) 2 | 3 | seqcover is a tool for viewing and evaluating depth-of-coverage with the following aims. It should: 4 | 5 | - show a global view where it's easy to see problematic samples and genes 6 | - offer an interactive gene-wise view to explore coverage characteristics of individual samples within each gene 7 | - **not** require a server (single html page) 8 | - be responsive for up to 20 samples * 200 genes and be useful for a single-sample [see how we do this](#how-it-works) 9 | - highlight outlier samples based on any number of (summarized) background samples 10 | 11 | It is available as a static linux binary. 12 | 13 | ## Example Output 14 | 15 | https://brentp.github.io/seqcover/ 16 | 17 | ### Usage 18 | 19 | `seqcover` can accept per-base coverage files in [d4](https://github.com/38/d4-format) or bgzipped bedgaph format. Either of 20 | these formats can be output by [mosdepth](https://github.com/brentp/mosdepth) but `d4` format will be much faster. 21 | 22 | Generate a report: 23 | ``` 24 | # generate per base depth files if you don't have them. can also parallize this... 25 | mkdir -p samples/ 26 | for b in *.bam; do 27 | n=$(basename $b .bam) 28 | mosdepth -x -t 4 samples/$n $b 29 | done 30 | seqcover report --genes PIGA,KCNQ2,ARX,DNM1,SLC25A22,CDKL5,GABRA1,CAD,MDH2,SCN1B,CNPY3,CPLX1,NEB,HNRNPA1,CCDC39,AIFM1,CHCHD10 \ 31 | --background seqcover/seqcover_p5.d4 \ 32 | --fasta $fasta samples/*.bed.gz \ 33 | -r my_genes_report.html 34 | ``` 35 | 36 | Generate a background level: 37 | ``` 38 | seqcover generate-background --percentile 5 -f $fasta -o seqcover/ d4s/HG00*.d4 39 | ``` 40 | Once generated, this can be sent to `seqcover report` to give a metric for each sample 41 | of the number of bases below the 5th percentile of the backgrounds, which is a nice quality-control 42 | value. 43 | 44 | These backgounds should be specific to the samples of interest, so if you are using exome data 45 | the backgrounds should be generated from samples from the same exome capture kit (and prefereably 46 | from the same sequencing center). 47 | 48 | Generate a transcript file: 49 | ``` 50 | # generate a transcripts file that can be used as input to the report option with the --transcripts-file option 51 | # this is useful if running on servers with restricted internet access 52 | 53 | seqcover save-transcripts --genes PIGA,KCNQ2,ARX,DNM1,SLC25A22,CDKL5,GABRA1,CAD,MDH2,SCN1B,CNPY3,CPLX1,NEB,HNRNPA1,CCDC39,AIFM1,CHCHD10 \ 54 | --output-path transcripts.json \ 55 | --hg19 56 | 57 | # use the file as input to report with the --transcripts-file option 58 | seqcover report --genes PIGA,KCNQ2,ARX,DNM1,SLC25A22,CDKL5,GABRA1,CAD,MDH2,SCN1B,CNPY3,CPLX1,NEB,HNRNPA1,CCDC39,AIFM1,CHCHD10 \ 59 | --background seqcover/seqcover_p5.d4 \ 60 | --fasta $fasta samples/*.bed.gz \ 61 | -r my_genes_report.html \ 62 | --transcripts-file transcripts.json 63 | ``` 64 | 65 | ## How It Works 66 | 67 | ### Performance 68 | 69 | `seqcover` is a command-line tool that extracts depth information for requested genes and generates a terse report. 70 | This is possible because we **excise introns** which are often the majority of bases in the gene. The user can specify to extend 71 | into the intron beyond the default of 10 bases. As that extension increases, `seqcover` will show more and more of the intronic space. 72 | But it's possible to display 20 samples * 100 genes in html because we remove introns and use webGL (via plotly) for rendering. 73 | 74 | ### Outliers 75 | 76 | An outlier is data-dependent. Every exome will appear as an outlier given a set of genomes and every 30X genome will 77 | appear as an outlier given a set of 60X genomes. Therefore, `seqcover` let's the user extract a depth percentile from a set 78 | of chosen backgrounds. For example, the default is to extract the 5th percentile from a set of samples. This percentile can then 79 | be shown in the report and used as a metric: **how many bases in each sample are below the 5th percentile**. 80 | 81 | -------------------------------------------------------------------------------- /test/transcript-test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | let data = require("./data.js") 3 | let transcript = require("../src/seqcoverpkg/transcript.js") 4 | 5 | let tr = new transcript.Transcript(data.tr_data) 6 | 7 | describe('Transcript', function () { 8 | describe('cdsstart', function () { 9 | it('should equal cdsstart', function () { 10 | assert.equal(tr.cdsstart, data.tr_data.cdsstart) 11 | }); 12 | }); 13 | 14 | describe('transcript tests', function () { 15 | it('inferred genomic coords should match actual', function () { 16 | let parts = tr.parts() 17 | parts.forEach((p, i) => { 18 | // u:Transcript(cdsstart: 15321505, cdsend: 15335554, chr: "X", position: @[[15319450, 15321772], [15324664, 15324871], [15325019, 15325152], [15325913, 15326046], [15331215, 15331992], [15335500, 15335554]], strand: -1, transcript: "union", txstart: 15319450, txend: 15335554) 19 | let s = p.hoverinfo(data.xs, data.gs) 20 | if(i == 0){ 21 | assert.equal(s.start, 15319450) 22 | assert.equal(s.stop, 15321505) 23 | } else if (i == parts.length) { 24 | assert.equal(s.start, 15335554) 25 | assert.equal(s.stop, 15335554) 26 | 27 | } else { 28 | 29 | if(s.type == transcript.FeatureType.CDS) { 30 | assert(s.start >= 15321505, s.start.toString() + " " + i.toString()) 31 | assert(s.stop <= 15335554, s.stop.toString() + " " + i.toString()) 32 | } 33 | if(s.type == transcript.FeatureType.EXON) { 34 | assert(s.start >= 15319450, s.start.toString() + " " + i.toString()) 35 | assert(s.stop <= 15335554, s.stop.toString() + " " + i.toString()) 36 | } 37 | } 38 | 39 | }) 40 | 41 | }); 42 | it('traces should work', function () { 43 | let traces = tr.traces(0, data.xs, data.gs) 44 | assert.equal(traces.length, 3) 45 | assert.equal(traces[0].x.length, 2, "only need start and end") 46 | assert.equal(traces[0].y[0], 0, "y offset should be 0") 47 | assert.equal(traces[0].genome_x.length, 2, "only need start and end") 48 | 49 | assert.equal(traces[0].name, tr.name) 50 | 51 | 52 | var trace = traces[traces.length - 1]; // get a CDS trace 53 | assert(trace.text[0].startsWith("exon 6 / 6")) 54 | assert(trace.text[trace.text.length - 1].startsWith("exon 1 / 6")) 55 | 56 | 57 | }) 58 | 59 | it('stats returns expected values', function() { 60 | var depths = { 61 | "sampleA": Array(60).fill(10), 62 | "sampleB": Array(60).fill(0), 63 | } 64 | var background_depths = { 65 | "p10": Array(60).fill(5), 66 | "p90": Array(60).fill(90), 67 | } 68 | 69 | background_depths.p10[4] = 500; 70 | //{ sampleA: { lt_background: 0, low: 0, mean: 10, median: 10 }, 71 | // sampleB: { lt_background: 20, low: 20, mean: 0, median: 0 } } 72 | 73 | let stats = tr.stats([{start:20, stop:40}], depths, background_depths, 5); 74 | assert.equal(stats.sampleA.median, 10) 75 | assert.equal(stats.sampleB.median, 0) 76 | 77 | assert.equal(stats.sampleA.low, 0) 78 | assert.equal(stats.sampleB.low, 20) 79 | 80 | assert.equal(stats.sampleA["lt-background"], 0) 81 | assert.equal(stats.sampleB["lt-background"], 20) 82 | 83 | 84 | 85 | }) 86 | }); 87 | describe('zscore tests', function () { 88 | it('should return correct zscore', function (){ 89 | var vals = [2, 4, 6,7,8,9] 90 | var expected = [-1.6803361 , -0.84016805, 0. , 0.42008403, 0.84016805, 91 | 1.26025208] 92 | 93 | let zs = transcript.z_transform(vals) 94 | for(var i=0; i isamp: 56 | out_vals[cutoff_index][i] = k 57 | cutoff_index += 1 58 | if cutoff_index > int_sample.high: 59 | high_set = true 60 | break cumsum_block 61 | isamp = int_sample[cutoff_index] 62 | # if we ran out of samples, just set it to highest value. 63 | if not high_set: 64 | out_vals[^1][i] = H.len.int32 65 | 66 | for i in 0..out_vals.high: 67 | outs[i].write(chrom, start, out_vals[i]) 68 | 69 | 70 | proc generate_backgrounds(dps: var seq[Cover], output_dir: string, percentiles: seq[int], fai:Fai) = 71 | let block_size = 1_000_000'u32 72 | let info = dps[0].chromosomes(fai) 73 | var chroms = newSeqOfCap[tuple[name:string, length:int]](info.len) 74 | for k, v in info: chroms.add((k, v.int)) 75 | 76 | discard outputDir.existsOrCreateDir 77 | var percentiles = sorted(percentiles) 78 | 79 | var pctiles = newSeq[float](percentiles.len) 80 | for i, p in percentiles: 81 | pctiles[i] = p.float / 100.0 82 | 83 | var outs = newSeq[D4](percentiles.len) 84 | for i in 0.. 0: 101 | echo &"{chrom}:{min(i + blocksize, length)} time:{cpuTime() - t0:.2f} bases/second: {min(i + blocksize, length).float / (cpuTime() - t0):.0f}" 102 | dps.summarize_block(chrom, i, min(i + block_size, length), pctiles, outs, vals, out_vals) 103 | j.inc 104 | 105 | for o in outs.mitems: 106 | o.close() 107 | 108 | proc generate_background_main*() = 109 | let p = newParser("seqcover generate-background"): 110 | option("-o", "--output-dir", help="directory for output", default="seqcover-backgrounds") 111 | option("-f", "--fasta", help="indexed fasta required for bed.gz files") 112 | option("-p", "--percentile", default="5", help="percentile to extract from backgrounds") 113 | arg("samples", nargs= -1, help="per-base bed.gz files or d4 files or a glob of either to generate background") 114 | 115 | var argv = commandLineParams() 116 | if len(argv) > 0 and argv[0] == "generate-background": 117 | argv = argv[1..argv.high] 118 | if len(argv) == 0: 119 | argv.add("--help") 120 | 121 | var fai:Fai 122 | 123 | var opts = p.parse(argv) 124 | if opts.help: 125 | quit 0 126 | 127 | let percentiles = @[parseInt(opts.percentile)] 128 | 129 | if opts.fasta != "": 130 | doAssert fai.open(opts.fasta), "[seqcover] error opening fasta file:" & opts.fasta 131 | 132 | let paths = get_glob_samples(opts.samples) 133 | echo paths 134 | if paths.len < 4: 135 | quit "[seqcover] need at least 4 samples to generate a background" 136 | if paths.len < 10: 137 | stderr.write_line "[seqcover] warning: creating background with fewer than 10 samples might give unexpected results" 138 | 139 | var covers = read_dps(paths) 140 | covers.generate_backgrounds(opts.output_dir, percentiles, fai) 141 | 142 | echo opts 143 | 144 | 145 | when isMainModule: 146 | generate_background_main() 147 | -------------------------------------------------------------------------------- /src/seqcoverpkg/seqcover.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 71 | 72 | 73 | 74 | 75 | 76 | seqcover 78 | 79 | 80 | 81 | __ samples; 82 | __ genes 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | Coverage summary 93 | 94 | 95 | 96 | 97 | 98 | 99 | Sample Scaling 100 | 101 | 102 | 103 | Gene Scaling 104 | 105 | 106 | 107 | 108 | 109 | Summary metric 110 | 111 | 112 | Mean CDS depth 113 | Mean transcript depth 114 | CDS Bases below 7 115 | CDS Bases below lower background percentile 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | Depth per base: 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | SEQCOVER_JAVASCRIPT 137 | 138 | 139 | -------------------------------------------------------------------------------- /src/seqcover.nim: -------------------------------------------------------------------------------- 1 | import argparse 2 | import strformat 3 | import tables 4 | import sequtils 5 | import algorithm 6 | import json 7 | import hts/fai 8 | import tables 9 | import os 10 | import d4 11 | import ./seqcoverpkg/utils 12 | import ./seqcoverpkg/transcript 13 | import ./seqcoverpkg/background 14 | import ./seqcoverpkg/seqcover_html 15 | 16 | proc get_pctile(path:string): int = 17 | let parts = path.split("_") 18 | if parts.len == 0: 19 | raise newException(OSError, &"[seqcover] path {path} doesn't have expected naming. use files generated by seqcover background") 20 | let last = parts[^1].split(".")[0] 21 | if last[0] != 'p': 22 | raise newException(OSError, &"[seqcover] path {path} doesn't have expected naming. use files generated by seqcover background") 23 | return parseInt(last[1..last.high]) 24 | 25 | 26 | proc read_backgrounds(dir:string): TableRef[int, D4] = 27 | if dir == "": return 28 | result = newTable[int, D4]() 29 | if not dirExists(dir): 30 | raise newException(OSError, "[seqcover] directory {dir} not found") 31 | for path in (&"{dir}/seqcover_*.d4").walkFiles: 32 | var d:D4 33 | if not d.open(path): 34 | quit &"error reading {path}" 35 | result[get_pctile(path)] = d 36 | 37 | if result.len == 0: 38 | var msg = &"[seqcover] no background d4 files found in {dir}" 39 | raise newException(OSError, msg) 40 | stderr.write_line &"[seqcover] read {result.len} background percentiles" 41 | 42 | proc report_main() = 43 | let p = newParser("seqcover report"): 44 | option("--background", default="", help="optional path to d4 file created with seqcover background") 45 | option("--genes", default="", help="comma-delimited list of genes for initial report") 46 | option("--fasta", default="", help="required path to fai indexed fasta file") 47 | option("-r", "--report-path", default="seqcover_report.html", help="path to html report to be written") 48 | option("-t", "--transcripts-file", default="", help="path to transcript file for use if no internet connection (can be made with the save-transcripts option)") 49 | option("--extend-intron", default="10",help="The number of nucleotides to extend into the intron") 50 | flag("--hg19", help="coordinates are in hg19/GRCh37 (default is hg38).") 51 | arg("samples", nargs= -1, help="d4 files, bed files or a glob of d4 or bed files") 52 | 53 | var argv = commandLineParams() 54 | if len(argv) > 0 and argv[0] == "report": 55 | argv = argv[1..argv.high] 56 | if len(argv) == 0: 57 | argv.add("--help") 58 | 59 | var opts = p.parse(argv) 60 | if opts.help: 61 | quit 0 62 | if opts.fasta == "": 63 | echo p.help 64 | stderr.write_line "[seqcover] --fasta argument is required." 65 | quit 1 66 | 67 | var fa:Fai 68 | if not fa.open(opts.fasta): 69 | quit "[seqcover] couldn't open fasta file" 70 | 71 | var extend_intron = parseInt(opts.extend_intron) 72 | if extend_intron < 0: 73 | echo p.help 74 | stderr.write_line "--extend-intron must be >= 0" 75 | quit 1 76 | 77 | var backgrounds = read_d4s_to_table(@[opts.background]) 78 | var sample_d4s = read_d4s_to_table(opts.samples) 79 | stderr.write_line &"[seqcover] read {sample_d4s.len} sample coverage files" 80 | var gpt: seq[GenePlotData] 81 | 82 | var genes = get_genes(opts.genes.split(","), hg19=opts.hg19, transcript_file=opts.transcripts_file) 83 | 84 | for gene in genes: 85 | var u = gene.transcripts.union 86 | echo &"{u.chr}\t{u.txstart - 500}\t{u.txend + 500}" 87 | var pd = gene.plot_data(sample_d4s, backgrounds, extend=extend_intron.uint32, fai=fa, max_gap=50) 88 | gpt.add(pd) 89 | 90 | gpt.sort(proc(a, b: GenePlotData): int = cmp(a.symbol, b.symbol)) 91 | 92 | write_html(opts.report_path, gpt) 93 | stderr.write_line &"[seqcover] wrote report to:{opts.report_path}" 94 | 95 | proc save_transcripts_main() = 96 | let p = newParser("seqcover save-transcripts"): 97 | option("--genes", default="", help="comma-delimited list of genes for initial report") 98 | option("-o", "--output-path", default="transcripts.json", help="path to transcript file to be written") 99 | flag("--hg19", help="coordinates are in hg19/GRCh37 (default is hg38).") 100 | 101 | var argv = commandLineParams() 102 | if len(argv) > 0 and argv[0] == "save-transcripts": 103 | argv = argv[1..argv.high] 104 | if len(argv) == 0: 105 | argv.add("--help") 106 | 107 | var opts = p.parse(argv) 108 | if opts.help: 109 | quit 0 110 | 111 | let g = get_genes(opts.genes.split(","), hg19=opts.hg19) 112 | writeFile(opts.output_path, $(pretty(%*g))) 113 | 114 | 115 | proc main() = 116 | type pair = object 117 | fn: proc() 118 | description: string 119 | 120 | var dispatcher = { 121 | "generate-background": pair(fn:generate_background_main, description: "generate background file(s) from a set of samples"), 122 | "report": pair(fn:report_main, description: "create an HTML report from a set of sample coverage files"), 123 | "save-transcripts": pair(fn:save_transcripts_main, description: "create a json-file with transcripts that can be used as input for report if you cannot access mygene.info") 124 | }.toOrderedTable 125 | 126 | var args = commandLineParams() 127 | if len(args) > 0 and args[0] in dispatcher: 128 | dispatcher[args[0]].fn() 129 | return 130 | 131 | if len(args) == 0 or args[0] in ["-h", "--help"]: 132 | stdout.write_line "Commands: " 133 | for k, v in dispatcher: 134 | echo &" {k:<19}: {v.description}" 135 | else: 136 | echo &"unknown program '{args[0]}'" 137 | quit "" 138 | 139 | 140 | when isMainModule: 141 | main() 142 | -------------------------------------------------------------------------------- /src/seqcoverpkg/transcript.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function binary_search(A, v) { 4 | var result = 0; 5 | var j = A.length 6 | while (j != 0) { 7 | let step = j >> 1 8 | let pos = result + step; 9 | if (A[pos] < v) { 10 | result = pos + 1 11 | j -= step + 1 12 | } else { 13 | j = step 14 | } 15 | 16 | } 17 | return result 18 | } 19 | 20 | // enum 21 | const FeatureType = Object.freeze({ 22 | EXON: "exon", 23 | CDS: "CDS", 24 | UTR: "UTR", 25 | TRANSCRIPT: "transcript" 26 | }) 27 | 28 | const aesthetics = { 29 | TRANSCRIPT_COLOR: "rgba(65, 65, 65, 0.6)", 30 | TRANSCRIPT_WIDTH: 4, 31 | EXON_COLOR: "rgba(105,105,105, 0.6)", 32 | EXON_WIDTH: 19, 33 | CDS_COLOR: 'rgb(195, 155, 155)', 34 | CDS_WIDTH: 12, 35 | } 36 | 37 | class Feature { 38 | constructor(start, stop, type, transcript) { 39 | this.start = start 40 | this.stop = stop 41 | this.type = type 42 | this.transcript = transcript 43 | } 44 | get coding() { 45 | return this.type == FeatureType.CDS 46 | } 47 | 48 | 49 | hoverinfo(xs, gs) { 50 | // xs is plot coordinates, gs is genome coordinates 51 | // get genomic coordinate by finding index of plot-coord 52 | // and then looking it up in genomic array 53 | var start = gs[binary_search(xs, this.start)] 54 | var stop = gs[binary_search(xs, this.stop)] 55 | return { 56 | "transcript": this.transcript.data.transcript, 57 | "strand": this.transcript.strand, 58 | "start": start, 59 | "stop": stop, 60 | "type": this.type.toString() 61 | } 62 | } 63 | } 64 | 65 | class Transcript { 66 | constructor(data) { 67 | this.data = data 68 | } 69 | get cdsstart() { 70 | return this.data.cdsstart 71 | } 72 | get cdsend() { 73 | return this.data.cdsend 74 | } 75 | get chr() { 76 | return this.data.chr 77 | } 78 | 79 | get exons() { 80 | return this.data.position 81 | } 82 | 83 | get position() { 84 | return this.data.position 85 | } 86 | get strand() { 87 | return this.data.strand == -1 ? "-" : "+" 88 | } 89 | 90 | get name() { 91 | return this.data.transcript 92 | } 93 | 94 | get txstart() { 95 | return this.data.txstart 96 | } 97 | get txend() { 98 | return this.data.txend 99 | } 100 | 101 | parts() { 102 | // return CDS,exon,UTR in an array. exon and CDS are potentially 103 | // (often) duplicated. 104 | var that = this 105 | var result = [] 106 | result.push(new Feature(this.data.txstart, this.data.cdsstart, FeatureType.UTR, that)) 107 | this.data.position.forEach((exon, i) => { 108 | result.push(new Feature(exon[0], exon[1], FeatureType.EXON, that)) 109 | if (exon[1] < this.data.cdsstart || exon[0] > this.data.cdsend) { 110 | // continue 111 | } else { 112 | result.push(new Feature(Math.max(this.data.cdsstart, exon[0]), Math.min(this.data.cdsend, exon[1]), FeatureType.CDS, that)) 113 | } 114 | 115 | }) 116 | result.push(new Feature(this.data.cdsend, this.data.txend, FeatureType.UTR, that)) 117 | return result.filter(f => f.stop - f.start > 0) 118 | 119 | } 120 | 121 | overlaps(position) { 122 | // return parts() that overlap with this position 123 | let that = this 124 | var result = [] 125 | if (position < this.txstart || position > this.txend) { return result } 126 | if (position < this.cdsstart) { result.push(new Feature(this.data.txstart, this.data.cdsstart, FeatureType.UTR, that)) } 127 | this.data.position.forEach((exon, i) => { 128 | if (exon[0] > position || exon[1] < position) { 129 | return 130 | } 131 | result.push(new Feature(exon[0], exon[1], FeatureType.EXON, that)) 132 | if (exon[1] < that.data.cdsstart || exon[0] > that.data.cdsend) { 133 | // continue 134 | } else { 135 | var f = new Feature(Math.max(this.data.cdsstart, exon[0]), Math.min(this.data.cdsend, exon[1]), FeatureType.CDS, that) 136 | if (f.stop - f.start > 0) { 137 | result.push(f) 138 | } 139 | } 140 | }) 141 | if (position >= this.cdsend) { 142 | result.push(new Feature(this.data.cdsend, this.data.txend, FeatureType.UTR, that)) 143 | } 144 | return result; 145 | } 146 | 147 | traces(y_offset, xs, gs) { 148 | 149 | function get_genomic_coord(x) { 150 | return isNaN(x) ? NaN : gs[binary_search(xs, x)] 151 | } 152 | 153 | var transcript_trace = { 154 | name: this.data.transcript, x: [this.data.txstart, this.data.txend], y: [y_offset, y_offset], 155 | type: "scatter", mode: "lines", showlegend: false, 156 | hoverinfo: "none", 157 | line: { color: aesthetics.TRANSCRIPT_COLOR, width: aesthetics.TRANSCRIPT_WIDTH } 158 | } 159 | 160 | let parts = this.parts() 161 | 162 | var exon_trace = { 163 | name: this.data.transcript + " exons", x: [], y: [], text:[], 164 | type: "scatter", mode: "lines", showlegend: false, 165 | hoverinfo: "text", 166 | line: { color: aesthetics.EXON_COLOR, width: aesthetics.EXON_WIDTH } 167 | } 168 | let exons = parts.filter(p => p.type == FeatureType.EXON) 169 | exons.forEach((e, i) => { 170 | if ((exon_trace.x.length) > 0) { 171 | exon_trace.x.push(NaN) 172 | exon_trace.y.push(y_offset) 173 | exon_trace.text.push(undefined) 174 | } 175 | var iex = this.data.strand == -1 ? (exons.length - i) : (i + 1) 176 | 177 | let txt = `exon ${iex} / ${exons.length}`; 178 | exon_trace.text.push(txt, txt) 179 | exon_trace.x.push(e.start, e.stop) 180 | exon_trace.y.push(y_offset, y_offset) 181 | }) 182 | 183 | var cds_trace = { 184 | name: this.data.transcript + " CDS", x: [], y: [], text:[], 185 | type: "scatter", mode: "lines", showlegend: false, 186 | hoverinfo: "text", 187 | hovermode:"closest-x", 188 | line: { color: aesthetics.CDS_COLOR, width: aesthetics.CDS_WIDTH } 189 | } 190 | parts.filter(p => p.type == FeatureType.CDS).forEach(c => { 191 | if ((cds_trace.x.length) > 0) { 192 | cds_trace.x.push(NaN) 193 | cds_trace.y.push(y_offset) 194 | cds_trace.text.push(undefined) 195 | } 196 | // index back into exon array even for CDS hover 197 | var ei = 0 198 | for(var e of exons) { 199 | ei += 1 200 | if(e.start <= c.start && e.stop >= c.stop) { 201 | break; 202 | } 203 | } 204 | 205 | var iex = this.data.strand == -1 ? (exons.length - ei + 1) : (ei + 1) 206 | let txt = `exon ${iex} / ${exons.length} (CDS)` 207 | cds_trace.text.push(txt, txt) 208 | cds_trace.x.push(c.start, c.stop) 209 | cds_trace.y.push(y_offset, y_offset) 210 | }) 211 | 212 | var result = [transcript_trace, exon_trace, cds_trace] 213 | result.forEach(trace => { 214 | trace.genome_x = trace.x.map(x => get_genomic_coord(x)) 215 | }) 216 | return result 217 | 218 | } 219 | 220 | stats(xranges, depths, background_depths, low_depth_cutoff) { 221 | // NOTE xranges is in reduced, plot coords, not genomic coords and it 222 | // has form [{start: 23, stop: 44}, ...] 223 | var result = {} 224 | var background_low; 225 | // handle missing or undefined backgrounds 226 | if (background_depths != undefined && background_depths != {} && background_depths != []) { 227 | // background depths might hvae string keys like p5, p95. so we sort to 228 | // get the lowest value. 229 | var lo_key = Object.keys(background_depths).sort((a, b) => { 230 | return parseInt(a.replace(/^\D+/g, "")) - parseInt(b.replace(/^\D+/g, "")) 231 | })[0] 232 | background_low = background_depths[lo_key] //.slice(xstart, xstop) 233 | } 234 | var H = Array(16384); 235 | for (var sample in depths) { 236 | var S = 0; var N = 0; var lo = 0; var bg_lo = 0; 237 | H.fill(0); 238 | var dps = depths[sample]; 239 | 240 | for (var rng of xranges) { 241 | for (var i = rng.start; i < rng.stop; i++) { 242 | let d = dps[i] 243 | if (d < 0 || isNaN(d)) { continue; } 244 | lo += (d < low_depth_cutoff); 245 | if (background_low != undefined) { 246 | bg_lo += d < background_low[i]; 247 | } 248 | H[Math.min(d, H.length - 1)] += 1; 249 | S += d; 250 | N += 1 251 | } 252 | } 253 | result[sample] = { "low": lo, "lt-background": bg_lo } 254 | var mid = N * 0.5; // 50% of data below this number of samples. 255 | var j = 0 256 | var nc = H[j] 257 | while (nc < mid) { 258 | j += 1 259 | nc += H[j] 260 | } 261 | result[sample]["mean"] = S / N 262 | result[sample]["median"] = j 263 | } 264 | return result; 265 | } 266 | } 267 | 268 | function z_transform(data) { 269 | // TODO: use 1-pass for mean, sd 270 | var s = 0 271 | for(let d of data) s += d; 272 | let mean = s / data.length; 273 | 274 | var sd = 0; 275 | for(let d of data) { sd += Math.pow(d - mean, 2); } 276 | sd = Math.sqrt(sd / data.length); 277 | 278 | return data.map(d => (d - mean) / sd) 279 | } 280 | 281 | try { 282 | // node.js stuff to allow testing 283 | exports.Transcript = Transcript 284 | exports.FeatureType = FeatureType 285 | exports.z_transform = z_transform 286 | 287 | 288 | if (require.main === module) { 289 | // xs and gs data for testing. 290 | let data = require("./test/data.js") 291 | let tr = new Transcript(data.tr_data) 292 | 293 | tr.parts().forEach(p => console.log(p.hoverinfo(data.xs, data.gs))) 294 | 295 | console.log(tr.traces(0)) 296 | 297 | } 298 | } catch (e) { 299 | // browser 300 | } 301 | -------------------------------------------------------------------------------- /src/seqcoverpkg/transcript.nim: -------------------------------------------------------------------------------- 1 | import binaryheap 2 | import json 3 | import sequtils 4 | import strformat 5 | import strutils 6 | import d4 7 | export d4 8 | import tables 9 | import ./typeenum 10 | import hts/fai 11 | 12 | type Transcript* = object 13 | cdsstart*: int 14 | cdsend*: int 15 | `chr`*: string 16 | position*: seq[array[2, int]] 17 | strand*: int 18 | transcript*: string 19 | txstart*:int 20 | txend*:int 21 | 22 | # see: https://github.com/nim-lang/Nim/issues/15025 23 | proc `%`*(a:array[2, int]): JsonNode = 24 | result = newJArray() 25 | result.add(newJint(a[0])) 26 | result.add(newJint(a[1])) 27 | 28 | 29 | proc `$`*(t:Transcript): string = 30 | result = &"Transcript{system.`$`(t)}" 31 | 32 | proc coding*(t:Transcript): bool = 33 | result = t.cdsstart != t.cdsend 34 | 35 | type Gene* = object 36 | symbol*: string 37 | description*: string 38 | transcripts*: seq[Transcript] 39 | 40 | proc `$`*(g:Gene): string = 41 | result = &"Gene{system.`$`(g)}" 42 | 43 | type plot_coords* = object 44 | x*: seq[uint32] 45 | depths*: TableRef[string, seq[int32]] 46 | background_depths*: TableRef[string, seq[int32]] 47 | g*: seq[uint32] 48 | 49 | type GenePlotData* = object 50 | plot_coords*: plot_coords 51 | symbol*: string 52 | description*: string 53 | unioned_transcript*: Transcript 54 | transcripts*: seq[Transcript] 55 | 56 | proc union*(trs:seq[Transcript]): Transcript = 57 | result = trs[0] 58 | result.transcript = "union" 59 | ## TODO: heap is not needed here. just sort and check i vs i+1 60 | var H = newHeap[array[2, int]] do (a, b: array[2, int]) -> int: 61 | if a[0] == b[0]: return a[1] - b[1] 62 | return a[0] - b[0] 63 | 64 | for t in trs: 65 | if t.`chr` != result.`chr`: continue 66 | if t.coding and t.cdsstart < result.cdsstart: 67 | result.cdsstart = t.cdsstart 68 | 69 | if t.coding and t.cdsend > result.cdsend: 70 | result.cdsend = t.cdsend 71 | 72 | if t.txstart < result.txstart: 73 | result.txstart = t.txstart 74 | if t.txend > result.txend: 75 | result.txend = t.txend 76 | for ex in t.position: 77 | H.push(ex) 78 | 79 | result.position = newSeqOfCap[array[2, int]](4) 80 | if H.size == 0: return 81 | var A = H.pop 82 | var B: array[2, int] 83 | while H.size > 0: 84 | B = H.pop 85 | 86 | if A == B: continue 87 | 88 | if B[0] > A[1]: 89 | result.position.add(A) 90 | A = B 91 | else: 92 | A[1] = max(A[1], B[1]) 93 | 94 | if result.position.len == 0 or result.position[^1] != A: 95 | if result.position.len > 0 and result.position[^1][0] <= A[0] and result.position[^1][1] >= A[1]: return 96 | # final interval needs to be merged/extended 97 | if result.position.len > 0 and result.position[^1][1] >= A[0]: 98 | result.position[^1][1] = max(A[1], result.position[^1][1]) 99 | else: 100 | result.position.add(A) 101 | 102 | proc find_offset*(u:Transcript, pos:int, extend:int, max_gap:int): int = 103 | doAssert pos >= u.txstart and pos <= u.txend, &"error can't translate position {pos} outside of unioned transcript ({u.txstart}, {u.txend})" 104 | if u.position.len == 0 or pos < u.position[0][0]: 105 | return pos - u.txstart 106 | 107 | result = u.position[0][0] - u.txstart 108 | 109 | for i, exon in u.position: 110 | if exon[0] > pos: break 111 | 112 | # add previous intron 113 | if i > 0: 114 | let intron = min(2 * extend + max_gap, exon[0] - u.position[i - 1][1]) 115 | result += intron 116 | 117 | # add full size of this exon 118 | if exon[1] <= pos: 119 | let exon_bases = exon[1] - exon[0] 120 | result += exon_bases 121 | else: 122 | # exon contains current position 123 | result += (pos - exon[0]) 124 | break 125 | 126 | if pos > u.position[^1][1]: 127 | result += pos - u.position[^1][1] 128 | 129 | 130 | proc translate*(u:Transcript, o:Transcript, extend:uint32, max_gap:uint32): Transcript = 131 | ## given a unioned transcript, translate the positions in u to plot 132 | ## coordinates and genomic coordinates. 133 | 134 | var extend = extend.int 135 | result.transcript = o.transcript 136 | result.strand = o.strand 137 | result.`chr` = o.`chr` 138 | 139 | # needs some padding on left to go upstream, and use same param as for 140 | # introns, but here we limit to 1000 bases. 141 | let left = min(1000, extend.int) 142 | 143 | doAssert o.txstart >= u.txstart, $o 144 | result.txstart = left + u.find_offset(o.txstart, extend.int, max_gap.int) 145 | result.cdsstart = left + u.find_offset(o.cdsstart, extend.int, max_gap.int) 146 | 147 | # todo: this in n^2 (but n is small. iterate over uexons first and calc offsets once)? 148 | for i, o_exon in o.position: 149 | let l_off = left + u.find_offset(o_exon[0], extend.int, max_gap.int) 150 | let r_off = left + u.find_offset(o_exon[1], extend.int, max_gap.int) 151 | doAssert r_off - l_off == o_exon[1] - o_exon[0], $(r_off, l_off, o_exon, i) 152 | 153 | result.position.add([l_off, r_off]) 154 | 155 | result.cdsend = left + u.find_offset(o.cdsend, extend.int, max_gap.int) 156 | result.txend = left + u.find_offset(o.txend, extend.int, max_gap.int) 157 | 158 | 159 | proc `%`*[T](table: TableRef[string, T]): JsonNode = 160 | result = json.`%`(table[]) 161 | 162 | proc get_chrom(chrom:string, dp:var Cover, fai:Fai): string = 163 | ## add or remove "chr" to match chromosome names. 164 | const MTs = ["MT", "chrM", "chrMT", "M"] 165 | var chroms = dp.chromosomes(fai) 166 | if chrom in chroms: return chrom 167 | if chrom[0] != 'c' and ("chr" & chrom) in chroms: 168 | result = "chr" & chrom 169 | elif chrom[0] == 'c' and chrom.len > 3 and chrom[1] == 'h' and chrom[2] == 'r' and chrom[3..chrom.high] in chroms: 170 | result = chrom[3..chrom.high] 171 | elif chrom in MTs: # try all the MT chroms. 172 | for c in MTs: 173 | if c in chroms: 174 | return c 175 | else: 176 | raise newException(KeyError, "chromosome not found:" & chrom) 177 | 178 | proc exon_plot_coords*(tr:Transcript, dps:TableRef[string, Cover], backgrounds:TableRef[string, Cover], extend:uint32, max_gap:uint32, fai:Fai, utrs:bool=true): plot_coords = 179 | ## extract exonic depths for the transcript, extending into intron and 180 | ## up/downstream. This handles conversion to plot coordinates by removing 181 | ## introns. g: is the actual genomic coordinates. 182 | var chrom = tr.`chr` 183 | var dp: Cover 184 | for k, v in dps: 185 | dp = v 186 | break 187 | if dps.len > 0: chrom = chrom.get_chrom(dp, fai) 188 | let left = max(0, tr.txstart - min(1000, extend.int)) 189 | 190 | result.depths = newTable[string, seq[int32]]() 191 | result.background_depths = newTable[string, seq[int32]]() 192 | 193 | var right = if tr.position.len > 0: tr.position[0][0].int else: tr.txstart 194 | var stop = tr.txend + min(1000, extend.int) 195 | 196 | if utrs: 197 | result.g = toSeq(left.uint32 ..< right.uint32) 198 | result.x = toSeq(0'u32 ..< result.g.len.uint32) 199 | when defined(debug): 200 | stderr.write_line &"utr: {result.g[0]} ..< {result.g[^1]}" 201 | for sample, dp in dps.mpairs: 202 | var x = newSeqUninitialized[int32](result.g[^1] - result.g[0]) 203 | dp.values(chrom, result.g[0], x) 204 | result.depths[sample] = x 205 | for lvl, dp in backgrounds.mpairs: 206 | var x = newSeqUninitialized[int32](result.g[^1] - result.g[0]) 207 | dp.values(chrom, result.g[0], x) 208 | result.background_depths[lvl] = x 209 | 210 | var lastx:uint32 211 | var lastg:uint32 212 | 213 | for i, p in tr.position: 214 | 215 | lastx = result.x[^1] + 1 216 | lastg = result.g[^1] + 1 217 | when defined(debug): 218 | stderr.write_line "\nbefore i:", $i, &" exon:{p} lastx: {lastx} lastg: {lastg}" 219 | 220 | 221 | # maxes and mins prevent going way past end of gene with huge extend value. 222 | let left = max(lastg, p[0].uint32 - (if i == 0: 0'u32 else: min(p[0].uint32, extend))) 223 | let right = min(stop.uint32, max(left, p[1].uint32 + (if i == tr.position.high: 0'u32 else: extend))) 224 | 225 | let isize = right.int - left.int 226 | if isize <= 0: continue 227 | let size = isize.uint32 228 | 229 | # insert value for missing data to interrupt plot 230 | if i > 0: 231 | let gap = min(max_gap, left - lastg) 232 | when defined(debug): 233 | stderr.write_line &"[seqcover] gap: {gap}" 234 | if gap > 0: 235 | result.x.add(lastx-1) 236 | result.g.add(lastg-1) 237 | lastx += gap 238 | result.x.add(lastx-1) 239 | result.g.add(lastg-1) 240 | for sample, dp in dps.mpairs: 241 | result.depths[sample].add(@[int32.low, int32.low]) 242 | for lvl, dp in backgrounds.mpairs: 243 | result.background_depths[lvl].add(@[int32.low, int32.low]) 244 | 245 | when defined(debug): 246 | stderr.write_line &" gap at {lastg} (x:{lastx})" 247 | 248 | when defined(debug): 249 | stderr.write_line "i:", $i, &"exon:{p}", &" {left} ..< {right}" 250 | result.g.add(toSeq(left.. 0: 268 | result.g.add(toSeq(left.. int: 43 | result = len(a) - len(b) 44 | var drop: seq[int] 45 | for i, t in g.transcripts: 46 | if t.`chr` != chroms[0]: drop.add(i) 47 | 48 | for i in reversed(drop): 49 | g.transcripts.delete(i) 50 | 51 | proc drop_dups(genes:var seq[Gene], symbol: string) = 52 | # keep gene with highest cound of CDS 53 | 54 | var idxs = newSeqOfCap[int](4) 55 | var sel = newSeq[Gene]() 56 | 57 | for i, gene in genes: 58 | if gene.symbol == symbol: 59 | idxs.add(i) 60 | sel.add(gene) 61 | 62 | idxs.sort do (i, j:int) -> int: 63 | var icds = cds_max(sel[i]) 64 | var jcds = cds_max(sel[j]) 65 | return icds - jcds 66 | 67 | # we drop all but the last idx from the genes 68 | # so we drop the one we want to keep here. 69 | idxs = idxs[0.. 0: 105 | echo "The following genes were not in the transcripts-file: "&query_genes.join(", ") 106 | 107 | else: 108 | var C = newHttpClient() 109 | var data = newMultipartData() 110 | data["q"] = genes.join(",") 111 | data["species"] = species 112 | data["scopes"] = "symbol,entrezgene,ensemblgene,retired" 113 | 114 | var r = C.postContent("http://mygene.info/v3/query", multipart=data) 115 | var js = parseJson(r) 116 | var ids = newSeq[string]() 117 | for res in js: 118 | if "_id" in res: 119 | ids.add($res["_id"]) 120 | elif "notfound" in res: 121 | stderr.write_line &"""[seqcover] {res["query"]} not found, skipping""" 122 | 123 | data = newMultipartData() 124 | data["species"] = species 125 | data["ids"] = ids.join(",") 126 | if hg19: 127 | data["fields"] = "name,symbol,exons_hg19,genomic_pos_hg19" 128 | else: 129 | data["fields"] = "name,symbol,exons,genomic_pos" 130 | 131 | 132 | for res in C.postContent("http://mygene.info/v3/gene", multipart=data).parseJson: 133 | var gene = Gene(symbol: $res["symbol"], description: $res["name"]) 134 | var res = res 135 | 136 | if hg19: 137 | if "exons_hg19" notin res: 138 | if "genomic_pos_hg19" in res: 139 | res["exons_hg19"] = fill(res, "genomic_pos_hg19") 140 | else: 141 | continue 142 | gene.transcripts = to(res["exons_hg19"], seq[Transcript]) 143 | else: 144 | if "exons" notin res: 145 | if "genomic_pos" in res: 146 | res["exons"] = fill(res, "genomic_pos") 147 | else: 148 | continue 149 | gene.transcripts = to(res["exons"], seq[Transcript]) 150 | 151 | gene.drop_alt_chroms 152 | result.add(gene) 153 | result.drop_dups 154 | 155 | 156 | proc get_glob_samples*(paths: seq[string]): seq[string] = 157 | result = newSeqOfCap[string](paths.len) 158 | for i, p in paths: 159 | var n = 0 160 | for w in p.walkFiles: 161 | n.inc 162 | result.add(w) 163 | if n == 0: 164 | raise newException(OSError, "[seqcover]: file:" & p & " not found") 165 | 166 | 167 | proc read_dps*(paths: seq[string]): seq[Cover] = 168 | result = newSeq[Cover](paths.len) 169 | for i, p in paths: 170 | result[i] = open_dp(p) 171 | 172 | proc read_d4s_to_table*(paths: seq[string]): TableRef[string, Cover] = 173 | result = newTable[string, Cover]() 174 | for pat in paths: 175 | for p in walkPattern(pat): 176 | var name = splitFile(p).name 177 | if name in result: 178 | raise newException(OSError, "[seqcover] repeated d4 name:" & p) 179 | if name.endsWith(".bed"): 180 | name = name[0..>> u:", u 214 | #for t in gene.transcripts: 215 | # if t.transcript == "NM_001324238": 216 | # stderr.write_line ">>> t:", $t 217 | stderr.write_line "######################" 218 | 219 | #var t = gene.transcripts[1] 220 | #var u = gene.transcripts.union 221 | 222 | #var last = t.position[^1] 223 | #t.position = @[last] 224 | #stderr.write_line "after:", $t 225 | 226 | #stderr.write_line u.translate(t, extend=10) 227 | #stderr.write_line "################" 228 | 229 | var pd = gene.plot_data(d4s, backgrounds, extend=10, fai=fa, max_gap=50) 230 | gpt.add(pd) 231 | 232 | for i, x in pd.plot_coords.x: 233 | if i == 0: continue 234 | doAssert x >= pd.plot_coords.x[i-1] 235 | doAssert pd.plot_coords.g[i] >= pd.plot_coords.g[i-1] 236 | 237 | 238 | var dlen:int 239 | for s, d in pd.plot_coords.depths.mpairs: 240 | dlen = d.len 241 | break 242 | for s, d in pd.plot_coords.background_depths.mpairs: 243 | doAssert d.len == dlen 244 | 245 | for i, p in pd.unioned_transcript.position: 246 | var idx = pd.plot_coords.x.lowerBound(p[0].uint32) 247 | if u.position[i][0].int != pd.plot_coords.g[idx].int: 248 | stderr.write_line &"i[{i}][0]:" 249 | stderr.write_line &"ERR LEFT : idx:{idx}, pd.plot_coords.g[idx]: {pd.plot_coords.g[idx]}, u.position[i][0]: {u.position[i][0]} l..r: {pd.plot_coords.g[idx-1..idx+1]} diff: {u.position[i][0].int - pd.plot_coords.g[idx].int}" 250 | 251 | idx = pd.plot_coords.x.lowerBound(p[1].uint32) 252 | if u.position[i][1].int != pd.plot_coords.g[idx].int: 253 | stderr.write_line &"i[{i}][1]:" 254 | stderr.write_line &"ERR RIGHT idx:{idx}, pd.plot_coords.g[idx]: {pd.plot_coords.g[idx]}, u.position[i][1]: {u.position[i][1]} l..r: {pd.plot_coords.g[idx-1..idx+1]} diff {u.position[i][1].int - pd.plot_coords.g[idx].int}" 255 | stderr.write_line "OK" 256 | 257 | 258 | #for t in pd.transcripts: 259 | # if t.transcript == "NM_001324238": 260 | # stderr.write_line ">>> t:", $t 261 | 262 | stderr.write_line " unioned:", pd.unioned_transcript 263 | 264 | gpt.sort(proc(a, b: GenePlotData): int = cmp(a.symbol, b.symbol)) 265 | echo "plot_data = ", (%(gpt)) 266 | ]# 267 | 268 | 269 | 270 | -------------------------------------------------------------------------------- /src/seqcoverpkg/seqcover.js: -------------------------------------------------------------------------------- 1 | var nan = NaN; // hack to support json dumped with NaN values. 2 | const gene_plot_height = 500 3 | const color_list = [ 4 | "#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", 5 | "#e377c2", "#7f7f7f", "#bcbd22", "#17becf", "#7CB5EC", "#434348", 6 | "#90ED7D", "#F7A35C", "#8085E9", "#F15C80", "#E4D354", "#2B908F", 7 | "#F45B5B", "#91E8E1", "#4E79A7", "#F28E2C", "#E15759", "#76B7B2", 8 | "#59A14F", "#EDC949", "#AF7AA1", "#FF9DA7", "#9C755F", "#BAB0AB", 9 | ] 10 | const HOVER_TEMPLATE = 'position:%{text}depth:%{y}(debug) x: %{x}'; 11 | 12 | 13 | // https://stackoverflow.com/a/4198132 14 | function getHashParams(h=false) { 15 | 16 | var hashParams = {}; 17 | var e, 18 | a = /\+/g, // Regex for replacing addition symbol with a space 19 | r = /([^&;=]+)=?([^&;]*)/g, 20 | d = function (s) { return decodeURIComponent(s.replace(a, " ")); }, 21 | q = h ? h : window.location.hash.substring(1) 22 | 23 | while (e = r.exec(q)) 24 | hashParams[d(e[1])] = d(e[2]); 25 | 26 | return hashParams; 27 | } 28 | 29 | function setHashParams(obj) { 30 | var old = getHashParams(); 31 | for(var k in obj){ 32 | if(obj[k] == null){ 33 | delete old[k]; 34 | } else { 35 | old[k] = obj[k] 36 | } 37 | } 38 | var str = "#"; 39 | for(var k in old){ 40 | str += `${k}=${old[k]}&` 41 | } 42 | 43 | window.location.hash = str.slice(0, -1); // drop final '&' 44 | return old; 45 | 46 | } 47 | 48 | window.onhashchange = function(event) { 49 | let old_hp = getHashParams(new URL(event.oldURL).hash.substring(1)) 50 | let new_hp = getHashParams() 51 | } 52 | 53 | function update_selected_gene_header(gene) { 54 | let g = plot_data.filter(g => g.symbol == gene)[0] 55 | if(g == undefined) g = plot_data[0]; 56 | let chrom = g.transcripts[0].chr 57 | let start = g.plot_coords.g[0] 58 | let stop = g.plot_coords.g[g.plot_coords.g.length - 1] 59 | document.getElementById("header-selected-gene").innerHTML = `${gene} ${chrom}:${start}-${stop}` 60 | } 61 | 62 | function update_header_metadata(n_samples, n_genes) { 63 | document.getElementById("sample-count").innerHTML = n_samples 64 | document.getElementById("gene-count").innerHTML = n_genes 65 | } 66 | 67 | // https://stackoverflow.com/questions/8069315/create-array-of-all-integers-between-two-numbers-inclusive-in-javascript-jquer/8069367 68 | function range(start, end, step = 1) { 69 | const len = Math.floor((end - start) / step) + 1 70 | return Array(len).fill().map((_, idx) => start + (idx * step)) 71 | } 72 | 73 | function get_gene_plot_layout(gene) { 74 | let step = Math.round(gene.plot_coords.x.length / 10) 75 | let layout = { 76 | grid: { rows: 3, columns: 1, }, 77 | autosize: true, 78 | height: gene_plot_height, 79 | margin: { t: 10, r: 0, b: 30 }, 80 | hovermode: 'x', 81 | xaxis: { 82 | tickvals: range(gene.plot_coords.x[0], gene.plot_coords.x[gene.plot_coords.x.length - 10], step), 83 | ticktext: range(gene.plot_coords.g[0], gene.plot_coords.g[gene.plot_coords.x.length - 10], step), 84 | showgrid: false, 85 | ticklen: 5, 86 | }, 87 | yaxis: { title: "Depth", domain: [0.28, 1]}, 88 | yaxis2: { 89 | range: [0.5, -0.5], 90 | showlegend: false, 91 | zeroline: false, 92 | tickvals: [0], 93 | ticktext: ["MergedTranscript"], 94 | tickangle: 40, 95 | domain: [0.25, 0.28], 96 | }, 97 | yaxis3: { 98 | range: [0, 2], 99 | zeroline: false, 100 | showlegend: false, 101 | domain: [0.0, 0.25], 102 | tickangle: 40, 103 | ticktext: gene.transcripts.map(t => t.data.transcript), 104 | }, 105 | hoverdistance: 100000, 106 | hoverinfo: "none", 107 | showlegend: false, 108 | } 109 | 110 | return (layout) 111 | } 112 | 113 | //Get a by position trace per sample of depth 114 | function get_depth_trace(gene) { 115 | 116 | var traces = []; 117 | var low_lvl = 10000; 118 | var low_dp; 119 | for (var slvl in gene.plot_coords.background_depths) { 120 | let lvl = slvl.replace(/^\D+/g, "") 121 | 122 | let dp = gene.plot_coords.background_depths[slvl].map(function (v) { return v < -1000 ? NaN : v }) 123 | if (lvl < low_lvl) { low_dp = dp; low_lvl = lvl } 124 | 125 | var trace = { 126 | x: gene.plot_coords.x, 127 | text: gene.plot_coords.g, 128 | y: dp, 129 | type: "scatter", 130 | mode: "lines", 131 | name: `Percentile: ${lvl}`, 132 | tracktype: "background", 133 | line: { width: 1, /* dash: "dot", */ color: 'rgb(200, 200, 200)' }, 134 | yaxis: "y", 135 | hoverinfo: "skip", 136 | } 137 | traces.push(trace) 138 | } 139 | 140 | var i = -1; 141 | var samples_ = [] 142 | 143 | for (var sample in gene.plot_coords.depths) { 144 | samples_.push(sample) 145 | } 146 | for (var sample of samples_.sort()) { 147 | 148 | 149 | i += 1 150 | var dp = gene.plot_coords.depths[sample] 151 | dp = dp.map(function (v) { return v < -1000 ? NaN : v }) 152 | var color = color_list[i % color_list.length]; 153 | var trace = { 154 | x: gene.plot_coords.x, text: gene.plot_coords.g, y: dp, 155 | type: 'scattergl', mode: 'lines', name: sample, line: { width: 0.36, color: color_list[i % color_list.length] }, 156 | hovertemplate: HOVER_TEMPLATE, 157 | hoverdistance: 1000, 158 | hoverinfo: "none", 159 | yaxis: "y", 160 | tracktype: "sample", 161 | }; 162 | 163 | traces.push(trace); 164 | 165 | // extract parts of this sample that are below the threshold and plot 166 | // in a similar trace with wider line. 167 | if (low_dp) { 168 | var low_trace = { 169 | x: [], y: [], text: [], type: 'scatter', mode: "lines", name: sample, line: { width: 3.0, color: color_list[i % color_list.length] }, 170 | hoverinfo: "none", 171 | connectgaps: false, 172 | yaxis: "y", 173 | }; 174 | 175 | dp.forEach((d, i) => { 176 | if (d < low_dp[i]) { 177 | low_trace.x.push(gene.plot_coords.x[i]) 178 | low_trace.y.push(d) 179 | } else { 180 | if (low_trace.x.length > 0 && !isNaN(low_trace.x[low_trace.x.length - 1])) { 181 | low_trace.x.push(gene.plot_coords.x[i]) 182 | low_trace.y.push(null) 183 | } 184 | } 185 | 186 | }) 187 | traces.push(low_trace) 188 | 189 | } 190 | }; 191 | 192 | return (traces) 193 | 194 | }; 195 | 196 | //Get the transcript shapes for the current region 197 | function get_transcript_traces(gene, transcripts, plotRef) { 198 | var traces = [] 199 | // use negative values for y_offset so we can tell from y-value if we 200 | // are in depth plot (which must be positive) or in transcript plot 201 | 202 | transcripts.forEach((transcript, y_offset) => { 203 | var trs = transcript.traces(-(y_offset), gene.plot_coords.x, gene.plot_coords.g) 204 | trs.forEach(t => { 205 | t.yaxis = plotRef 206 | traces.push(t) 207 | }) 208 | }) 209 | return traces 210 | }; 211 | 212 | 213 | function plot_per_base_depth(gene) { 214 | setHashParams({'gene': gene.symbol}) 215 | update_selected_gene_header(gene.symbol) 216 | let gene_layout = get_gene_plot_layout(gene) 217 | let depth_traces = get_depth_trace(gene) 218 | let unioned_transcript_traces = get_transcript_traces(gene, [gene.unioned_transcript], "y2") 219 | unioned_transcript_traces.forEach(t => depth_traces.push(t)) 220 | 221 | let transcript_traces = get_transcript_traces(gene, gene.transcripts, "y3") 222 | transcript_traces.forEach(t => depth_traces.push(t)) 223 | // each transcript is centered on the negative integers. 224 | gene_layout.yaxis3.range = [0.5, -(1 + transcript_traces.length / 3)] 225 | gene_layout.yaxis3.tickvals = [...Array(gene.transcripts.length).keys()].map(f => -f) 226 | gene_layout.yaxis3.ticktext = gene.transcripts.map(t => t.data.transcript) 227 | 228 | let d = document.getElementById("gene_plot") 229 | let p = Plotly.newPlot(d, depth_traces, gene_layout, {responsive: true}) 230 | d.on("plotly_hover", data => { 231 | // handle_hover(data, depth_traces, gene, gene_layout) 232 | Plotly.react("gene_plot", depth_traces, gene_layout) 233 | data.event.stopPropagation() 234 | }).on("plotly_unhover", data => { 235 | if (gene_layout.shapes != undefined && gene_layout.shapes.length > 0) { 236 | gene_layout.shapes.pop() 237 | } 238 | Plotly.react("gene_plot", depth_traces, gene_layout) 239 | data.event.stopPropagation() 240 | }) 241 | return p 242 | } 243 | 244 | function mean(data) { 245 | var result = 0 246 | var n = 0 247 | for (var i = 0; i < data.length; i++) { 248 | let d = data[i] 249 | if (d >= 0) { 250 | n += 1 251 | result += d 252 | } 253 | } 254 | return result / n 255 | } 256 | 257 | function handle_hover(data, depth_traces, gene, gene_layout) { 258 | // this function handles hover events in the transcript plots. it 259 | // finds the correct transcript and feature (exon/transcript/UTR, etc) 260 | // and calculates stats, and updates the gene_layout to draw the 261 | // region shape. 262 | // 263 | let ax = data.yaxes[0]._attr; 264 | // don't handle hover in depth plot 265 | if (ax == "yaxis") { return false; } 266 | // this is the x in plot coordinates. 267 | var x = Math.round(data.xvals[0]) 268 | var y = Math.round(Math.abs(data.yvals[0])) // can now use this as an index to get the transcript. 269 | var transcript = ax == "yaxis2" ? gene.unioned_transcript : gene.transcripts[y] 270 | 271 | 272 | if (transcript == undefined) { return false } 273 | 274 | let overlaps = transcript.overlaps(x) 275 | if (overlaps.length > 1) { 276 | if (overlaps.some(p => p.type == FeatureType.CDS)) { 277 | overlaps = overlaps.filter(p => p.type == FeatureType.CDS) 278 | } else if (overlaps.some(p => p.type == FeatureType.EXON)) { 279 | overlaps = overlaps.filter(p => p.type == FeatureType.EXON) 280 | } 281 | } 282 | if (overlaps.length == 0) { 283 | overlaps.push(new Feature(transcript.txstart, transcript.txend, FeatureType.TRANSCRIPT)) 284 | } 285 | if (gene_layout.shapes == undefined) { gene_layout.shapes = [] } 286 | gene_layout.shapes.push({ 287 | type: "rect", 288 | xref: "x", 289 | yref: "paper", 290 | y0: 0, 291 | y1: 1, 292 | fillcolor: "#cccccc", 293 | opacity: 0.2, 294 | x0: overlaps[0].start, 295 | x1: overlaps[0].stop, 296 | }) 297 | 298 | var start_idx = binary_search(gene.plot_coords.x, overlaps[0].start) 299 | var stop_idx = binary_search(gene.plot_coords.x, overlaps[0].stop) 300 | 301 | let low_depth_cutoff = 7; // TODO: get this from user-form input. 302 | var selection_stats = transcript.stats([{ start: start_idx, stop: stop_idx }], gene.plot_coords.depths, gene.plot_coords.background_depths, low_depth_cutoff) 303 | 304 | var tx_stats = transcript.stats([{ start: 0, stop: gene.plot_coords.x.length }], gene.plot_coords.depths, gene.plot_coords.background_depths, low_depth_cutoff) 305 | 306 | // example of how to get stats for all CDSs. NOTE: this should only be done 307 | // once per gene and result cached. 308 | var cds = transcript.parts().filter(p => p.type == FeatureType.CDS) 309 | var cds_stats = transcript.stats(cds, gene.plot_coords.depths, gene.plot_coords.background_depths, low_depth_cutoff) 310 | 311 | var gstart = gene.plot_coords.g[start_idx] 312 | var gstop = gene.plot_coords.g[stop_idx] 313 | 314 | var columns = [{ title: "sample" }, { title: "transcript mean" }, { title: "CDS mean" }, { title: "selection mean" }, 315 | { title: "transcript bases < lower" }, { title: "CDS bases < lower" }, { title: "selection bases < lower" }]; 316 | var table = [] 317 | var samples_ = []; 318 | 319 | //var means = {} 320 | //hoverInfo.innerHTML = `${gene.unioned_transcript.chr}:${gstart}-${gstop}` 321 | for (var sample in gene.plot_coords.depths) { samples_.push(sample) } 322 | for (var sample of samples_.sort()) { 323 | 324 | var row = [sample, tx_stats[sample].mean.toFixed(2), cds_stats[sample].mean.toFixed(2), selection_stats[sample].mean.toFixed(2), tx_stats[sample].low, cds_stats[sample].low, selection_stats[sample].low] 325 | table.push(row) 326 | //let depths = gene.plot_coords.depths[sample]; 327 | //means[sample] = mean(depths.slice(start_idx, stop_idx)).toPrecision(4) 328 | } 329 | if (datatable) { 330 | datatable.clear() 331 | datatable.rows.add(table) 332 | datatable.draw() 333 | } else { 334 | datatable = jQuery('table#stats_table').DataTable({ data: table, columns: columns }) 335 | } 336 | } 337 | 338 | function highlight_sample(sample) { 339 | setHashParams({'sample': sample}) 340 | let d = document.getElementById("gene_plot") 341 | let vals = d.data.map((t, i) => { 342 | if (t.tracktype == "background") { 343 | return [1, 1.5] 344 | } 345 | if (t.tracktype != 'sample') { 346 | return [undefined, undefined] 347 | } else { 348 | if (t.name == sample) { 349 | return [1, 1.8] 350 | } else { 351 | return [0.15, 0.36] 352 | } 353 | } 354 | }) 355 | 356 | Plotly.restyle(d, {'opacity': vals.map(i => i[0]), 'line.width': vals.map(i => i[1]), 'hovertemplate': vals.map(i => i[1] > 0.8 ? HOVER_TEMPLATE: null)}) 357 | } 358 | 359 | function tie_heatmap_to_line_plot() { 360 | // clicks on heatmap x-axis labels highlights sample in line plot 361 | // https://issue.life/questions/44297012 362 | var xticks = jQuery("#heatmap_plot .xaxislayer-above > .xtick > text") 363 | xticks.css({ "cursor": "pointer" }) 364 | xticks.attr('class', 'unselected_label') 365 | var d = document.getElementById("gene_plot") 366 | 367 | xticks.each(function (i) { 368 | var item = jQuery(this); 369 | item.css({ "fill": color_list[i % color_list.length] }) 370 | item.attr('pointer-events', 'all') 371 | item.on("click", function (e) { 372 | var n = jQuery(this) 373 | var undo = n.css("font-weight") == "800"; 374 | xticks.attr('class', 'unselected_label') 375 | var sample = n.text() 376 | var vals = null 377 | if (undo) { 378 | vals = d.data.map((t, i) => [1, (t.tracktype == 'sample' || t.tracktype == 'background') ? 0.36 : undefined]) 379 | Plotly.restyle(d, {'opacity': vals.map(i => i[0]), 'line.width': vals.map(i => i[1]), 'hovertemplate': d.data.map((t) => t.tracktype == "sample" ? HOVER_TEMPLATE: null)}) 380 | setHashParams({'sample': null}) 381 | } else { 382 | n.attr('class', 'selected_label') 383 | highlight_sample(sample) 384 | } 385 | // see: https://codepen.io/etpinard/pen/RQQqzq?editors=0010 386 | }).on("unhover", function (e) { 387 | }) 388 | }) 389 | } 390 | 391 | function draw_heatmap() { 392 | 393 | let sample_z_scale = jQuery('#z_scale_samples').is(":checked") 394 | let gene_z_scale = jQuery('#z_scale_genes').is(":checked") 395 | 396 | let stat_metric = jQuery("#metric_select").val() 397 | 398 | var low_depth_cutoff = 7; 399 | var z = [] 400 | var x = [] // samples 401 | var y = [] // genes 402 | 403 | var is_cds = stat_metric.startsWith("cds") 404 | var metric = stat_metric.split("_")[1] 405 | for (var i = 0; i < plot_data.length; i++) { 406 | var g = plot_data[i] 407 | y.push(g.symbol) 408 | 409 | var transcript = g.unioned_transcript 410 | var stats = null 411 | if (is_cds) { 412 | var cds = transcript.parts().filter(p => p.type == FeatureType.CDS) 413 | stats = transcript.stats(cds, g.plot_coords.depths, g.plot_coords.background_depths, low_depth_cutoff) 414 | } else { 415 | stats = transcript.stats([{ start: 0, stop: g.plot_coords.x.length }], g.plot_coords.depths, g.plot_coords.background_depths, low_depth_cutoff) 416 | } 417 | 418 | var row = [] 419 | 420 | // get samples in alphabetical order 421 | if (i == 0) { 422 | for(var s in stats) { x.push(s) } 423 | x = x.sort() 424 | } 425 | 426 | for(var s of x) { 427 | row.push(stats[s][metric]) 428 | } 429 | // row is a set of samples for a given metric 430 | if(gene_z_scale) { 431 | row = z_transform(row) 432 | } 433 | 434 | z.push(row) 435 | } 436 | 437 | if(sample_z_scale) { 438 | z = z_transform_columns(z) 439 | } 440 | 441 | // heatmap draws from bottom up by default 442 | z = z.reverse() 443 | y = y.reverse() 444 | 445 | let hlayout = { 446 | title: "", 447 | margin: { t: 10 }, 448 | height: 20 * y.length + 60, 449 | width: 20 * x.length + 30, 450 | paper_bgcolor: '#f8f9fa', 451 | uniformtext: { mode: 'show', minsize: 2 }, 452 | xaxis: { tickfont: { size: 12 }, fixedrange: true, automargin: true, type: "category"}, 453 | yaxis: { fixedrange: true, automargin: true, }, 454 | font: { size: 14 }, 455 | } 456 | // TODO: this should be based on window width. 457 | if(hlayout.width < jQuery('#heatmap_plot').width()) { 458 | hlayout.width = jQuery('#heatmap_plot').width(); 459 | } 460 | if(hlayout.height < 300) { 461 | hlayout.height = 300; 462 | } 463 | let heatmap_config = { 464 | displaylogo: false, 465 | toImageButtonOptions: { 466 | format: 'svg', // one of png, svg, jpeg, webp 467 | filename: 'seqcover_summary', 468 | height: 18 * y.length + 60, 469 | width: 1200, 470 | scale: 1 // Multiply title/legend/axis/canvas sizes by this factor 471 | }, 472 | // responsive: true, 473 | } 474 | let trace = [{ x: x, y: y, z: z, type: 'heatmap', hoverongaps: false, colorscale: "Cividis" }] 475 | 476 | //https://plotly.com/javascript/reference/heatmap/ 477 | let p = document.getElementById('heatmap_plot') 478 | 479 | let yticks = jQuery("#heatmap_plot .yaxislayer-above > .ytick > text") 480 | let selected = false 481 | yticks.each(function (i) { 482 | let elem = jQuery(this) 483 | if (elem.css("font-weight") == "800") { 484 | selected = i 485 | } 486 | }) 487 | 488 | Plotly.newPlot(p, trace, hlayout, heatmap_config) 489 | p.removeAllListeners("plotly_click") 490 | p.on('plotly_click', function (click_data) { 491 | let selected_gene_idx = click_data.points[0].pointIndex[0] 492 | let gene_idx = plot_data.length - selected_gene_idx - 1 493 | let selected_sample_idx = click_data.points[0].pointIndex[1] 494 | let sample = click_data.points[0].x 495 | let gene = click_data.points[0].y 496 | 497 | // y-axis ticks 498 | let yticks = jQuery("#heatmap_plot .yaxislayer-above > .ytick > text") 499 | yticks.attr('class', 'unselected_label') 500 | yticks.each(function (i) { 501 | if (i == selected_gene_idx) { 502 | jQuery(this).attr('class', 'selected_label') 503 | } 504 | }) 505 | // x-axis ticks 506 | let xticks = jQuery("#heatmap_plot .xaxislayer-above > .xtick > text") 507 | xticks.attr("class", "unselected_label") 508 | xticks.each(function (i) { 509 | if (i == selected_sample_idx) { 510 | jQuery(this).attr("class", "selected_label") 511 | } 512 | }) 513 | 514 | if(selected_gene_idx != selected) { 515 | // only redraw if it's a new gene 516 | plot_per_base_depth(plot_data[gene_idx]).then(highlight_sample(sample)) 517 | selected = selected_gene_idx; 518 | setHashParams({'gene': gene, 'sample': sample}); 519 | } else { 520 | highlight_sample(sample) 521 | } 522 | 523 | }) 524 | p.on('plotly_afterplot', function () { 525 | // initialize first gene as selected; or re-use selected (when changing metric) 526 | let yticks = jQuery("#heatmap_plot .yaxislayer-above > .ytick > text") 527 | yticks.attr('class', 'unselected_label') 528 | if (!selected) { 529 | jQuery(yticks[plot_data.length - 1]).attr("class", "selected_label") 530 | } else { 531 | jQuery(yticks[selected]).attr("class", "selected_label") 532 | } 533 | 534 | // x-axis ticks 535 | tie_heatmap_to_line_plot() 536 | // y-axis ticks 537 | Plotly.d3.selectAll(".yaxislayer-above").selectAll('text') 538 | .on("click", function (d) { 539 | yticks.attr("class", "unselected_label") 540 | jQuery(this).attr('class', 'selected_label') 541 | var hash = setHashParams({'gene': d.text}) 542 | plot_per_base_depth(plot_data[plot_data.length - d.x - 1]) 543 | if("sample" in hash){ highlight_sample(hash.sample) } 544 | }) 545 | }) 546 | } 547 | 548 | function update_popovers() { 549 | $('[data-toggle="tooltip"]').tooltip() 550 | 551 | $("#navigation-popover").popover({ 552 | container: "body", 553 | html: true, 554 | title: "Navigating the report", 555 | content: ` 556 | 557 | X and Y axes labels are clickable 558 | Clicking gene names (y-axis labels) updates 'Depth per base' plot 559 | Clicking sample names (x-axis labels) highlights the sample trace 560 | Clicking heatmap values updates the selected gene and highlights the sample 561 | 562 | `, 563 | }) 564 | $("#depth-plot-popover").popover({ 565 | container: "body", 566 | html: true, 567 | title: "Depths by position across gene", 568 | content: ` 569 | 570 | Depths per base per sample are displayed over gene length 571 | Individual transcripts are merged into the Merged transcript 572 | CDS segments are colored light red 573 | UTR segments are light gray 574 | 575 | `, 576 | }) 577 | } 578 | 579 | jQuery(document).ready(function () { 580 | update_header_metadata(Object.keys(plot_data[0].plot_coords.depths).length, plot_data.length) 581 | update_popovers() 582 | 583 | if (Object.keys(plot_data[0].plot_coords.background_depths).length == 0) { 584 | jQuery('#cds_lt_bg').remove() 585 | } 586 | 587 | for(var g of plot_data) { 588 | if (g.unioned_transcript.constructor.name == "Object") { 589 | g.unioned_transcript = new Transcript(g.unioned_transcript) 590 | g.transcripts = g.transcripts.map(t => new Transcript(t)) 591 | for(var s in g.plot_coords.depths) { 592 | g.plot_coords.depths[s] = Float32Array.from(g.plot_coords.depths[s]) 593 | } 594 | 595 | g.plot_coords.x = Int32Array.from(g.plot_coords.x) 596 | g.plot_coords.g = Int32Array.from(g.plot_coords.g) 597 | } 598 | } 599 | 600 | jQuery('#metric_select,.z_scale').on('change', function() { draw_heatmap() }) 601 | jQuery('#metric_select').trigger('change') 602 | var p = getHashParams(); 603 | var i = 0; // TODO: rename `i` 604 | if ("gene" in p) { 605 | var gene = p.gene; 606 | for(var j = 0; j < plot_data.length; j++){ 607 | if(plot_data[j].symbol == gene){ 608 | i = j 609 | break 610 | } 611 | } 612 | 613 | } 614 | plot_per_base_depth(plot_data[i]).then(function() { 615 | // NOTE we should put these events directly in the plot fn so we don't 616 | // have code duplication, but here for now... 617 | let yticks = jQuery("#heatmap_plot .yaxislayer-above > .ytick > text") 618 | yticks.attr('class', 'unselected_label') 619 | yticks.each(function (k) { 620 | if (i == plot_data.length - k - 1) { 621 | jQuery(this).attr('class', 'selected_label') 622 | } 623 | }) 624 | if("sample" in p) { 625 | highlight_sample(p.sample) 626 | jQuery("#heatmap_plot .xaxislayer-above > .xtick > text").each(function(i, v) { 627 | if(v.innerHTML == p.sample){ 628 | jQuery(v).attr('class', 'selected_label') 629 | } 630 | }) 631 | 632 | } 633 | }) 634 | }) 635 | 636 | function z_transform_columns(z) { 637 | var ztr = new Array(z.length); 638 | for(var rowi=0; rowi < z.length; rowi++){ 639 | ztr[rowi] = new Array(z[0].length) 640 | } 641 | 642 | for(var coli=0; coli < z[0].length; coli++){ 643 | var col = [] 644 | for(var rowi=0; rowi < z.length; rowi++) { 645 | col.push(z[rowi][coli]) 646 | } 647 | var tr = z_transform(col) 648 | for(var rowi=0; rowi < z.length; rowi++) { 649 | ztr[rowi][coli] = tr[rowi]; 650 | } 651 | 652 | } 653 | return ztr 654 | } 655 | -------------------------------------------------------------------------------- /test/data.js: -------------------------------------------------------------------------------- 1 | exports.tr_data = {"cdsstart":2065,"cdsend":3986,"chr":"X","position":[[10,2332],[2402,2609],[2679,2812],[2882,3015],[3085,3862],[3932,3986]],"strand":-1,"transcript":"union","txstart":10,"txend":3986} 2 | 3 | 4 | exports.xs = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502,503,504,505,506,507,508,509,510,511,512,513,514,515,516,517,518,519,520,521,522,523,524,525,526,527,528,529,530,531,532,533,534,535,536,537,538,539,540,541,542,543,544,545,546,547,548,549,550,551,552,553,554,555,556,557,558,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585,586,587,588,589,590,591,592,593,594,595,596,597,598,599,600,601,602,603,604,605,606,607,608,609,610,611,612,613,614,615,616,617,618,619,620,621,622,623,624,625,626,627,628,629,630,631,632,633,634,635,636,637,638,639,640,641,642,643,644,645,646,647,648,649,650,651,652,653,654,655,656,657,658,659,660,661,662,663,664,665,666,667,668,669,670,671,672,673,674,675,676,677,678,679,680,681,682,683,684,685,686,687,688,689,690,691,692,693,694,695,696,697,698,699,700,701,702,703,704,705,706,707,708,709,710,711,712,713,714,715,716,717,718,719,720,721,722,723,724,725,726,727,728,729,730,731,732,733,734,735,736,737,738,739,740,741,742,743,744,745,746,747,748,749,750,751,752,753,754,755,756,757,758,759,760,761,762,763,764,765,766,767,768,769,770,771,772,773,774,775,776,777,778,779,780,781,782,783,784,785,786,787,788,789,790,791,792,793,794,795,796,797,798,799,800,801,802,803,804,805,806,807,808,809,810,811,812,813,814,815,816,817,818,819,820,821,822,823,824,825,826,827,828,829,830,831,832,833,834,835,836,837,838,839,840,841,842,843,844,845,846,847,848,849,850,851,852,853,854,855,856,857,858,859,860,861,862,863,864,865,866,867,868,869,870,871,872,873,874,875,876,877,878,879,880,881,882,883,884,885,886,887,888,889,890,891,892,893,894,895,896,897,898,899,900,901,902,903,904,905,906,907,908,909,910,911,912,913,914,915,916,917,918,919,920,921,922,923,924,925,926,927,928,929,930,931,932,933,934,935,936,937,938,939,940,941,942,943,944,945,946,947,948,949,950,951,952,953,954,955,956,957,958,959,960,961,962,963,964,965,966,967,968,969,970,971,972,973,974,975,976,977,978,979,980,981,982,983,984,985,986,987,988,989,990,991,992,993,994,995,996,997,998,999,1000,1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1011,1012,1013,1014,1015,1016,1017,1018,1019,1020,1021,1022,1023,1024,1025,1026,1027,1028,1029,1030,1031,1032,1033,1034,1035,1036,1037,1038,1039,1040,1041,1042,1043,1044,1045,1046,1047,1048,1049,1050,1051,1052,1053,1054,1055,1056,1057,1058,1059,1060,1061,1062,1063,1064,1065,1066,1067,1068,1069,1070,1071,1072,1073,1074,1075,1076,1077,1078,1079,1080,1081,1082,1083,1084,1085,1086,1087,1088,1089,1090,1091,1092,1093,1094,1095,1096,1097,1098,1099,1100,1101,1102,1103,1104,1105,1106,1107,1108,1109,1110,1111,1112,1113,1114,1115,1116,1117,1118,1119,1120,1121,1122,1123,1124,1125,1126,1127,1128,1129,1130,1131,1132,1133,1134,1135,1136,1137,1138,1139,1140,1141,1142,1143,1144,1145,1146,1147,1148,1149,1150,1151,1152,1153,1154,1155,1156,1157,1158,1159,1160,1161,1162,1163,1164,1165,1166,1167,1168,1169,1170,1171,1172,1173,1174,1175,1176,1177,1178,1179,1180,1181,1182,1183,1184,1185,1186,1187,1188,1189,1190,1191,1192,1193,1194,1195,1196,1197,1198,1199,1200,1201,1202,1203,1204,1205,1206,1207,1208,1209,1210,1211,1212,1213,1214,1215,1216,1217,1218,1219,1220,1221,1222,1223,1224,1225,1226,1227,1228,1229,1230,1231,1232,1233,1234,1235,1236,1237,1238,1239,1240,1241,1242,1243,1244,1245,1246,1247,1248,1249,1250,1251,1252,1253,1254,1255,1256,1257,1258,1259,1260,1261,1262,1263,1264,1265,1266,1267,1268,1269,1270,1271,1272,1273,1274,1275,1276,1277,1278,1279,1280,1281,1282,1283,1284,1285,1286,1287,1288,1289,1290,1291,1292,1293,1294,1295,1296,1297,1298,1299,1300,1301,1302,1303,1304,1305,1306,1307,1308,1309,1310,1311,1312,1313,1314,1315,1316,1317,1318,1319,1320,1321,1322,1323,1324,1325,1326,1327,1328,1329,1330,1331,1332,1333,1334,1335,1336,1337,1338,1339,1340,1341,1342,1343,1344,1345,1346,1347,1348,1349,1350,1351,1352,1353,1354,1355,1356,1357,1358,1359,1360,1361,1362,1363,1364,1365,1366,1367,1368,1369,1370,1371,1372,1373,1374,1375,1376,1377,1378,1379,1380,1381,1382,1383,1384,1385,1386,1387,1388,1389,1390,1391,1392,1393,1394,1395,1396,1397,1398,1399,1400,1401,1402,1403,1404,1405,1406,1407,1408,1409,1410,1411,1412,1413,1414,1415,1416,1417,1418,1419,1420,1421,1422,1423,1424,1425,1426,1427,1428,1429,1430,1431,1432,1433,1434,1435,1436,1437,1438,1439,1440,1441,1442,1443,1444,1445,1446,1447,1448,1449,1450,1451,1452,1453,1454,1455,1456,1457,1458,1459,1460,1461,1462,1463,1464,1465,1466,1467,1468,1469,1470,1471,1472,1473,1474,1475,1476,1477,1478,1479,1480,1481,1482,1483,1484,1485,1486,1487,1488,1489,1490,1491,1492,1493,1494,1495,1496,1497,1498,1499,1500,1501,1502,1503,1504,1505,1506,1507,1508,1509,1510,1511,1512,1513,1514,1515,1516,1517,1518,1519,1520,1521,1522,1523,1524,1525,1526,1527,1528,1529,1530,1531,1532,1533,1534,1535,1536,1537,1538,1539,1540,1541,1542,1543,1544,1545,1546,1547,1548,1549,1550,1551,1552,1553,1554,1555,1556,1557,1558,1559,1560,1561,1562,1563,1564,1565,1566,1567,1568,1569,1570,1571,1572,1573,1574,1575,1576,1577,1578,1579,1580,1581,1582,1583,1584,1585,1586,1587,1588,1589,1590,1591,1592,1593,1594,1595,1596,1597,1598,1599,1600,1601,1602,1603,1604,1605,1606,1607,1608,1609,1610,1611,1612,1613,1614,1615,1616,1617,1618,1619,1620,1621,1622,1623,1624,1625,1626,1627,1628,1629,1630,1631,1632,1633,1634,1635,1636,1637,1638,1639,1640,1641,1642,1643,1644,1645,1646,1647,1648,1649,1650,1651,1652,1653,1654,1655,1656,1657,1658,1659,1660,1661,1662,1663,1664,1665,1666,1667,1668,1669,1670,1671,1672,1673,1674,1675,1676,1677,1678,1679,1680,1681,1682,1683,1684,1685,1686,1687,1688,1689,1690,1691,1692,1693,1694,1695,1696,1697,1698,1699,1700,1701,1702,1703,1704,1705,1706,1707,1708,1709,1710,1711,1712,1713,1714,1715,1716,1717,1718,1719,1720,1721,1722,1723,1724,1725,1726,1727,1728,1729,1730,1731,1732,1733,1734,1735,1736,1737,1738,1739,1740,1741,1742,1743,1744,1745,1746,1747,1748,1749,1750,1751,1752,1753,1754,1755,1756,1757,1758,1759,1760,1761,1762,1763,1764,1765,1766,1767,1768,1769,1770,1771,1772,1773,1774,1775,1776,1777,1778,1779,1780,1781,1782,1783,1784,1785,1786,1787,1788,1789,1790,1791,1792,1793,1794,1795,1796,1797,1798,1799,1800,1801,1802,1803,1804,1805,1806,1807,1808,1809,1810,1811,1812,1813,1814,1815,1816,1817,1818,1819,1820,1821,1822,1823,1824,1825,1826,1827,1828,1829,1830,1831,1832,1833,1834,1835,1836,1837,1838,1839,1840,1841,1842,1843,1844,1845,1846,1847,1848,1849,1850,1851,1852,1853,1854,1855,1856,1857,1858,1859,1860,1861,1862,1863,1864,1865,1866,1867,1868,1869,1870,1871,1872,1873,1874,1875,1876,1877,1878,1879,1880,1881,1882,1883,1884,1885,1886,1887,1888,1889,1890,1891,1892,1893,1894,1895,1896,1897,1898,1899,1900,1901,1902,1903,1904,1905,1906,1907,1908,1909,1910,1911,1912,1913,1914,1915,1916,1917,1918,1919,1920,1921,1922,1923,1924,1925,1926,1927,1928,1929,1930,1931,1932,1933,1934,1935,1936,1937,1938,1939,1940,1941,1942,1943,1944,1945,1946,1947,1948,1949,1950,1951,1952,1953,1954,1955,1956,1957,1958,1959,1960,1961,1962,1963,1964,1965,1966,1967,1968,1969,1970,1971,1972,1973,1974,1975,1976,1977,1978,1979,1980,1981,1982,1983,1984,1985,1986,1987,1988,1989,1990,1991,1992,1993,1994,1995,1996,1997,1998,1999,2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022,2023,2024,2025,2026,2027,2028,2029,2030,2031,2032,2033,2034,2035,2036,2037,2038,2039,2040,2041,2042,2043,2044,2045,2046,2047,2048,2049,2050,2051,2052,2053,2054,2055,2056,2057,2058,2059,2060,2061,2062,2063,2064,2065,2066,2067,2068,2069,2070,2071,2072,2073,2074,2075,2076,2077,2078,2079,2080,2081,2082,2083,2084,2085,2086,2087,2088,2089,2090,2091,2092,2093,2094,2095,2096,2097,2098,2099,2100,2101,2102,2103,2104,2105,2106,2107,2108,2109,2110,2111,2112,2113,2114,2115,2116,2117,2118,2119,2120,2121,2122,2123,2124,2125,2126,2127,2128,2129,2130,2131,2132,2133,2134,2135,2136,2137,2138,2139,2140,2141,2142,2143,2144,2145,2146,2147,2148,2149,2150,2151,2152,2153,2154,2155,2156,2157,2158,2159,2160,2161,2162,2163,2164,2165,2166,2167,2168,2169,2170,2171,2172,2173,2174,2175,2176,2177,2178,2179,2180,2181,2182,2183,2184,2185,2186,2187,2188,2189,2190,2191,2192,2193,2194,2195,2196,2197,2198,2199,2200,2201,2202,2203,2204,2205,2206,2207,2208,2209,2210,2211,2212,2213,2214,2215,2216,2217,2218,2219,2220,2221,2222,2223,2224,2225,2226,2227,2228,2229,2230,2231,2232,2233,2234,2235,2236,2237,2238,2239,2240,2241,2242,2243,2244,2245,2246,2247,2248,2249,2250,2251,2252,2253,2254,2255,2256,2257,2258,2259,2260,2261,2262,2263,2264,2265,2266,2267,2268,2269,2270,2271,2272,2273,2274,2275,2276,2277,2278,2279,2280,2281,2282,2283,2284,2285,2286,2287,2288,2289,2290,2291,2292,2293,2294,2295,2296,2297,2298,2299,2300,2301,2302,2303,2304,2305,2306,2307,2308,2309,2310,2311,2312,2313,2314,2315,2316,2317,2318,2319,2320,2321,2322,2323,2324,2325,2326,2327,2328,2329,2330,2331,2332,2333,2334,2335,2336,2337,2338,2339,2340,2341,2341,2391,2392,2393,2394,2395,2396,2397,2398,2399,2400,2401,2402,2403,2404,2405,2406,2407,2408,2409,2410,2411,2412,2413,2414,2415,2416,2417,2418,2419,2420,2421,2422,2423,2424,2425,2426,2427,2428,2429,2430,2431,2432,2433,2434,2435,2436,2437,2438,2439,2440,2441,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2453,2454,2455,2456,2457,2458,2459,2460,2461,2462,2463,2464,2465,2466,2467,2468,2469,2470,2471,2472,2473,2474,2475,2476,2477,2478,2479,2480,2481,2482,2483,2484,2485,2486,2487,2488,2489,2490,2491,2492,2493,2494,2495,2496,2497,2498,2499,2500,2501,2502,2503,2504,2505,2506,2507,2508,2509,2510,2511,2512,2513,2514,2515,2516,2517,2518,2519,2520,2521,2522,2523,2524,2525,2526,2527,2528,2529,2530,2531,2532,2533,2534,2535,2536,2537,2538,2539,2540,2541,2542,2543,2544,2545,2546,2547,2548,2549,2550,2551,2552,2553,2554,2555,2556,2557,2558,2559,2560,2561,2562,2563,2564,2565,2566,2567,2568,2569,2570,2571,2572,2573,2574,2575,2576,2577,2578,2579,2580,2581,2582,2583,2584,2585,2586,2587,2588,2589,2590,2591,2592,2593,2594,2595,2596,2597,2598,2599,2600,2601,2602,2603,2604,2605,2606,2607,2608,2609,2610,2611,2612,2613,2614,2615,2616,2617,2618,2618,2668,2669,2670,2671,2672,2673,2674,2675,2676,2677,2678,2679,2680,2681,2682,2683,2684,2685,2686,2687,2688,2689,2690,2691,2692,2693,2694,2695,2696,2697,2698,2699,2700,2701,2702,2703,2704,2705,2706,2707,2708,2709,2710,2711,2712,2713,2714,2715,2716,2717,2718,2719,2720,2721,2722,2723,2724,2725,2726,2727,2728,2729,2730,2731,2732,2733,2734,2735,2736,2737,2738,2739,2740,2741,2742,2743,2744,2745,2746,2747,2748,2749,2750,2751,2752,2753,2754,2755,2756,2757,2758,2759,2760,2761,2762,2763,2764,2765,2766,2767,2768,2769,2770,2771,2772,2773,2774,2775,2776,2777,2778,2779,2780,2781,2782,2783,2784,2785,2786,2787,2788,2789,2790,2791,2792,2793,2794,2795,2796,2797,2798,2799,2800,2801,2802,2803,2804,2805,2806,2807,2808,2809,2810,2811,2812,2813,2814,2815,2816,2817,2818,2819,2820,2821,2821,2871,2872,2873,2874,2875,2876,2877,2878,2879,2880,2881,2882,2883,2884,2885,2886,2887,2888,2889,2890,2891,2892,2893,2894,2895,2896,2897,2898,2899,2900,2901,2902,2903,2904,2905,2906,2907,2908,2909,2910,2911,2912,2913,2914,2915,2916,2917,2918,2919,2920,2921,2922,2923,2924,2925,2926,2927,2928,2929,2930,2931,2932,2933,2934,2935,2936,2937,2938,2939,2940,2941,2942,2943,2944,2945,2946,2947,2948,2949,2950,2951,2952,2953,2954,2955,2956,2957,2958,2959,2960,2961,2962,2963,2964,2965,2966,2967,2968,2969,2970,2971,2972,2973,2974,2975,2976,2977,2978,2979,2980,2981,2982,2983,2984,2985,2986,2987,2988,2989,2990,2991,2992,2993,2994,2995,2996,2997,2998,2999,3000,3001,3002,3003,3004,3005,3006,3007,3008,3009,3010,3011,3012,3013,3014,3015,3016,3017,3018,3019,3020,3021,3022,3023,3024,3024,3074,3075,3076,3077,3078,3079,3080,3081,3082,3083,3084,3085,3086,3087,3088,3089,3090,3091,3092,3093,3094,3095,3096,3097,3098,3099,3100,3101,3102,3103,3104,3105,3106,3107,3108,3109,3110,3111,3112,3113,3114,3115,3116,3117,3118,3119,3120,3121,3122,3123,3124,3125,3126,3127,3128,3129,3130,3131,3132,3133,3134,3135,3136,3137,3138,3139,3140,3141,3142,3143,3144,3145,3146,3147,3148,3149,3150,3151,3152,3153,3154,3155,3156,3157,3158,3159,3160,3161,3162,3163,3164,3165,3166,3167,3168,3169,3170,3171,3172,3173,3174,3175,3176,3177,3178,3179,3180,3181,3182,3183,3184,3185,3186,3187,3188,3189,3190,3191,3192,3193,3194,3195,3196,3197,3198,3199,3200,3201,3202,3203,3204,3205,3206,3207,3208,3209,3210,3211,3212,3213,3214,3215,3216,3217,3218,3219,3220,3221,3222,3223,3224,3225,3226,3227,3228,3229,3230,3231,3232,3233,3234,3235,3236,3237,3238,3239,3240,3241,3242,3243,3244,3245,3246,3247,3248,3249,3250,3251,3252,3253,3254,3255,3256,3257,3258,3259,3260,3261,3262,3263,3264,3265,3266,3267,3268,3269,3270,3271,3272,3273,3274,3275,3276,3277,3278,3279,3280,3281,3282,3283,3284,3285,3286,3287,3288,3289,3290,3291,3292,3293,3294,3295,3296,3297,3298,3299,3300,3301,3302,3303,3304,3305,3306,3307,3308,3309,3310,3311,3312,3313,3314,3315,3316,3317,3318,3319,3320,3321,3322,3323,3324,3325,3326,3327,3328,3329,3330,3331,3332,3333,3334,3335,3336,3337,3338,3339,3340,3341,3342,3343,3344,3345,3346,3347,3348,3349,3350,3351,3352,3353,3354,3355,3356,3357,3358,3359,3360,3361,3362,3363,3364,3365,3366,3367,3368,3369,3370,3371,3372,3373,3374,3375,3376,3377,3378,3379,3380,3381,3382,3383,3384,3385,3386,3387,3388,3389,3390,3391,3392,3393,3394,3395,3396,3397,3398,3399,3400,3401,3402,3403,3404,3405,3406,3407,3408,3409,3410,3411,3412,3413,3414,3415,3416,3417,3418,3419,3420,3421,3422,3423,3424,3425,3426,3427,3428,3429,3430,3431,3432,3433,3434,3435,3436,3437,3438,3439,3440,3441,3442,3443,3444,3445,3446,3447,3448,3449,3450,3451,3452,3453,3454,3455,3456,3457,3458,3459,3460,3461,3462,3463,3464,3465,3466,3467,3468,3469,3470,3471,3472,3473,3474,3475,3476,3477,3478,3479,3480,3481,3482,3483,3484,3485,3486,3487,3488,3489,3490,3491,3492,3493,3494,3495,3496,3497,3498,3499,3500,3501,3502,3503,3504,3505,3506,3507,3508,3509,3510,3511,3512,3513,3514,3515,3516,3517,3518,3519,3520,3521,3522,3523,3524,3525,3526,3527,3528,3529,3530,3531,3532,3533,3534,3535,3536,3537,3538,3539,3540,3541,3542,3543,3544,3545,3546,3547,3548,3549,3550,3551,3552,3553,3554,3555,3556,3557,3558,3559,3560,3561,3562,3563,3564,3565,3566,3567,3568,3569,3570,3571,3572,3573,3574,3575,3576,3577,3578,3579,3580,3581,3582,3583,3584,3585,3586,3587,3588,3589,3590,3591,3592,3593,3594,3595,3596,3597,3598,3599,3600,3601,3602,3603,3604,3605,3606,3607,3608,3609,3610,3611,3612,3613,3614,3615,3616,3617,3618,3619,3620,3621,3622,3623,3624,3625,3626,3627,3628,3629,3630,3631,3632,3633,3634,3635,3636,3637,3638,3639,3640,3641,3642,3643,3644,3645,3646,3647,3648,3649,3650,3651,3652,3653,3654,3655,3656,3657,3658,3659,3660,3661,3662,3663,3664,3665,3666,3667,3668,3669,3670,3671,3672,3673,3674,3675,3676,3677,3678,3679,3680,3681,3682,3683,3684,3685,3686,3687,3688,3689,3690,3691,3692,3693,3694,3695,3696,3697,3698,3699,3700,3701,3702,3703,3704,3705,3706,3707,3708,3709,3710,3711,3712,3713,3714,3715,3716,3717,3718,3719,3720,3721,3722,3723,3724,3725,3726,3727,3728,3729,3730,3731,3732,3733,3734,3735,3736,3737,3738,3739,3740,3741,3742,3743,3744,3745,3746,3747,3748,3749,3750,3751,3752,3753,3754,3755,3756,3757,3758,3759,3760,3761,3762,3763,3764,3765,3766,3767,3768,3769,3770,3771,3772,3773,3774,3775,3776,3777,3778,3779,3780,3781,3782,3783,3784,3785,3786,3787,3788,3789,3790,3791,3792,3793,3794,3795,3796,3797,3798,3799,3800,3801,3802,3803,3804,3805,3806,3807,3808,3809,3810,3811,3812,3813,3814,3815,3816,3817,3818,3819,3820,3821,3822,3823,3824,3825,3826,3827,3828,3829,3830,3831,3832,3833,3834,3835,3836,3837,3838,3839,3840,3841,3842,3843,3844,3845,3846,3847,3848,3849,3850,3851,3852,3853,3854,3855,3856,3857,3858,3859,3860,3861,3862,3863,3864,3865,3866,3867,3868,3869,3870,3871,3871,3921,3922,3923,3924,3925,3926,3927,3928,3929,3930,3931,3932,3933,3934,3935,3936,3937,3938,3939,3940,3941,3942,3943,3944,3945,3946,3947,3948,3949,3950,3951,3952,3953,3954,3955,3956,3957,3958,3959,3960,3961,3962,3963,3964,3965,3966,3967,3968,3969,3970,3971,3972,3973,3974,3975,3976,3977,3978,3979,3980,3981,3982,3983,3984,3985,3986,3987,3988,3989,3990,3991,3992,3993,3994,3995] 5 | exports.gs = [15319440,15319441,15319442,15319443,15319444,15319445,15319446,15319447,15319448,15319449,15319450,15319451,15319452,15319453,15319454,15319455,15319456,15319457,15319458,15319459,15319460,15319461,15319462,15319463,15319464,15319465,15319466,15319467,15319468,15319469,15319470,15319471,15319472,15319473,15319474,15319475,15319476,15319477,15319478,15319479,15319480,15319481,15319482,15319483,15319484,15319485,15319486,15319487,15319488,15319489,15319490,15319491,15319492,15319493,15319494,15319495,15319496,15319497,15319498,15319499,15319500,15319501,15319502,15319503,15319504,15319505,15319506,15319507,15319508,15319509,15319510,15319511,15319512,15319513,15319514,15319515,15319516,15319517,15319518,15319519,15319520,15319521,15319522,15319523,15319524,15319525,15319526,15319527,15319528,15319529,15319530,15319531,15319532,15319533,15319534,15319535,15319536,15319537,15319538,15319539,15319540,15319541,15319542,15319543,15319544,15319545,15319546,15319547,15319548,15319549,15319550,15319551,15319552,15319553,15319554,15319555,15319556,15319557,15319558,15319559,15319560,15319561,15319562,15319563,15319564,15319565,15319566,15319567,15319568,15319569,15319570,15319571,15319572,15319573,15319574,15319575,15319576,15319577,15319578,15319579,15319580,15319581,15319582,15319583,15319584,15319585,15319586,15319587,15319588,15319589,15319590,15319591,15319592,15319593,15319594,15319595,15319596,15319597,15319598,15319599,15319600,15319601,15319602,15319603,15319604,15319605,15319606,15319607,15319608,15319609,15319610,15319611,15319612,15319613,15319614,15319615,15319616,15319617,15319618,15319619,15319620,15319621,15319622,15319623,15319624,15319625,15319626,15319627,15319628,15319629,15319630,15319631,15319632,15319633,15319634,15319635,15319636,15319637,15319638,15319639,15319640,15319641,15319642,15319643,15319644,15319645,15319646,15319647,15319648,15319649,15319650,15319651,15319652,15319653,15319654,15319655,15319656,15319657,15319658,15319659,15319660,15319661,15319662,15319663,15319664,15319665,15319666,15319667,15319668,15319669,15319670,15319671,15319672,15319673,15319674,15319675,15319676,15319677,15319678,15319679,15319680,15319681,15319682,15319683,15319684,15319685,15319686,15319687,15319688,15319689,15319690,15319691,15319692,15319693,15319694,15319695,15319696,15319697,15319698,15319699,15319700,15319701,15319702,15319703,15319704,15319705,15319706,15319707,15319708,15319709,15319710,15319711,15319712,15319713,15319714,15319715,15319716,15319717,15319718,15319719,15319720,15319721,15319722,15319723,15319724,15319725,15319726,15319727,15319728,15319729,15319730,15319731,15319732,15319733,15319734,15319735,15319736,15319737,15319738,15319739,15319740,15319741,15319742,15319743,15319744,15319745,15319746,15319747,15319748,15319749,15319750,15319751,15319752,15319753,15319754,15319755,15319756,15319757,15319758,15319759,15319760,15319761,15319762,15319763,15319764,15319765,15319766,15319767,15319768,15319769,15319770,15319771,15319772,15319773,15319774,15319775,15319776,15319777,15319778,15319779,15319780,15319781,15319782,15319783,15319784,15319785,15319786,15319787,15319788,15319789,15319790,15319791,15319792,15319793,15319794,15319795,15319796,15319797,15319798,15319799,15319800,15319801,15319802,15319803,15319804,15319805,15319806,15319807,15319808,15319809,15319810,15319811,15319812,15319813,15319814,15319815,15319816,15319817,15319818,15319819,15319820,15319821,15319822,15319823,15319824,15319825,15319826,15319827,15319828,15319829,15319830,15319831,15319832,15319833,15319834,15319835,15319836,15319837,15319838,15319839,15319840,15319841,15319842,15319843,15319844,15319845,15319846,15319847,15319848,15319849,15319850,15319851,15319852,15319853,15319854,15319855,15319856,15319857,15319858,15319859,15319860,15319861,15319862,15319863,15319864,15319865,15319866,15319867,15319868,15319869,15319870,15319871,15319872,15319873,15319874,15319875,15319876,15319877,15319878,15319879,15319880,15319881,15319882,15319883,15319884,15319885,15319886,15319887,15319888,15319889,15319890,15319891,15319892,15319893,15319894,15319895,15319896,15319897,15319898,15319899,15319900,15319901,15319902,15319903,15319904,15319905,15319906,15319907,15319908,15319909,15319910,15319911,15319912,15319913,15319914,15319915,15319916,15319917,15319918,15319919,15319920,15319921,15319922,15319923,15319924,15319925,15319926,15319927,15319928,15319929,15319930,15319931,15319932,15319933,15319934,15319935,15319936,15319937,15319938,15319939,15319940,15319941,15319942,15319943,15319944,15319945,15319946,15319947,15319948,15319949,15319950,15319951,15319952,15319953,15319954,15319955,15319956,15319957,15319958,15319959,15319960,15319961,15319962,15319963,15319964,15319965,15319966,15319967,15319968,15319969,15319970,15319971,15319972,15319973,15319974,15319975,15319976,15319977,15319978,15319979,15319980,15319981,15319982,15319983,15319984,15319985,15319986,15319987,15319988,15319989,15319990,15319991,15319992,15319993,15319994,15319995,15319996,15319997,15319998,15319999,15320000,15320001,15320002,15320003,15320004,15320005,15320006,15320007,15320008,15320009,15320010,15320011,15320012,15320013,15320014,15320015,15320016,15320017,15320018,15320019,15320020,15320021,15320022,15320023,15320024,15320025,15320026,15320027,15320028,15320029,15320030,15320031,15320032,15320033,15320034,15320035,15320036,15320037,15320038,15320039,15320040,15320041,15320042,15320043,15320044,15320045,15320046,15320047,15320048,15320049,15320050,15320051,15320052,15320053,15320054,15320055,15320056,15320057,15320058,15320059,15320060,15320061,15320062,15320063,15320064,15320065,15320066,15320067,15320068,15320069,15320070,15320071,15320072,15320073,15320074,15320075,15320076,15320077,15320078,15320079,15320080,15320081,15320082,15320083,15320084,15320085,15320086,15320087,15320088,15320089,15320090,15320091,15320092,15320093,15320094,15320095,15320096,15320097,15320098,15320099,15320100,15320101,15320102,15320103,15320104,15320105,15320106,15320107,15320108,15320109,15320110,15320111,15320112,15320113,15320114,15320115,15320116,15320117,15320118,15320119,15320120,15320121,15320122,15320123,15320124,15320125,15320126,15320127,15320128,15320129,15320130,15320131,15320132,15320133,15320134,15320135,15320136,15320137,15320138,15320139,15320140,15320141,15320142,15320143,15320144,15320145,15320146,15320147,15320148,15320149,15320150,15320151,15320152,15320153,15320154,15320155,15320156,15320157,15320158,15320159,15320160,15320161,15320162,15320163,15320164,15320165,15320166,15320167,15320168,15320169,15320170,15320171,15320172,15320173,15320174,15320175,15320176,15320177,15320178,15320179,15320180,15320181,15320182,15320183,15320184,15320185,15320186,15320187,15320188,15320189,15320190,15320191,15320192,15320193,15320194,15320195,15320196,15320197,15320198,15320199,15320200,15320201,15320202,15320203,15320204,15320205,15320206,15320207,15320208,15320209,15320210,15320211,15320212,15320213,15320214,15320215,15320216,15320217,15320218,15320219,15320220,15320221,15320222,15320223,15320224,15320225,15320226,15320227,15320228,15320229,15320230,15320231,15320232,15320233,15320234,15320235,15320236,15320237,15320238,15320239,15320240,15320241,15320242,15320243,15320244,15320245,15320246,15320247,15320248,15320249,15320250,15320251,15320252,15320253,15320254,15320255,15320256,15320257,15320258,15320259,15320260,15320261,15320262,15320263,15320264,15320265,15320266,15320267,15320268,15320269,15320270,15320271,15320272,15320273,15320274,15320275,15320276,15320277,15320278,15320279,15320280,15320281,15320282,15320283,15320284,15320285,15320286,15320287,15320288,15320289,15320290,15320291,15320292,15320293,15320294,15320295,15320296,15320297,15320298,15320299,15320300,15320301,15320302,15320303,15320304,15320305,15320306,15320307,15320308,15320309,15320310,15320311,15320312,15320313,15320314,15320315,15320316,15320317,15320318,15320319,15320320,15320321,15320322,15320323,15320324,15320325,15320326,15320327,15320328,15320329,15320330,15320331,15320332,15320333,15320334,15320335,15320336,15320337,15320338,15320339,15320340,15320341,15320342,15320343,15320344,15320345,15320346,15320347,15320348,15320349,15320350,15320351,15320352,15320353,15320354,15320355,15320356,15320357,15320358,15320359,15320360,15320361,15320362,15320363,15320364,15320365,15320366,15320367,15320368,15320369,15320370,15320371,15320372,15320373,15320374,15320375,15320376,15320377,15320378,15320379,15320380,15320381,15320382,15320383,15320384,15320385,15320386,15320387,15320388,15320389,15320390,15320391,15320392,15320393,15320394,15320395,15320396,15320397,15320398,15320399,15320400,15320401,15320402,15320403,15320404,15320405,15320406,15320407,15320408,15320409,15320410,15320411,15320412,15320413,15320414,15320415,15320416,15320417,15320418,15320419,15320420,15320421,15320422,15320423,15320424,15320425,15320426,15320427,15320428,15320429,15320430,15320431,15320432,15320433,15320434,15320435,15320436,15320437,15320438,15320439,15320440,15320441,15320442,15320443,15320444,15320445,15320446,15320447,15320448,15320449,15320450,15320451,15320452,15320453,15320454,15320455,15320456,15320457,15320458,15320459,15320460,15320461,15320462,15320463,15320464,15320465,15320466,15320467,15320468,15320469,15320470,15320471,15320472,15320473,15320474,15320475,15320476,15320477,15320478,15320479,15320480,15320481,15320482,15320483,15320484,15320485,15320486,15320487,15320488,15320489,15320490,15320491,15320492,15320493,15320494,15320495,15320496,15320497,15320498,15320499,15320500,15320501,15320502,15320503,15320504,15320505,15320506,15320507,15320508,15320509,15320510,15320511,15320512,15320513,15320514,15320515,15320516,15320517,15320518,15320519,15320520,15320521,15320522,15320523,15320524,15320525,15320526,15320527,15320528,15320529,15320530,15320531,15320532,15320533,15320534,15320535,15320536,15320537,15320538,15320539,15320540,15320541,15320542,15320543,15320544,15320545,15320546,15320547,15320548,15320549,15320550,15320551,15320552,15320553,15320554,15320555,15320556,15320557,15320558,15320559,15320560,15320561,15320562,15320563,15320564,15320565,15320566,15320567,15320568,15320569,15320570,15320571,15320572,15320573,15320574,15320575,15320576,15320577,15320578,15320579,15320580,15320581,15320582,15320583,15320584,15320585,15320586,15320587,15320588,15320589,15320590,15320591,15320592,15320593,15320594,15320595,15320596,15320597,15320598,15320599,15320600,15320601,15320602,15320603,15320604,15320605,15320606,15320607,15320608,15320609,15320610,15320611,15320612,15320613,15320614,15320615,15320616,15320617,15320618,15320619,15320620,15320621,15320622,15320623,15320624,15320625,15320626,15320627,15320628,15320629,15320630,15320631,15320632,15320633,15320634,15320635,15320636,15320637,15320638,15320639,15320640,15320641,15320642,15320643,15320644,15320645,15320646,15320647,15320648,15320649,15320650,15320651,15320652,15320653,15320654,15320655,15320656,15320657,15320658,15320659,15320660,15320661,15320662,15320663,15320664,15320665,15320666,15320667,15320668,15320669,15320670,15320671,15320672,15320673,15320674,15320675,15320676,15320677,15320678,15320679,15320680,15320681,15320682,15320683,15320684,15320685,15320686,15320687,15320688,15320689,15320690,15320691,15320692,15320693,15320694,15320695,15320696,15320697,15320698,15320699,15320700,15320701,15320702,15320703,15320704,15320705,15320706,15320707,15320708,15320709,15320710,15320711,15320712,15320713,15320714,15320715,15320716,15320717,15320718,15320719,15320720,15320721,15320722,15320723,15320724,15320725,15320726,15320727,15320728,15320729,15320730,15320731,15320732,15320733,15320734,15320735,15320736,15320737,15320738,15320739,15320740,15320741,15320742,15320743,15320744,15320745,15320746,15320747,15320748,15320749,15320750,15320751,15320752,15320753,15320754,15320755,15320756,15320757,15320758,15320759,15320760,15320761,15320762,15320763,15320764,15320765,15320766,15320767,15320768,15320769,15320770,15320771,15320772,15320773,15320774,15320775,15320776,15320777,15320778,15320779,15320780,15320781,15320782,15320783,15320784,15320785,15320786,15320787,15320788,15320789,15320790,15320791,15320792,15320793,15320794,15320795,15320796,15320797,15320798,15320799,15320800,15320801,15320802,15320803,15320804,15320805,15320806,15320807,15320808,15320809,15320810,15320811,15320812,15320813,15320814,15320815,15320816,15320817,15320818,15320819,15320820,15320821,15320822,15320823,15320824,15320825,15320826,15320827,15320828,15320829,15320830,15320831,15320832,15320833,15320834,15320835,15320836,15320837,15320838,15320839,15320840,15320841,15320842,15320843,15320844,15320845,15320846,15320847,15320848,15320849,15320850,15320851,15320852,15320853,15320854,15320855,15320856,15320857,15320858,15320859,15320860,15320861,15320862,15320863,15320864,15320865,15320866,15320867,15320868,15320869,15320870,15320871,15320872,15320873,15320874,15320875,15320876,15320877,15320878,15320879,15320880,15320881,15320882,15320883,15320884,15320885,15320886,15320887,15320888,15320889,15320890,15320891,15320892,15320893,15320894,15320895,15320896,15320897,15320898,15320899,15320900,15320901,15320902,15320903,15320904,15320905,15320906,15320907,15320908,15320909,15320910,15320911,15320912,15320913,15320914,15320915,15320916,15320917,15320918,15320919,15320920,15320921,15320922,15320923,15320924,15320925,15320926,15320927,15320928,15320929,15320930,15320931,15320932,15320933,15320934,15320935,15320936,15320937,15320938,15320939,15320940,15320941,15320942,15320943,15320944,15320945,15320946,15320947,15320948,15320949,15320950,15320951,15320952,15320953,15320954,15320955,15320956,15320957,15320958,15320959,15320960,15320961,15320962,15320963,15320964,15320965,15320966,15320967,15320968,15320969,15320970,15320971,15320972,15320973,15320974,15320975,15320976,15320977,15320978,15320979,15320980,15320981,15320982,15320983,15320984,15320985,15320986,15320987,15320988,15320989,15320990,15320991,15320992,15320993,15320994,15320995,15320996,15320997,15320998,15320999,15321000,15321001,15321002,15321003,15321004,15321005,15321006,15321007,15321008,15321009,15321010,15321011,15321012,15321013,15321014,15321015,15321016,15321017,15321018,15321019,15321020,15321021,15321022,15321023,15321024,15321025,15321026,15321027,15321028,15321029,15321030,15321031,15321032,15321033,15321034,15321035,15321036,15321037,15321038,15321039,15321040,15321041,15321042,15321043,15321044,15321045,15321046,15321047,15321048,15321049,15321050,15321051,15321052,15321053,15321054,15321055,15321056,15321057,15321058,15321059,15321060,15321061,15321062,15321063,15321064,15321065,15321066,15321067,15321068,15321069,15321070,15321071,15321072,15321073,15321074,15321075,15321076,15321077,15321078,15321079,15321080,15321081,15321082,15321083,15321084,15321085,15321086,15321087,15321088,15321089,15321090,15321091,15321092,15321093,15321094,15321095,15321096,15321097,15321098,15321099,15321100,15321101,15321102,15321103,15321104,15321105,15321106,15321107,15321108,15321109,15321110,15321111,15321112,15321113,15321114,15321115,15321116,15321117,15321118,15321119,15321120,15321121,15321122,15321123,15321124,15321125,15321126,15321127,15321128,15321129,15321130,15321131,15321132,15321133,15321134,15321135,15321136,15321137,15321138,15321139,15321140,15321141,15321142,15321143,15321144,15321145,15321146,15321147,15321148,15321149,15321150,15321151,15321152,15321153,15321154,15321155,15321156,15321157,15321158,15321159,15321160,15321161,15321162,15321163,15321164,15321165,15321166,15321167,15321168,15321169,15321170,15321171,15321172,15321173,15321174,15321175,15321176,15321177,15321178,15321179,15321180,15321181,15321182,15321183,15321184,15321185,15321186,15321187,15321188,15321189,15321190,15321191,15321192,15321193,15321194,15321195,15321196,15321197,15321198,15321199,15321200,15321201,15321202,15321203,15321204,15321205,15321206,15321207,15321208,15321209,15321210,15321211,15321212,15321213,15321214,15321215,15321216,15321217,15321218,15321219,15321220,15321221,15321222,15321223,15321224,15321225,15321226,15321227,15321228,15321229,15321230,15321231,15321232,15321233,15321234,15321235,15321236,15321237,15321238,15321239,15321240,15321241,15321242,15321243,15321244,15321245,15321246,15321247,15321248,15321249,15321250,15321251,15321252,15321253,15321254,15321255,15321256,15321257,15321258,15321259,15321260,15321261,15321262,15321263,15321264,15321265,15321266,15321267,15321268,15321269,15321270,15321271,15321272,15321273,15321274,15321275,15321276,15321277,15321278,15321279,15321280,15321281,15321282,15321283,15321284,15321285,15321286,15321287,15321288,15321289,15321290,15321291,15321292,15321293,15321294,15321295,15321296,15321297,15321298,15321299,15321300,15321301,15321302,15321303,15321304,15321305,15321306,15321307,15321308,15321309,15321310,15321311,15321312,15321313,15321314,15321315,15321316,15321317,15321318,15321319,15321320,15321321,15321322,15321323,15321324,15321325,15321326,15321327,15321328,15321329,15321330,15321331,15321332,15321333,15321334,15321335,15321336,15321337,15321338,15321339,15321340,15321341,15321342,15321343,15321344,15321345,15321346,15321347,15321348,15321349,15321350,15321351,15321352,15321353,15321354,15321355,15321356,15321357,15321358,15321359,15321360,15321361,15321362,15321363,15321364,15321365,15321366,15321367,15321368,15321369,15321370,15321371,15321372,15321373,15321374,15321375,15321376,15321377,15321378,15321379,15321380,15321381,15321382,15321383,15321384,15321385,15321386,15321387,15321388,15321389,15321390,15321391,15321392,15321393,15321394,15321395,15321396,15321397,15321398,15321399,15321400,15321401,15321402,15321403,15321404,15321405,15321406,15321407,15321408,15321409,15321410,15321411,15321412,15321413,15321414,15321415,15321416,15321417,15321418,15321419,15321420,15321421,15321422,15321423,15321424,15321425,15321426,15321427,15321428,15321429,15321430,15321431,15321432,15321433,15321434,15321435,15321436,15321437,15321438,15321439,15321440,15321441,15321442,15321443,15321444,15321445,15321446,15321447,15321448,15321449,15321450,15321451,15321452,15321453,15321454,15321455,15321456,15321457,15321458,15321459,15321460,15321461,15321462,15321463,15321464,15321465,15321466,15321467,15321468,15321469,15321470,15321471,15321472,15321473,15321474,15321475,15321476,15321477,15321478,15321479,15321480,15321481,15321482,15321483,15321484,15321485,15321486,15321487,15321488,15321489,15321490,15321491,15321492,15321493,15321494,15321495,15321496,15321497,15321498,15321499,15321500,15321501,15321502,15321503,15321504,15321505,15321506,15321507,15321508,15321509,15321510,15321511,15321512,15321513,15321514,15321515,15321516,15321517,15321518,15321519,15321520,15321521,15321522,15321523,15321524,15321525,15321526,15321527,15321528,15321529,15321530,15321531,15321532,15321533,15321534,15321535,15321536,15321537,15321538,15321539,15321540,15321541,15321542,15321543,15321544,15321545,15321546,15321547,15321548,15321549,15321550,15321551,15321552,15321553,15321554,15321555,15321556,15321557,15321558,15321559,15321560,15321561,15321562,15321563,15321564,15321565,15321566,15321567,15321568,15321569,15321570,15321571,15321572,15321573,15321574,15321575,15321576,15321577,15321578,15321579,15321580,15321581,15321582,15321583,15321584,15321585,15321586,15321587,15321588,15321589,15321590,15321591,15321592,15321593,15321594,15321595,15321596,15321597,15321598,15321599,15321600,15321601,15321602,15321603,15321604,15321605,15321606,15321607,15321608,15321609,15321610,15321611,15321612,15321613,15321614,15321615,15321616,15321617,15321618,15321619,15321620,15321621,15321622,15321623,15321624,15321625,15321626,15321627,15321628,15321629,15321630,15321631,15321632,15321633,15321634,15321635,15321636,15321637,15321638,15321639,15321640,15321641,15321642,15321643,15321644,15321645,15321646,15321647,15321648,15321649,15321650,15321651,15321652,15321653,15321654,15321655,15321656,15321657,15321658,15321659,15321660,15321661,15321662,15321663,15321664,15321665,15321666,15321667,15321668,15321669,15321670,15321671,15321672,15321673,15321674,15321675,15321676,15321677,15321678,15321679,15321680,15321681,15321682,15321683,15321684,15321685,15321686,15321687,15321688,15321689,15321690,15321691,15321692,15321693,15321694,15321695,15321696,15321697,15321698,15321699,15321700,15321701,15321702,15321703,15321704,15321705,15321706,15321707,15321708,15321709,15321710,15321711,15321712,15321713,15321714,15321715,15321716,15321717,15321718,15321719,15321720,15321721,15321722,15321723,15321724,15321725,15321726,15321727,15321728,15321729,15321730,15321731,15321732,15321733,15321734,15321735,15321736,15321737,15321738,15321739,15321740,15321741,15321742,15321743,15321744,15321745,15321746,15321747,15321748,15321749,15321750,15321751,15321752,15321753,15321754,15321755,15321756,15321757,15321758,15321759,15321760,15321761,15321762,15321763,15321764,15321765,15321766,15321767,15321768,15321769,15321770,15321771,15321772,15321773,15321774,15321775,15321776,15321777,15321778,15321779,15321780,15321781,15321781,15321781,15324654,15324655,15324656,15324657,15324658,15324659,15324660,15324661,15324662,15324663,15324664,15324665,15324666,15324667,15324668,15324669,15324670,15324671,15324672,15324673,15324674,15324675,15324676,15324677,15324678,15324679,15324680,15324681,15324682,15324683,15324684,15324685,15324686,15324687,15324688,15324689,15324690,15324691,15324692,15324693,15324694,15324695,15324696,15324697,15324698,15324699,15324700,15324701,15324702,15324703,15324704,15324705,15324706,15324707,15324708,15324709,15324710,15324711,15324712,15324713,15324714,15324715,15324716,15324717,15324718,15324719,15324720,15324721,15324722,15324723,15324724,15324725,15324726,15324727,15324728,15324729,15324730,15324731,15324732,15324733,15324734,15324735,15324736,15324737,15324738,15324739,15324740,15324741,15324742,15324743,15324744,15324745,15324746,15324747,15324748,15324749,15324750,15324751,15324752,15324753,15324754,15324755,15324756,15324757,15324758,15324759,15324760,15324761,15324762,15324763,15324764,15324765,15324766,15324767,15324768,15324769,15324770,15324771,15324772,15324773,15324774,15324775,15324776,15324777,15324778,15324779,15324780,15324781,15324782,15324783,15324784,15324785,15324786,15324787,15324788,15324789,15324790,15324791,15324792,15324793,15324794,15324795,15324796,15324797,15324798,15324799,15324800,15324801,15324802,15324803,15324804,15324805,15324806,15324807,15324808,15324809,15324810,15324811,15324812,15324813,15324814,15324815,15324816,15324817,15324818,15324819,15324820,15324821,15324822,15324823,15324824,15324825,15324826,15324827,15324828,15324829,15324830,15324831,15324832,15324833,15324834,15324835,15324836,15324837,15324838,15324839,15324840,15324841,15324842,15324843,15324844,15324845,15324846,15324847,15324848,15324849,15324850,15324851,15324852,15324853,15324854,15324855,15324856,15324857,15324858,15324859,15324860,15324861,15324862,15324863,15324864,15324865,15324866,15324867,15324868,15324869,15324870,15324871,15324872,15324873,15324874,15324875,15324876,15324877,15324878,15324879,15324880,15324880,15324880,15325009,15325010,15325011,15325012,15325013,15325014,15325015,15325016,15325017,15325018,15325019,15325020,15325021,15325022,15325023,15325024,15325025,15325026,15325027,15325028,15325029,15325030,15325031,15325032,15325033,15325034,15325035,15325036,15325037,15325038,15325039,15325040,15325041,15325042,15325043,15325044,15325045,15325046,15325047,15325048,15325049,15325050,15325051,15325052,15325053,15325054,15325055,15325056,15325057,15325058,15325059,15325060,15325061,15325062,15325063,15325064,15325065,15325066,15325067,15325068,15325069,15325070,15325071,15325072,15325073,15325074,15325075,15325076,15325077,15325078,15325079,15325080,15325081,15325082,15325083,15325084,15325085,15325086,15325087,15325088,15325089,15325090,15325091,15325092,15325093,15325094,15325095,15325096,15325097,15325098,15325099,15325100,15325101,15325102,15325103,15325104,15325105,15325106,15325107,15325108,15325109,15325110,15325111,15325112,15325113,15325114,15325115,15325116,15325117,15325118,15325119,15325120,15325121,15325122,15325123,15325124,15325125,15325126,15325127,15325128,15325129,15325130,15325131,15325132,15325133,15325134,15325135,15325136,15325137,15325138,15325139,15325140,15325141,15325142,15325143,15325144,15325145,15325146,15325147,15325148,15325149,15325150,15325151,15325152,15325153,15325154,15325155,15325156,15325157,15325158,15325159,15325160,15325161,15325161,15325161,15325903,15325904,15325905,15325906,15325907,15325908,15325909,15325910,15325911,15325912,15325913,15325914,15325915,15325916,15325917,15325918,15325919,15325920,15325921,15325922,15325923,15325924,15325925,15325926,15325927,15325928,15325929,15325930,15325931,15325932,15325933,15325934,15325935,15325936,15325937,15325938,15325939,15325940,15325941,15325942,15325943,15325944,15325945,15325946,15325947,15325948,15325949,15325950,15325951,15325952,15325953,15325954,15325955,15325956,15325957,15325958,15325959,15325960,15325961,15325962,15325963,15325964,15325965,15325966,15325967,15325968,15325969,15325970,15325971,15325972,15325973,15325974,15325975,15325976,15325977,15325978,15325979,15325980,15325981,15325982,15325983,15325984,15325985,15325986,15325987,15325988,15325989,15325990,15325991,15325992,15325993,15325994,15325995,15325996,15325997,15325998,15325999,15326000,15326001,15326002,15326003,15326004,15326005,15326006,15326007,15326008,15326009,15326010,15326011,15326012,15326013,15326014,15326015,15326016,15326017,15326018,15326019,15326020,15326021,15326022,15326023,15326024,15326025,15326026,15326027,15326028,15326029,15326030,15326031,15326032,15326033,15326034,15326035,15326036,15326037,15326038,15326039,15326040,15326041,15326042,15326043,15326044,15326045,15326046,15326047,15326048,15326049,15326050,15326051,15326052,15326053,15326054,15326055,15326055,15326055,15331205,15331206,15331207,15331208,15331209,15331210,15331211,15331212,15331213,15331214,15331215,15331216,15331217,15331218,15331219,15331220,15331221,15331222,15331223,15331224,15331225,15331226,15331227,15331228,15331229,15331230,15331231,15331232,15331233,15331234,15331235,15331236,15331237,15331238,15331239,15331240,15331241,15331242,15331243,15331244,15331245,15331246,15331247,15331248,15331249,15331250,15331251,15331252,15331253,15331254,15331255,15331256,15331257,15331258,15331259,15331260,15331261,15331262,15331263,15331264,15331265,15331266,15331267,15331268,15331269,15331270,15331271,15331272,15331273,15331274,15331275,15331276,15331277,15331278,15331279,15331280,15331281,15331282,15331283,15331284,15331285,15331286,15331287,15331288,15331289,15331290,15331291,15331292,15331293,15331294,15331295,15331296,15331297,15331298,15331299,15331300,15331301,15331302,15331303,15331304,15331305,15331306,15331307,15331308,15331309,15331310,15331311,15331312,15331313,15331314,15331315,15331316,15331317,15331318,15331319,15331320,15331321,15331322,15331323,15331324,15331325,15331326,15331327,15331328,15331329,15331330,15331331,15331332,15331333,15331334,15331335,15331336,15331337,15331338,15331339,15331340,15331341,15331342,15331343,15331344,15331345,15331346,15331347,15331348,15331349,15331350,15331351,15331352,15331353,15331354,15331355,15331356,15331357,15331358,15331359,15331360,15331361,15331362,15331363,15331364,15331365,15331366,15331367,15331368,15331369,15331370,15331371,15331372,15331373,15331374,15331375,15331376,15331377,15331378,15331379,15331380,15331381,15331382,15331383,15331384,15331385,15331386,15331387,15331388,15331389,15331390,15331391,15331392,15331393,15331394,15331395,15331396,15331397,15331398,15331399,15331400,15331401,15331402,15331403,15331404,15331405,15331406,15331407,15331408,15331409,15331410,15331411,15331412,15331413,15331414,15331415,15331416,15331417,15331418,15331419,15331420,15331421,15331422,15331423,15331424,15331425,15331426,15331427,15331428,15331429,15331430,15331431,15331432,15331433,15331434,15331435,15331436,15331437,15331438,15331439,15331440,15331441,15331442,15331443,15331444,15331445,15331446,15331447,15331448,15331449,15331450,15331451,15331452,15331453,15331454,15331455,15331456,15331457,15331458,15331459,15331460,15331461,15331462,15331463,15331464,15331465,15331466,15331467,15331468,15331469,15331470,15331471,15331472,15331473,15331474,15331475,15331476,15331477,15331478,15331479,15331480,15331481,15331482,15331483,15331484,15331485,15331486,15331487,15331488,15331489,15331490,15331491,15331492,15331493,15331494,15331495,15331496,15331497,15331498,15331499,15331500,15331501,15331502,15331503,15331504,15331505,15331506,15331507,15331508,15331509,15331510,15331511,15331512,15331513,15331514,15331515,15331516,15331517,15331518,15331519,15331520,15331521,15331522,15331523,15331524,15331525,15331526,15331527,15331528,15331529,15331530,15331531,15331532,15331533,15331534,15331535,15331536,15331537,15331538,15331539,15331540,15331541,15331542,15331543,15331544,15331545,15331546,15331547,15331548,15331549,15331550,15331551,15331552,15331553,15331554,15331555,15331556,15331557,15331558,15331559,15331560,15331561,15331562,15331563,15331564,15331565,15331566,15331567,15331568,15331569,15331570,15331571,15331572,15331573,15331574,15331575,15331576,15331577,15331578,15331579,15331580,15331581,15331582,15331583,15331584,15331585,15331586,15331587,15331588,15331589,15331590,15331591,15331592,15331593,15331594,15331595,15331596,15331597,15331598,15331599,15331600,15331601,15331602,15331603,15331604,15331605,15331606,15331607,15331608,15331609,15331610,15331611,15331612,15331613,15331614,15331615,15331616,15331617,15331618,15331619,15331620,15331621,15331622,15331623,15331624,15331625,15331626,15331627,15331628,15331629,15331630,15331631,15331632,15331633,15331634,15331635,15331636,15331637,15331638,15331639,15331640,15331641,15331642,15331643,15331644,15331645,15331646,15331647,15331648,15331649,15331650,15331651,15331652,15331653,15331654,15331655,15331656,15331657,15331658,15331659,15331660,15331661,15331662,15331663,15331664,15331665,15331666,15331667,15331668,15331669,15331670,15331671,15331672,15331673,15331674,15331675,15331676,15331677,15331678,15331679,15331680,15331681,15331682,15331683,15331684,15331685,15331686,15331687,15331688,15331689,15331690,15331691,15331692,15331693,15331694,15331695,15331696,15331697,15331698,15331699,15331700,15331701,15331702,15331703,15331704,15331705,15331706,15331707,15331708,15331709,15331710,15331711,15331712,15331713,15331714,15331715,15331716,15331717,15331718,15331719,15331720,15331721,15331722,15331723,15331724,15331725,15331726,15331727,15331728,15331729,15331730,15331731,15331732,15331733,15331734,15331735,15331736,15331737,15331738,15331739,15331740,15331741,15331742,15331743,15331744,15331745,15331746,15331747,15331748,15331749,15331750,15331751,15331752,15331753,15331754,15331755,15331756,15331757,15331758,15331759,15331760,15331761,15331762,15331763,15331764,15331765,15331766,15331767,15331768,15331769,15331770,15331771,15331772,15331773,15331774,15331775,15331776,15331777,15331778,15331779,15331780,15331781,15331782,15331783,15331784,15331785,15331786,15331787,15331788,15331789,15331790,15331791,15331792,15331793,15331794,15331795,15331796,15331797,15331798,15331799,15331800,15331801,15331802,15331803,15331804,15331805,15331806,15331807,15331808,15331809,15331810,15331811,15331812,15331813,15331814,15331815,15331816,15331817,15331818,15331819,15331820,15331821,15331822,15331823,15331824,15331825,15331826,15331827,15331828,15331829,15331830,15331831,15331832,15331833,15331834,15331835,15331836,15331837,15331838,15331839,15331840,15331841,15331842,15331843,15331844,15331845,15331846,15331847,15331848,15331849,15331850,15331851,15331852,15331853,15331854,15331855,15331856,15331857,15331858,15331859,15331860,15331861,15331862,15331863,15331864,15331865,15331866,15331867,15331868,15331869,15331870,15331871,15331872,15331873,15331874,15331875,15331876,15331877,15331878,15331879,15331880,15331881,15331882,15331883,15331884,15331885,15331886,15331887,15331888,15331889,15331890,15331891,15331892,15331893,15331894,15331895,15331896,15331897,15331898,15331899,15331900,15331901,15331902,15331903,15331904,15331905,15331906,15331907,15331908,15331909,15331910,15331911,15331912,15331913,15331914,15331915,15331916,15331917,15331918,15331919,15331920,15331921,15331922,15331923,15331924,15331925,15331926,15331927,15331928,15331929,15331930,15331931,15331932,15331933,15331934,15331935,15331936,15331937,15331938,15331939,15331940,15331941,15331942,15331943,15331944,15331945,15331946,15331947,15331948,15331949,15331950,15331951,15331952,15331953,15331954,15331955,15331956,15331957,15331958,15331959,15331960,15331961,15331962,15331963,15331964,15331965,15331966,15331967,15331968,15331969,15331970,15331971,15331972,15331973,15331974,15331975,15331976,15331977,15331978,15331979,15331980,15331981,15331982,15331983,15331984,15331985,15331986,15331987,15331988,15331989,15331990,15331991,15331992,15331993,15331994,15331995,15331996,15331997,15331998,15331999,15332000,15332001,15332001,15332001,15335490,15335491,15335492,15335493,15335494,15335495,15335496,15335497,15335498,15335499,15335500,15335501,15335502,15335503,15335504,15335505,15335506,15335507,15335508,15335509,15335510,15335511,15335512,15335513,15335514,15335515,15335516,15335517,15335518,15335519,15335520,15335521,15335522,15335523,15335524,15335525,15335526,15335527,15335528,15335529,15335530,15335531,15335532,15335533,15335534,15335535,15335536,15335537,15335538,15335539,15335540,15335541,15335542,15335543,15335544,15335545,15335546,15335547,15335548,15335549,15335550,15335551,15335552,15335553,15335554,15335555,15335556,15335557,15335558,15335559,15335560,15335561,15335562,15335563] 6 | --------------------------------------------------------------------------------