ourotools 0.2.0__py3-none-any.whl
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.
- ourotools/__init__.py +10 -0
- ourotools/__main__.py +10 -0
- ourotools/core/BA.py +225 -0
- ourotools/core/MAP.py +38 -0
- ourotools/core/ONT.py +174 -0
- ourotools/core/OT.py +125 -0
- ourotools/core/SAM.py +588 -0
- ourotools/core/SC.py +647 -0
- ourotools/core/SEQ.py +99 -0
- ourotools/core/STR.py +398 -0
- ourotools/core/__init__.py +3 -0
- ourotools/core/alternative_splicing_analysis.py +903 -0
- ourotools/core/biobookshelf.py +2538 -0
- ourotools/core/core.py +17252 -0
- ourotools-0.2.0.dist-info/LICENSE +21 -0
- ourotools-0.2.0.dist-info/METADATA +545 -0
- ourotools-0.2.0.dist-info/RECORD +20 -0
- ourotools-0.2.0.dist-info/WHEEL +5 -0
- ourotools-0.2.0.dist-info/entry_points.txt +2 -0
- ourotools-0.2.0.dist-info/top_level.txt +1 -0
ourotools/__init__.py
ADDED
ourotools/__main__.py
ADDED
ourotools/core/BA.py
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
from bitarray import bitarray
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def Find(ba, val=1):
|
|
6
|
+
"""deprecated
|
|
7
|
+
# 2022-06-12 21:09:22
|
|
8
|
+
search the given value in the bitarray iteratively and return the start position of the occurrences
|
|
9
|
+
"""
|
|
10
|
+
len_ba = len(ba)
|
|
11
|
+
l_int_pos_occurrence = []
|
|
12
|
+
int_pos_start = 0
|
|
13
|
+
while int_pos_start < len_ba:
|
|
14
|
+
int_pos_occurrence = ba.find(val, int_pos_start + 1)
|
|
15
|
+
if int_pos_occurrence < 0:
|
|
16
|
+
break
|
|
17
|
+
else:
|
|
18
|
+
l_int_pos_occurrence.append(int_pos_occurrence)
|
|
19
|
+
int_pos_start = int_pos_occurrence
|
|
20
|
+
return l_int_pos_occurrence
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def find(ba, val=1):
|
|
24
|
+
"""# 2022-07-03 21:41:36
|
|
25
|
+
generator that returns the location of 'val' from the start of the bitarray.
|
|
26
|
+
Use this function for memory-efficient iteration of position of active entries in a bitarray (np.int64 takes 64 times more memory than an entry in bitarray, which is 1 bit).
|
|
27
|
+
"""
|
|
28
|
+
len_ba = len(ba)
|
|
29
|
+
if len(ba) == 0: # handle empty bitarray input
|
|
30
|
+
return
|
|
31
|
+
int_pos_start = 0
|
|
32
|
+
if ba[0] == val: # handle the case when the first bit is equal to 'val'
|
|
33
|
+
yield int_pos_start
|
|
34
|
+
while int_pos_start < len_ba:
|
|
35
|
+
int_pos_occurrence = ba.find(val, int_pos_start + 1)
|
|
36
|
+
if int_pos_occurrence < 0:
|
|
37
|
+
break
|
|
38
|
+
else:
|
|
39
|
+
yield int_pos_occurrence
|
|
40
|
+
int_pos_start = int_pos_occurrence
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# def Find_Segment( ba, background = 1, flag_use_bitwise_operation = True ) :
|
|
44
|
+
# ''' # 2022-06-12 19:36:38
|
|
45
|
+
# find segment of a given bitarray for the given background 'background'. for example, when background = 1, find segment of 0
|
|
46
|
+
|
|
47
|
+
# 'flag_use_bitwise_operation' : use bitwise operation. useful when the length of bitarray is very long
|
|
48
|
+
# '''
|
|
49
|
+
# len_ba = len( ba )
|
|
50
|
+
# if flag_use_bitwise_operation :
|
|
51
|
+
# ''' the implementation using bitwise operation '''
|
|
52
|
+
# mask = ( ba ^ ba >> 1 ) # find boundary of value change
|
|
53
|
+
# l_int_pos = Find( mask, 1 )
|
|
54
|
+
# flag_start_is_a_segment = ba[ 0 ] != background
|
|
55
|
+
# if flag_start_is_a_segment :
|
|
56
|
+
# l_int_pos = [ 0 ] + l_int_pos
|
|
57
|
+
# if len( l_int_pos ) % 2 != 0 :
|
|
58
|
+
# l_int_pos += [ len_ba ]
|
|
59
|
+
# return np.array( l_int_pos ).reshape( ( int( len( l_int_pos ) / 2 ), 2 ) )
|
|
60
|
+
# else :
|
|
61
|
+
# ''' the implementation using simple iteration '''
|
|
62
|
+
# int_pos = 0
|
|
63
|
+
# int_seg_start = None
|
|
64
|
+
# l_seg = [ ]
|
|
65
|
+
# while int_pos < len_ba :
|
|
66
|
+
# if int_seg_start is None and ba[ int_pos ] != background :
|
|
67
|
+
# int_seg_start = int_pos
|
|
68
|
+
# elif int_seg_start is not None and ba[ int_pos ] == background :
|
|
69
|
+
# l_seg.append( [ int_seg_start, int_pos ] )
|
|
70
|
+
# int_seg_start = None
|
|
71
|
+
# int_pos += 1
|
|
72
|
+
# if int_seg_start is not None :
|
|
73
|
+
# l_seg.append( [ int_seg_start, len_ba ] )
|
|
74
|
+
# return l_seg
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def Find_Segment(ba, background=1):
|
|
78
|
+
"""# 2022-06-12 19:36:38
|
|
79
|
+
find segment of a given bitarray for the given background 'background'. for example, when background = 1, find segment of 0
|
|
80
|
+
|
|
81
|
+
# 2022-06-22 18:36:45
|
|
82
|
+
updated to a new implementation that does not require generating a copy of the bitarray.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
def toggle_bit(bit):
|
|
86
|
+
return (bit + 1) % 2 # toggle the bit
|
|
87
|
+
|
|
88
|
+
# initialize
|
|
89
|
+
l_int_pos = []
|
|
90
|
+
state_of_interest = toggle_bit(background) # look for a non-background state
|
|
91
|
+
int_pos = 0
|
|
92
|
+
|
|
93
|
+
while True:
|
|
94
|
+
int_pos = ba.find(state_of_interest, int_pos)
|
|
95
|
+
if int_pos < 0:
|
|
96
|
+
break
|
|
97
|
+
l_int_pos.append(int_pos)
|
|
98
|
+
state_of_interest = toggle_bit(
|
|
99
|
+
state_of_interest
|
|
100
|
+
) # toggle the state of interest
|
|
101
|
+
if len(l_int_pos) % 2 != 0:
|
|
102
|
+
l_int_pos.append(len(ba))
|
|
103
|
+
return np.array(l_int_pos).reshape(
|
|
104
|
+
(int(len(l_int_pos) / 2), 2)
|
|
105
|
+
) # reshape a list of int_pos to list of segments
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def Retrieve_Integer_Indices(ba, background=0):
|
|
109
|
+
"""deprecated (slow)
|
|
110
|
+
# 2022-06-23 08:31:45
|
|
111
|
+
returns a list of integer indices of non-background bits in a given bitarray 'ba'
|
|
112
|
+
|
|
113
|
+
'background' : a bit representing background. either 0 or 1
|
|
114
|
+
"""
|
|
115
|
+
# compose a list of integer indices of active rows after applying filter
|
|
116
|
+
l = []
|
|
117
|
+
for st, en in Find_Segment(
|
|
118
|
+
ba, background=background
|
|
119
|
+
): # retrieve active segments from bitarray filter
|
|
120
|
+
l.extend(range(st, en)) # retrieve integer indices of the active rows
|
|
121
|
+
return l
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def to_array(ba):
|
|
125
|
+
"""# 2022-06-24 23:54:12
|
|
126
|
+
return a boolean numpy array of the given bitarray"""
|
|
127
|
+
return np.frombuffer(ba.unpack(), dtype=bool)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def to_bitarray(arr_bool):
|
|
131
|
+
"""# 2022-06-27 15:29:56
|
|
132
|
+
convert numpy boolean array to bitarray
|
|
133
|
+
"""
|
|
134
|
+
ba = bitarray()
|
|
135
|
+
ba.pack(arr_bool.tobytes())
|
|
136
|
+
return ba
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def from_integer_indices_to_bitarray(l_int_index, length):
|
|
140
|
+
"""# 2022-07-01 11:21:12
|
|
141
|
+
convert list of integer indices to bitarray
|
|
142
|
+
|
|
143
|
+
'length' : length of the output bitarray object
|
|
144
|
+
"""
|
|
145
|
+
ba = bitarray(length)
|
|
146
|
+
ba.setall(0)
|
|
147
|
+
for int_index in l_int_index:
|
|
148
|
+
ba[int_index] = True
|
|
149
|
+
return ba
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def to_integer_indices(ba):
|
|
153
|
+
"""# 2022-07-01 21:38:17
|
|
154
|
+
retrieve integer indices of the active entries of the given bitarray
|
|
155
|
+
|
|
156
|
+
'ba' : input bitarray object
|
|
157
|
+
"""
|
|
158
|
+
return np.where(to_array(ba))[0]
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def COUNTER(l_values, dict_counter=None, ignore_float=True): # 2020-07-29 23:49:51
|
|
162
|
+
"""Count values in l_values and return a dictionary containing count values. if 'dict_counter' is given, countinue counting by using the 'dict_counter'. if 'ignore_float' is True, ignore float values, including np.nan"""
|
|
163
|
+
if dict_counter is None:
|
|
164
|
+
dict_counter = dict()
|
|
165
|
+
if ignore_float: # if 'ignore_float' is True, ignore float values, including np.nan
|
|
166
|
+
for value in l_values:
|
|
167
|
+
if isinstance(value, float):
|
|
168
|
+
continue # ignore float values
|
|
169
|
+
if value in dict_counter:
|
|
170
|
+
dict_counter[value] += 1
|
|
171
|
+
else:
|
|
172
|
+
dict_counter[value] = 1
|
|
173
|
+
else: # faster counting by not checking type of value
|
|
174
|
+
for value in l_values:
|
|
175
|
+
if value in dict_counter:
|
|
176
|
+
dict_counter[value] += 1
|
|
177
|
+
else:
|
|
178
|
+
dict_counter[value] = 1
|
|
179
|
+
return dict_counter
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def detect_boolean_mask(ba):
|
|
183
|
+
"""# 2022-08-10 23:29:06
|
|
184
|
+
detect boolean mask by looking up to 10 values
|
|
185
|
+
"""
|
|
186
|
+
# extract the first row from the ndarray data
|
|
187
|
+
if isinstance(ba, np.ndarray) and len(ba.shape) > 1:
|
|
188
|
+
for i in range(len(ba.shape) - 1):
|
|
189
|
+
ba = ba[0]
|
|
190
|
+
if not hasattr(ba, "__iter__"): # 'ba' should be iterable to be a boolean mask
|
|
191
|
+
return False
|
|
192
|
+
ba = ba[:10]
|
|
193
|
+
# if the length of array of interest is <= 2 and only consists of 0, 1, consider it as an integer array, not boolean array
|
|
194
|
+
if len(ba) <= 2 and set(COUNTER(ba[:10])).issubset({0, 1}):
|
|
195
|
+
return False
|
|
196
|
+
return set(COUNTER(ba[:10])).issubset({0, 1, True, False})
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def convert_mask_to_bitarray(ba):
|
|
200
|
+
"""# 2022-07-03 00:54:46
|
|
201
|
+
convert boolean mask to a bitarray
|
|
202
|
+
"""
|
|
203
|
+
# handle when a list type has been given (convert it to np.ndarray)
|
|
204
|
+
if isinstance(ba, list):
|
|
205
|
+
ba = np.array(ba, dtype=bool)
|
|
206
|
+
# handle when a numpy ndarray has been given (convert it to bitarray)
|
|
207
|
+
if isinstance(ba, np.ndarray):
|
|
208
|
+
ba = to_bitarray(ba)
|
|
209
|
+
assert isinstance(ba, bitarray) # check the return value is bitarray object
|
|
210
|
+
return ba
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def convert_mask_to_array(ba):
|
|
214
|
+
"""# 2022-07-03 00:54:46
|
|
215
|
+
convert boolean mask to a array
|
|
216
|
+
"""
|
|
217
|
+
""" handle non-bitarray mask types """
|
|
218
|
+
# handle when a list type has been given (convert it to np.ndarray)
|
|
219
|
+
if isinstance(ba, list):
|
|
220
|
+
ba = np.array(ba, dtype=bool)
|
|
221
|
+
# handle when a numpy ndarray has been given (convert it to np.ndarray)
|
|
222
|
+
if isinstance(ba, bitarray):
|
|
223
|
+
ba = to_array(ba)
|
|
224
|
+
assert isinstance(ba, np.ndarray) # check the return value is np.ndarray object
|
|
225
|
+
return ba
|
ourotools/core/MAP.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# In[ ]:
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
dict_a2b = dict() # an empty dictionary for mapping
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# In[ ]:
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def Remove_version_info(ID):
|
|
14
|
+
return ID.split(".")[0]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# In[ ]:
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def Retrive_Length(entry):
|
|
21
|
+
return len(entry)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Map(object):
|
|
25
|
+
def __init__(self, dict_a2b):
|
|
26
|
+
self.dict_a2b = dict_a2b
|
|
27
|
+
|
|
28
|
+
def a2b(self, a):
|
|
29
|
+
if a in self.dict_a2b:
|
|
30
|
+
return self.dict_a2b[a]
|
|
31
|
+
else:
|
|
32
|
+
return np.nan
|
|
33
|
+
|
|
34
|
+
def a2b_if_mapping_available_else_Map_a2a(self, a):
|
|
35
|
+
if a in self.dict_a2b:
|
|
36
|
+
return self.dict_a2b[a]
|
|
37
|
+
else:
|
|
38
|
+
return a
|
ourotools/core/ONT.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# load internal module
|
|
2
|
+
from .biobookshelf import *
|
|
3
|
+
from . import biobookshelf as bk
|
|
4
|
+
from typing import Union, List, Literal, Dict, Callable, Set, Iterable, Tuple
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def Minimap2_Align(
|
|
8
|
+
path_file_fastq,
|
|
9
|
+
path_file_minimap2_index="/node210data/shared/ensembl/Mus_musculus/index/minimap2/Mus_musculus.GRCm38.dna.primary_assembly.k_14.idx",
|
|
10
|
+
path_folder_minimap2_output=None,
|
|
11
|
+
n_threads=20,
|
|
12
|
+
verbose=True,
|
|
13
|
+
drop_unaligned=False,
|
|
14
|
+
return_bash_shellscript=False,
|
|
15
|
+
n_threads_for_sort=10,
|
|
16
|
+
flag_use_split_prefix: bool = False,
|
|
17
|
+
path_file_junc_bed: Union[
|
|
18
|
+
None, str
|
|
19
|
+
] = None, # if given, the bed file will be used for prioritizing known splice sites.
|
|
20
|
+
path_file_gtf: Union[
|
|
21
|
+
None, str
|
|
22
|
+
] = None, # path to gene and exon annotation files, required if 'path_file_junc_bed' is given but the file does not exist
|
|
23
|
+
):
|
|
24
|
+
"""
|
|
25
|
+
# 2023-04-23 01:18:58
|
|
26
|
+
align given fastq file of nanopore reads using minimap2 and write an output as a bam file
|
|
27
|
+
'path_file_fastq' : input fastq or fasta file (gzipped or uncompressed file is accepted)
|
|
28
|
+
'path_file_minimap2_index' : minimap2 index file
|
|
29
|
+
'path_folder_minimap2_output' : minimap2 output folder
|
|
30
|
+
'drop_unaligned' : a flag indicating whether reads not aligned to the reference ('SAM flag == 4') are included in the output bam file
|
|
31
|
+
'return_bash_shellscript' : return shellscript instead of running minimap2 using the subprocess module
|
|
32
|
+
'flag_use_split_prefix' = False # for large index, split-prefix should be used
|
|
33
|
+
"""
|
|
34
|
+
path_folder_fastq, name_file_fastq = path_file_fastq.rsplit("/", 1)
|
|
35
|
+
if (
|
|
36
|
+
path_folder_minimap2_output is None
|
|
37
|
+
): # default output folder is a subdirectory of the folder containing the input fastq file
|
|
38
|
+
path_folder_minimap2_output = f"{path_folder_fastq}/minimap2/"
|
|
39
|
+
if (
|
|
40
|
+
path_folder_minimap2_output[-1] != "/"
|
|
41
|
+
): # add '/' at the end of the output directory if it does not exist
|
|
42
|
+
path_folder_minimap2_output += "/"
|
|
43
|
+
os.makedirs(
|
|
44
|
+
path_folder_minimap2_output, exist_ok=True
|
|
45
|
+
) # create folder if it does not exist
|
|
46
|
+
|
|
47
|
+
path_file_sam = (
|
|
48
|
+
f"{path_folder_minimap2_output}{name_file_fastq}.minimap2_aligned.sam"
|
|
49
|
+
)
|
|
50
|
+
path_file_bam = (
|
|
51
|
+
f"{path_folder_minimap2_output}{name_file_fastq}.minimap2_aligned.bam"
|
|
52
|
+
)
|
|
53
|
+
# if index file of the output BAM file exists, exit
|
|
54
|
+
if os.path.exists( f"{path_file_bam}.bai" ) :
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
l_bash_shellscript = []
|
|
58
|
+
|
|
59
|
+
""" perform minimap2 alignment """
|
|
60
|
+
l_arg = [
|
|
61
|
+
"minimap2",
|
|
62
|
+
"-t",
|
|
63
|
+
str(int(n_threads)),
|
|
64
|
+
"-ax",
|
|
65
|
+
"splice",
|
|
66
|
+
"-o",
|
|
67
|
+
path_file_sam,
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
# for large index, split-prefix should be used
|
|
71
|
+
if flag_use_split_prefix:
|
|
72
|
+
l_arg += [f"--split-prefix={path_folder_minimap2_output}{UUID( )}"]
|
|
73
|
+
|
|
74
|
+
if path_file_junc_bed is not None:
|
|
75
|
+
if (
|
|
76
|
+
not os.path.exists(path_file_junc_bed) and path_file_gtf is not None
|
|
77
|
+
): # if the bed file does not exist, create the bed file using paftools.js, packaged with the minimap2 executable
|
|
78
|
+
l_args_for_creating_junc_bed = ["paftools.js", "gff2bed", path_file_gtf]
|
|
79
|
+
if (
|
|
80
|
+
return_bash_shellscript
|
|
81
|
+
): # perform minimap2 alignment using subprocess module
|
|
82
|
+
l_bash_shellscript.append(
|
|
83
|
+
" ".join(l_args_for_creating_junc_bed + [">", path_file_junc_bed])
|
|
84
|
+
)
|
|
85
|
+
else:
|
|
86
|
+
bk.OS_Run(
|
|
87
|
+
l_args_for_creating_junc_bed,
|
|
88
|
+
path_file_stdout=path_file_junc_bed,
|
|
89
|
+
stdout_binary=False,
|
|
90
|
+
)
|
|
91
|
+
if os.path.exists(path_file_junc_bed):
|
|
92
|
+
l_arg += ["--junc-bed", path_file_junc_bed]
|
|
93
|
+
|
|
94
|
+
if drop_unaligned:
|
|
95
|
+
l_arg += ["--sam-hit-only"]
|
|
96
|
+
l_arg += [path_file_minimap2_index, path_file_fastq]
|
|
97
|
+
if return_bash_shellscript: # perform minimap2 alignment using subprocess module
|
|
98
|
+
l_bash_shellscript.append(" ".join(l_arg))
|
|
99
|
+
else:
|
|
100
|
+
run = subprocess.run(l_arg, capture_output=True)
|
|
101
|
+
with open(
|
|
102
|
+
f"{path_folder_minimap2_output}{name_file_fastq}.minimap2_aligned.out", "w"
|
|
103
|
+
) as file:
|
|
104
|
+
file.write(run.stdout.decode())
|
|
105
|
+
if verbose:
|
|
106
|
+
print("minimap2 completed")
|
|
107
|
+
|
|
108
|
+
""" sort output SAM file """
|
|
109
|
+
l_arg = [
|
|
110
|
+
"samtools",
|
|
111
|
+
"sort",
|
|
112
|
+
"-@",
|
|
113
|
+
str(int(min(n_threads_for_sort, 10))),
|
|
114
|
+
"-O",
|
|
115
|
+
"BAM",
|
|
116
|
+
"-o",
|
|
117
|
+
path_file_bam,
|
|
118
|
+
path_file_sam,
|
|
119
|
+
]
|
|
120
|
+
if return_bash_shellscript: # perform minimap2 alignment using subprocess module
|
|
121
|
+
l_bash_shellscript.append(" ".join(l_arg))
|
|
122
|
+
l_bash_shellscript.append(" ".join(["rm", "-f", path_file_sam]))
|
|
123
|
+
else:
|
|
124
|
+
run = subprocess.run(l_arg, capture_output=False)
|
|
125
|
+
os.remove(path_file_sam) # remove sam file
|
|
126
|
+
|
|
127
|
+
""" index resulting BAM file """
|
|
128
|
+
l_arg = ["samtools", "index", path_file_bam]
|
|
129
|
+
if return_bash_shellscript: # perform minimap2 alignment using subprocess module
|
|
130
|
+
l_bash_shellscript.append(" ".join(l_arg))
|
|
131
|
+
else:
|
|
132
|
+
run = subprocess.run(l_arg, capture_output=False)
|
|
133
|
+
if verbose:
|
|
134
|
+
print("samtools bam file compressing and indexing completed")
|
|
135
|
+
|
|
136
|
+
if return_bash_shellscript: # retrun bash shell scripts
|
|
137
|
+
return " && ".join(l_bash_shellscript)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def Minimap2_Index(path_file_fasta, path_file_minimap2_index=None, verbose=False):
|
|
141
|
+
"""
|
|
142
|
+
# 2021-03-24 00:44:51
|
|
143
|
+
index given fasta file for nanopore reads alignment
|
|
144
|
+
'path_file_fasta' : input reference fasta file
|
|
145
|
+
'path_file_minimap2_index' : minimap2 index file
|
|
146
|
+
"""
|
|
147
|
+
path_folder_fastq, name_file_fasta = path_file_fasta.rsplit("/", 1)
|
|
148
|
+
if (
|
|
149
|
+
path_file_minimap2_index is None
|
|
150
|
+
): # set the default directory of the minimap index
|
|
151
|
+
path_file_minimap2_index = (
|
|
152
|
+
f"{path_folder_fastq}/index/minimap2/{name_file_fasta}.ont.mmi"
|
|
153
|
+
)
|
|
154
|
+
path_folder_minimap2_index, name_file_index = path_file_minimap2_index.rsplit(
|
|
155
|
+
"/", 1
|
|
156
|
+
)
|
|
157
|
+
path_folder_minimap2_index += "/"
|
|
158
|
+
os.makedirs(
|
|
159
|
+
path_folder_minimap2_index, exist_ok=True
|
|
160
|
+
) # create folder if it does not exist
|
|
161
|
+
if os.path.exists(path_file_minimap2_index): # exit if an index file already exists
|
|
162
|
+
return
|
|
163
|
+
# build minimap2 index
|
|
164
|
+
run = subprocess.run(
|
|
165
|
+
["minimap2", "-x", "map-ont", "-d", path_file_minimap2_index, path_file_fasta],
|
|
166
|
+
capture_output=True,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
with open(
|
|
170
|
+
f"{path_folder_minimap2_index}{name_file_index}.minimap2_index.out", "w"
|
|
171
|
+
) as file:
|
|
172
|
+
file.write(run.stdout.decode())
|
|
173
|
+
if verbose:
|
|
174
|
+
print("minimap2 indexing completed")
|
ourotools/core/OT.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
from . import biobookshelf as bk
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Implementing Ontology functions
|
|
5
|
+
"""
|
|
6
|
+
import owlready2 as ol
|
|
7
|
+
|
|
8
|
+
class OntologyTerms :
|
|
9
|
+
def __init__( self, path_file_owl : str, name_prefix : str, name_root_term : str ) :
|
|
10
|
+
"""
|
|
11
|
+
load an ontology file, given as 'path_file_owl'
|
|
12
|
+
|
|
13
|
+
name_prefix : str # prefix of the name
|
|
14
|
+
name_root_term : str # name of the root term
|
|
15
|
+
# 2024-02-29 22:50:55
|
|
16
|
+
"""
|
|
17
|
+
self.path_file_owl = path_file_owl
|
|
18
|
+
self.name_prefix = name_prefix
|
|
19
|
+
self.name_root_term = name_root_term
|
|
20
|
+
self.onto = ol.get_ontology(f"file://{path_file_owl}").load( )
|
|
21
|
+
self._set_terms = set( self.onto.classes( ) )
|
|
22
|
+
self._root_term = self[ self.name_root_term ]
|
|
23
|
+
def __repr__( self ) :
|
|
24
|
+
return f"<{len( self._set_terms )} ontology terms stored at {self.path_file_owl}>"
|
|
25
|
+
def __contains__( self, term ) :
|
|
26
|
+
"""
|
|
27
|
+
# 2024-03-01 21:35:55
|
|
28
|
+
"""
|
|
29
|
+
return self[ term ] in self._set_terms
|
|
30
|
+
def __getitem__( self, ontology_id : str ) :
|
|
31
|
+
"""
|
|
32
|
+
get ontology term using an ID
|
|
33
|
+
# 2024-02-29 22:51:49
|
|
34
|
+
"""
|
|
35
|
+
# handle 'ontology_id' that is not a string
|
|
36
|
+
if not isinstance( ontology_id, str ) :
|
|
37
|
+
ontology_id
|
|
38
|
+
l = self.onto.search( iri = f"*{ontology_id}*")
|
|
39
|
+
if len( l ) == 0 :
|
|
40
|
+
return None
|
|
41
|
+
elif len( l ) == 1 :
|
|
42
|
+
return l[ 0 ]
|
|
43
|
+
else :
|
|
44
|
+
return l # if more then one terms are matched, return more than one elements
|
|
45
|
+
def __iter__( self ) :
|
|
46
|
+
"""
|
|
47
|
+
return an iterater returning each class
|
|
48
|
+
# 2024-03-01 00:54:16
|
|
49
|
+
"""
|
|
50
|
+
return self.onto.classes( )
|
|
51
|
+
def get_ancestor_chain( self, term ) :
|
|
52
|
+
"""
|
|
53
|
+
get a chain of ancestors (excluding restriction objects), excluding self, from the most distant ancestor (owl.Thing) to the closest ancestor.
|
|
54
|
+
Note)
|
|
55
|
+
This function utilizes a recursive algorithm to explore the tree structure.
|
|
56
|
+
# 2024-03-01 21:45:16
|
|
57
|
+
"""
|
|
58
|
+
# get the ontology term
|
|
59
|
+
term = self[ term ]
|
|
60
|
+
# initialize the 'l_ancestor'
|
|
61
|
+
def get_superclasses( term ) :
|
|
62
|
+
"""
|
|
63
|
+
get filtered super classes of a term
|
|
64
|
+
# 2024-03-01 23:15:04
|
|
65
|
+
"""
|
|
66
|
+
return list( e for e in term.is_a if hasattr( e, 'name' ) and e.name[ : len( self.name_prefix ) ] == self.name_prefix )
|
|
67
|
+
def get_ancestor_chains( term ) :
|
|
68
|
+
l_ancestor_chain = [ ]
|
|
69
|
+
l_term_super = get_superclasses( term )
|
|
70
|
+
# termination condition
|
|
71
|
+
if len( l_term_super ) == 0 :
|
|
72
|
+
if term == self._root_term :
|
|
73
|
+
return [ [ ] ]
|
|
74
|
+
else : # if the chain terminate with a term that is not a root term, add the root term
|
|
75
|
+
return [ [ self._root_term ] ]
|
|
76
|
+
# recursive condition
|
|
77
|
+
for e in l_term_super :
|
|
78
|
+
for l_ancestor in get_ancestor_chains( e ) :
|
|
79
|
+
l_ancestor_chain.append( [ e ] + l_ancestor )
|
|
80
|
+
return l_ancestor_chain
|
|
81
|
+
# reverse the order (from the most distant ancestor (the root term) to the closest ancestor)
|
|
82
|
+
l_ancestor_chain_reverse_order = get_ancestor_chains( term )
|
|
83
|
+
l_ancestor_chain = [ ] # l_ancestor_chain
|
|
84
|
+
for ancestor_chain_reverse_order in l_ancestor_chain_reverse_order :
|
|
85
|
+
l_ancestor_chain.append( ancestor_chain_reverse_order[ : : -1 ] )
|
|
86
|
+
return l_ancestor_chain
|
|
87
|
+
def get_longest_shared_ancestor_chains( self, term_1, term_2 ) -> set :
|
|
88
|
+
"""
|
|
89
|
+
return the ancestor chains to the most closest shared ancestors between the term1 and term2
|
|
90
|
+
|
|
91
|
+
# 2024-02-29 22:58:13
|
|
92
|
+
"""
|
|
93
|
+
# retrieve ancestor chains
|
|
94
|
+
l_ancestor_chain_1 = self.get_ancestor_chain( term_1 )
|
|
95
|
+
l_ancestor_chain_2 = self.get_ancestor_chain( term_2 )
|
|
96
|
+
|
|
97
|
+
# collect the ancestor chains to the most closest shared ancestors
|
|
98
|
+
set_ancestor_chain_to_most_closest_shared_ancestor = set( ) # initialize 'set_ancestor_chain_to_most_closest_shared_ancestor'
|
|
99
|
+
for ancestor_chain_1 in l_ancestor_chain_1 : # iterate over chain list #1
|
|
100
|
+
# find the chain in the chain list # 2 that contains the longest shared chain with the chain in the chain list #1
|
|
101
|
+
l_index_most_closest_shared_ancestor = [ ]
|
|
102
|
+
for ancestor_chain_2 in l_ancestor_chain_2 : # iterate over chain list #2
|
|
103
|
+
index_most_closest_shared_ancestor = 0 # initialize the index that indicate the location of the most closest shared ancestor between the two chains # initialize with the index of the root term
|
|
104
|
+
for ancestor_1, ancestor_2 in zip( ancestor_chain_1[ 1 : ], ancestor_chain_2[ 1 : ] ) : # retrieve ancester from chain #1 and chain #2 (from the most distant ancestor (excluding the root term) to the closest ancestor)
|
|
105
|
+
if ancestor_1 != ancestor_2 : # if the ancestors diverged between chain_1 and chain_2
|
|
106
|
+
break
|
|
107
|
+
index_most_closest_shared_ancestor += 1 # increase the pointer (take into account the current shared ancestor)
|
|
108
|
+
l_index_most_closest_shared_ancestor.append( index_most_closest_shared_ancestor )
|
|
109
|
+
index_chain_2_with_most_closest_shared_ancestor = np.argmax( l_index_most_closest_shared_ancestor ) # define 'most closest shared ancestor' as the ancestor that has the the largest number of ancestors between itself and the root term.
|
|
110
|
+
ancestor_chain_to_most_closest_shared_ancestor = tuple( l_ancestor_chain_2[ index_chain_2_with_most_closest_shared_ancestor ][ : l_index_most_closest_shared_ancestor[ index_chain_2_with_most_closest_shared_ancestor ] + 1 ] ) # including the most_closest_shared_ancestor in the chain
|
|
111
|
+
set_ancestor_chain_to_most_closest_shared_ancestor.add( ancestor_chain_to_most_closest_shared_ancestor )
|
|
112
|
+
return set_ancestor_chain_to_most_closest_shared_ancestor
|
|
113
|
+
def get_properties( self, term ) :
|
|
114
|
+
"""
|
|
115
|
+
return the properties of the termk
|
|
116
|
+
# 2024-03-01 12:51:18
|
|
117
|
+
"""
|
|
118
|
+
# get ontology terms
|
|
119
|
+
term = self[ term ]
|
|
120
|
+
# retrieve properties
|
|
121
|
+
l_label, l_comment, l_broadsynonym, l_exactsynonym = list( set( term.label ) ), list( set( term.comment ) ), list( set( term.hasBroadSynonym ) ), list( set( term.hasExactSynonym ) )
|
|
122
|
+
def _parse_property( l ) :
|
|
123
|
+
return l[ 0 ] if len( l ) > 0 else None
|
|
124
|
+
dict_property = { 'label' : _parse_property( l_label ), 'comment' : _parse_property( l_comment ), 'broad_synonym' : l_broadsynonym, 'exact_synonym' : l_exactsynonym }
|
|
125
|
+
return dict_property
|