RNApolis 0.10.5__py3-none-any.whl → 0.10.7__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.
- rnapolis/adapter.py +357 -6
- rnapolis/common.py +21 -13
- {rnapolis-0.10.5.dist-info → rnapolis-0.10.7.dist-info}/METADATA +1 -1
- {rnapolis-0.10.5.dist-info → rnapolis-0.10.7.dist-info}/RECORD +8 -8
- {rnapolis-0.10.5.dist-info → rnapolis-0.10.7.dist-info}/WHEEL +0 -0
- {rnapolis-0.10.5.dist-info → rnapolis-0.10.7.dist-info}/entry_points.txt +0 -0
- {rnapolis-0.10.5.dist-info → rnapolis-0.10.7.dist-info}/licenses/LICENSE +0 -0
- {rnapolis-0.10.5.dist-info → rnapolis-0.10.7.dist-info}/top_level.txt +0 -0
rnapolis/adapter.py
CHANGED
|
@@ -8,7 +8,7 @@ from collections import defaultdict
|
|
|
8
8
|
from dataclasses import dataclass
|
|
9
9
|
from enum import Enum
|
|
10
10
|
from tempfile import NamedTemporaryFile
|
|
11
|
-
from typing import DefaultDict, Dict, List, Optional, Set, Tuple
|
|
11
|
+
from typing import Any, DefaultDict, Dict, List, Optional, Set, Tuple, Union
|
|
12
12
|
|
|
13
13
|
import orjson
|
|
14
14
|
|
|
@@ -49,6 +49,7 @@ class ExternalTool(Enum):
|
|
|
49
49
|
BPNET = "bpnet"
|
|
50
50
|
MAXIT = "maxit"
|
|
51
51
|
BARNABA = "barnaba"
|
|
52
|
+
MCANNOTATE = "mc-annotate"
|
|
52
53
|
|
|
53
54
|
|
|
54
55
|
logging.basicConfig(level=os.getenv("LOGLEVEL", "INFO").upper())
|
|
@@ -68,25 +69,30 @@ def auto_detect_tool(external_files: List[str]) -> ExternalTool:
|
|
|
68
69
|
return ExternalTool.MAXIT
|
|
69
70
|
|
|
70
71
|
for file_path in external_files:
|
|
72
|
+
basename = os.path.basename(file_path)
|
|
73
|
+
|
|
71
74
|
# Check for FR3D pattern
|
|
72
|
-
if
|
|
75
|
+
if basename.endswith("basepair_detail.txt"):
|
|
73
76
|
return ExternalTool.FR3D
|
|
74
77
|
|
|
75
78
|
# Check for RNAView pattern
|
|
76
|
-
if
|
|
79
|
+
if basename.endswith(".out"):
|
|
77
80
|
return ExternalTool.RNAVIEW
|
|
78
81
|
|
|
79
82
|
# Check for BPNet pattern
|
|
80
|
-
if
|
|
83
|
+
if basename.endswith("basepair.json"):
|
|
81
84
|
return ExternalTool.BPNET
|
|
82
85
|
|
|
86
|
+
# Check for MC-Annotate pattern
|
|
87
|
+
if basename.endswith("stdout.txt"):
|
|
88
|
+
return ExternalTool.MCANNOTATE
|
|
89
|
+
|
|
83
90
|
# Check for Barnaba pattern
|
|
84
|
-
basename = os.path.basename(file_path)
|
|
85
91
|
if "pairing" in basename or "stacking" in basename:
|
|
86
92
|
return ExternalTool.BARNABA
|
|
87
93
|
|
|
88
94
|
# Check for JSON files (DSSR)
|
|
89
|
-
if
|
|
95
|
+
if basename.endswith(".json"):
|
|
90
96
|
return ExternalTool.DSSR
|
|
91
97
|
|
|
92
98
|
# Default to MAXIT if no patterns match
|
|
@@ -1157,6 +1163,346 @@ def parse_barnaba_output(
|
|
|
1157
1163
|
)
|
|
1158
1164
|
|
|
1159
1165
|
|
|
1166
|
+
class MCAnnotateAdapter:
|
|
1167
|
+
# Represents state of parsing MC-Annotate result
|
|
1168
|
+
# Luckily every important part of file
|
|
1169
|
+
# begins with a unique sentence
|
|
1170
|
+
class ParseState(str, Enum):
|
|
1171
|
+
RESIDUES_INFORMATION = "Residue conformations"
|
|
1172
|
+
ADJACENT_STACKINGS = "Adjacent stackings"
|
|
1173
|
+
NON_ADJACENT_STACKINGS = "Non-Adjacent stackings"
|
|
1174
|
+
BASE_PAIRS_SECTION = "Base-pairs"
|
|
1175
|
+
SUMMARY_SECTION = "Number of"
|
|
1176
|
+
|
|
1177
|
+
# This dictionary maps our model edges
|
|
1178
|
+
# to edge representation used by MC-Annotate
|
|
1179
|
+
EDGES: Dict[str, Tuple[str, ...]] = {
|
|
1180
|
+
"H": ("Hh", "Hw", "Bh", "C8"),
|
|
1181
|
+
"W": ("Wh", "Ww", "Ws"),
|
|
1182
|
+
"S": ("Ss", "Sw", "Bs"),
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
# Contains flatten EDGES values (in one touple)
|
|
1186
|
+
ALL_EDGES = sum(EDGES.values(), ())
|
|
1187
|
+
|
|
1188
|
+
# Based on these tokens
|
|
1189
|
+
# BaseRiboseInteractions and BasePhosphateInteractions are created
|
|
1190
|
+
RIBOSE_ATOM = "O2'"
|
|
1191
|
+
PHOSPHATE_ATOM = "O2P"
|
|
1192
|
+
|
|
1193
|
+
# Single hydrogen bond - for us it's OtherInteraction
|
|
1194
|
+
ONE_HBOND = "one_hbond"
|
|
1195
|
+
|
|
1196
|
+
# Cis/trans tokens used by MC-Annotate
|
|
1197
|
+
CIS = "cis"
|
|
1198
|
+
TRANS = "trans"
|
|
1199
|
+
|
|
1200
|
+
# Tokens used in PDB files
|
|
1201
|
+
ATOM = "ATOM"
|
|
1202
|
+
HETATM = "HETATM"
|
|
1203
|
+
|
|
1204
|
+
# This regex is used to capture 6 groups of residues information:
|
|
1205
|
+
# (1) (2) (3) (4) (5) (6)
|
|
1206
|
+
# 1, 4 - chain IDs
|
|
1207
|
+
# 2, 5 - numbers
|
|
1208
|
+
# 3, 6 - icodes (or empty string if no icode)
|
|
1209
|
+
# Example - match and groups:
|
|
1210
|
+
# A-100.X-B200
|
|
1211
|
+
# ('A'), ('-100'), ('X'), ('B'), ('200'), ('')
|
|
1212
|
+
RESIDUE_REGEX = re.compile(
|
|
1213
|
+
r"'?(.)'?(-?[0-9]+)\.?([a-zA-Z]?)-'?(.)'?(-?[0-9]+)\.?([a-zA-Z]?)"
|
|
1214
|
+
)
|
|
1215
|
+
|
|
1216
|
+
# Roman numerals used by Saenger
|
|
1217
|
+
# both in our model and MC-Annotate
|
|
1218
|
+
ROMAN_NUMERALS = ("I", "V", "X")
|
|
1219
|
+
|
|
1220
|
+
# Positions of residues info in PDB files
|
|
1221
|
+
CHAIN_INDEX = 21
|
|
1222
|
+
NUMBER_INDEX = slice(22, 26)
|
|
1223
|
+
ICODE_INDEX = 26
|
|
1224
|
+
NAME_INDEX = slice(17, 20)
|
|
1225
|
+
|
|
1226
|
+
def __init__(self) -> None:
|
|
1227
|
+
# Since names are not present in adjacent and non-adjacent stackings
|
|
1228
|
+
# we need save these values eariler
|
|
1229
|
+
self.names: Dict[str, str] = {}
|
|
1230
|
+
self.base_pairs: List[BasePair] = []
|
|
1231
|
+
self.stackings: List[Stacking] = []
|
|
1232
|
+
self.base_ribose_interactions: List[BaseRibose] = []
|
|
1233
|
+
self.base_phosphate_interactions: List[BasePhosphate] = []
|
|
1234
|
+
self.other_interactions: List[OtherInteraction] = []
|
|
1235
|
+
|
|
1236
|
+
def classify_edge(self, edge_type: str) -> Optional[str]:
|
|
1237
|
+
for edge, edges in self.EDGES.items():
|
|
1238
|
+
if edge_type in edges:
|
|
1239
|
+
return edge
|
|
1240
|
+
logging.warning('Edge type "{type}" unknown')
|
|
1241
|
+
return None
|
|
1242
|
+
|
|
1243
|
+
def get_residue(self, residue_info_list: Tuple[Union[str, Any], ...]) -> Residue:
|
|
1244
|
+
chain = residue_info_list[0]
|
|
1245
|
+
number = int(residue_info_list[1])
|
|
1246
|
+
|
|
1247
|
+
if residue_info_list[2] == "":
|
|
1248
|
+
icode = None
|
|
1249
|
+
residue_info = f"{chain}{number}"
|
|
1250
|
+
else:
|
|
1251
|
+
icode = residue_info_list[2]
|
|
1252
|
+
residue_info = f"{chain}{number}.{icode}"
|
|
1253
|
+
|
|
1254
|
+
return Residue(
|
|
1255
|
+
None, ResidueAuth(chain, number, icode, self.names[residue_info])
|
|
1256
|
+
)
|
|
1257
|
+
|
|
1258
|
+
def get_residues(
|
|
1259
|
+
self, residues_info: str
|
|
1260
|
+
) -> Tuple[Optional[Residue], Optional[Residue]]:
|
|
1261
|
+
regex_result = re.search(self.RESIDUE_REGEX, residues_info)
|
|
1262
|
+
if regex_result is None:
|
|
1263
|
+
logging.error("MC-Annotate regex failed: {residues_info}")
|
|
1264
|
+
return None, None
|
|
1265
|
+
residues_info_list = regex_result.groups()
|
|
1266
|
+
# Expects (chain1, number1, icode1, chain2, number2, icode2)
|
|
1267
|
+
if len(residues_info_list) != 6:
|
|
1268
|
+
logging.error(f"MC-Annotate regex failed for {residues_info}")
|
|
1269
|
+
return None, None
|
|
1270
|
+
residue_left = self.get_residue(residues_info_list[:3])
|
|
1271
|
+
residue_right = self.get_residue(residues_info_list[3:])
|
|
1272
|
+
return residue_left, residue_right
|
|
1273
|
+
|
|
1274
|
+
def append_stacking(self, line: str, topology_position: int) -> None:
|
|
1275
|
+
splitted_line = line.split()
|
|
1276
|
+
topology_info = splitted_line[topology_position]
|
|
1277
|
+
residue_left, residue_right = self.get_residues(splitted_line[0])
|
|
1278
|
+
if residue_left is None or residue_right is None:
|
|
1279
|
+
logging.warning(f"Could not parse residues in line: {line}")
|
|
1280
|
+
return
|
|
1281
|
+
stacking = Stacking(
|
|
1282
|
+
residue_left, residue_right, StackingTopology[topology_info]
|
|
1283
|
+
)
|
|
1284
|
+
self.stackings.append(stacking)
|
|
1285
|
+
|
|
1286
|
+
def get_ribose_interaction(
|
|
1287
|
+
self, residues: Tuple[Residue, Residue], token: str
|
|
1288
|
+
) -> BaseRibose:
|
|
1289
|
+
# BasePair is preffered first so swap if necessary
|
|
1290
|
+
if token.split("/", 1)[0] == self.RIBOSE_ATOM:
|
|
1291
|
+
residue_left, residue_right = residues[1], residues[0]
|
|
1292
|
+
else:
|
|
1293
|
+
residue_left, residue_right = residues[0], residues[1]
|
|
1294
|
+
return BaseRibose(residue_left, residue_right, None)
|
|
1295
|
+
|
|
1296
|
+
def get_phosphate_interaction(
|
|
1297
|
+
self, residues: Tuple[Residue, Residue], token: str
|
|
1298
|
+
) -> BasePhosphate:
|
|
1299
|
+
# BasePair is preffered first so swap if necessary
|
|
1300
|
+
if token.split("/", 1)[0] == self.PHOSPHATE_ATOM:
|
|
1301
|
+
residue_left, residue_right = residues[1], residues[0]
|
|
1302
|
+
else:
|
|
1303
|
+
residue_left, residue_right = residues[0], residues[1]
|
|
1304
|
+
return BasePhosphate(residue_left, residue_right, None)
|
|
1305
|
+
|
|
1306
|
+
def get_base_interaction(
|
|
1307
|
+
self,
|
|
1308
|
+
residues: Tuple[Residue, Residue],
|
|
1309
|
+
token: str,
|
|
1310
|
+
tokens: List[str],
|
|
1311
|
+
) -> Optional[BasePair]:
|
|
1312
|
+
if self.CIS in tokens:
|
|
1313
|
+
cis_trans = "c"
|
|
1314
|
+
elif self.TRANS in tokens:
|
|
1315
|
+
cis_trans = "t"
|
|
1316
|
+
else:
|
|
1317
|
+
logging.warning(f"Cis/trans expected, but not present in {tokens}")
|
|
1318
|
+
return None
|
|
1319
|
+
|
|
1320
|
+
# example saenger: XIX or XII,XIII (?)
|
|
1321
|
+
for potential_saenger_token in tokens:
|
|
1322
|
+
potential_saenger_without_comma = potential_saenger_token.split(",")[0]
|
|
1323
|
+
if all(
|
|
1324
|
+
char in self.ROMAN_NUMERALS for char in potential_saenger_without_comma
|
|
1325
|
+
):
|
|
1326
|
+
saenger = Saenger[potential_saenger_without_comma]
|
|
1327
|
+
break
|
|
1328
|
+
else:
|
|
1329
|
+
saenger = None
|
|
1330
|
+
|
|
1331
|
+
left_edge, right_edge = token.split("/", 1)
|
|
1332
|
+
leontis_westhof_left = self.classify_edge(left_edge)
|
|
1333
|
+
leontis_westohf_right = self.classify_edge(right_edge)
|
|
1334
|
+
|
|
1335
|
+
if leontis_westhof_left is None or leontis_westohf_right is None:
|
|
1336
|
+
return None
|
|
1337
|
+
|
|
1338
|
+
leontis_westhof = LeontisWesthof[
|
|
1339
|
+
f"{cis_trans}{leontis_westhof_left}{leontis_westohf_right}"
|
|
1340
|
+
]
|
|
1341
|
+
residue_left, residue_right = residues
|
|
1342
|
+
return BasePair(residue_left, residue_right, leontis_westhof, saenger)
|
|
1343
|
+
|
|
1344
|
+
def get_other_interaction(
|
|
1345
|
+
self, residues: Tuple[Residue, Residue]
|
|
1346
|
+
) -> OtherInteraction:
|
|
1347
|
+
return OtherInteraction(residues[0], residues[1])
|
|
1348
|
+
|
|
1349
|
+
def append_interactions(self, line: str) -> None:
|
|
1350
|
+
splitted_line = line.split()
|
|
1351
|
+
residues = self.get_residues(splitted_line[0])
|
|
1352
|
+
if residues[0] is None or residues[1] is None:
|
|
1353
|
+
logging.warning(f"Could not parse residues in line: {line}")
|
|
1354
|
+
return
|
|
1355
|
+
# Assumes that one pair can belong to every interaction type
|
|
1356
|
+
# no more than once!
|
|
1357
|
+
base_added, ribose_added, phosphate_added = False, False, False
|
|
1358
|
+
# example tokens: Ww/Ww pairing antiparallel cis XX
|
|
1359
|
+
tokens: List[str] = splitted_line[3:]
|
|
1360
|
+
|
|
1361
|
+
# Special case
|
|
1362
|
+
# IF single hydrogen bond and base pairs only THEN
|
|
1363
|
+
# append to OtherIneraction list
|
|
1364
|
+
if self.ONE_HBOND in tokens:
|
|
1365
|
+
for token in tokens:
|
|
1366
|
+
if self.RIBOSE_ATOM in token or self.PHOSPHATE_ATOM in token:
|
|
1367
|
+
break
|
|
1368
|
+
else:
|
|
1369
|
+
other_interaction = self.get_other_interaction(residues)
|
|
1370
|
+
self.other_interactions.append(other_interaction)
|
|
1371
|
+
return
|
|
1372
|
+
|
|
1373
|
+
for token in tokens:
|
|
1374
|
+
if self.RIBOSE_ATOM in token and not ribose_added:
|
|
1375
|
+
# example token: Ss/O2'
|
|
1376
|
+
ribose_interaction = self.get_ribose_interaction(residues, token)
|
|
1377
|
+
self.base_ribose_interactions.append(ribose_interaction)
|
|
1378
|
+
ribose_added = True
|
|
1379
|
+
|
|
1380
|
+
elif self.PHOSPHATE_ATOM in token and not phosphate_added:
|
|
1381
|
+
# example token: O2P/Bh
|
|
1382
|
+
phosphate_interaction = self.get_phosphate_interaction(residues, token)
|
|
1383
|
+
self.base_phosphate_interactions.append(phosphate_interaction)
|
|
1384
|
+
phosphate_added = True
|
|
1385
|
+
|
|
1386
|
+
elif len(token.split("/", 1)) > 1:
|
|
1387
|
+
token_left, token_right = token.split("/", 1)
|
|
1388
|
+
tokens_in_edges = (
|
|
1389
|
+
token_left in self.ALL_EDGES and token_right in self.ALL_EDGES
|
|
1390
|
+
)
|
|
1391
|
+
if tokens_in_edges and not base_added:
|
|
1392
|
+
# example token_left: Ww | example token_right: Ws
|
|
1393
|
+
base_pair_interaction = self.get_base_interaction(
|
|
1394
|
+
residues, token, tokens
|
|
1395
|
+
)
|
|
1396
|
+
if base_pair_interaction is not None:
|
|
1397
|
+
self.base_pairs.append(base_pair_interaction)
|
|
1398
|
+
base_added = True
|
|
1399
|
+
|
|
1400
|
+
def append_names(self, file_content: str) -> None:
|
|
1401
|
+
for line in file_content.splitlines():
|
|
1402
|
+
if line.startswith(self.ATOM) or line.startswith(self.HETATM):
|
|
1403
|
+
chain = line[self.CHAIN_INDEX].strip()
|
|
1404
|
+
number = line[self.NUMBER_INDEX].strip()
|
|
1405
|
+
icode = line[self.ICODE_INDEX].strip()
|
|
1406
|
+
name = line[self.NAME_INDEX].strip()
|
|
1407
|
+
residue_info = (
|
|
1408
|
+
f"{chain}{number}" if icode == "" else f"{chain}{number}.{icode}"
|
|
1409
|
+
)
|
|
1410
|
+
self.names[residue_info] = name
|
|
1411
|
+
|
|
1412
|
+
def analyze_by_mc_annotate(
|
|
1413
|
+
self, pdb_content: str, mc_result: str, **_: Dict[str, Any]
|
|
1414
|
+
) -> BaseInteractions:
|
|
1415
|
+
self.append_names(pdb_content)
|
|
1416
|
+
current_state = None
|
|
1417
|
+
|
|
1418
|
+
for line in mc_result.splitlines():
|
|
1419
|
+
for state in self.ParseState:
|
|
1420
|
+
if line.startswith(state.value):
|
|
1421
|
+
current_state = state
|
|
1422
|
+
break
|
|
1423
|
+
# Loop ended without break - parse file
|
|
1424
|
+
else:
|
|
1425
|
+
if current_state == self.ParseState.RESIDUES_INFORMATION:
|
|
1426
|
+
# example line: X7.H : G C3p_endo anti
|
|
1427
|
+
# Skip residues information - meaningless information
|
|
1428
|
+
pass
|
|
1429
|
+
elif current_state == self.ParseState.ADJACENT_STACKINGS:
|
|
1430
|
+
# example line: X4.E-X5.F : adjacent_5p upward
|
|
1431
|
+
self.append_stacking(line, 3)
|
|
1432
|
+
elif current_state == self.ParseState.NON_ADJACENT_STACKINGS:
|
|
1433
|
+
# example line: Y40.M-Y67.N : inward pairing
|
|
1434
|
+
self.append_stacking(line, 2)
|
|
1435
|
+
elif current_state == self.ParseState.BASE_PAIRS_SECTION:
|
|
1436
|
+
# example line: Y38.K-Y51.X : A-U Ww/Ww pairing antiparallel cis XX
|
|
1437
|
+
self.append_interactions(line)
|
|
1438
|
+
elif current_state == self.ParseState.SUMMARY_SECTION:
|
|
1439
|
+
# example line: Number of non adjacent stackings = 26
|
|
1440
|
+
# Skip summary section - meaningless information
|
|
1441
|
+
pass
|
|
1442
|
+
|
|
1443
|
+
return (
|
|
1444
|
+
self.base_pairs,
|
|
1445
|
+
self.stackings,
|
|
1446
|
+
self.base_ribose_interactions,
|
|
1447
|
+
self.base_phosphate_interactions,
|
|
1448
|
+
self.other_interactions,
|
|
1449
|
+
)
|
|
1450
|
+
|
|
1451
|
+
|
|
1452
|
+
def parse_mcannotate_output(
|
|
1453
|
+
file_paths: List[str], structure3d: Structure3D
|
|
1454
|
+
) -> BaseInteractions:
|
|
1455
|
+
"""
|
|
1456
|
+
Parse mc-annotate output and convert to BaseInteractions.
|
|
1457
|
+
This function expects a file with mc-annotate stdout and a PDB file.
|
|
1458
|
+
"""
|
|
1459
|
+
stdout_file = None
|
|
1460
|
+
structure_file = None
|
|
1461
|
+
for file_path in file_paths:
|
|
1462
|
+
if os.path.basename(file_path).endswith("stdout.txt"):
|
|
1463
|
+
stdout_file = file_path
|
|
1464
|
+
elif file_path.endswith(".pdb"):
|
|
1465
|
+
structure_file = file_path
|
|
1466
|
+
|
|
1467
|
+
if not stdout_file:
|
|
1468
|
+
logging.warning("No stdout.txt file found for mc-annotate.")
|
|
1469
|
+
return BaseInteractions([], [], [], [], [])
|
|
1470
|
+
|
|
1471
|
+
if not structure_file:
|
|
1472
|
+
logging.warning("No PDB file found for mc-annotate.")
|
|
1473
|
+
return BaseInteractions([], [], [], [], [])
|
|
1474
|
+
|
|
1475
|
+
logging.info(f"Processing mc-annotate stdout file: {stdout_file}")
|
|
1476
|
+
logging.info(f"Using structure file for residue names: {structure_file}")
|
|
1477
|
+
|
|
1478
|
+
try:
|
|
1479
|
+
with open(stdout_file, "r") as f:
|
|
1480
|
+
mc_result = f.read()
|
|
1481
|
+
with open(structure_file, "r") as f:
|
|
1482
|
+
pdb_content = f.read()
|
|
1483
|
+
except Exception as e:
|
|
1484
|
+
logging.warning(f"Could not read input files for mc-annotate: {e}")
|
|
1485
|
+
return BaseInteractions([], [], [], [], [])
|
|
1486
|
+
|
|
1487
|
+
adapter = MCAnnotateAdapter()
|
|
1488
|
+
(
|
|
1489
|
+
base_pairs,
|
|
1490
|
+
stackings,
|
|
1491
|
+
base_ribose_interactions,
|
|
1492
|
+
base_phosphate_interactions,
|
|
1493
|
+
other_interactions,
|
|
1494
|
+
) = adapter.analyze_by_mc_annotate(pdb_content, mc_result)
|
|
1495
|
+
|
|
1496
|
+
return BaseInteractions.from_structure3d(
|
|
1497
|
+
structure3d,
|
|
1498
|
+
base_pairs,
|
|
1499
|
+
stackings,
|
|
1500
|
+
base_ribose_interactions,
|
|
1501
|
+
base_phosphate_interactions,
|
|
1502
|
+
other_interactions,
|
|
1503
|
+
)
|
|
1504
|
+
|
|
1505
|
+
|
|
1160
1506
|
def parse_external_output(
|
|
1161
1507
|
file_paths: List[str], tool: ExternalTool, structure3d: Structure3D
|
|
1162
1508
|
) -> BaseInteractions:
|
|
@@ -1183,6 +1529,8 @@ def parse_external_output(
|
|
|
1183
1529
|
return parse_rnaview_output(file_paths, structure3d)
|
|
1184
1530
|
elif tool == ExternalTool.BARNABA:
|
|
1185
1531
|
return parse_barnaba_output(file_paths, structure3d)
|
|
1532
|
+
elif tool == ExternalTool.MCANNOTATE:
|
|
1533
|
+
return parse_mcannotate_output(file_paths, structure3d)
|
|
1186
1534
|
else:
|
|
1187
1535
|
raise ValueError(f"Unsupported external tool: {tool}")
|
|
1188
1536
|
|
|
@@ -1259,6 +1607,9 @@ def process_external_tool_output(
|
|
|
1259
1607
|
if not external_file_paths:
|
|
1260
1608
|
# For MAXIT or when no external files are provided, use the input file
|
|
1261
1609
|
file_paths_to_process = [input_file_path]
|
|
1610
|
+
elif tool == ExternalTool.MCANNOTATE:
|
|
1611
|
+
# MC-Annotate requires both the stdout and the PDB file
|
|
1612
|
+
file_paths_to_process = external_file_paths + [input_file_path]
|
|
1262
1613
|
else:
|
|
1263
1614
|
# Process all external files
|
|
1264
1615
|
file_paths_to_process = external_file_paths
|
rnapolis/common.py
CHANGED
|
@@ -1084,33 +1084,41 @@ class BaseInteractions:
|
|
|
1084
1084
|
base_phosphate_interactions: List[BasePhosphate],
|
|
1085
1085
|
other_interactions: List[OtherInteraction],
|
|
1086
1086
|
) -> "BaseInteractions":
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1087
|
+
cni2residue = {}
|
|
1088
|
+
cni2label = {}
|
|
1089
|
+
cni2auth = {}
|
|
1090
1090
|
|
|
1091
1091
|
for residue3d in structure3d.residues:
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1092
|
+
cni = (residue3d.chain, residue3d.number, residue3d.icode or None)
|
|
1093
|
+
cni2auth[cni] = residue3d.auth
|
|
1094
|
+
cni2label[cni] = residue3d.label
|
|
1095
|
+
cni2residue[cni] = residue3d
|
|
1095
1096
|
|
|
1096
1097
|
def unify_nt(nt: Residue) -> Residue:
|
|
1097
1098
|
if nt.auth is not None and nt.label is not None:
|
|
1098
1099
|
return nt
|
|
1100
|
+
cni = (nt.chain, nt.number, nt.icode or None)
|
|
1099
1101
|
if nt.auth is not None:
|
|
1100
|
-
return Residue(label=
|
|
1102
|
+
return Residue(label=cni2label.get(cni, None), auth=nt.auth)
|
|
1101
1103
|
if nt.label is not None:
|
|
1102
|
-
return Residue(label=nt.label, auth=
|
|
1104
|
+
return Residue(label=nt.label, auth=cni2auth.get(cni, None))
|
|
1103
1105
|
return nt
|
|
1104
1106
|
|
|
1105
1107
|
base_pairs_new = []
|
|
1106
1108
|
for base_pair in base_pairs:
|
|
1107
1109
|
nt1 = unify_nt(base_pair.nt1)
|
|
1108
1110
|
nt2 = unify_nt(base_pair.nt2)
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1111
|
+
|
|
1112
|
+
cni1 = (nt1.chain, nt1.number, nt1.icode or None)
|
|
1113
|
+
cni2 = (nt2.chain, nt2.number, nt2.icode or None)
|
|
1114
|
+
if cni1 not in cni2residue or cni2 not in cni2residue:
|
|
1115
|
+
saenger = base_pair.saenger
|
|
1116
|
+
else:
|
|
1117
|
+
saenger = base_pair.saenger or Saenger.from_leontis_westhof(
|
|
1118
|
+
cni2residue[cni1].one_letter_name,
|
|
1119
|
+
cni2residue[cni2].one_letter_name,
|
|
1120
|
+
base_pair.lw,
|
|
1121
|
+
)
|
|
1114
1122
|
if (
|
|
1115
1123
|
nt1 != base_pair.nt1
|
|
1116
1124
|
or nt2 != base_pair.nt2
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
rnapolis/adapter.py,sha256=
|
|
1
|
+
rnapolis/adapter.py,sha256=6hJTweIqUXH8CEGvi8oupFzk5etkIt8Q2bqRvgsqako,62169
|
|
2
2
|
rnapolis/aligner.py,sha256=o7rQyjAZ3n4VXcnSPY3HVB8nLNRkVbl552O3NVh0mfg,3429
|
|
3
3
|
rnapolis/annotator.py,sha256=HA2hfEUXdmBElObqRlASAB1FgkysjiHgwMTjEhsDiDE,30277
|
|
4
4
|
rnapolis/clashfinder.py,sha256=AC9_tIx7QIk57sELq_aKfU1u3UMOXbgcccQeGHhMR6c,8517
|
|
5
|
-
rnapolis/common.py,sha256=
|
|
5
|
+
rnapolis/common.py,sha256=qifqTIiq43jeR1xKK3301PbRMo7vaZgjQauG-G7asSc,36686
|
|
6
6
|
rnapolis/component_A.csv,sha256=koirS-AwUZwoYGItT8yn3wS6Idvmh2FANfTQcOS_xh8,2897
|
|
7
7
|
rnapolis/component_C.csv,sha256=NtvsAu_YrUgTjzZm3j4poW4IZ99x3dPARB09XVIiMCc,2803
|
|
8
8
|
rnapolis/component_G.csv,sha256=Z5wl8OnHRyx4XhTyBiWgRZiEvmZXhoxtVRH8bn6Vxf0,2898
|
|
@@ -22,9 +22,9 @@ rnapolis/tertiary_v2.py,sha256=SgijTv0bPqMJwsMqyQk0O8QAnS2Ozk45vk8igxt9hRs,38001
|
|
|
22
22
|
rnapolis/transformer.py,sha256=aC0nBmHHJf5TyLvBIV57Jj3tlwpvHbPo347opfAOlQA,3844
|
|
23
23
|
rnapolis/unifier.py,sha256=2ge7IB9FdRgzSAiVD39U_ciwtdDJ2fGzf8mUIudbrqY,5820
|
|
24
24
|
rnapolis/util.py,sha256=IdquFO3PV1_KDqodjupzm0Rqvgy0CeSzxGHaGEHYXVU,543
|
|
25
|
-
rnapolis-0.10.
|
|
26
|
-
rnapolis-0.10.
|
|
27
|
-
rnapolis-0.10.
|
|
28
|
-
rnapolis-0.10.
|
|
29
|
-
rnapolis-0.10.
|
|
30
|
-
rnapolis-0.10.
|
|
25
|
+
rnapolis-0.10.7.dist-info/licenses/LICENSE,sha256=ZGRu12MzCgbYA-Lt8MyBlmjvPZh7xfiD5u5wBx0enq4,1066
|
|
26
|
+
rnapolis-0.10.7.dist-info/METADATA,sha256=QPuGPZ96VIjvQPiLkk4bS4vstWqO6cok6e4vID33vg0,54611
|
|
27
|
+
rnapolis-0.10.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
28
|
+
rnapolis-0.10.7.dist-info/entry_points.txt,sha256=MZMWnYBUYnis-zWDmFfuA5yXtU3W5YdQrm5HA5LrkeM,474
|
|
29
|
+
rnapolis-0.10.7.dist-info/top_level.txt,sha256=LcO18koxZcWoJ21KDRRRo_tyIbmXL5z61dPitZpy8yc,9
|
|
30
|
+
rnapolis-0.10.7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|