isotracks 1.0.0__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.
Files changed (59) hide show
  1. isotracks-1.0.0/LICENSE +21 -0
  2. isotracks-1.0.0/PKG-INFO +500 -0
  3. isotracks-1.0.0/README.md +468 -0
  4. isotracks-1.0.0/pyproject.toml +51 -0
  5. isotracks-1.0.0/setup.cfg +4 -0
  6. isotracks-1.0.0/src/isotracks/__init__.py +30 -0
  7. isotracks-1.0.0/src/isotracks/__main__.py +6 -0
  8. isotracks-1.0.0/src/isotracks/_dictutil.py +57 -0
  9. isotracks-1.0.0/src/isotracks/annotation.py +560 -0
  10. isotracks-1.0.0/src/isotracks/browser.py +1790 -0
  11. isotracks-1.0.0/src/isotracks/cache.py +200 -0
  12. isotracks-1.0.0/src/isotracks/canvas.py +340 -0
  13. isotracks-1.0.0/src/isotracks/cli.py +681 -0
  14. isotracks-1.0.0/src/isotracks/fusion.py +706 -0
  15. isotracks-1.0.0/src/isotracks/fusion_view.py +781 -0
  16. isotracks-1.0.0/src/isotracks/highlight.py +181 -0
  17. isotracks-1.0.0/src/isotracks/introns.py +112 -0
  18. isotracks-1.0.0/src/isotracks/io/__init__.py +49 -0
  19. isotracks-1.0.0/src/isotracks/io/_intervals.py +38 -0
  20. isotracks-1.0.0/src/isotracks/io/alignments.py +211 -0
  21. isotracks-1.0.0/src/isotracks/io/clusters.py +125 -0
  22. isotracks-1.0.0/src/isotracks/io/ends.py +144 -0
  23. isotracks-1.0.0/src/isotracks/io/features.py +259 -0
  24. isotracks-1.0.0/src/isotracks/io/longreads.py +249 -0
  25. isotracks-1.0.0/src/isotracks/io/moddiff.py +177 -0
  26. isotracks-1.0.0/src/isotracks/io/modifications.py +182 -0
  27. isotracks-1.0.0/src/isotracks/isoforge.py +74 -0
  28. isotracks-1.0.0/src/isotracks/layout.py +141 -0
  29. isotracks-1.0.0/src/isotracks/theme.py +277 -0
  30. isotracks-1.0.0/src/isotracks/tracks/__init__.py +56 -0
  31. isotracks-1.0.0/src/isotracks/tracks/axis.py +274 -0
  32. isotracks-1.0.0/src/isotracks/tracks/base.py +278 -0
  33. isotracks-1.0.0/src/isotracks/tracks/coverage.py +176 -0
  34. isotracks-1.0.0/src/isotracks/tracks/ends.py +106 -0
  35. isotracks-1.0.0/src/isotracks/tracks/features.py +457 -0
  36. isotracks-1.0.0/src/isotracks/tracks/fusion.py +40 -0
  37. isotracks-1.0.0/src/isotracks/tracks/fusion_schematic.py +258 -0
  38. isotracks-1.0.0/src/isotracks/tracks/moddiff.py +99 -0
  39. isotracks-1.0.0/src/isotracks/tracks/modifications.py +111 -0
  40. isotracks-1.0.0/src/isotracks/tracks/reads.py +121 -0
  41. isotracks-1.0.0/src/isotracks/tracks/sites.py +69 -0
  42. isotracks-1.0.0/src/isotracks/tracks/spacer.py +103 -0
  43. isotracks-1.0.0/src/isotracks.egg-info/PKG-INFO +500 -0
  44. isotracks-1.0.0/src/isotracks.egg-info/SOURCES.txt +57 -0
  45. isotracks-1.0.0/src/isotracks.egg-info/dependency_links.txt +1 -0
  46. isotracks-1.0.0/src/isotracks.egg-info/entry_points.txt +2 -0
  47. isotracks-1.0.0/src/isotracks.egg-info/requires.txt +9 -0
  48. isotracks-1.0.0/src/isotracks.egg-info/top_level.txt +1 -0
  49. isotracks-1.0.0/tests/test_annotation.py +235 -0
  50. isotracks-1.0.0/tests/test_cli.py +473 -0
  51. isotracks-1.0.0/tests/test_detail.py +172 -0
  52. isotracks-1.0.0/tests/test_fusion.py +791 -0
  53. isotracks-1.0.0/tests/test_highlight.py +288 -0
  54. isotracks-1.0.0/tests/test_introns.py +187 -0
  55. isotracks-1.0.0/tests/test_io.py +530 -0
  56. isotracks-1.0.0/tests/test_isoforge.py +151 -0
  57. isotracks-1.0.0/tests/test_plotting.py +1266 -0
  58. isotracks-1.0.0/tests/test_strand.py +197 -0
  59. isotracks-1.0.0/tests/test_theme.py +215 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mustafa Elshani
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,500 @@
1
+ Metadata-Version: 2.4
2
+ Name: isotracks
3
+ Version: 1.0.0
4
+ Summary: Isoform, read, poly(A), RNA modification and gene fusion figures from IsoForge long-read RNA-seq results
5
+ Author: Mustafa Elshani
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/MustafaElshani/IsoTracks
8
+ Project-URL: Issues, https://github.com/MustafaElshani/IsoTracks/issues
9
+ Keywords: nanopore,long-read,RNA-seq,isoform,poly(A),RNA modification,gene fusion,isoforge
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
19
+ Classifier: Topic :: Scientific/Engineering :: Visualization
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: matplotlib>=3.8
24
+ Requires-Dist: numpy>=1.24
25
+ Requires-Dist: pandas>=2.0
26
+ Requires-Dist: pyarrow>=14
27
+ Requires-Dist: pysam>=0.22
28
+ Requires-Dist: scipy>=1.11
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest>=8; extra == "dev"
31
+ Dynamic: license-file
32
+
33
+ <p align="center">
34
+ <picture>
35
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/MustafaElshani/IsoTracks/main/docs/img/isotracks_logo_dark.png">
36
+ <img src="https://raw.githubusercontent.com/MustafaElshani/IsoTracks/main/docs/img/isotracks_logo.png" alt="IsoTracks: isoforms, reads, modifications and fusions" width="380">
37
+ </picture>
38
+ </p>
39
+
40
+ # IsoTracks
41
+
42
+ **Isoform models, reads with poly(A) tails, RNA modifications, coverage and gene fusions from
43
+ [IsoForge](https://github.com/MustafaElshani/IsoForge) long-read RNA-seq results, one gene per figure.**
44
+
45
+ Give IsoTracks an IsoForge run:
46
+
47
+ ```bash
48
+ isotracks plot --gene SAT1 --isoforge results/project --output figures/SAT1
49
+ ```
50
+
51
+ ```python
52
+ from isotracks import plot_gene
53
+
54
+ plot_gene("SAT1", isoforge="results/project", output="figures/SAT1")
55
+ ```
56
+
57
+ `results/project` is the prefix IsoForge was run with (`isoforge annotate --write_bam --o results/project`).
58
+ Both write `figures/SAT1.pdf` and `figures/SAT1.png` at journal column width.
59
+
60
+ For BAM files of your own, or to name the conditions, add them:
61
+
62
+ ```bash
63
+ isotracks plot --gene SAT1 --isoforge results/project \
64
+ --bam "Control:Control_1.bam,Control_2.bam;Treated:Treated_1.bam,Treated_2.bam" \
65
+ --output figures/SAT1
66
+ ```
67
+
68
+ ```python
69
+ plot_gene(
70
+ "SAT1",
71
+ isoforge="results/project",
72
+ bam_files={"Control": ["Control_1.bam", "Control_2.bam"],
73
+ "Treated": ["Treated_1.bam", "Treated_2.bam"]},
74
+ output="figures/SAT1",
75
+ )
76
+ ```
77
+
78
+ <p align="center"><img src="https://raw.githubusercontent.com/MustafaElshani/IsoTracks/main/docs/img/anatomy.png" alt="An IsoTracks figure" width="420"></p>
79
+
80
+ ## Contents
81
+
82
+ - [Install](#install)
83
+ - [Input](#input)
84
+ - [The figure](#the-figure)
85
+ - [Isoforms and reads](#isoforms-and-reads)
86
+ - [Views](#views): zoom, close-up, shortened introns, highlights
87
+ - [Fusion transcripts](#fusion-transcripts)
88
+ - [RNA modifications](#rna-modifications)
89
+ - [Tables](#tables)
90
+ - [Appearance](#appearance)
91
+ - [Command line](#command-line)
92
+ - [Python API](#python-api)
93
+ - [Development](#development)
94
+ - [Citation](#citation)
95
+
96
+ ## Install
97
+
98
+ ```bash
99
+ pip install isotracks
100
+ ```
101
+
102
+ Python 3.10 or later; matplotlib, numpy, pandas, pyarrow, pysam and scipy are installed with it. From source:
103
+
104
+ ```bash
105
+ git clone https://github.com/MustafaElshani/IsoTracks
106
+ cd IsoTracks
107
+ pip install .
108
+ ```
109
+
110
+ ## Input
111
+
112
+ ### An IsoForge run
113
+
114
+ `--isoforge` (`isoforge=`) takes the prefix IsoForge was run with (`--o results/project`), the run's directory,
115
+ or any one of its files. IsoTracks reads from it:
116
+
117
+ | File | Used for |
118
+ |---|---|
119
+ | `<prefix>_isoforge.gtf` | the transcript models and the gene's position |
120
+ | `<prefix>_isoforge_read_annot.parquet` (or `.tsv`) | each read's isoform and poly(A) tail length |
121
+ | `<prefix>_TSS_TES_Clusters/tss_clusters.bed`, `tes_clusters.bed` | transcription start sites and poly(A) sites |
122
+ | `<prefix>_isoforge_fusion*.parquet` | fusion calls, their transcripts and their reads |
123
+ | `<prefix>_alignments/*.bam` | the alignments, when IsoForge kept them (`--write_bam`) |
124
+ | `<prefix>_isoforge_sqanti3.bed` | coding extent of the models, when [IsoForge_Sqanti3](https://github.com/MustafaElshani/IsoForge_Sqanti3) was run |
125
+
126
+ Any of these can be given on its own instead (`--annotation-gtf`, `--read-annot`, `--tss`, `--polya`,
127
+ `--fusion-table`, `--bam`, `--bed`); an explicit file takes precedence over the run.
128
+
129
+ Only whole reads of the transcripts IsoForge reports are drawn (`cluster_supported` in the read table).
130
+ `--read-filter all` draws every read in the table.
131
+
132
+ ### BAM files
133
+
134
+ The sorted, indexed BAMs IsoForge was given (`--bam`) or kept (`--write_bam`). Replicates within a condition are
135
+ pooled into one read stack.
136
+
137
+ - From the run, samples are grouped as IsoForge groups them: the sample name up to its first underscore is the
138
+ condition (`Control_1`, `Control_2` → `Control`).
139
+ - With `--bam`, conditions are named explicitly:
140
+
141
+ ```
142
+ --bam "Control:Control_1.bam,Control_2.bam;Treated:Treated_1.bam,Treated_2.bam"
143
+ bam_files={"Control": ["Control_1.bam", "Control_2.bam"], "Treated": ["Treated_1.bam", "Treated_2.bam"]}
144
+ ```
145
+
146
+ Poly(A) tail lengths are read from the `pt` tag of the reads, or from the read annotation when the BAM has none.
147
+
148
+ ### Optional
149
+
150
+ | Option | Adds |
151
+ |---|---|
152
+ | `--bed` (`bed_file=`) | BED12 models with coding extent: CDS is drawn tall and solid, UTRs shorter and hollow. Without it models are drawn from the GTF exons. |
153
+ | `--reference-gtf` (`reference_gtf=`) | A reference annotation: places genes that are not in the run's GTF, and draws the whole genes of a fusion. |
154
+ | `--modkit`, `--dmr` | [RNA modification](#rna-modifications) tracks. |
155
+
156
+ ## The figure
157
+
158
+ Top to bottom:
159
+
160
+ | Track | Shows |
161
+ |---|---|
162
+ | **poly(A)** | poly(A) sites at the 3′ ends of the drawn isoforms, with their signal hexamer |
163
+ | **TSS** | transcription start sites at their 5′ ends |
164
+ | **read ends** | density of read 5′ and 3′ ends, in reads per 100 bp |
165
+ | **gene** | the drawn isoforms overlaid in grey, with the gene name and direction of transcription |
166
+ | **isoform models** | one row per isoform |
167
+ | **read stacks** | the reads of each isoform, one stack per condition, with poly(A) tails |
168
+ | **modifications** | modified fraction per site, one row per condition |
169
+ | **differential modification** | change in modified fraction between two sample sets |
170
+ | **coverage** | read depth per condition with splice-junction arcs, on one depth axis |
171
+ | **scale bar and axis** | genomic coordinates, chromosome and strand |
172
+
173
+ - Every figure reads 5′ to 3′ from left to right. A minus-strand gene is drawn with the axis reversed.
174
+ - Every read stack has the same height. Its read count is in the gutter (`n = 373`).
175
+ - Coverage is built from the reads of the drawn isoforms. An arc's thickness is the number of reads across
176
+ that junction.
177
+
178
+ ## Isoforms and reads
179
+
180
+ | Option | Default | Meaning |
181
+ |---|---|---|
182
+ | `--min-reads` | 5 | least reads per isoform, summed over conditions |
183
+ | `--min-reads-per-condition` | off | least reads per isoform in every condition |
184
+ | `--max-isoforms` | 12 | most isoforms drawn, best supported first; 0 for no limit |
185
+ | `--max-reads` | 20,000 | most reads drawn per stack; 0 draws all. A thinned stack shows `n = 20,000/84,512`. |
186
+ | `--coverage-reads` | `drawn` | `gene` builds coverage from every read of the gene |
187
+ | `--sort-style` | `waterfall` | order of reads within a stack |
188
+
189
+ | `waterfall` | `diamond` |
190
+ |---|---|
191
+ | <img src="https://raw.githubusercontent.com/MustafaElshani/IsoTracks/main/docs/img/sort_waterfall.png" alt="Reads ordered longest tail first" width="330"> | <img src="https://raw.githubusercontent.com/MustafaElshani/IsoTracks/main/docs/img/sort_diamond.png" alt="Reads ordered with the longest tails in the middle" width="330"> |
192
+ | Longest tail at the top. | Longest tail in the middle. |
193
+
194
+ ## Views
195
+
196
+ ### Zoom
197
+
198
+ ```bash
199
+ isotracks plot --gene SAT1 ... --zoom last_exon
200
+ ```
201
+
202
+ ```python
203
+ view.plot(region=view.zoom("last_exon", padding=400))
204
+ ```
205
+
206
+ Targets: `first_exon`, `last_exon`, `exon_<N>`, `5_prime`, `3_prime`, `whole_gene`. Exons are numbered 5′ to 3′.
207
+ A window reaching the 3′ end is widened to fit the longest poly(A) tail.
208
+
209
+ ### A close-up beside the gene
210
+
211
+ `--detail` (`detail=`) draws a region again beside the whole figure, row for row, at true scale.
212
+
213
+ | default | `--detail last_exon` |
214
+ |---|---|
215
+ | <img src="https://raw.githubusercontent.com/MustafaElshani/IsoTracks/main/docs/img/copz1_default.png" alt="A gene at true scale" width="330"> | <img src="https://raw.githubusercontent.com/MustafaElshani/IsoTracks/main/docs/img/copz1_detail.png" alt="The same gene with a close-up of its last exon" width="330"> |
216
+
217
+ It takes `last_exon`, `exon_<N>`, `3_utr`, `junction_<N>`, `3_prime`, `5_prime` or a range. `--detail-share` sets
218
+ the fraction of the width the close-up takes (default 0.4). Depth and read-end density share one scale across
219
+ both panels.
220
+
221
+ ### Shortened introns
222
+
223
+ | `--introns scaled` | `--introns fixed` |
224
+ |---|---|
225
+ | <img src="https://raw.githubusercontent.com/MustafaElshani/IsoTracks/main/docs/img/copz1_scaled.png" alt="Introns shrunk by one factor" width="330"> | <img src="https://raw.githubusercontent.com/MustafaElshani/IsoTracks/main/docs/img/copz1_fixed.png" alt="Introns drawn at one width" width="330"> |
226
+ | Every intron shrunk by one factor; together they take `--intron-share` of the width (default 0.3). | Every intron drawn at the same small width. |
227
+
228
+ Exons, flanks and poly(A) tails stay at true scale. Each shortened intron is marked with `//` on the axis.
229
+
230
+ ### Highlighting a region
231
+
232
+ `--highlight` (`highlight=`) marks regions with a band through every track.
233
+
234
+ <p align="center"><img src="https://raw.githubusercontent.com/MustafaElshani/IsoTracks/main/docs/img/highlight.png" alt="A junction and a 3′ UTR highlighted" width="420"></p>
235
+
236
+ ```bash
237
+ isotracks plot --gene SAT1 ... --highlight junction_2 3_utr
238
+ ```
239
+
240
+ ```python
241
+ view.plot(highlight=["junction_2", "3_utr:ENST00000111.1"])
242
+ view.plot(highlight=(31_950_000, 31_951_000), highlight_style="outline")
243
+ ```
244
+
245
+ | Name | Marks |
246
+ |---|---|
247
+ | `exon_<N>`, `first_exon`, `last_exon` | one exon, numbered 5′ to 3′ |
248
+ | `intron_<N>`, `junction_<N>` | the intron between exon N and exon N+1 |
249
+ | `5_utr`, `3_utr`, `cds` | from the coding extent in `--bed` |
250
+ | `chr22:31,950,000-31,951,000`, `(start, end)` | that range |
251
+
252
+ A named region is measured on the isoform with the most exons, or on the one given after a colon
253
+ (`exon_2:ENST00000111.1`). `--highlight-style` is `both` (a faint box with a dotted edge), `box` or `outline`.
254
+
255
+ ## Fusion transcripts
256
+
257
+ `--fusion` draws a fusion called by IsoForge, by its genes or its call ID:
258
+
259
+ ```bash
260
+ isotracks plot --fusion RIMS2::ATP6V1C1 --isoforge results/project \
261
+ --reference-gtf gencode.gtf --output figures/RIMS2_ATP6V1C1
262
+ ```
263
+
264
+ ```python
265
+ from isotracks import FusionView
266
+
267
+ view = FusionView("RIMS2::ATP6V1C1", isoforge="results/project", reference_gtf="gencode.gtf")
268
+ view.plot(output="figures/RIMS2_ATP6V1C1")
269
+ ```
270
+
271
+ <p align="center"><img src="https://raw.githubusercontent.com/MustafaElshani/IsoTracks/main/docs/img/fusion_all.png" alt="A fusion: schematic, sites, fusion gene, transcripts and reads" width="420"></p>
272
+
273
+ **The schematic** is drawn at the top of every fusion figure (`--no-fusion-schematic` leaves it out).
274
+
275
+ <p align="center"><img src="https://raw.githubusercontent.com/MustafaElshani/IsoTracks/main/docs/img/fusion_schematic.png" alt="The fusion schematic" width="420"></p>
276
+
277
+ | Row | Shows |
278
+ |---|---|
279
+ | **genes** | each partner whole, 5′ to 3′, as its MANE Select isoform. Introns are drawn as equal short gaps; the row is not to scale. The fusion's exons are coloured, the rest of the gene is grey, and each breakpoint is marked with its coordinate. |
280
+ | **fusion** | the fused transcript, exons only, to scale in nucleotides, with its length |
281
+
282
+ **The panels** below it follow the gene figure, one panel per partner in transcript order:
283
+
284
+ | Row | Shows |
285
+ |---|---|
286
+ | **poly(A)**, **TSS** | sites at the ends of the fusion transcripts (`--polya`, `--tss`, or from `--isoforge`) |
287
+ | **read ends** | density of the fusion reads' 5′ and 3′ ends, on one scale across the panels |
288
+ | **fusion gene** | the fusion's transcripts overlaid in grey, with an arc and read count over each junction |
289
+ | **transcripts** | one row per fusion transcript, labelled with its IsoForge ID (`ISOFORF000000067`) |
290
+ | **read stacks** | the reads of each transcript, per condition |
291
+
292
+ - A dashed line marks each breakpoint through every row.
293
+ - Transcripts and reads are joined across the gap between panels: by an intron line where the junction is on
294
+ splice sites at both ends, and by a dotted line where it is not.
295
+ - Introns are shortened within each panel (`--introns scaled` is the default for a fusion).
296
+ - For a fusion of three or more genes, transcripts through every partner are drawn first, then those joining
297
+ only some. `--fusion-reads complete` draws only the former.
298
+
299
+ **Input.** The fusion tables hold every read, so BAMs are optional: they add the poly(A) tails. Samples are
300
+ grouped into conditions as for a gene; `--conditions "Control:rep1,rep2;Treated:rep3"` names the groups without
301
+ BAMs. `--reference-gtf` gives the schematic each partner's whole gene.
302
+
303
+ A fusion can also be drawn without IsoForge's tables, from a line of breakpoints with its reads found in the
304
+ BAMs. A junction is `donor>acceptor`, the last base kept upstream and the first kept downstream:
305
+
306
+ ```python
307
+ FusionView("RIMS2::ATP6V1C1 chr8::chr8 chr8:103501062>chr8:103062955",
308
+ bam_files=bam_files, reference_gtf="gencode.gtf")
309
+ ```
310
+
311
+ ## RNA modifications
312
+
313
+ Modification tracks are drawn from [`modkit pileup`](https://nanoporetech.github.io/modkit/) bedMethyl files in
314
+ genome coordinates, bgzipped and tabix-indexed, one per replicate:
315
+
316
+ ```bash
317
+ modkit pileup sample.bam sample.bed --ref genome.fa
318
+ bgzip sample.bed && tabix -p bed sample.bed.gz
319
+ ```
320
+
321
+ ```bash
322
+ isotracks plot --gene ACTB --isoforge results/project \
323
+ --modkit "Control:Control_1.bed.gz;Treated:Treated_1.bed.gz" --mods a 17802 \
324
+ --output figures/ACTB
325
+ ```
326
+
327
+ <p align="center"><img src="https://raw.githubusercontent.com/MustafaElshani/IsoTracks/main/docs/img/modifications.png" alt="Modification tracks per condition" width="420"></p>
328
+
329
+ Each lollipop is one site; its height is the modified fraction, from 0 to 100%: reads called modified over reads
330
+ assessed at that position. Replicates are pooled by summing counts.
331
+
332
+ | Option | Default | Meaning |
333
+ |---|---|---|
334
+ | `--mods` | `a,17802` | codes to draw. Codes joined by commas share a track; separate arguments get a track each. At most three per track. |
335
+ | `--min-mod-coverage` | 10 | least reads at a site |
336
+ | `--min-mod-fraction` | 0.3 | least modified fraction at a site |
337
+ | `--mod-regions` | `exons` | `window` also draws sites outside the exons of the drawn isoforms |
338
+
339
+ The gutter reports sites drawn over sites assessed (`89/1,933 sites`).
340
+
341
+ | Base | Codes |
342
+ |---|---|
343
+ | A | `a` (m6A), `17596` (inosine), `69426` (Am) |
344
+ | C | `m` (5mC), `19228` (Cm) |
345
+ | G | `19229` (Gm) |
346
+ | U | `17802` (pseudouridine), `19227` (Um) |
347
+
348
+ ### Differential modification
349
+
350
+ `--dmr` draws the change in modified fraction per site from a `modkit dmr pair` site table, sorted, bgzipped
351
+ and tabix-indexed:
352
+
353
+ ```bash
354
+ modkit dmr pair -a Control_1.bed.gz -a Control_2.bed.gz -b Treated_1.bed.gz -b Treated_2.bed.gz \
355
+ --ref genome.fa --base A --header -o sites.bed
356
+ grep -v '^#' sites.bed | sort -k1,1 -k2,2n | bgzip > sites.bed.gz
357
+ tabix -p bed sites.bed.gz
358
+ ```
359
+
360
+ ```bash
361
+ isotracks plot --gene ACTB ... --dmr "Control vs Treated:sites.bed.gz"
362
+ ```
363
+
364
+ <p align="center"><img src="https://raw.githubusercontent.com/MustafaElshani/IsoTracks/main/docs/img/modification_diff.png" alt="A differential modification track" width="420"></p>
365
+
366
+ The axis runs from −100% to +100%. modkit's effect size is `a − b`, so a stem pointing up is a site less
367
+ modified in the second sample set, and a stem pointing down one more modified.
368
+
369
+ | Option | Default | Meaning |
370
+ |---|---|---|
371
+ | `--max-diff-pvalue` | 0.05 | largest p-value drawn |
372
+ | `--min-diff-effect` | 0.2 | smallest change in modified fraction drawn |
373
+ | `--balanced-diff` | off | use modkit's replicate-balanced effect size and p-value |
374
+
375
+ ## Tables
376
+
377
+ Coordinates of a gene's isoforms and exons, 1-based and inclusive:
378
+
379
+ ```bash
380
+ isotracks info --gene SAT1 --isoforge results/project
381
+ isotracks info --gene SAT1 --isoforge results/project --format tsv --exons-table -o exons.tsv
382
+ ```
383
+
384
+ ```python
385
+ view.print_info() # the report
386
+ view.isoform_table() # one row per isoform: exons, length, span, reads
387
+ view.exons() # one row per exon
388
+ ```
389
+
390
+ Per-read poly(A) tail lengths:
391
+
392
+ ```bash
393
+ isotracks polya --gene SAT1 --isoforge results/project -o SAT1_polya.csv
394
+ ```
395
+
396
+ ```python
397
+ view.polya_table() # gene, transcript_id, condition, sample, read_name, polya_length
398
+ ```
399
+
400
+ ## Appearance
401
+
402
+ | Option | Values |
403
+ |---|---|
404
+ | `--width` | `single` (89 mm), `onehalf` (120), `double` (183), `slide` (254), or millimetres |
405
+ | `--readability` | `strict`, `comfortable` (default), `large` |
406
+ | `--roomy` | taller read, coverage and modification tracks; type size is unchanged |
407
+ | `--polya-scale` | base pairs drawn per nucleotide of poly(A) tail (default 1) |
408
+ | `--condition-labels` | display names, `"Ctrl:Control,Trt:Treated"` |
409
+ | `--condition-colors` | hex colours, `"Control:#9AA5B1,Treated:#1B2A49"` |
410
+ | `--formats`, `--dpi` | output formats (default PDF and PNG) and raster resolution (default 600) |
411
+
412
+ <p align="center"><img src="https://raw.githubusercontent.com/MustafaElshani/IsoTracks/main/docs/img/roomy.png" alt="The same figure with --roomy" width="420"></p>
413
+
414
+ Figures are built at the printed width. Isoform colours are Okabe–Ito. PDF output embeds TrueType fonts and SVG
415
+ keeps live text.
416
+
417
+ ```python
418
+ from isotracks import IsoTracks, Layout, theme
419
+
420
+ theme.use(width="double", readability="large")
421
+ IsoTracks(..., isoform_colors=["#0072B2", "#D55E00"], layout=Layout(read_stack=4.5, coverage=2.0))
422
+ ```
423
+
424
+ ## Command line
425
+
426
+ | Command | Does |
427
+ |---|---|
428
+ | `isotracks plot` | draw one gene (`--gene`), region (`--region chr:start-end[:strand]`) or fusion (`--fusion`) |
429
+ | `isotracks batch` | draw one figure per gene from a list (`--genes genes.txt --outdir figures`) |
430
+ | `isotracks info` | report isoform and exon coordinates |
431
+ | `isotracks polya` | write per-read poly(A) tail lengths as CSV |
432
+ | `isotracks index` | index a TSV read annotation by gene; not needed for Parquet |
433
+
434
+ `isotracks <command> --help` lists every option.
435
+
436
+ ## Python API
437
+
438
+ ```python
439
+ from isotracks import IsoTracks
440
+
441
+ view = IsoTracks(gene_name="SAT1", isoforge="results/project", min_reads_per_condition=5)
442
+
443
+ view.transcripts() # the isoforms drawn
444
+ view.plot(output="figures/SAT1") # PDF and PNG
445
+ view.plot(output="figures/SAT1.svg") # one format
446
+ view.plot(region=view.zoom("last_exon"), sort_style="diamond")
447
+ view.plot(detail="last_exon", introns="scaled", highlight="3_utr")
448
+ ```
449
+
450
+ `plot_gene(gene, isoforge=..., output=..., **options)` builds and draws in one call.
451
+ [`examples/`](https://github.com/MustafaElshani/IsoTracks/tree/main/examples) has a script and a notebook.
452
+
453
+ Tracks can also be stacked directly:
454
+
455
+ ```python
456
+ from isotracks import Canvas
457
+ from isotracks.io import CoverageReader
458
+
459
+ canvas = Canvas()
460
+ canvas.add("TranscriptTrack", file_name="models.bed", label="models")
461
+ canvas.add("CoverageTrack", reader=CoverageReader(file_names=["a.bam"]), label="depth")
462
+ canvas.add("CoordinateAxisTrack")
463
+ canvas.render(region=("chr1", 1_000_000, 1_010_000, "+"))
464
+ ```
465
+
466
+ ## Development
467
+
468
+ ```bash
469
+ pip install -e ".[dev]"
470
+ pytest
471
+ ```
472
+
473
+ The figures in this README are written by the scripts in [`docs/`](https://github.com/MustafaElshani/IsoTracks/tree/main/docs): `make_examples.py` from synthetic
474
+ data, `make_gene_examples.py` and `make_fusion_example.py` from an IsoForge run, and `make_logo.py`.
475
+
476
+ ```
477
+ src/isotracks/
478
+ ├── browser.py IsoTracks, plot_gene: the gene view
479
+ ├── fusion_view.py FusionView: the fusion view
480
+ ├── fusion.py fusion calls, partners, reads and transcripts
481
+ ├── isoforge.py the files of an IsoForge run
482
+ ├── annotation.py GTF, read annotation and BED12
483
+ ├── canvas.py the figure: tracks in rows, in one or more panels
484
+ ├── theme.py widths, type sizes and colours
485
+ ├── layout.py track heights
486
+ ├── cli.py the isotracks command
487
+ ├── io/ readers for alignments, features, sites and modifications
488
+ └── tracks/ one module per track type
489
+ ```
490
+
491
+ ## Citation
492
+
493
+ IsoTracks is a companion tool of IsoForge. The IsoForge manuscript is in preparation. Until it is out, please
494
+ cite the IsoForge repository:
495
+
496
+ > Elshani M, Zhang Y, Harrison DJ. IsoForge, version 1.0.0. https://github.com/MustafaElshani/IsoForge
497
+
498
+ ## License
499
+
500
+ MIT