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.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: ModDotPlot
3
- Version: 0.9.1
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
  ![](images/logo.png)
2
+ ---
3
+ [![PyPI](https://img.shields.io/pypi/v/ModDotPlot?color=blue&label=PyPI)](https://pypi.org/project/ModDotPlot/)
4
+ [![CI](https://github.com/marbl/ModDotPlot/actions/workflows/black.yml/badge.svg)](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://pmc.ncbi.nlm.nih.gov/articles/PMC11321072/)
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
- 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!
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
  ![](images/demo.gif)
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 ModDotPlot:
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
- ModDotPlot must be run either in `interactive` mode, or `static` mode:
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
- This skips running Dash and quickly creates plots under the specified output directory using [plotnine](https://plotnine.readthedocs.io/en/v0.12.4/). By default, running ModDotPlot in static mode this will produce the following files:
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, containing intervals alongside their corresponding identity estimates.
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
- See [static mode commands](#static-mode-commands) for further info.
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
- `-b / --bed <.bed file>`
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-bed <bool>`
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 <bool>`
220
+ `--dpi <int>`
208
221
 
209
- Image resolution in dots per inch (not to be confused with dotplot resolution). Default is 600.
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 for comparing multiple plots, allowing them to stay in scale.
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:14M-18M....
361
+ Retrieving k-mers from Chr1:14000000-18000000....
342
362
 
343
363
  Progress: |████████████████████████████████████████| 100.0% Completed
344
364
 
345
- Chr1:14M-18M k-mers retrieved!
365
+ Chr1:14000000-18000000 k-mers retrieved!
346
366
 
347
- Computing self identity matrix for chr1:14M-18M...
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:14M-18M.bed
380
+ Saved bed file to Chr1_cen_plots/Chr1:14000000-18000000.bed
361
381
 
362
- Plots created! Saving to Chr1_cen_plots/Chr1:14M-18M...
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
- ![](images/Chr1:14M-18M_FULL.png)
386
+ ![](images/Chr1:14000000-18000000_FULL.png)
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
+ ![](images/chr14:2000000-5000000_chr21:2000000-5000000.png)
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`, this 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`.
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.1"
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.2
1
+ Metadata-Version: 2.4
2
2
  Name: ModDotPlot
3
- Version: 0.9.1
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
@@ -5,12 +5,13 @@ dash
5
5
  plotnine==0.12.4
6
6
  palettable
7
7
  mmh3
8
- tk
9
8
  setproctitle
10
9
  numpy
11
10
  pillow
12
11
  patchworklib==0.6.3
13
12
  cairosvg
13
+ pygenometracks
14
+ svgutils
14
15
 
15
16
  [test]
16
17
  pytest
@@ -1,4 +1,4 @@
1
- VERSION = "0.9.1"
1
+ VERSION = "0.9.5"
2
2
  COLS = [
3
3
  "#query_name",
4
4
  "query_start",
@@ -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("test")
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 + 1
191
- end_x = (x + 1) * window_size
192
- start_y = y * window_size + 1
193
- end_y = (y + 1) * window_size
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 Complex Repeat Structures",
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
- "-b",
180
- "--bed",
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
- "--no-bed", action="store_true", help="Skip output of bed file."
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=18, type=float, help="Plot width (also height for _FULL)."
286
+ "--width", default=9, type=float, help="Plot width (also height for _FULL)."
277
287
  )
278
288
 
279
- static_parser.add_argument("--dpi", default=600, type=int, help="Plot dpi.")
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.no_bed = config.get("no_bed", args.no_bed)
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
- """if args.grid or args.grid_only:
456
- if not (args.compare or args.compare_only) and not args.bed:
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, "bed") and args.bed:
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.bed:
487
- # If args.bed is provided as input, run static mode directly from the bed file. Skip counting input k-mers.
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
- sequences = list(zip(seq_list, k_list))
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 {sequences[i][0]}... \n")
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: {len(k_list[i]) + args.kmer - 1}\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, win, args.identity, seq_list[i], seq_list[i], True
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(sequences[i][0])
992
+ grid_val_single_names.append(seq_name)
968
993
 
969
- if not args.no_bed:
994
+ if not args.no_bedpe:
970
995
  # Log saving bed file
971
996
  if not args.output_dir:
972
- bedfile_output = sequences[i][0] + ".bed"
997
+ bedfile_output = seq_name + ".bedpe"
973
998
  else:
974
999
  bedfile_output = os.path.join(
975
- args.output_dir, sequences[i][0] + ".bed"
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(f"Saved bed file to {bedfile_output}\n")
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=sequences[i][0],
987
- name_y=sequences[i][0],
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 larger_length < smaller_length:
1027
- smaller_seq = sequences[i][1]
1028
- larger_seq = sequences[j][1]
1029
- larger_length = len(larger_seq)
1030
- smaller_length = len(smaller_seq)
1031
- larger_seq_name = sequences[j][0]
1032
- smaller_seq_name = sequences[i][0]
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
- larger_seq,
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.no_bed:
1129
+ if not args.no_bedpe:
1098
1130
  # Log saving bed file
1099
1131
  if not args.output_dir:
1100
1132
  bedfile_output = (
1101
- smaller_seq_name
1133
+ larger_seq_name
1102
1134
  + "_"
1103
- + larger_seq_name
1104
- + "_COMPARE.bed"
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
- smaller_seq_name
1141
+ larger_seq_name
1110
1142
  + "_"
1111
- + larger_seq_name
1112
- + "_COMPARE.bed",
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(f"Saved bed file to {bedfile_output}\n")
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=smaller_seq_name,
1124
- name_y=larger_seq_name,
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
- breaks = np.asfarray(custom_breakpoints)
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(number, min_breaks=6, max_breaks=8):
386
+ def generate_breaks(min_number, max_number, min_breaks=5, max_breaks=9):
286
387
  # Determine the order of magnitude
287
- magnitude = 10 ** int(
288
- np.floor(np.log10(number))
289
- ) # Base power of 10 (e.g., 10M, 1M, 100K)
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
- # Find a reasonable step size
292
- possible_steps = [
293
- magnitude // d
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
- step = (
299
- possible_steps[0] if possible_steps else magnitude // 5
300
- ) # Default to 1/5th if no exact match
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
- breaks = list(range(0, ((number // step) + 1) * step, step))
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
- title_name,
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=element_blank(), # Center title
378
- axis_title_x=element_text(size=(width * 1.2), family=["DejaVu Sans"]),
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(labels=make_scale, limits=[0, max_val], breaks=breaks)
392
- + scale_y_continuous(labels=make_scale, limits=[0, max_val], breaks=breaks)
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(labels=make_scale, limits=[0, max_val], breaks=breaks)
682
- + scale_y_continuous(labels=make_scale, limits=[0, max_val], breaks=breaks)
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.2), family=["DejaVu Sans"]),
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(labels=make_scale, limits=[0, max_val], breaks=breaks)
743
- + scale_y_continuous(labels=make_scale, limits=[0, max_val], breaks=breaks)
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(labels=make_scale, limits=[0, max_val], breaks=breaks)
772
- + scale_y_continuous(labels=make_scale, limits=[0, max_val], breaks=breaks)
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
- plot_filename,
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
- try:
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}_HIST.{vector_format}",
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}_HIST.png",
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}_COMPARE.pdf, {plot_filename}_COMPARE.png, {plot_filename}_HIST.pdf and {plot_filename}_HIST.png saved sucessfully. \n"
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
- plot_filename,
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