ModDotPlot 0.9.1__tar.gz → 0.9.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {moddotplot-0.9.1 → moddotplot-0.9.5}/PKG-INFO +5 -3
- {moddotplot-0.9.1 → moddotplot-0.9.5}/README.md +47 -29
- {moddotplot-0.9.1 → moddotplot-0.9.5}/pyproject.toml +4 -3
- {moddotplot-0.9.1 → moddotplot-0.9.5}/src/ModDotPlot.egg-info/PKG-INFO +5 -3
- {moddotplot-0.9.1 → moddotplot-0.9.5}/src/ModDotPlot.egg-info/requires.txt +2 -1
- {moddotplot-0.9.1 → moddotplot-0.9.5}/src/moddotplot/const.py +1 -1
- {moddotplot-0.9.1 → moddotplot-0.9.5}/src/moddotplot/estimate_identity.py +6 -6
- {moddotplot-0.9.1 → moddotplot-0.9.5}/src/moddotplot/moddotplot.py +86 -51
- {moddotplot-0.9.1 → moddotplot-0.9.5}/src/moddotplot/parse_fasta.py +18 -0
- {moddotplot-0.9.1 → moddotplot-0.9.5}/src/moddotplot/static_plots.py +305 -54
- {moddotplot-0.9.1 → moddotplot-0.9.5}/LICENSE +0 -0
- {moddotplot-0.9.1 → moddotplot-0.9.5}/setup.cfg +0 -0
- {moddotplot-0.9.1 → moddotplot-0.9.5}/src/ModDotPlot.egg-info/SOURCES.txt +0 -0
- {moddotplot-0.9.1 → moddotplot-0.9.5}/src/ModDotPlot.egg-info/dependency_links.txt +0 -0
- {moddotplot-0.9.1 → moddotplot-0.9.5}/src/ModDotPlot.egg-info/entry_points.txt +0 -0
- {moddotplot-0.9.1 → moddotplot-0.9.5}/src/ModDotPlot.egg-info/top_level.txt +0 -0
- {moddotplot-0.9.1 → moddotplot-0.9.5}/src/moddotplot/__init__.py +0 -0
- {moddotplot-0.9.1 → moddotplot-0.9.5}/src/moddotplot/__main__.py +0 -0
- {moddotplot-0.9.1 → moddotplot-0.9.5}/src/moddotplot/interactive.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: ModDotPlot
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.5
|
|
4
4
|
Author: Michael Schatz, Adam Phillippy
|
|
5
5
|
Author-email: Alex Sweeten <alex.sweeten@nih.gov>
|
|
6
6
|
Maintainer-email: Alex Sweeten <alex.sweeten@nih.gov>
|
|
@@ -37,12 +37,14 @@ Requires-Dist: dash
|
|
|
37
37
|
Requires-Dist: plotnine==0.12.4
|
|
38
38
|
Requires-Dist: palettable
|
|
39
39
|
Requires-Dist: mmh3
|
|
40
|
-
Requires-Dist: tk
|
|
41
40
|
Requires-Dist: setproctitle
|
|
42
41
|
Requires-Dist: numpy
|
|
43
42
|
Requires-Dist: pillow
|
|
44
43
|
Requires-Dist: patchworklib==0.6.3
|
|
45
44
|
Requires-Dist: cairosvg
|
|
45
|
+
Requires-Dist: pygenometracks
|
|
46
|
+
Requires-Dist: svgutils
|
|
46
47
|
Provides-Extra: test
|
|
47
48
|
Requires-Dist: pytest; extra == "test"
|
|
48
49
|
Requires-Dist: pytest-cov; extra == "test"
|
|
50
|
+
Dynamic: license-file
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|

|
|
2
|
+
---
|
|
3
|
+
[](https://pypi.org/project/ModDotPlot/)
|
|
4
|
+
[](https://github.com/marbl/ModDotPlot/actions/workflows/black.yml)
|
|
2
5
|
|
|
3
6
|
- [Cite](#cite)
|
|
4
7
|
- [About](#about)
|
|
@@ -19,7 +22,7 @@
|
|
|
19
22
|
|
|
20
23
|
## Cite
|
|
21
24
|
|
|
22
|
-
Alexander P Sweeten, Michael C Schatz, Adam M Phillippy, ModDotPlot—rapid and interactive visualization of tandem repeats, Bioinformatics, Volume 40, Issue 8, August 2024, btae493, [https://doi.org/10.1093/bioinformatics/btae493](https://
|
|
25
|
+
Alexander P Sweeten, Michael C Schatz, Adam M Phillippy, ModDotPlot—rapid and interactive visualization of tandem repeats, Bioinformatics, Volume 40, Issue 8, August 2024, btae493, [https://doi.org/10.1093/bioinformatics/btae493](https://doi.org/10.1093/bioinformatics/btae493)
|
|
23
26
|
|
|
24
27
|
If you use ModDotPlot for your research, please cite our software!
|
|
25
28
|
|
|
@@ -27,7 +30,7 @@ If you use ModDotPlot for your research, please cite our software!
|
|
|
27
30
|
|
|
28
31
|
## About
|
|
29
32
|
|
|
30
|
-
|
|
33
|
+
_ModDotPlot_ is a dot plot visualization tool designed for large sequences and whole genomes. _ModDotPlot_ outputs an identity heatmap similar to [StainedGlass](https://mrvollger.github.io/StainedGlass/) by rapidly approximating the Average Nucleotide Identity between pairwise combinations of genomic intervals. This significantly reduces the computational time required to produce these plots, enough to view multiple layers of resolution in real time!
|
|
31
34
|
|
|
32
35
|

|
|
33
36
|
|
|
@@ -35,12 +38,14 @@ ModDotPlot is a dot plot visualization tool designed for large sequences and who
|
|
|
35
38
|
|
|
36
39
|
## Installation
|
|
37
40
|
|
|
41
|
+
_ModDotPlot_ can be installed by running `pip install moddotplot`. It requires Python 3.7+ to run. Alternatively, you can download the current release from GitHub by using:
|
|
42
|
+
|
|
38
43
|
```
|
|
39
44
|
git clone https://github.com/marbl/ModDotPlot.git
|
|
40
45
|
cd ModDotPlot
|
|
41
46
|
```
|
|
42
47
|
|
|
43
|
-
Although optional, it's recommended to setup a virtual environment before using
|
|
48
|
+
Although optional, it's recommended to setup a virtual environment before using _ModDotPlot_:
|
|
44
49
|
|
|
45
50
|
```
|
|
46
51
|
python -m venv venv
|
|
@@ -53,7 +58,7 @@ Once activated, you can install the required dependencies:
|
|
|
53
58
|
python -m pip install .
|
|
54
59
|
```
|
|
55
60
|
|
|
56
|
-
Finally, confirm that the installation was installed correctly by running `moddotplot -h`:
|
|
61
|
+
Finally, confirm that the installation was installed correctly and that your version is up to date by running `moddotplot -h`:
|
|
57
62
|
```
|
|
58
63
|
__ __ _ _____ _ _____ _ _
|
|
59
64
|
| \/ | | | | __ \ | | | __ \| | | |
|
|
@@ -62,6 +67,8 @@ Finally, confirm that the installation was installed correctly by running `moddo
|
|
|
62
67
|
| | | | (_) | (_| | | |__| | (_) | |_ | | | | (_) | |_
|
|
63
68
|
|_| |_|\___/ \__,_| |_____/ \___/ \__| |_| |_|\___/ \__|
|
|
64
69
|
|
|
70
|
+
v0.9.4
|
|
71
|
+
|
|
65
72
|
usage: moddotplot [-h] {interactive,static} ...
|
|
66
73
|
|
|
67
74
|
ModDotPlot: Visualization of Complex Repeat Structures
|
|
@@ -78,7 +85,7 @@ options:
|
|
|
78
85
|
|
|
79
86
|
## Usage
|
|
80
87
|
|
|
81
|
-
|
|
88
|
+
_ModDotPlot_ must be run either in `interactive` mode, or `static` mode:
|
|
82
89
|
|
|
83
90
|
### Interactive Mode
|
|
84
91
|
|
|
@@ -94,13 +101,15 @@ This will launch a [Dash application](https://plotly.com/dash/) on your machine'
|
|
|
94
101
|
moddotplot static <ARGS>
|
|
95
102
|
```
|
|
96
103
|
|
|
97
|
-
|
|
104
|
+
Running _ModDotPlot_ in static mode skips running Dash and quickly creates plots under the specified output directory `-o`. By default, running _ModDotPlot_ in static mode this will produce the following files:
|
|
98
105
|
|
|
99
|
-
- A paired-end bed file
|
|
100
|
-
- A self-identity dotplot for each sequence.
|
|
106
|
+
- A paired-end bed file `.bedpe`, containing intervals alongside their corresponding identity estimates.
|
|
107
|
+
- A self-identity dotplot for each sequence, as both an upper triangle matrix `_TRI` and full matrix `_FULL` representation.
|
|
101
108
|
- A histogram of identity values for each sequence.
|
|
102
|
-
|
|
103
|
-
|
|
109
|
+
|
|
110
|
+
All plots and histograms are output in a vectorized (default: `.svg`) and rasterized `.png` image. [Plotnine](https://plotnine.readthedocs.io/en/v0.12.4/) is the Python plotting library used, with [CairoSVG](https://cairosvg.org) used for converting between image formats.
|
|
111
|
+
|
|
112
|
+
_ModDotPlot_ supports highly customizable plotting features in static mode. See [static mode commands](#static-mode-commands) for a complete list of features.
|
|
104
113
|
|
|
105
114
|
---
|
|
106
115
|
|
|
@@ -112,6 +121,10 @@ The following arguments are the same in both interactive and static mode:
|
|
|
112
121
|
|
|
113
122
|
Fasta files to input. Multifasta files are accepted. Interactive mode will only support a maximum of two sequences at a time.
|
|
114
123
|
|
|
124
|
+
`-b / --bed <.bed file>`
|
|
125
|
+
|
|
126
|
+
Input bedfile used for dotplot annotation (note: this is not the same as the paired-end bed file produced by ModDotPlot). If selected, this will produce an annotated bedtrack image `_ANNOTATION.svg`. Name in the bedfile must match the name of the fasta sequence header (or input, if `-l` is used instead) in order to produce a correct bed track.
|
|
127
|
+
|
|
115
128
|
`-k / --kmer <int>`
|
|
116
129
|
|
|
117
130
|
K-mer size to use. This should be large enough to distinguish unique k-mers with enough specificity, but not too large that sensitivity is removed. Default: 21.
|
|
@@ -184,15 +197,15 @@ Load previously saved matrices. Used instead of `-f/--fasta`
|
|
|
184
197
|
|
|
185
198
|
Run `moddotplot static` with a config file, rather than (sample syntax). Recommended when creating a really customized plot. Used instead of `-f/--fasta`.
|
|
186
199
|
|
|
187
|
-
`-
|
|
200
|
+
`-l / --load <.bedpe file>`
|
|
188
201
|
|
|
189
|
-
Create a plot from a previously computed pairwise bed file. Skips Average Nucleotide Identity computation. Used instead of `-f/--fasta`.
|
|
202
|
+
Create a plot from a previously computed pairwise bed file. Skips Average Nucleotide Identity computation. Used instead of `-f/--fasta`. Will only accept paired-end bed files produced by ModDotPlot.
|
|
190
203
|
|
|
191
204
|
`-w / --window <int>`
|
|
192
205
|
|
|
193
206
|
Window size. Unlike interactive mode, only one matrix will be created, so this represents the *only* window size. Default is set to `n/1000` (eg. 3000bp for a 3Mbp sequence).
|
|
194
207
|
|
|
195
|
-
`--no-
|
|
208
|
+
`--no-bedpe <bool>`
|
|
196
209
|
|
|
197
210
|
Skip output of bed file.
|
|
198
211
|
|
|
@@ -204,9 +217,17 @@ Skip output of histogram legend.
|
|
|
204
217
|
|
|
205
218
|
Adjust width of self dot plots. Default is 9 inches.
|
|
206
219
|
|
|
207
|
-
`--dpi <
|
|
220
|
+
`--dpi <int>`
|
|
208
221
|
|
|
209
|
-
Image resolution in dots per inch (not to be confused with dotplot resolution). Default is
|
|
222
|
+
Image resolution in dots per inch (not to be confused with dotplot resolution). Default is `300`.
|
|
223
|
+
|
|
224
|
+
`--vector <str>`
|
|
225
|
+
|
|
226
|
+
Vectorized image format to output to. Must be one of ["svg", "pdf", "ps"]. Default: `svg`
|
|
227
|
+
|
|
228
|
+
`--deraster <bool>`
|
|
229
|
+
|
|
230
|
+
By default, vectorized ouptuts rasterize the actual plot (not the axis). This is done to save space, as a high-resolution dotplot can be extremely space inefficient and prevent use of image manipulation software. This plot rasterization can be removed using this flag.
|
|
210
231
|
|
|
211
232
|
`--palette <str>`
|
|
212
233
|
|
|
@@ -226,11 +247,11 @@ Add custom identity threshold breakpoints. Note that the number of breakpoints m
|
|
|
226
247
|
|
|
227
248
|
`-t / --axes-ticks <list of ints>`
|
|
228
249
|
|
|
229
|
-
Custom tickmarks for x and y axis. Values outside of the axes-limits will not be shown.
|
|
250
|
+
Custom tickmarks for x and y axis. Values outside of the `--axes-limits` will not be shown.
|
|
230
251
|
|
|
231
252
|
`-a / --axes-limits <int>`
|
|
232
253
|
|
|
233
|
-
Change axis limits for x and y axis. Useful
|
|
254
|
+
Change axis limits for x and y axis. Useful when comparing multiple plots, allowing them to stay in scale.
|
|
234
255
|
|
|
235
256
|
`--bin-freq <bool>`
|
|
236
257
|
|
|
@@ -308,7 +329,6 @@ $ cat config/config.json
|
|
|
308
329
|
|
|
309
330
|
{
|
|
310
331
|
"identity": 90,
|
|
311
|
-
"sparsity": 10,
|
|
312
332
|
"palette": "OrRd_7",
|
|
313
333
|
"breakpoints": [
|
|
314
334
|
90,
|
|
@@ -338,13 +358,13 @@ $ moddotplot static -c config/config.json
|
|
|
338
358
|
|
|
339
359
|
Running ModDotPlot in static mode
|
|
340
360
|
|
|
341
|
-
Retrieving k-mers from Chr1:
|
|
361
|
+
Retrieving k-mers from Chr1:14000000-18000000....
|
|
342
362
|
|
|
343
363
|
Progress: |████████████████████████████████████████| 100.0% Completed
|
|
344
364
|
|
|
345
|
-
Chr1:
|
|
365
|
+
Chr1:14000000-18000000 k-mers retrieved!
|
|
346
366
|
|
|
347
|
-
Computing self identity matrix for chr1:
|
|
367
|
+
Computing self identity matrix for chr1:14000000-18000000...
|
|
348
368
|
|
|
349
369
|
Sequence length n: 4000000
|
|
350
370
|
|
|
@@ -357,13 +377,13 @@ Computing self identity matrix for chr1:14M-18M...
|
|
|
357
377
|
Progress: |████████████████████████████████████████| 100.0% Completed
|
|
358
378
|
|
|
359
379
|
|
|
360
|
-
Saved bed file to Chr1_cen_plots/Chr1:
|
|
380
|
+
Saved bed file to Chr1_cen_plots/Chr1:14000000-18000000.bed
|
|
361
381
|
|
|
362
|
-
Plots created! Saving to Chr1_cen_plots/Chr1:
|
|
382
|
+
Plots created! Saving to Chr1_cen_plots/Chr1:14000000-18000000...
|
|
363
383
|
|
|
364
384
|
Chr1_cen_plots/Chr1:14M-18M_TRI.png, Chr1_cen_plots/Chr1:14M-18M_TRI.pdf, Chr1_cen_plots/Chr1:14M-18M_FULL.png, Chr1_cen_plots/Chr1:14M-18M_FULL.png, Chr1_cen_plots/Chr1:14M-18M_HIST.png and Chr1_cen_plots/Chr1:14M-18M_HIST.pdf, saved sucessfully.
|
|
365
385
|
```
|
|
366
|
-

|
|
367
387
|
|
|
368
388
|
---
|
|
369
389
|
|
|
@@ -376,6 +396,8 @@ ModDotPlot can produce an a vs. b style dotplot for each pairwise combination of
|
|
|
376
396
|
moddotplot interactive -f sequences/chr15_segment.fa sequences/chr21_segment.fa --compare-only
|
|
377
397
|
```
|
|
378
398
|
|
|
399
|
+

|
|
400
|
+
|
|
379
401
|
---
|
|
380
402
|
|
|
381
403
|
## Questions
|
|
@@ -386,10 +408,6 @@ For bug reports or general usage questions, please raise a GitHub issue, or emai
|
|
|
386
408
|
|
|
387
409
|
## Known Issues
|
|
388
410
|
|
|
389
|
-
-
|
|
390
|
-
|
|
391
411
|
- Mac users might encounter the following unexpected command line output: `/bin/sh: lscpu: command not found`. This is a known issue with Plotnine, the Python plotting library used by ModDotPlot. This can be safely ignored.
|
|
392
412
|
|
|
393
|
-
- If you encounter an error with the following traceback: `rv = reductor(4) TypeError: cannot pickle 'generator' object`,
|
|
394
|
-
|
|
395
|
-
- There are a number of errors
|
|
413
|
+
- If you encounter an error with the following traceback: `rv = reductor(4) TypeError: cannot pickle 'generator' object`, ths means that you have a newer version of Plotnine that is incompatible with ModDotPlot. Please uninstall plotnine and reinstall version 0.12.4 `pip install plotnine==0.12.4`.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ModDotPlot"
|
|
7
|
-
version = "0.9.
|
|
7
|
+
version = "0.9.5"
|
|
8
8
|
requires-python = ">= 3.7"
|
|
9
9
|
dependencies = [
|
|
10
10
|
"pysam",
|
|
@@ -14,12 +14,13 @@ dependencies = [
|
|
|
14
14
|
"plotnine==0.12.4",
|
|
15
15
|
"palettable",
|
|
16
16
|
"mmh3",
|
|
17
|
-
"tk",
|
|
18
17
|
"setproctitle",
|
|
19
18
|
"numpy",
|
|
20
19
|
"pillow",
|
|
21
20
|
"patchworklib==0.6.3",
|
|
22
|
-
"cairosvg"
|
|
21
|
+
"cairosvg",
|
|
22
|
+
"pygenometracks",
|
|
23
|
+
"svgutils",
|
|
23
24
|
]
|
|
24
25
|
authors = [
|
|
25
26
|
{name = "Alex Sweeten", email = "alex.sweeten@nih.gov"},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: ModDotPlot
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.5
|
|
4
4
|
Author: Michael Schatz, Adam Phillippy
|
|
5
5
|
Author-email: Alex Sweeten <alex.sweeten@nih.gov>
|
|
6
6
|
Maintainer-email: Alex Sweeten <alex.sweeten@nih.gov>
|
|
@@ -37,12 +37,14 @@ Requires-Dist: dash
|
|
|
37
37
|
Requires-Dist: plotnine==0.12.4
|
|
38
38
|
Requires-Dist: palettable
|
|
39
39
|
Requires-Dist: mmh3
|
|
40
|
-
Requires-Dist: tk
|
|
41
40
|
Requires-Dist: setproctitle
|
|
42
41
|
Requires-Dist: numpy
|
|
43
42
|
Requires-Dist: pillow
|
|
44
43
|
Requires-Dist: patchworklib==0.6.3
|
|
45
44
|
Requires-Dist: cairosvg
|
|
45
|
+
Requires-Dist: pygenometracks
|
|
46
|
+
Requires-Dist: svgutils
|
|
46
47
|
Provides-Extra: test
|
|
47
48
|
Requires-Dist: pytest; extra == "test"
|
|
48
49
|
Requires-Dist: pytest-cov; extra == "test"
|
|
50
|
+
Dynamic: license-file
|
|
@@ -128,7 +128,7 @@ def partitionOverlaps(
|
|
|
128
128
|
try:
|
|
129
129
|
kmer_list.append(lst[delta_start_index:delta_end_index])
|
|
130
130
|
except Exception as e:
|
|
131
|
-
print("
|
|
131
|
+
print("Error in appending list of kmers...\n")
|
|
132
132
|
print(e)
|
|
133
133
|
kmer_list.append(lst[delta_start_index:seq_len])
|
|
134
134
|
counter += win
|
|
@@ -167,7 +167,7 @@ def convertToModimizers(
|
|
|
167
167
|
|
|
168
168
|
|
|
169
169
|
def convertMatrixToBed(
|
|
170
|
-
matrix, window_size, id_threshold, x_name, y_name, self_identity
|
|
170
|
+
matrix, window_size, id_threshold, x_name, y_name, self_identity, x_offset, y_offset
|
|
171
171
|
):
|
|
172
172
|
bed = [
|
|
173
173
|
(
|
|
@@ -187,10 +187,10 @@ def convertMatrixToBed(
|
|
|
187
187
|
value = matrix[x, y]
|
|
188
188
|
if (not self_identity) or (self_identity and x <= y):
|
|
189
189
|
if value >= id_threshold / 100:
|
|
190
|
-
start_x = x * window_size +
|
|
191
|
-
end_x =
|
|
192
|
-
start_y = y * window_size +
|
|
193
|
-
end_y =
|
|
190
|
+
start_x = x * window_size + x_offset
|
|
191
|
+
end_x = start_x + window_size - 1
|
|
192
|
+
start_y = y * window_size + y_offset
|
|
193
|
+
end_y = start_y + window_size - 1
|
|
194
194
|
|
|
195
195
|
bed.append(
|
|
196
196
|
(
|
|
@@ -5,6 +5,7 @@ from moddotplot.parse_fasta import (
|
|
|
5
5
|
getInputHeaders,
|
|
6
6
|
isValidFasta,
|
|
7
7
|
extractFiles,
|
|
8
|
+
extractRegion,
|
|
8
9
|
)
|
|
9
10
|
|
|
10
11
|
from moddotplot.estimate_identity import (
|
|
@@ -35,7 +36,7 @@ def get_parser():
|
|
|
35
36
|
"""
|
|
36
37
|
parser = argparse.ArgumentParser(
|
|
37
38
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
38
|
-
description="ModDotPlot: Visualization of
|
|
39
|
+
description="ModDotPlot: Visualization of Tandem Repeats",
|
|
39
40
|
)
|
|
40
41
|
subparsers = parser.add_subparsers(
|
|
41
42
|
dest="command", help="Choose mode: interactive or static"
|
|
@@ -176,8 +177,8 @@ def get_parser():
|
|
|
176
177
|
)
|
|
177
178
|
|
|
178
179
|
static_input_group.add_argument(
|
|
179
|
-
"-
|
|
180
|
-
"--
|
|
180
|
+
"-l",
|
|
181
|
+
"--load",
|
|
181
182
|
default=argparse.SUPPRESS,
|
|
182
183
|
help="Path to input paired-end bed file(s). Exclusively used in static mode.",
|
|
183
184
|
nargs="+",
|
|
@@ -197,6 +198,8 @@ def get_parser():
|
|
|
197
198
|
required=False
|
|
198
199
|
)
|
|
199
200
|
|
|
201
|
+
static_parser.add_argument("-b", "--bed", default=None, help="Bed file annotation.")
|
|
202
|
+
|
|
200
203
|
static_parser.add_argument(
|
|
201
204
|
"-k", "--kmer", default=21, type=int, help="k-mer length."
|
|
202
205
|
)
|
|
@@ -261,7 +264,14 @@ def get_parser():
|
|
|
261
264
|
)
|
|
262
265
|
|
|
263
266
|
static_parser.add_argument(
|
|
264
|
-
"--
|
|
267
|
+
"--compare-order",
|
|
268
|
+
choices=["sequential", "size"],
|
|
269
|
+
default="sequential",
|
|
270
|
+
help="Order in which sequences appear in the comparative plot. Default is 'sequential': First file on x-axis, second file on y-axis. Another option is 'size': The larger sequence on the x-axis and the smaller on y-axis.",
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
static_parser.add_argument(
|
|
274
|
+
"--no-bedpe", action="store_true", help="Skip output of paired-end bed file."
|
|
265
275
|
)
|
|
266
276
|
|
|
267
277
|
static_parser.add_argument(
|
|
@@ -273,10 +283,10 @@ def get_parser():
|
|
|
273
283
|
)
|
|
274
284
|
|
|
275
285
|
static_parser.add_argument(
|
|
276
|
-
"--width", default=
|
|
286
|
+
"--width", default=9, type=float, help="Plot width (also height for _FULL)."
|
|
277
287
|
)
|
|
278
288
|
|
|
279
|
-
static_parser.add_argument("--dpi", default=
|
|
289
|
+
static_parser.add_argument("--dpi", default=300, type=int, help="Plot dpi.")
|
|
280
290
|
|
|
281
291
|
# TODO: Create list of accepted colors.
|
|
282
292
|
static_parser.add_argument(
|
|
@@ -423,6 +433,7 @@ def main():
|
|
|
423
433
|
config = json.load(f)
|
|
424
434
|
# TODO: Remove args that are interactive only
|
|
425
435
|
args.fasta = config.get("fasta")
|
|
436
|
+
args.load = config.get("load")
|
|
426
437
|
args.bed = config.get("bed")
|
|
427
438
|
|
|
428
439
|
# Distance matrix commands
|
|
@@ -436,7 +447,7 @@ def main():
|
|
|
436
447
|
args.compare = config.get("compare", args.compare)
|
|
437
448
|
args.compare_only = config.get("compare_only", args.compare_only)
|
|
438
449
|
|
|
439
|
-
args.
|
|
450
|
+
args.no_bedpe = config.get("no_bedpe", args.no_bedpe)
|
|
440
451
|
args.no_plot = config.get("no_plot", args.no_plot)
|
|
441
452
|
args.no_hist = config.get("no_hist", args.no_hist)
|
|
442
453
|
args.width = config.get("width", args.width)
|
|
@@ -450,18 +461,13 @@ def main():
|
|
|
450
461
|
args.axes_ticks = config.get("axes_ticks", args.axes_ticks)
|
|
451
462
|
args.breakpoints = config.get("breakpoints", args.breakpoints)
|
|
452
463
|
args.bin_freq = config.get("bin_freq", args.bin_freq)
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
print(
|
|
458
|
-
f"Option --grid was selected, but no comparative plots will be produced. Please rerun ModDotPlot with the `--compare` or `--compare-only` option.\n"
|
|
459
|
-
)
|
|
460
|
-
sys.exit(10)"""
|
|
464
|
+
args.axes_limits = config.get("axes_limits", args.axes_limits)
|
|
465
|
+
args.axes_ticks = config.get("axes_ticks", args.axes_ticks)
|
|
466
|
+
args.vector = config.get("vector", args.vector)
|
|
467
|
+
args.deraster = config.get("deraster", args.deraster)
|
|
461
468
|
|
|
462
469
|
# -----------INPUT COMMAND VALIDATION-----------
|
|
463
470
|
# TODO: More tests!
|
|
464
|
-
# Validation for breakpoints command
|
|
465
471
|
if args.breakpoints:
|
|
466
472
|
# Check that start value for breakpoints = identity threshold value
|
|
467
473
|
if float(args.breakpoints[0]) != float(args.identity):
|
|
@@ -476,15 +482,15 @@ def main():
|
|
|
476
482
|
sys.exit(2)
|
|
477
483
|
|
|
478
484
|
# -----------BEDFILE INPUT FOR STATIC MODE-----------
|
|
479
|
-
if hasattr(args, "
|
|
485
|
+
if hasattr(args, "load") and args.load:
|
|
480
486
|
if args.grid or args.grid_only:
|
|
481
487
|
single_vals = []
|
|
482
488
|
double_vals = []
|
|
483
489
|
single_val_name = []
|
|
484
490
|
double_val_name = []
|
|
485
491
|
xlim_val_grid = 0
|
|
486
|
-
for bed in args.
|
|
487
|
-
# If args.
|
|
492
|
+
for bed in args.load:
|
|
493
|
+
# If args.load is provided as input, run static mode directly from the paired-end bed file. Skip counting input k-mers.
|
|
488
494
|
df = read_df_from_file(bed)
|
|
489
495
|
|
|
490
496
|
unique_query_names = df["#query_name"].unique()
|
|
@@ -522,6 +528,7 @@ def main():
|
|
|
522
528
|
axes_tick_number=args.axes_number,
|
|
523
529
|
vector_format=args.vector,
|
|
524
530
|
deraster=args.deraster,
|
|
531
|
+
annotation=args.bed,
|
|
525
532
|
)
|
|
526
533
|
if args.grid or args.grid_only:
|
|
527
534
|
single_vals.append(df)
|
|
@@ -559,6 +566,7 @@ def main():
|
|
|
559
566
|
axes_tick_number=args.axes_number,
|
|
560
567
|
vector_format=args.vector,
|
|
561
568
|
deraster=args.deraster,
|
|
569
|
+
annotation=args.bed,
|
|
562
570
|
)
|
|
563
571
|
if args.grid or args.grid_only:
|
|
564
572
|
double_vals.append(df)
|
|
@@ -906,7 +914,11 @@ def main():
|
|
|
906
914
|
if args.grid or args.grid_only:
|
|
907
915
|
grid_val_singles = []
|
|
908
916
|
grid_val_single_names = []
|
|
909
|
-
|
|
917
|
+
new_sequences = list(zip(seq_list, k_list))
|
|
918
|
+
if args.compare_order == "size":
|
|
919
|
+
sequences = sorted(new_sequences, key=lambda seq: len(seq[1]), reverse=True)
|
|
920
|
+
else:
|
|
921
|
+
sequences = new_sequences
|
|
910
922
|
if len(sequences) > 6 and (args.grid or args.grid_only):
|
|
911
923
|
print("Too many sequences to create a grid. Skipping. \n")
|
|
912
924
|
|
|
@@ -917,6 +929,12 @@ def main():
|
|
|
917
929
|
if not args.compare_only:
|
|
918
930
|
for i in range(len(sequences)):
|
|
919
931
|
seq_length = len(sequences[i][1])
|
|
932
|
+
seq_name = sequences[i][0]
|
|
933
|
+
seq_range = extractRegion(seq_name)
|
|
934
|
+
if not seq_range:
|
|
935
|
+
seq_start_pos = 1
|
|
936
|
+
else:
|
|
937
|
+
seq_start_pos = int(seq_range[1])
|
|
920
938
|
win = args.window
|
|
921
939
|
res = args.resolution
|
|
922
940
|
if args.window:
|
|
@@ -941,10 +959,10 @@ def main():
|
|
|
941
959
|
seq_sparsity = 2 ** (int(math.log2(seq_sparsity - 1)) + 1)
|
|
942
960
|
expectation = round(win / seq_sparsity)
|
|
943
961
|
|
|
944
|
-
print(f"Computing self identity matrix for {
|
|
962
|
+
print(f"Computing self identity matrix for {seq_name}... \n")
|
|
945
963
|
# TODO: Logging here
|
|
946
964
|
# print(f"\tSparsity value s: {seq_sparsity}\n")
|
|
947
|
-
print(f"\tSequence length n: {
|
|
965
|
+
print(f"\tSequence length n: {seq_length + args.kmer - 1}\n")
|
|
948
966
|
print(f"\tWindow size w: {win}\n")
|
|
949
967
|
print(f"\tModimizer sketch size: {expectation}\n")
|
|
950
968
|
print(f"\tPlot Resolution r: {res}\n")
|
|
@@ -960,31 +978,40 @@ def main():
|
|
|
960
978
|
expectation,
|
|
961
979
|
)
|
|
962
980
|
bed = convertMatrixToBed(
|
|
963
|
-
self_mat,
|
|
981
|
+
self_mat,
|
|
982
|
+
win,
|
|
983
|
+
args.identity,
|
|
984
|
+
seq_name,
|
|
985
|
+
seq_name,
|
|
986
|
+
True,
|
|
987
|
+
seq_start_pos,
|
|
988
|
+
seq_start_pos,
|
|
964
989
|
)
|
|
965
990
|
if args.grid or args.grid_only:
|
|
966
991
|
grid_val_singles.append(bed)
|
|
967
|
-
grid_val_single_names.append(
|
|
992
|
+
grid_val_single_names.append(seq_name)
|
|
968
993
|
|
|
969
|
-
if not args.
|
|
994
|
+
if not args.no_bedpe:
|
|
970
995
|
# Log saving bed file
|
|
971
996
|
if not args.output_dir:
|
|
972
|
-
bedfile_output =
|
|
997
|
+
bedfile_output = seq_name + ".bedpe"
|
|
973
998
|
else:
|
|
974
999
|
bedfile_output = os.path.join(
|
|
975
|
-
args.output_dir,
|
|
1000
|
+
args.output_dir, seq_name + ".bedpe"
|
|
976
1001
|
)
|
|
977
1002
|
with open(bedfile_output, "w") as bedfile:
|
|
978
1003
|
for row in bed:
|
|
979
1004
|
bedfile.write("\t".join(map(str, row)) + "\n")
|
|
980
|
-
print(
|
|
1005
|
+
print(
|
|
1006
|
+
f"Saved self-identity matrix as a paired-end bed file to {bedfile_output}\n"
|
|
1007
|
+
)
|
|
981
1008
|
|
|
982
1009
|
if (not args.no_plot) and (not args.grid_only):
|
|
983
1010
|
create_plots(
|
|
984
1011
|
sdf=[bed],
|
|
985
1012
|
directory=args.output_dir if args.output_dir else ".",
|
|
986
|
-
name_x=
|
|
987
|
-
name_y=
|
|
1013
|
+
name_x=seq_name,
|
|
1014
|
+
name_y=seq_name,
|
|
988
1015
|
palette=args.palette,
|
|
989
1016
|
palette_orientation=args.palette_orientation,
|
|
990
1017
|
no_hist=args.no_hist,
|
|
@@ -1000,6 +1027,7 @@ def main():
|
|
|
1000
1027
|
axes_tick_number=args.axes_number,
|
|
1001
1028
|
vector_format=args.vector,
|
|
1002
1029
|
deraster=args.deraster,
|
|
1030
|
+
annotation=args.bed,
|
|
1003
1031
|
)
|
|
1004
1032
|
|
|
1005
1033
|
# -----------COMPUTE COMPARATIVE PLOTS-----------
|
|
@@ -1016,21 +1044,23 @@ def main():
|
|
|
1016
1044
|
|
|
1017
1045
|
for i in range(len(sequences)):
|
|
1018
1046
|
for j in range(i + 1, len(sequences)):
|
|
1047
|
+
# Larger = x, smaller = y. This is pre-sorted earlier.
|
|
1019
1048
|
larger_seq = sequences[i][1]
|
|
1020
1049
|
smaller_seq = sequences[j][1]
|
|
1021
1050
|
larger_length = len(larger_seq)
|
|
1022
1051
|
smaller_length = len(smaller_seq)
|
|
1023
1052
|
larger_seq_name = sequences[i][0]
|
|
1024
1053
|
smaller_seq_name = sequences[j][0]
|
|
1025
|
-
|
|
1026
|
-
if
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1054
|
+
larger_seq_range = extractRegion(larger_seq_name)
|
|
1055
|
+
if not larger_seq_range:
|
|
1056
|
+
larger_seq_start_pos = 1
|
|
1057
|
+
else:
|
|
1058
|
+
larger_seq_start_pos = int(larger_seq_range[1])
|
|
1059
|
+
smaller_seq_range = extractRegion(smaller_seq_name)
|
|
1060
|
+
if not larger_seq_range:
|
|
1061
|
+
smaller_seq_start_pos = 1
|
|
1062
|
+
else:
|
|
1063
|
+
smaller_seq_start_pos = int(smaller_seq_range[1])
|
|
1034
1064
|
win = args.window
|
|
1035
1065
|
res = args.resolution
|
|
1036
1066
|
if args.window:
|
|
@@ -1062,10 +1092,10 @@ def main():
|
|
|
1062
1092
|
print(f"\tPlot Resolution r: {res}\n")
|
|
1063
1093
|
|
|
1064
1094
|
pair_mat = createPairwiseMatrix(
|
|
1065
|
-
larger_length,
|
|
1066
1095
|
smaller_length,
|
|
1067
|
-
|
|
1096
|
+
larger_length,
|
|
1068
1097
|
smaller_seq,
|
|
1098
|
+
larger_seq,
|
|
1069
1099
|
win,
|
|
1070
1100
|
seq_sparsity,
|
|
1071
1101
|
args.delta,
|
|
@@ -1087,6 +1117,8 @@ def main():
|
|
|
1087
1117
|
seq_list[i],
|
|
1088
1118
|
seq_list[j],
|
|
1089
1119
|
False,
|
|
1120
|
+
larger_seq_start_pos,
|
|
1121
|
+
smaller_seq_start_pos,
|
|
1090
1122
|
)
|
|
1091
1123
|
if args.grid or args.grid_only:
|
|
1092
1124
|
grid_val_doubles.append(bed)
|
|
@@ -1094,34 +1126,36 @@ def main():
|
|
|
1094
1126
|
[larger_seq_name, smaller_seq_name]
|
|
1095
1127
|
)
|
|
1096
1128
|
xlim_val_grid = max(larger_length, xlim_val_grid)
|
|
1097
|
-
if not args.
|
|
1129
|
+
if not args.no_bedpe:
|
|
1098
1130
|
# Log saving bed file
|
|
1099
1131
|
if not args.output_dir:
|
|
1100
1132
|
bedfile_output = (
|
|
1101
|
-
|
|
1133
|
+
larger_seq_name
|
|
1102
1134
|
+ "_"
|
|
1103
|
-
+
|
|
1104
|
-
+ "_COMPARE.
|
|
1135
|
+
+ smaller_seq_name
|
|
1136
|
+
+ "_COMPARE.bedpe"
|
|
1105
1137
|
)
|
|
1106
1138
|
else:
|
|
1107
1139
|
bedfile_output = os.path.join(
|
|
1108
1140
|
args.output_dir,
|
|
1109
|
-
|
|
1141
|
+
larger_seq_name
|
|
1110
1142
|
+ "_"
|
|
1111
|
-
+
|
|
1112
|
-
+ "_COMPARE.
|
|
1143
|
+
+ smaller_seq_name
|
|
1144
|
+
+ "_COMPARE.bedpe",
|
|
1113
1145
|
)
|
|
1114
1146
|
with open(bedfile_output, "w") as bedfile:
|
|
1115
1147
|
for row in bed:
|
|
1116
1148
|
bedfile.write("\t".join(map(str, row)) + "\n")
|
|
1117
|
-
print(
|
|
1149
|
+
print(
|
|
1150
|
+
f"Saved comparative matrix as a paired-end bed file to {bedfile_output}\n"
|
|
1151
|
+
)
|
|
1118
1152
|
|
|
1119
1153
|
if (not args.no_plot) and (not args.grid_only):
|
|
1120
1154
|
create_plots(
|
|
1121
1155
|
sdf=[bed],
|
|
1122
1156
|
directory=args.output_dir if args.output_dir else ".",
|
|
1123
|
-
name_x=
|
|
1124
|
-
name_y=
|
|
1157
|
+
name_x=larger_seq_name,
|
|
1158
|
+
name_y=smaller_seq_name,
|
|
1125
1159
|
palette=args.palette,
|
|
1126
1160
|
palette_orientation=args.palette_orientation,
|
|
1127
1161
|
no_hist=args.no_hist,
|
|
@@ -1137,6 +1171,7 @@ def main():
|
|
|
1137
1171
|
axes_tick_number=args.axes_number,
|
|
1138
1172
|
vector_format=args.vector,
|
|
1139
1173
|
deraster=args.deraster,
|
|
1174
|
+
annotation=args.bed,
|
|
1140
1175
|
)
|
|
1141
1176
|
|
|
1142
1177
|
if args.grid or args.grid_only:
|
|
@@ -12,6 +12,24 @@ import gzip
|
|
|
12
12
|
tab_b = bytes.maketrans(b"ACTG", b"TGAC")
|
|
13
13
|
|
|
14
14
|
|
|
15
|
+
def extractRegion(seq_name):
|
|
16
|
+
"""Check if seq_name contains a region and extract the bounds."""
|
|
17
|
+
# Define the regular expression to match the format "chrY:50-3000"
|
|
18
|
+
region_pattern = r"([a-zA-Z0-9]+):(\d+)-(\d+)"
|
|
19
|
+
|
|
20
|
+
match = re.match(region_pattern, seq_name)
|
|
21
|
+
if match:
|
|
22
|
+
# Extract chromosome, lower bound, and upper bound
|
|
23
|
+
chrom = match.group(1)
|
|
24
|
+
lower_bound = int(match.group(2))
|
|
25
|
+
upper_bound = int(match.group(3))
|
|
26
|
+
|
|
27
|
+
return chrom, lower_bound, upper_bound
|
|
28
|
+
else:
|
|
29
|
+
# No region found
|
|
30
|
+
return None
|
|
31
|
+
|
|
32
|
+
|
|
15
33
|
def generateKmersFromFasta(seq: Sequence[str], k: int, quiet: bool) -> Iterable[int]:
|
|
16
34
|
n = len(seq)
|
|
17
35
|
if not quiet:
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
from ast import excepthandler
|
|
2
1
|
from plotnine import (
|
|
3
2
|
ggsave,
|
|
4
3
|
ggplot,
|
|
@@ -28,6 +27,7 @@ from plotnine import (
|
|
|
28
27
|
theme_minimal,
|
|
29
28
|
geom_raster,
|
|
30
29
|
)
|
|
30
|
+
import svgutils.transform as sg
|
|
31
31
|
import cairosvg
|
|
32
32
|
import pandas as pd
|
|
33
33
|
import numpy as np
|
|
@@ -39,7 +39,9 @@ import xml.etree.ElementTree as ET
|
|
|
39
39
|
import sys
|
|
40
40
|
import re
|
|
41
41
|
from moddotplot.parse_fasta import printProgressBar
|
|
42
|
-
|
|
42
|
+
from lxml import etree
|
|
43
|
+
from pygenometracks.utilities import get_region
|
|
44
|
+
import matplotlib.pyplot as plt
|
|
43
45
|
from moddotplot.const import (
|
|
44
46
|
DIVERGING_PALETTES,
|
|
45
47
|
QUALITATIVE_PALETTES,
|
|
@@ -47,6 +49,18 @@ from moddotplot.const import (
|
|
|
47
49
|
)
|
|
48
50
|
from typing import List
|
|
49
51
|
from palettable.colorbrewer import qualitative, sequential, diverging
|
|
52
|
+
import logging
|
|
53
|
+
|
|
54
|
+
# Set log level BEFORE importing pygenometracks
|
|
55
|
+
for name in logging.root.manager.loggerDict:
|
|
56
|
+
if name.startswith("pygenometracks"):
|
|
57
|
+
logging.getLogger(name).setLevel(logging.CRITICAL)
|
|
58
|
+
logging.getLogger(name).propagate = False # Don't pass to root logger
|
|
59
|
+
|
|
60
|
+
# Also make sure the root logger isn’t outputting debug messages
|
|
61
|
+
logging.basicConfig(level=logging.CRITICAL)
|
|
62
|
+
|
|
63
|
+
from pygenometracks.tracksClass import PlotTracks
|
|
50
64
|
|
|
51
65
|
|
|
52
66
|
def is_plot_empty(p):
|
|
@@ -75,6 +89,93 @@ def check_pascal(single_val, double_val):
|
|
|
75
89
|
sys.exit(8)
|
|
76
90
|
|
|
77
91
|
|
|
92
|
+
def generate_ini_file(
|
|
93
|
+
bedfile, ininame, chrom, color_value="bed_rgb", x_axis=True, display="collapsed"
|
|
94
|
+
):
|
|
95
|
+
try:
|
|
96
|
+
thing = chrom.split(":")[0]
|
|
97
|
+
sections = [
|
|
98
|
+
"[spacer]",
|
|
99
|
+
"# height of space in cm (optional)",
|
|
100
|
+
"height = 0.5",
|
|
101
|
+
"",
|
|
102
|
+
f"[{thing}]",
|
|
103
|
+
f"file = {bedfile}",
|
|
104
|
+
f"Title=",
|
|
105
|
+
"height = 1",
|
|
106
|
+
f"display = {display}",
|
|
107
|
+
f"color = {color_value}",
|
|
108
|
+
"labels = false",
|
|
109
|
+
"fontsize = 10",
|
|
110
|
+
"file_type = bed",
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
if x_axis:
|
|
114
|
+
sections.insert(0, "[x-axis]")
|
|
115
|
+
|
|
116
|
+
ini_content = "\n".join(sections)
|
|
117
|
+
with open(f"{ininame}.ini", "w") as file:
|
|
118
|
+
file.write(ini_content)
|
|
119
|
+
print(f"Successfully generated {ininame}.ini\n")
|
|
120
|
+
return f"{ininame}.ini"
|
|
121
|
+
except Exception as err:
|
|
122
|
+
print(f"Error producing ini file: {err}\n")
|
|
123
|
+
return None
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def read_annotation_bed(filepath):
|
|
127
|
+
"""Reads a BED file into a Pandas DataFrame and ensures correct formatting."""
|
|
128
|
+
col_names = [
|
|
129
|
+
"chrom",
|
|
130
|
+
"start",
|
|
131
|
+
"end",
|
|
132
|
+
"name",
|
|
133
|
+
"score",
|
|
134
|
+
"strand",
|
|
135
|
+
"thickStart",
|
|
136
|
+
"thickEnd",
|
|
137
|
+
"itemRgb",
|
|
138
|
+
] # Include additional fields
|
|
139
|
+
|
|
140
|
+
df = pd.read_csv(filepath, sep="\t", comment="#", header=None)
|
|
141
|
+
|
|
142
|
+
# Ensure at least three required columns exist
|
|
143
|
+
if df.shape[1] < 3:
|
|
144
|
+
raise ValueError(
|
|
145
|
+
"Invalid BED file: must have at least 3 columns (chrom, start, end)."
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# Rename only the expected columns
|
|
149
|
+
df.columns = col_names[: df.shape[1]]
|
|
150
|
+
|
|
151
|
+
# Ensure start and end columns contain valid integers
|
|
152
|
+
if df["start"].isna().any() or df["end"].isna().any():
|
|
153
|
+
raise ValueError(
|
|
154
|
+
"Invalid BED file: 'start' and 'end' columns must be integers and contain no missing values."
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
return df
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def run_pygenometracks(inifile, region, output_file, width):
|
|
161
|
+
output_dir = os.path.dirname(output_file)
|
|
162
|
+
if output_dir and not os.path.exists(output_dir):
|
|
163
|
+
os.makedirs(output_dir) # Create directory if it doesn't exist
|
|
164
|
+
|
|
165
|
+
trp = PlotTracks(
|
|
166
|
+
inifile,
|
|
167
|
+
width,
|
|
168
|
+
fig_height=None,
|
|
169
|
+
fontsize=10,
|
|
170
|
+
dpi=300,
|
|
171
|
+
track_label_width=0.05,
|
|
172
|
+
plot_regions=region,
|
|
173
|
+
plot_width=width,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
return trp
|
|
177
|
+
|
|
178
|
+
|
|
78
179
|
def reverse_pascal(double_vals):
|
|
79
180
|
if len(double_vals) == 1:
|
|
80
181
|
return 2
|
|
@@ -190,7 +291,7 @@ def get_colors(sdf, ncolors, is_freq, custom_breakpoints):
|
|
|
190
291
|
else:
|
|
191
292
|
breaks = [bot + i * interval for i in range(ncolors + 1)]
|
|
192
293
|
if custom_breakpoints:
|
|
193
|
-
|
|
294
|
+
np.asarray(custom_breakpoints, dtype=np.float64)
|
|
194
295
|
labels = np.arange(len(breaks) - 1)
|
|
195
296
|
# corner case of only one %id value
|
|
196
297
|
if len(breaks) == 1:
|
|
@@ -282,32 +383,35 @@ def read_df(
|
|
|
282
383
|
return df
|
|
283
384
|
|
|
284
385
|
|
|
285
|
-
def generate_breaks(
|
|
386
|
+
def generate_breaks(min_number, max_number, min_breaks=5, max_breaks=9):
|
|
286
387
|
# Determine the order of magnitude
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
388
|
+
difference = max_number - min_number
|
|
389
|
+
|
|
390
|
+
magnitude = 10 ** int(math.floor(math.log10(difference)))
|
|
391
|
+
threshold = math.ceil(difference / magnitude)
|
|
290
392
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
for d in [10, 5, 4, 2, 1]
|
|
295
|
-
if (number // (magnitude // d)) in range(min_breaks, max_breaks + 1)
|
|
296
|
-
]
|
|
393
|
+
while threshold > max_breaks:
|
|
394
|
+
magnitude *= 2
|
|
395
|
+
threshold = math.ceil(difference / magnitude)
|
|
297
396
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
397
|
+
while threshold < min_breaks:
|
|
398
|
+
magnitude /= 2
|
|
399
|
+
threshold = math.ceil(difference / magnitude)
|
|
400
|
+
|
|
401
|
+
# Round down min_number to the nearest multiple of magnitude
|
|
402
|
+
min_aligned = int(min_number // magnitude * magnitude)
|
|
301
403
|
|
|
302
404
|
# Generate breakpoints
|
|
303
|
-
|
|
405
|
+
upper_bound = int(min_aligned + (threshold + 1) * magnitude)
|
|
406
|
+
breaks = list(range(min_aligned, upper_bound, int(magnitude)))
|
|
304
407
|
|
|
305
408
|
return breaks
|
|
306
409
|
|
|
307
410
|
|
|
308
411
|
def make_dot(
|
|
309
412
|
sdf,
|
|
310
|
-
|
|
413
|
+
name_x,
|
|
414
|
+
name_y,
|
|
311
415
|
palette,
|
|
312
416
|
palette_orientation,
|
|
313
417
|
colors,
|
|
@@ -316,7 +420,17 @@ def make_dot(
|
|
|
316
420
|
xlim,
|
|
317
421
|
deraster,
|
|
318
422
|
width,
|
|
423
|
+
is_pairwise,
|
|
319
424
|
):
|
|
425
|
+
if is_pairwise:
|
|
426
|
+
title_name = f"Comparative Plot: {name_x} vs {name_y}"
|
|
427
|
+
else:
|
|
428
|
+
title_name = f"Self-Identity Plot: {name_x}"
|
|
429
|
+
title_length = 2 * width
|
|
430
|
+
if len(title_name) > 50:
|
|
431
|
+
title_length = 1.5 * width
|
|
432
|
+
elif len(title_name) > 80:
|
|
433
|
+
title_length = width
|
|
320
434
|
# Select the color palette
|
|
321
435
|
if hasattr(diverging, palette):
|
|
322
436
|
function_name = getattr(diverging, palette)
|
|
@@ -340,11 +454,12 @@ def make_dot(
|
|
|
340
454
|
if not xlim:
|
|
341
455
|
xlim = 0
|
|
342
456
|
# Determine maximum genomic position for scaling
|
|
457
|
+
min_val = min(sdf["q_st"].min(), sdf["r_st"].min())
|
|
343
458
|
max_val = max(sdf["q_en"].max(), sdf["r_en"].max(), xlim)
|
|
344
459
|
|
|
345
460
|
# If user provides breaks, convert to ints
|
|
346
461
|
if not breaks:
|
|
347
|
-
breaks = generate_breaks(int(max_val))
|
|
462
|
+
breaks = generate_breaks(int(min_val), int(max_val))
|
|
348
463
|
else:
|
|
349
464
|
[int(x) for x in breaks]
|
|
350
465
|
xlim = xlim or 0
|
|
@@ -374,8 +489,10 @@ def make_dot(
|
|
|
374
489
|
axis_ticks_major=element_line(
|
|
375
490
|
size=(width), color="black"
|
|
376
491
|
), # Increased tick length
|
|
377
|
-
title=
|
|
378
|
-
|
|
492
|
+
title=element_text(
|
|
493
|
+
family=["DejaVu Sans"], size=title_length, hjust=0.5
|
|
494
|
+
), # Center title
|
|
495
|
+
axis_title_x=element_text(size=(width * 1.4), family=["DejaVu Sans"]),
|
|
379
496
|
strip_background=element_blank(), # Remove facet strip background
|
|
380
497
|
strip_text=element_text(
|
|
381
498
|
size=(width * 1.2), family=["DejaVu Sans"]
|
|
@@ -388,8 +505,12 @@ def make_dot(
|
|
|
388
505
|
+ scale_color_discrete(guide=False)
|
|
389
506
|
+ scale_fill_manual(values=new_hexcodes, guide=False)
|
|
390
507
|
+ common_theme
|
|
391
|
-
+ scale_x_continuous(
|
|
392
|
-
|
|
508
|
+
+ scale_x_continuous(
|
|
509
|
+
labels=make_scale, limits=[min_val, max_val], breaks=breaks
|
|
510
|
+
)
|
|
511
|
+
+ scale_y_continuous(
|
|
512
|
+
labels=make_scale, limits=[min_val, max_val], breaks=breaks
|
|
513
|
+
)
|
|
393
514
|
+ coord_fixed(ratio=1)
|
|
394
515
|
+ facet_grid("r ~ q")
|
|
395
516
|
+ labs(x=x_label, y="", title=title_name)
|
|
@@ -438,11 +559,12 @@ def make_dot_grid(
|
|
|
438
559
|
if not xlim:
|
|
439
560
|
xlim = 0
|
|
440
561
|
# Determine maximum genomic position for scaling
|
|
562
|
+
min_val = max(sdf["q_st"].min(), sdf["r_st"].min())
|
|
441
563
|
max_val = max(sdf["q_en"].max(), sdf["r_en"].max(), xlim)
|
|
442
564
|
|
|
443
565
|
# If user provides breaks, convert to ints
|
|
444
566
|
if not breaks:
|
|
445
|
-
breaks = generate_breaks(int(max_val))
|
|
567
|
+
breaks = generate_breaks(int(min_val), int(max_val))
|
|
446
568
|
else:
|
|
447
569
|
[int(x) for x in breaks]
|
|
448
570
|
xlim = xlim or 0
|
|
@@ -533,11 +655,12 @@ def make_dot_final(
|
|
|
533
655
|
if not xlim:
|
|
534
656
|
xlim = 0
|
|
535
657
|
# Determine maximum genomic position for scaling
|
|
658
|
+
min_val = min(sdf["q_st"].min(), sdf["r_st"].min())
|
|
536
659
|
max_val = max(sdf["q_en"].max(), sdf["r_en"].max(), xlim)
|
|
537
660
|
|
|
538
661
|
# If user provides breaks, convert to ints
|
|
539
662
|
if not breaks:
|
|
540
|
-
breaks = generate_breaks(int(max_val))
|
|
663
|
+
breaks = generate_breaks(int(min_val), int(max_val))
|
|
541
664
|
else:
|
|
542
665
|
[int(x) for x in breaks]
|
|
543
666
|
xlim = xlim or 0
|
|
@@ -647,11 +770,12 @@ def make_tri(
|
|
|
647
770
|
if not xlim:
|
|
648
771
|
xlim = 0
|
|
649
772
|
# Determine maximum genomic position for scaling
|
|
773
|
+
min_val = max(sdf["q_st"].min(), sdf["r_st"].min())
|
|
650
774
|
max_val = max(sdf["q_en"].max(), sdf["r_en"].max(), xlim)
|
|
651
775
|
|
|
652
776
|
# If user provides breaks, convert to ints
|
|
653
777
|
if not breaks:
|
|
654
|
-
breaks = generate_breaks(int(max_val))
|
|
778
|
+
breaks = generate_breaks(int(min_val), int(max_val))
|
|
655
779
|
else:
|
|
656
780
|
[int(x) for x in breaks]
|
|
657
781
|
xlim = xlim or 0
|
|
@@ -678,8 +802,12 @@ def make_tri(
|
|
|
678
802
|
) # Ensure full opacity
|
|
679
803
|
+ scale_fill_manual(values=new_hexcodes, guide=False)
|
|
680
804
|
+ scale_color_discrete(guide=False)
|
|
681
|
-
+ scale_x_continuous(
|
|
682
|
-
|
|
805
|
+
+ scale_x_continuous(
|
|
806
|
+
labels=make_scale, limits=[min_val, max_val], breaks=breaks
|
|
807
|
+
)
|
|
808
|
+
+ scale_y_continuous(
|
|
809
|
+
labels=make_scale, limits=[min_val, max_val], breaks=breaks
|
|
810
|
+
)
|
|
683
811
|
+ coord_fixed(ratio=1)
|
|
684
812
|
+ labs(x=x_label, y="", title=title_name)
|
|
685
813
|
+ theme(
|
|
@@ -695,7 +823,7 @@ def make_tri(
|
|
|
695
823
|
axis_ticks_major_y=element_blank(),
|
|
696
824
|
axis_ticks_major=element_line(size=(width)),
|
|
697
825
|
title=element_text(size=(width * 1.4), hjust=0.5),
|
|
698
|
-
axis_title_x=element_text(size=(width * 1.
|
|
826
|
+
axis_title_x=element_text(size=(width * 1.4), family=["DejaVu Sans"]),
|
|
699
827
|
axis_text_y=element_blank(),
|
|
700
828
|
)
|
|
701
829
|
)
|
|
@@ -739,8 +867,12 @@ def make_tri(
|
|
|
739
867
|
) # Ensure full opacity
|
|
740
868
|
+ scale_fill_manual(values=new_hexcodes, guide=False)
|
|
741
869
|
+ scale_color_discrete(guide=False)
|
|
742
|
-
+ scale_x_continuous(
|
|
743
|
-
|
|
870
|
+
+ scale_x_continuous(
|
|
871
|
+
labels=make_scale, limits=[min_val, max_val], breaks=breaks
|
|
872
|
+
)
|
|
873
|
+
+ scale_y_continuous(
|
|
874
|
+
labels=make_scale, limits=[min_val, max_val], breaks=breaks
|
|
875
|
+
)
|
|
744
876
|
+ coord_fixed(ratio=1)
|
|
745
877
|
+ labs(x=x_label, y="", title=title_name)
|
|
746
878
|
+ theme(
|
|
@@ -768,8 +900,12 @@ def make_tri(
|
|
|
768
900
|
)
|
|
769
901
|
+ scale_color_discrete(guide=False)
|
|
770
902
|
+ scale_fill_manual(values=new_hexcodes, guide=False)
|
|
771
|
-
+ scale_x_continuous(
|
|
772
|
-
|
|
903
|
+
+ scale_x_continuous(
|
|
904
|
+
labels=make_scale, limits=[min_val, max_val], breaks=breaks
|
|
905
|
+
)
|
|
906
|
+
+ scale_y_continuous(
|
|
907
|
+
labels=make_scale, limits=[min_val, max_val], breaks=breaks
|
|
908
|
+
)
|
|
773
909
|
+ coord_fixed(ratio=1)
|
|
774
910
|
+ labs(x="", y="", title="")
|
|
775
911
|
+ theme(
|
|
@@ -888,6 +1024,81 @@ def rotate_rasterized_tri(svg_path, shift_x, shift_y):
|
|
|
888
1024
|
tree.write(svg_path)
|
|
889
1025
|
|
|
890
1026
|
|
|
1027
|
+
from lxml import etree
|
|
1028
|
+
|
|
1029
|
+
|
|
1030
|
+
def append_svg(svg1_path, svg2_path, output_path):
|
|
1031
|
+
"""Appends SVG2 to the bottom of SVG1 without modifying its width."""
|
|
1032
|
+
# Load SVG1 and SVG2
|
|
1033
|
+
tree1 = etree.parse(svg1_path)
|
|
1034
|
+
root1 = tree1.getroot()
|
|
1035
|
+
|
|
1036
|
+
tree2 = etree.parse(svg2_path)
|
|
1037
|
+
root2 = tree2.getroot()
|
|
1038
|
+
|
|
1039
|
+
# Extract width and height of SVG1
|
|
1040
|
+
width1 = float(root1.get("width", "0").replace("pt", ""))
|
|
1041
|
+
height1 = float(root1.get("height", "0").replace("pt", ""))
|
|
1042
|
+
|
|
1043
|
+
# Extract width and height of SVG2
|
|
1044
|
+
width2 = float(root2.get("width", "0").replace("pt", ""))
|
|
1045
|
+
height2 = float(root2.get("height", "0").replace("pt", ""))
|
|
1046
|
+
|
|
1047
|
+
# Update SVG1 height to accommodate SVG2
|
|
1048
|
+
new_height = height1 + height2
|
|
1049
|
+
root1.set("height", f"{new_height}pt")
|
|
1050
|
+
|
|
1051
|
+
# Create a translation group for SVG2 and shift it down
|
|
1052
|
+
group = etree.Element("g", attrib={"transform": f"translate(0,{height1 + 400})"})
|
|
1053
|
+
for child in root2:
|
|
1054
|
+
group.append(child)
|
|
1055
|
+
|
|
1056
|
+
# Append translated SVG2 to SVG1
|
|
1057
|
+
root1.append(group)
|
|
1058
|
+
|
|
1059
|
+
# Save the new merged SVG
|
|
1060
|
+
tree1.write(output_path, pretty_print=True, xml_declaration=True, encoding="utf-8")
|
|
1061
|
+
|
|
1062
|
+
|
|
1063
|
+
def get_svg_size(svg_path):
|
|
1064
|
+
"""Returns the width and height of an SVG file."""
|
|
1065
|
+
fig = sg.fromfile(svg_path)
|
|
1066
|
+
return fig.get_size()
|
|
1067
|
+
|
|
1068
|
+
|
|
1069
|
+
def merge_svgs(svg1_path, svg2_path, output_path):
|
|
1070
|
+
"""Merges two SVG files into a single SVG file.
|
|
1071
|
+
|
|
1072
|
+
Args:
|
|
1073
|
+
svg1_path: Path to the first SVG file.
|
|
1074
|
+
svg2_path: Path to the second SVG file.
|
|
1075
|
+
output_path: Path to save the merged SVG file.
|
|
1076
|
+
"""
|
|
1077
|
+
# Create figure and subplots
|
|
1078
|
+
|
|
1079
|
+
w, l = get_svg_size(svg1_path)
|
|
1080
|
+
print(w, l)
|
|
1081
|
+
w2, l2 = get_svg_size(svg2_path)
|
|
1082
|
+
print(w2, l2)
|
|
1083
|
+
# fig = sg.SVGFigure("780pt", "432pt")
|
|
1084
|
+
# fig = sg.SVGFigure(810, 450)
|
|
1085
|
+
fig = sg.SVGFigure("110pt", "100pt")
|
|
1086
|
+
# Load the two SVG files
|
|
1087
|
+
svg1 = sg.fromfile(svg1_path).getroot()
|
|
1088
|
+
svg2 = sg.fromfile(svg2_path).getroot()
|
|
1089
|
+
|
|
1090
|
+
# Optionally, adjust the position and size of the loaded SVGs
|
|
1091
|
+
svg1.moveto(0, 0)
|
|
1092
|
+
svg2.moveto(0, 600)
|
|
1093
|
+
|
|
1094
|
+
# Append the loaded SVGs to the figure
|
|
1095
|
+
fig.append([svg1, svg2])
|
|
1096
|
+
|
|
1097
|
+
# Save the merged SVG to a new file
|
|
1098
|
+
fig.save(output_path)
|
|
1099
|
+
print(get_svg_size(output_path))
|
|
1100
|
+
|
|
1101
|
+
|
|
891
1102
|
def make_tri_axis(sdf, title_name, palette, palette_orientation, colors, breaks, xlim):
|
|
892
1103
|
if not breaks:
|
|
893
1104
|
breaks = True
|
|
@@ -1340,6 +1551,7 @@ def create_plots(
|
|
|
1340
1551
|
axes_tick_number,
|
|
1341
1552
|
vector_format,
|
|
1342
1553
|
deraster,
|
|
1554
|
+
annotation,
|
|
1343
1555
|
):
|
|
1344
1556
|
df = read_df(
|
|
1345
1557
|
sdf,
|
|
@@ -1359,10 +1571,44 @@ def create_plots(
|
|
|
1359
1571
|
sdf, palette, palette_orientation, custom_colors, custom_breakpoints
|
|
1360
1572
|
)
|
|
1361
1573
|
|
|
1574
|
+
# Just doing triangle plots for now.
|
|
1575
|
+
if annotation:
|
|
1576
|
+
print(f"Generating ini file for annotation track:\n")
|
|
1577
|
+
iniprefix = plot_filename
|
|
1578
|
+
inifile = generate_ini_file(
|
|
1579
|
+
bedfile=annotation,
|
|
1580
|
+
ininame=iniprefix,
|
|
1581
|
+
chrom=name_x,
|
|
1582
|
+
)
|
|
1583
|
+
min_val = max(sdf["q_st"].min(), sdf["r_st"].min())
|
|
1584
|
+
max_val = max(sdf["q_en"].max(), sdf["r_en"].max())
|
|
1585
|
+
region = [(name_x.split(":")[0], min_val, max_val)]
|
|
1586
|
+
if inifile:
|
|
1587
|
+
bed_track = run_pygenometracks(
|
|
1588
|
+
inifile=inifile,
|
|
1589
|
+
region=region,
|
|
1590
|
+
output_file=f"{iniprefix}_ANNOTATION.{vector_format}",
|
|
1591
|
+
width=width * 2.05,
|
|
1592
|
+
)
|
|
1593
|
+
name_x.split(":")[0]
|
|
1594
|
+
bed_track.plot(
|
|
1595
|
+
f"{iniprefix}_ANNOTATION.{vector_format}",
|
|
1596
|
+
name_x.split(":")[0],
|
|
1597
|
+
min_val,
|
|
1598
|
+
max_val,
|
|
1599
|
+
)
|
|
1600
|
+
bed_track.plot(
|
|
1601
|
+
f"{iniprefix}_ANNOTATION.png", name_x.split(":")[0], min_val, max_val
|
|
1602
|
+
)
|
|
1603
|
+
print(
|
|
1604
|
+
f"\nAnnotation track saved to {iniprefix}_ANNOTATION.{vector_format} & {iniprefix}_ANNOTATION.png\n"
|
|
1605
|
+
)
|
|
1606
|
+
|
|
1362
1607
|
if is_pairwise:
|
|
1363
1608
|
heatmap = make_dot(
|
|
1364
1609
|
sdf,
|
|
1365
|
-
|
|
1610
|
+
name_x,
|
|
1611
|
+
name_y,
|
|
1366
1612
|
palette,
|
|
1367
1613
|
palette_orientation,
|
|
1368
1614
|
custom_colors,
|
|
@@ -1371,6 +1617,7 @@ def create_plots(
|
|
|
1371
1617
|
xlim,
|
|
1372
1618
|
deraster,
|
|
1373
1619
|
width,
|
|
1620
|
+
True,
|
|
1374
1621
|
)
|
|
1375
1622
|
print(f"Creating plots and saving to {plot_filename}...\n")
|
|
1376
1623
|
ggsave(
|
|
@@ -1391,29 +1638,14 @@ def create_plots(
|
|
|
1391
1638
|
filename=f"{plot_filename}_COMPARE.png",
|
|
1392
1639
|
verbose=False,
|
|
1393
1640
|
)
|
|
1394
|
-
|
|
1395
|
-
if not heatmap.data:
|
|
1396
|
-
print(
|
|
1397
|
-
f"{plot_filename}_COMPARE.pdf and {plot_filename}_COMPARE.png saved sucessfully. \n"
|
|
1398
|
-
)
|
|
1399
|
-
return 0
|
|
1400
|
-
except ValueError:
|
|
1401
|
-
print(
|
|
1402
|
-
f"{plot_filename}_COMPARE.pdf and {plot_filename}_COMPARE.png saved sucessfully. \n"
|
|
1403
|
-
)
|
|
1404
|
-
return 0
|
|
1405
|
-
if no_hist:
|
|
1406
|
-
print(
|
|
1407
|
-
f"{plot_filename}_COMPARE.pdf and {plot_filename}_COMPARE.png saved sucessfully. \n"
|
|
1408
|
-
)
|
|
1409
|
-
else:
|
|
1641
|
+
if not no_hist:
|
|
1410
1642
|
ggsave(
|
|
1411
1643
|
histy,
|
|
1412
1644
|
width=3,
|
|
1413
1645
|
height=3,
|
|
1414
1646
|
dpi=dpi,
|
|
1415
1647
|
format=vector_format,
|
|
1416
|
-
filename=f"{plot_filename}
|
|
1648
|
+
filename=f"{plot_filename}_COMPARE_HIST.{vector_format}",
|
|
1417
1649
|
verbose=False,
|
|
1418
1650
|
)
|
|
1419
1651
|
ggsave(
|
|
@@ -1422,11 +1654,23 @@ def create_plots(
|
|
|
1422
1654
|
height=3,
|
|
1423
1655
|
dpi=dpi,
|
|
1424
1656
|
format="png",
|
|
1425
|
-
filename=f"{plot_filename}
|
|
1657
|
+
filename=f"{plot_filename}_COMPARE_HIST.png",
|
|
1426
1658
|
verbose=False,
|
|
1427
1659
|
)
|
|
1660
|
+
try:
|
|
1661
|
+
if not heatmap.data:
|
|
1662
|
+
print(
|
|
1663
|
+
f"{plot_filename} comparative plots and histogram saved sucessfully. \n"
|
|
1664
|
+
)
|
|
1665
|
+
return 0
|
|
1666
|
+
except ValueError:
|
|
1428
1667
|
print(
|
|
1429
|
-
f"{plot_filename}
|
|
1668
|
+
f"{plot_filename} comparative plots and histogram saved sucessfully. \n"
|
|
1669
|
+
)
|
|
1670
|
+
return 0
|
|
1671
|
+
if no_hist:
|
|
1672
|
+
print(
|
|
1673
|
+
f"{plot_filename}_COMPARE.{vector_format} and {plot_filename}_COMPARE.png saved sucessfully. \n"
|
|
1430
1674
|
)
|
|
1431
1675
|
# Self-identity plots: Output _TRI, _FULL, and _HIST
|
|
1432
1676
|
else:
|
|
@@ -1446,7 +1690,8 @@ def create_plots(
|
|
|
1446
1690
|
)
|
|
1447
1691
|
full_plot = make_dot(
|
|
1448
1692
|
check_st_en_equality(sdf),
|
|
1449
|
-
|
|
1693
|
+
name_x,
|
|
1694
|
+
name_y,
|
|
1450
1695
|
palette,
|
|
1451
1696
|
palette_orientation,
|
|
1452
1697
|
custom_colors,
|
|
@@ -1455,7 +1700,9 @@ def create_plots(
|
|
|
1455
1700
|
xlim,
|
|
1456
1701
|
deraster,
|
|
1457
1702
|
width,
|
|
1703
|
+
False,
|
|
1458
1704
|
)
|
|
1705
|
+
|
|
1459
1706
|
ggsave(
|
|
1460
1707
|
full_plot,
|
|
1461
1708
|
width=width,
|
|
@@ -1491,6 +1738,10 @@ def create_plots(
|
|
|
1491
1738
|
rotate_vectorized_tri(
|
|
1492
1739
|
f"{tri_prefix}.svg", scaling_values[0], scaling_values[1]
|
|
1493
1740
|
)
|
|
1741
|
+
if annotation:
|
|
1742
|
+
"""merge_svgs(f"{plot_filename}_TRI.{vector_format}","test.svg", "test2.svg")
|
|
1743
|
+
append_svg(f"{plot_filename}_TRI.{vector_format}", "test.svg", "test2.svg")
|
|
1744
|
+
"""
|
|
1494
1745
|
try:
|
|
1495
1746
|
cairosvg.svg2png(
|
|
1496
1747
|
url=f"{tri_prefix}.svg", write_to=f"{tri_prefix}.png", dpi=dpi
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|