Skip to content

Commit f01d1da

Browse files
committed
update v0.5.6, add --show-mean-junction-number to explicit the display logic
1 parent c02bf4b commit f01d1da

12 files changed

Lines changed: 354 additions & 358 deletions

File tree

docs/command.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,13 +102,15 @@ Options:
102102
--local-domain TEXT Load local domain folder and load into
103103
annotation track, download from https://hgdo
104104
wnload.soe.ucsc.edu/gbdb/hg38/uniprot/
105+
[default: ""]
105106
--domain-include TEXT Which domain will be included in annotation
106107
plot
107108
--domain-exclude TEXT Which domain will be excluded in annotation
108109
plot
109110
--remove-empty Whether to plot empty transcript
110111
--transcripts-to-show TEXT Which transcript to show, transcript name or
111112
id in gtf file, eg: transcript1,transcript2
113+
[default: ""]
112114
--choose-primary Whether choose primary transcript to plot.
113115
--ref-color TEXT The color of exons [default: black]
114116
--intron-scale FLOAT The scale of intron [default: 0.5]
@@ -143,6 +145,8 @@ Options:
143145
all [default: all]
144146
--included-junctions TEXT The junction id for including, chr1:1-100
145147
--show-junction-num Whether to show the number of junctions
148+
--show-mean-junction-num Whether to show the mean junction count
149+
averaged across multiple samples.
146150
--fill-step [pre|post|mid] Define step if the filling should be a step
147151
function, i.e. constant in between x. The
148152
value determines where the step will occur:
@@ -286,7 +290,7 @@ Options:
286290
the following 4 columns is the weight of
287291
ATCG.
288292
--motif-region TEXT The region of motif to plot in start-end
289-
format
293+
format [default: ""]
290294
--motif-width FLOAT The width of ATCG characters [default: 0.8]
291295
Layout settings:
292296
--n-y-ticks INTEGER RANGE The number of ticks of y-axis [x>=0]

docs/installation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ trackplot --help
163163

164164
---
165165

166-
#### 6. for `pipenv` or `poetry` users
166+
#### 6. for `uv` users
167167

168168
> Install [uv](https://github.com/astral-sh/uv)
169169

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "trackplot"
3-
version = "0.5.5"
3+
version = "0.5.6"
44
description = "The trackplot is a tool for visualizing various next-generation sequencing (NGS) data, including DNA-seq, RNA-seq, single-cell RNA-seq and full-length sequencing datasets. https://sashimi.readthedocs.io/"
55
authors = [
66
{ name = "ygidtu", email = "ygidtu@gmail.com" }
@@ -25,7 +25,7 @@ dependencies = [
2525
"requests>=2.32.4",
2626
"scipy>=1.15.3",
2727
"seaborn>=0.13.2",
28-
"xmltodict>=0.14.2",
28+
"xmltodict>=0.15.0",
2929
]
3030

3131

requirements.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ numexpr>=2.11.0
3333
numpy>=2.3.0
3434
packaging>=25.0
3535
pandas>=2.3.0
36-
pillow>=11.3.0
36+
pillow>=11.2.1
3737
platformdirs>=4.3.8
3838
py-cpuinfo>=9.0.0
3939
pybigwig>=0.3.24
@@ -56,4 +56,4 @@ typing-extensions>=4.14.0
5656
tzdata>=2025.2
5757
urllib3>=2.5.0
5858
werkzeug>=3.1.3
59-
xmltodict>=0.14.2
59+
xmltodict>=1.0.2

trackplot/base/ReadDepth.py

Lines changed: 70 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@ class ReadDepth(object):
2121
"""
2222

2323
__slots__ = [
24-
"_junction_dict_plus_", "_junction_dict_minus_",
25-
"_minus_", "_plus_", "_number_of_merged_",
24+
"__junction_dict__plus___", "__junction_dict__minus__",
25+
"__minus__", "__plus__", "__number_of_merged__",
2626
"strand_aware", "site_plus", "site_minus",
2727
]
2828

2929
def __init__(self,
30-
wiggle: np.array,
30+
wiggle: np.ndarray,
3131
site_plus: Optional[np.array] = None,
3232
site_minus: Optional[np.array] = None,
3333
minus: Optional[np.array] = None,
@@ -45,58 +45,65 @@ def __init__(self,
4545
:param junction_dict_plus: these splice junction from plus strand
4646
:param junction_dict_minus: these splice junction from minus strand
4747
"""
48-
self._plus_ = wiggle
48+
self.__plus__ = wiggle
4949
self.strand_aware = strand_aware
50-
self._minus_ = abs(minus) if minus is not None else minus
51-
self._junction_dict_plus_ = junction_dict_plus
52-
self._junction_dict_minus_ = junction_dict_minus
50+
self.__minus__ = abs(minus) if minus is not None else minus
51+
self.__junction_dict__plus___ = junction_dict_plus
52+
self.__junction_dict__minus__ = junction_dict_minus
5353
self.site_plus = site_plus
5454
self.site_minus = site_minus * -1 if site_minus is not None else site_minus
5555

56-
self._number_of_merged_ = 1
56+
self.__number_of_merged__ = 1
5757

5858
@property
5959
def plus(self) -> Optional[np.array]:
60-
if self._plus_ is not None and self._number_of_merged_ > 0:
61-
return self._plus_ / self._number_of_merged_
62-
return self._plus_
60+
if self.__plus__ is not None and self.__number_of_merged__ > 0:
61+
return self.__plus__ / self.__number_of_merged__
62+
return self.__plus__
6363

6464
@property
6565
def minus(self) -> Optional[np.array]:
66-
if self._minus_ is not None and self._number_of_merged_ > 0:
67-
return self._minus_ / self._number_of_merged_
68-
return self._minus_
66+
if self.__minus__ is not None and self.__number_of_merged__ > 0:
67+
return self.__minus__ / self.__number_of_merged__
68+
return self.__minus__
6969

7070
@property
71-
def wiggle(self) -> np.array:
72-
if (self._plus_ is None or not self._plus_.any()) and self._minus_ is not None:
71+
def wiggle(self) -> np.ndarray:
72+
if (self.__plus__ is None or not self.__plus__.any()) and self.__minus__ is not None:
7373
return self.minus
7474

75-
if self._plus_ is not None and self._minus_ is not None:
75+
if self.__plus__ is not None and self.__minus__ is not None:
7676
return self.plus + self.minus
7777

7878
return self.plus
7979

8080
@property
81-
def junctions_plus(self) -> dict:
82-
if self._number_of_merged_ > 1:
83-
return {k: v / self._number_of_merged_ for k, v in self._junction_dict_plus_.items()}
84-
return self._junction_dict_plus_
81+
def mean_junctions_plus(self) -> dict:
82+
if self.__number_of_merged__ > 1:
83+
return {k: v / self.__number_of_merged__ for k, v in self.__junction_dict__plus___.items()}
84+
return self.__junction_dict__plus___
8585

8686
@property
87-
def junctions_minus(self) -> dict:
88-
if self._number_of_merged_ > 1:
89-
return {k: v / self._number_of_merged_ for k, v in self._junction_dict_minus_.items()}
90-
return self._junction_dict_minus_
87+
def mean_junctions_minus(self) -> dict:
88+
if self.__number_of_merged__ > 1:
89+
return {k: v / self.__number_of_merged__ for k, v in self.__junction_dict__minus__.items()}
90+
return self.__junction_dict__minus__
9191

92-
@property
93-
def junctions_dict(self) -> dict:
92+
def junctions_dict(self, show_mean_jxn_number: bool = False) -> dict:
9493
res = {}
95-
if self._junction_dict_plus_:
96-
res.update(self.junctions_plus)
9794

98-
if self._junction_dict_minus_:
99-
res.update(self.junctions_minus)
95+
if show_mean_jxn_number:
96+
if self.__junction_dict__plus___:
97+
res.update(self.mean_junctions_plus)
98+
99+
if self.__junction_dict__minus__:
100+
res.update(self.mean_junctions_minus)
101+
else:
102+
if self.__junction_dict__plus___:
103+
res.update(self.__junction_dict__plus___)
104+
105+
if self.__junction_dict__minus__:
106+
res.update(self.__junction_dict__minus__)
100107
return res
101108

102109
@property
@@ -117,27 +124,27 @@ def __add__(self, other):
117124
if len(self.wiggle) == len(other.wiggle):
118125
junc_plus, junc_minus = {}, {}
119126

120-
for i in [self._junction_dict_plus_, other._junction_dict_plus_]:
127+
for i in [self.__junction_dict__plus___, other.__junction_dict__plus___]:
121128
if i:
122129
junc_plus.update(i)
123-
for i in [self._junction_dict_minus_, other._junction_dict_minus_]:
130+
for i in [self.__junction_dict__minus__, other.__junction_dict__minus__]:
124131
if i:
125132
junc_minus.update(i)
126133

127134
minus = None
128-
if self._minus_ is not None and other._minus_ is not None:
129-
minus = self._minus_ + other._minus_
130-
elif self._minus_ is None and other._minus_ is not None:
135+
if self.__minus__ is not None and other.__minus__ is not None:
136+
minus = self.__minus__ + other.__minus__
137+
elif self.__minus__ is None and other.__minus__ is not None:
131138
minus = other.minus
132-
elif self._minus_ is not None and other._minus_ is None:
133-
minus = self._minus_
139+
elif self.__minus__ is not None and other.__minus__ is None:
140+
minus = self.__minus__
134141

135142
merged = ReadDepth(
136-
self._plus_ + other._plus_, minus=minus,
143+
self.__plus__ + other.__plus__, minus=minus,
137144
junction_dict_plus=junc_plus,
138145
junction_dict_minus=junc_minus
139146
)
140-
merged._number_of_merged_ = self._number_of_merged_ + other._number_of_merged_
147+
merged.__number_of_merged__ = self.__number_of_merged__ + other.__number_of_merged__
141148
return merged
142149
else:
143150
raise ValueError(f"ReadDepth objects are not equal length: {len(self.wiggle)} != {len(other.wiggle)}")
@@ -164,23 +171,23 @@ def add_customized_junctions(self, other):
164171
:return:
165172
"""
166173

167-
for k, v in other._junction_dict_plus_:
168-
self._junction_dict_plus_[k] = v + self._junction_dict_plus_.get(k, 0)
174+
for k, v in other.__junction_dict__plus___:
175+
self.__junction_dict__plus___[k] = v + self.__junction_dict__plus___.get(k, 0)
169176

170-
for k, v in other._junction_dict_minus_:
171-
self._junction_dict_minus_[k] = v + self._junction_dict_minus_.get(k, 0)
177+
for k, v in other.__junction_dict__minus__:
178+
self.__junction_dict__minus__[k] = v + self.__junction_dict__minus__.get(k, 0)
172179

173180
return self.junctions_dict
174181

175182
def transform(self, log_trans: str):
176183
funcs = {"10": np.log10, "2": np.log2, "zscore": zscore, "e": np.log}
177184

178185
if log_trans in funcs.keys():
179-
if self._plus_ is not None:
180-
self._plus_ = funcs[log_trans](self._plus_ + 1)
186+
if self.__plus__ is not None:
187+
self.__plus__ = funcs[log_trans](self.__plus__ + 1)
181188

182189
if self.minus is not None:
183-
self._minus_ = funcs[log_trans](self._minus_ + 1)
190+
self.__minus__ = funcs[log_trans](self.__minus__ + 1)
184191

185192
def normalize(self, size_factor: float, format_: str = "normal", read_length: float = 0):
186193
u"""
@@ -192,35 +199,35 @@ def normalize(self, size_factor: float, format_: str = "normal", read_length: fl
192199

193200
if format_ == "rpkm" and read_length > 0:
194201
# for rpkm the size_factor is total reads
195-
self._plus_ = np.divide(
196-
self._plus_,
202+
self.__plus__ = np.divide(
203+
self.__plus__,
197204
np.multiply(
198-
(np.sum(self._plus_ != 0) - read_length + 1) / 1e3,
205+
(np.sum(self.__plus__ != 0) - read_length + 1) / 1e3,
199206
size_factor / 1e6
200207
)
201208
)
202-
if self._minus_ is not None:
203-
self._minus_ = np.divide(
204-
self._minus_,
209+
if self.__minus__ is not None:
210+
self.__minus__ = np.divide(
211+
self.__minus__,
205212
np.multiply(
206-
(np.sum(self._minus_ != 0) - read_length + 1) / 1e3,
213+
(np.sum(self.__minus__ != 0) - read_length + 1) / 1e3,
207214
size_factor / 1e6
208215
)
209216
)
210217
elif format_ == "cpm" and read_length > 0:
211218
# for cpm the size_factor is total reads
212-
self._plus_ = np.divide(self._plus_, np.divide(size_factor, 1e6))
213-
if self._minus_ is not None:
214-
self._minus_ = np.divide(self._minus_, np.divide(size_factor, 1e6))
219+
self.__plus__ = np.divide(self.__plus__, np.divide(size_factor, 1e6))
220+
if self.__minus__ is not None:
221+
self.__minus__ = np.divide(self.__minus__, np.divide(size_factor, 1e6))
215222
elif format_ == "cpm" and read_length > 0:
216223
# for cpm the size_factor is total reads
217-
self._plus_ = np.divide(self._plus_, np.divide(size_factor, 1e6))
218-
if self._minus_ is not None:
219-
self._minus_ = np.divide(self._minus_, np.divide(size_factor, 1e6))
224+
self.__plus__ = np.divide(self.__plus__, np.divide(size_factor, 1e6))
225+
if self.__minus__ is not None:
226+
self.__minus__ = np.divide(self.__minus__, np.divide(size_factor, 1e6))
220227
elif size_factor is not None and size_factor > 0 and format_ == "atac":
221-
self._plus_ = np.divide(self._plus_, size_factor) # * 100
222-
if self._minus_ is not None:
223-
self._minus_ = np.divide(self._minus_, size_factor)
228+
self.__plus__ = np.divide(self.__plus__, size_factor) # * 100
229+
if self.__minus__ is not None:
230+
self.__minus__ = np.divide(self.__minus__, size_factor)
224231

225232

226233
if __name__ == '__main__':

trackplot/cli.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,8 @@ def process_file_list(infile: str, category: str = "density"):
315315
help="The junction id for including, chr1:1-100", show_default=True)
316316
@optgroup.option("--show-junction-num", type=click.BOOL, is_flag=True, show_default=True,
317317
help="Whether to show the number of junctions")
318+
@optgroup.option("--show-mean-junction-num", type=click.BOOL, is_flag=True, show_default=True,
319+
help="Whether to show the mean junction count averaged across multiple samples.")
318320
@optgroup.option("--fill-step", type=click.Choice(["pre", "post", "mid"]), default="post", show_default=True,
319321
help="""
320322
Define step if the filling should be a step function, i.e. constant in between x.
@@ -523,8 +525,6 @@ def main(**kwargs):
523525

524526
size_factors = {}
525527

526-
# add annotation
527-
# print(kwargs.keys())
528528
for key in kwargs.keys():
529529
if key in IMAGE_TYPE and kwargs[key] and os.path.exists(kwargs[key]):
530530
if key == "annotation":
@@ -573,6 +573,7 @@ def main(**kwargs):
573573
color=sc_colors.get(group, f.color),
574574
font_size=kwargs["font_size"],
575575
show_junction_number=kwargs["show_junction_num"],
576+
show_mean_jxn_number=kwargs["show_mean_junction_num"],
576577
n_y_ticks=kwargs["n_y_ticks"],
577578
show_y_label=not kwargs["hide_y_label"],
578579
show_site_plot=kwargs["show_site"],
@@ -591,6 +592,7 @@ def main(**kwargs):
591592
color=f.color,
592593
font_size=kwargs["font_size"],
593594
show_junction_number=kwargs["show_junction_num"],
595+
show_mean_jxn_number=kwargs["show_mean_junction_num"],
594596
n_y_ticks=kwargs["n_y_ticks"],
595597
show_y_label=not kwargs["hide_y_label"],
596598
show_site_plot=kwargs["show_site"],

trackplot/file/Bam.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ def check_junction_exists(ref: List[Junction], dst: Junction, with_strand: bool
3232

3333
for i in ref:
3434
if abs(i.start - dst.start) < 2 and abs(i.end - dst.end) < 2:
35-
logger.warning(f"1 bp mismatch between {i} (from bam file) and {dst} (user input), please check the coordinates")
35+
if not with_strand:
36+
# in outer code, this function will called twice, with with_strand = True and False. therefore, only print log once is enough
37+
logger.warning(f"1 bp mismatch between {i} (from bam file) and {dst} (user input), please check the coordinates")
3638

3739
if i.is_downstream(dst):
3840
return False
@@ -269,7 +271,8 @@ def load(self,
269271
logger.info(region)
270272
logger.info(cigar_string)
271273
logger.info(start, i)
272-
exit(err)
274+
logger.error(err)
275+
exit(1)
273276

274277
# remove the deletion.
275278
if cigar not in (1, 4, 5): # I, S, H

trackplot/file/Junction.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
Handle the customized junction
66
"""
77
import os
8-
import re
98
from typing import Dict
109

1110
from trackplot.base.Junction import Junction

0 commit comments

Comments
 (0)