tmquick 1.0.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.
- tmq/__init__.py +14 -0
- tmq/cli.py +250 -0
- tmq/commands/__init__.py +2 -0
- tmq/commands/average_trajmap_cli.py +36 -0
- tmq/commands/csv2shift_cli.py +27 -0
- tmq/commands/diff2graph_cli.py +35 -0
- tmq/commands/hotspot_cli.py +89 -0
- tmq/commands/pdb2csv_cli.py +29 -0
- tmq/commands/shift2graph_cli.py +29 -0
- tmq/commands/traj2pdb_cli.py +21 -0
- tmq/commands/trajmap_cli.py +29 -0
- tmq/core/__init__.py +2 -0
- tmq/core/average_trajmap.py +35 -0
- tmq/core/csv2shift.py +17 -0
- tmq/core/diff2graph.py +75 -0
- tmq/core/hotspot.py +164 -0
- tmq/core/pdb2csv.py +53 -0
- tmq/core/shift2graph.py +63 -0
- tmq/core/traj2pdb.py +12 -0
- tmq/core/trajmap.py +52 -0
- tmq/utils/__init__.py +2 -0
- tmq/utils/utils.py +233 -0
- tmquick-1.0.0.dist-info/METADATA +23 -0
- tmquick-1.0.0.dist-info/RECORD +28 -0
- tmquick-1.0.0.dist-info/WHEEL +5 -0
- tmquick-1.0.0.dist-info/entry_points.txt +2 -0
- tmquick-1.0.0.dist-info/licenses/LICENSE.md +21 -0
- tmquick-1.0.0.dist-info/top_level.txt +1 -0
tmq/core/diff2graph.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
""" Render a difference heatmap (A - B) and return the figure
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pandas as pd
|
|
7
|
+
import matplotlib.pyplot as plt
|
|
8
|
+
import seaborn as sns
|
|
9
|
+
from matplotlib.ticker import MultipleLocator
|
|
10
|
+
from ..utils.utils import compute_mat_diff
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def diff_map(matrix1, matrix2, vmax, residue_start, residue_end):
|
|
16
|
+
|
|
17
|
+
D = compute_mat_diff(matrix1, matrix2)
|
|
18
|
+
|
|
19
|
+
# Internal style settings
|
|
20
|
+
x_minor = 20
|
|
21
|
+
y_major = 50
|
|
22
|
+
y_minor = 10
|
|
23
|
+
|
|
24
|
+
frames = D.shape[1]
|
|
25
|
+
|
|
26
|
+
if frames <= 100:
|
|
27
|
+
x_step = 10
|
|
28
|
+
elif frames <= 500:
|
|
29
|
+
x_step = 50
|
|
30
|
+
else:
|
|
31
|
+
x_step = 100
|
|
32
|
+
|
|
33
|
+
fig, ax = plt.subplots(figsize=(12, 8))
|
|
34
|
+
|
|
35
|
+
#handle NAN
|
|
36
|
+
mask = np.isnan(D)
|
|
37
|
+
|
|
38
|
+
sns.heatmap(D, cmap="bwr", center=0, vmin=-vmax, vmax=vmax, mask=mask, cbar=False)
|
|
39
|
+
|
|
40
|
+
xticks = np.arange(0, frames + 1, x_step)
|
|
41
|
+
ax.set_xticks(xticks)
|
|
42
|
+
ax.set_xticklabels(xticks, rotation=0, fontsize=14)
|
|
43
|
+
ax.xaxis.set_minor_locator(MultipleLocator(x_minor))
|
|
44
|
+
|
|
45
|
+
for tick in ax.get_xticklabels():
|
|
46
|
+
tick.set_fontweight("bold")
|
|
47
|
+
|
|
48
|
+
# Y ticks
|
|
49
|
+
residues = D.shape[0]
|
|
50
|
+
ax.set_yticks(np.arange(0, residues, y_major))
|
|
51
|
+
ax.set_yticklabels(np.arange(residue_start, residue_end, y_major), fontsize=14)
|
|
52
|
+
ax.yaxis.set_minor_locator(MultipleLocator(y_minor))
|
|
53
|
+
|
|
54
|
+
for tick in ax.get_yticklabels():
|
|
55
|
+
tick.set_fontweight("bold")
|
|
56
|
+
|
|
57
|
+
ax.invert_yaxis()
|
|
58
|
+
|
|
59
|
+
# Labels
|
|
60
|
+
ax.set_title("Difference Map", fontsize=20, fontweight="bold")
|
|
61
|
+
ax.set_ylabel("Residue", fontsize=20, fontweight="bold")
|
|
62
|
+
ax.set_xlabel("Frames", fontsize=20, fontweight="bold")
|
|
63
|
+
|
|
64
|
+
# Colorbar
|
|
65
|
+
im = ax.collections[0]
|
|
66
|
+
cbar = fig.colorbar(im, fraction=0.029, pad=0.028)
|
|
67
|
+
cbar.set_label("Shift (Å)", fontsize=14, fontweight="bold")
|
|
68
|
+
|
|
69
|
+
for t in cbar.ax.get_yticklabels():
|
|
70
|
+
t.set_fontweight("bold")
|
|
71
|
+
t.set_fontsize(16)
|
|
72
|
+
|
|
73
|
+
plt.tight_layout()
|
|
74
|
+
plt.close()
|
|
75
|
+
return fig
|
tmq/core/hotspot.py
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
""" Core logic for generating shift graphs of detected
|
|
2
|
+
peak regions of a protein in an automated way
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import pandas as pd
|
|
8
|
+
import matplotlib
|
|
9
|
+
matplotlib.use("Agg")
|
|
10
|
+
import matplotlib.pyplot as plt
|
|
11
|
+
from matplotlib.ticker import MultipleLocator
|
|
12
|
+
from ..utils.utils import detect, extract_region_submatrices, slice_matrix2shift, auto_limits
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def detect_shifts(matrix, window, mindist, hotspot):
|
|
16
|
+
regions, mean_shift, smooth = detect(
|
|
17
|
+
matrix=matrix,
|
|
18
|
+
window=window,
|
|
19
|
+
mindist=mindist,
|
|
20
|
+
hotspot=hotspot,)
|
|
21
|
+
return mean_shift, smooth, regions
|
|
22
|
+
|
|
23
|
+
def create_submatrices(matrix, window, mindist, hotspot):
|
|
24
|
+
regions, mean_shift, smooth = detect(
|
|
25
|
+
matrix=matrix,
|
|
26
|
+
window=window,
|
|
27
|
+
mindist=mindist,
|
|
28
|
+
hotspot=hotspot)
|
|
29
|
+
|
|
30
|
+
regions_only = [(start, end) for (start, peak, end) in regions]
|
|
31
|
+
submatrices = extract_region_submatrices(matrix, regions_only)
|
|
32
|
+
return submatrices, regions, mean_shift, smooth
|
|
33
|
+
|
|
34
|
+
def plot_shift_regions(shift_data, start_residue, end_residue, color="blue", window=10, title=None):
|
|
35
|
+
df = pd.DataFrame(shift_data)
|
|
36
|
+
roll = df.rolling(window, center=True, min_periods=1).mean()
|
|
37
|
+
x_min, x_max, y_min, y_max = auto_limits(df)
|
|
38
|
+
|
|
39
|
+
fig, ax = plt.subplots(figsize=(10, 6))
|
|
40
|
+
ax.plot(df, linewidth=0.3, color=color,
|
|
41
|
+
label=f"residues {start_residue}-{end_residue}")
|
|
42
|
+
ax.plot(roll, linewidth=3.0, color=color)
|
|
43
|
+
ax.legend(loc='upper left', fancybox=True, shadow=True,
|
|
44
|
+
prop={'size': 14, 'weight': 'bold'})
|
|
45
|
+
|
|
46
|
+
ax.set_xlim(x_min, x_max)
|
|
47
|
+
ax.set_ylim(y_min, y_max)
|
|
48
|
+
x_range = x_max - x_min
|
|
49
|
+
if x_range <= 500:
|
|
50
|
+
x_step = 50
|
|
51
|
+
elif x_range <= 1000:
|
|
52
|
+
x_step = 100
|
|
53
|
+
else:
|
|
54
|
+
x_step = 200
|
|
55
|
+
ax.set_xticks(np.arange(x_min, x_max + 1, x_step))
|
|
56
|
+
ax.tick_params(axis='x', labelsize=16)
|
|
57
|
+
y_range = y_max - y_min
|
|
58
|
+
if y_range <= 2:
|
|
59
|
+
y_step = 0.2
|
|
60
|
+
elif y_range <= 5:
|
|
61
|
+
y_step = 0.5
|
|
62
|
+
elif y_range <= 10:
|
|
63
|
+
y_step = 1
|
|
64
|
+
else:
|
|
65
|
+
y_step = 5
|
|
66
|
+
ax.set_yticks(np.arange(y_min, y_max + 0.5, y_step))
|
|
67
|
+
ax.tick_params(axis='y', labelsize=16)
|
|
68
|
+
ax.set_yticklabels([f"{y:.2f}" for y in ax.get_yticks()])
|
|
69
|
+
for tick in ax.get_xticklabels():
|
|
70
|
+
tick.set_fontweight("bold")
|
|
71
|
+
for tick in ax.get_yticklabels():
|
|
72
|
+
tick.set_fontweight("bold")
|
|
73
|
+
ax.set_title(title if title else f"Shift Graph {start_residue}-{end_residue}",
|
|
74
|
+
fontsize=20, fontweight="bold")
|
|
75
|
+
ax.set_xlabel("Frames", fontsize=20, fontweight="bold")
|
|
76
|
+
ax.set_ylabel("Shift / Å", fontsize=20, fontweight="bold")
|
|
77
|
+
ax.xaxis.set_minor_locator(MultipleLocator((x_max - x_min) / 50))
|
|
78
|
+
fig.tight_layout()
|
|
79
|
+
plt.close(fig)
|
|
80
|
+
return fig
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def slice_shifts(matrix, window, mindist, hotspot):
|
|
84
|
+
regions, mean_shift, smooth = detect(
|
|
85
|
+
matrix=matrix,
|
|
86
|
+
window=window,
|
|
87
|
+
mindist=mindist,
|
|
88
|
+
hotspot=hotspot)
|
|
89
|
+
|
|
90
|
+
regions_only = [(start, end) for (start, peak, end) in regions]
|
|
91
|
+
submatrices = extract_region_submatrices(matrix, regions_only)
|
|
92
|
+
results = []
|
|
93
|
+
|
|
94
|
+
for start, end, submatrix in submatrices:
|
|
95
|
+
params = [0, submatrix.shape[0] - 1, 0, submatrix.shape[1] - 1]
|
|
96
|
+
shift_data = slice_matrix2shift(submatrix, params)
|
|
97
|
+
shift_data = shift_data[2:]
|
|
98
|
+
results.append((start, end, shift_data))
|
|
99
|
+
return results
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def plot_slice_regions(shift_data, start, end):
|
|
103
|
+
return plot_shift_regions(
|
|
104
|
+
shift_data=shift_data,
|
|
105
|
+
start_residue=start,
|
|
106
|
+
end_residue=end,
|
|
107
|
+
title=f"Region {start}-{end}" )
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def plot_hotspot_regions(mean_shift, smooth, regions, title="Residue Shift Hotspots"):
|
|
111
|
+
residues = np.arange(len(mean_shift))
|
|
112
|
+
df = pd.DataFrame(mean_shift)
|
|
113
|
+
x_min, x_max, y_min, y_max = auto_limits(df)
|
|
114
|
+
|
|
115
|
+
fig, ax = plt.subplots(figsize=(10, 6))
|
|
116
|
+
ax.plot(residues, mean_shift, color="gray", alpha=0.4, linewidth=0.8, label="Mean shift")
|
|
117
|
+
ax.plot(residues, smooth, color="blue", linewidth=3.5, label="Smoothed")
|
|
118
|
+
|
|
119
|
+
for (start, peak, end) in regions:
|
|
120
|
+
ax.axvspan(start, end, color="white", alpha=0.7)
|
|
121
|
+
ax.plot(peak, smooth[peak], "ro")
|
|
122
|
+
ax.text(peak, smooth[peak], f" {peak}", color="red", fontsize=9, fontweight="bold")
|
|
123
|
+
|
|
124
|
+
ax.legend(loc='upper left', fancybox=True, shadow=True,
|
|
125
|
+
prop={'size': 8, 'weight': 'bold'})
|
|
126
|
+
ax.set_xlim(x_min, x_max)
|
|
127
|
+
ax.set_ylim(y_min, y_max)
|
|
128
|
+
x_range = x_max - x_min
|
|
129
|
+
if x_range <= 200:
|
|
130
|
+
x_step = 25
|
|
131
|
+
elif x_range <= 500:
|
|
132
|
+
x_step = 50
|
|
133
|
+
elif x_range <= 1000:
|
|
134
|
+
x_step = 100
|
|
135
|
+
else:
|
|
136
|
+
x_step = 200
|
|
137
|
+
ax.set_xticks(np.arange(x_min, x_max + 1, x_step))
|
|
138
|
+
ax.tick_params(axis='x', labelsize=12)
|
|
139
|
+
|
|
140
|
+
y_range = y_max - y_min
|
|
141
|
+
if y_range <= 2:
|
|
142
|
+
y_step = 0.2
|
|
143
|
+
elif y_range <= 5:
|
|
144
|
+
y_step = 0.5
|
|
145
|
+
elif y_range <= 10:
|
|
146
|
+
y_step = 1
|
|
147
|
+
else:
|
|
148
|
+
y_step = 5
|
|
149
|
+
ax.set_yticks(np.arange(y_min, y_max + 0.5, y_step))
|
|
150
|
+
ax.tick_params(axis='y', labelsize=12)
|
|
151
|
+
for tick in ax.get_xticklabels():
|
|
152
|
+
tick.set_fontweight("bold")
|
|
153
|
+
for tick in ax.get_yticklabels():
|
|
154
|
+
tick.set_fontweight("bold")
|
|
155
|
+
|
|
156
|
+
ax.set_title(title, fontsize=18, fontweight="bold")
|
|
157
|
+
ax.set_xlabel("Residue index", fontsize=16, fontweight="bold")
|
|
158
|
+
ax.set_ylabel("Mean shift / Å", fontsize=16, fontweight="bold")
|
|
159
|
+
ax.xaxis.set_minor_locator(MultipleLocator((x_max - x_min) / 50))
|
|
160
|
+
ax.yaxis.set_minor_locator(MultipleLocator((y_max - y_min) / 50))
|
|
161
|
+
ax.grid(True, alpha=0.3)
|
|
162
|
+
fig.tight_layout()
|
|
163
|
+
plt.close(fig)
|
|
164
|
+
return fig
|
tmq/core/pdb2csv.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
""" This module loops over the Multi-model pdb and
|
|
2
|
+
compute residual fluctuations across the frames
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import pandas as pd
|
|
7
|
+
import numpy as np
|
|
8
|
+
from ..utils.utils import (load_pdb_models, build_residue_lookup, get_ca_coord, get_backbone_com, distance)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def pdb2csv(pdb_file, residue_start, residue_end, mode):
|
|
12
|
+
weights = {"N": 14.0, "CA": 12.0, "C": 12.0, "O": 16.0}
|
|
13
|
+
residue_numbers = list(range(residue_start, residue_end + 1))
|
|
14
|
+
|
|
15
|
+
models = load_pdb_models(pdb_file)
|
|
16
|
+
n_frames = len(models)
|
|
17
|
+
|
|
18
|
+
ref_lookup = build_residue_lookup(models[0])
|
|
19
|
+
|
|
20
|
+
ref_coords = {}
|
|
21
|
+
for resnum in residue_numbers:
|
|
22
|
+
if resnum in ref_lookup:
|
|
23
|
+
residue = ref_lookup[resnum]
|
|
24
|
+
if mode == "ca":
|
|
25
|
+
ref_coords[resnum] = get_ca_coord(residue)
|
|
26
|
+
else:
|
|
27
|
+
ref_coords[resnum] = get_backbone_com(residue, weights)
|
|
28
|
+
else:
|
|
29
|
+
ref_coords[resnum] = np.zeros(3)
|
|
30
|
+
|
|
31
|
+
matrix = pd.DataFrame(0.0, index=residue_numbers, columns=range(n_frames))
|
|
32
|
+
|
|
33
|
+
""" Compute distances for frames 1..N-1
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
for t in range(1, n_frames):
|
|
37
|
+
lookup = build_residue_lookup(models[t])
|
|
38
|
+
|
|
39
|
+
for resnum in residue_numbers:
|
|
40
|
+
ref = ref_coords[resnum]
|
|
41
|
+
|
|
42
|
+
if resnum in lookup:
|
|
43
|
+
residue = lookup[resnum]
|
|
44
|
+
if mode == "ca":
|
|
45
|
+
cur = get_ca_coord(residue)
|
|
46
|
+
else:
|
|
47
|
+
cur = get_backbone_com(residue, weights)
|
|
48
|
+
else:
|
|
49
|
+
cur = np.zeros(3)
|
|
50
|
+
|
|
51
|
+
matrix.loc[resnum, t] = distance(cur, ref)
|
|
52
|
+
|
|
53
|
+
return matrix
|
tmq/core/shift2graph.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
""" Core logic for making the shift plot
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import matplotlib
|
|
8
|
+
matplotlib.use("Agg")
|
|
9
|
+
import matplotlib.pyplot as plt
|
|
10
|
+
from matplotlib.ticker import MultipleLocator
|
|
11
|
+
from ..utils.utils import auto_limits, rolling_average
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def shift2graph(df, residue_label, window=10):
|
|
15
|
+
roll_df = rolling_average(df, window)
|
|
16
|
+
x_min, x_max, y_min, y_max = auto_limits(df)
|
|
17
|
+
color = "blue"
|
|
18
|
+
fig, ax = plt.subplots(figsize=(10, 6))
|
|
19
|
+
|
|
20
|
+
ax.plot(df, linewidth=0.3, color=color, label=f"residues {residue_label}")
|
|
21
|
+
ax.plot(roll_df, linewidth=3.0, color=color)
|
|
22
|
+
ax.legend(loc='upper left', fancybox=True, shadow=True,
|
|
23
|
+
prop={'size': 14, 'weight': 'bold'})
|
|
24
|
+
|
|
25
|
+
ax.set_xlim(x_min, x_max)
|
|
26
|
+
ax.set_ylim(y_min, y_max)
|
|
27
|
+
x_range = x_max - x_min
|
|
28
|
+
if x_range <= 100:
|
|
29
|
+
x_step = 10
|
|
30
|
+
elif x_range <= 500:
|
|
31
|
+
x_step = 50
|
|
32
|
+
elif x_range <= 1000:
|
|
33
|
+
x_step = 100
|
|
34
|
+
else:
|
|
35
|
+
x_step = 200
|
|
36
|
+
ax.set_xticks(np.arange(x_min, x_max + 1, x_step))
|
|
37
|
+
ax.tick_params(axis='x', labelsize=14)
|
|
38
|
+
|
|
39
|
+
y_range = y_max - y_min
|
|
40
|
+
if y_range <= 2:
|
|
41
|
+
y_step = 0.2
|
|
42
|
+
elif y_range <= 5:
|
|
43
|
+
y_step = 0.5
|
|
44
|
+
elif y_range <= 10:
|
|
45
|
+
y_step = 1
|
|
46
|
+
else:
|
|
47
|
+
y_step = 5
|
|
48
|
+
ax.set_yticks(np.arange(y_min, y_max + 0.5, y_step))
|
|
49
|
+
ax.tick_params(axis='y', labelsize=14)
|
|
50
|
+
ax.set_yticklabels([f"{y:.2f}" for y in ax.get_yticks()])
|
|
51
|
+
for tick in ax.get_xticklabels():
|
|
52
|
+
tick.set_fontweight("bold")
|
|
53
|
+
for tick in ax.get_yticklabels():
|
|
54
|
+
tick.set_fontweight("bold")
|
|
55
|
+
|
|
56
|
+
ax.set_title("Shift Graph", fontsize=20, fontweight="bold")
|
|
57
|
+
ax.set_xlabel("Frames", fontsize=18, fontweight="bold")
|
|
58
|
+
ax.set_ylabel("Shift / Å", fontsize=18, fontweight="bold")
|
|
59
|
+
|
|
60
|
+
ax.yaxis.set_minor_locator(MultipleLocator((y_max - y_min) / 10))
|
|
61
|
+
fig.tight_layout()
|
|
62
|
+
plt.close(fig)
|
|
63
|
+
return fig
|
tmq/core/traj2pdb.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
""" This is the engine for the conversion of trajectory files into multi-model PDB
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from ..utils.utils import (load_traj, select_backbone, align_to_first_frame)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def traj2pdb(topology, trajectory, stride):
|
|
9
|
+
traj = load_traj(topology, trajectory, stride)
|
|
10
|
+
traj = select_backbone(traj)
|
|
11
|
+
traj = align_to_first_frame(traj)
|
|
12
|
+
return traj
|
tmq/core/trajmap.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
""" This module instantiates the trajectory map plot
|
|
2
|
+
for the residue range specified by the user
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import matplotlib
|
|
8
|
+
matplotlib.use("Agg")
|
|
9
|
+
import matplotlib.pyplot as plt
|
|
10
|
+
import matplotlib.pyplot as plt
|
|
11
|
+
from matplotlib.ticker import MultipleLocator
|
|
12
|
+
from ..utils.utils import choose_cmap, choose_aspect
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def trajmap(matrix, residue_start, residue_end, vmax):
|
|
16
|
+
x_minor = 20
|
|
17
|
+
y_major = 50
|
|
18
|
+
y_minor = 10
|
|
19
|
+
vmin = matrix.iloc[:, 1:].values.min()
|
|
20
|
+
cmap = choose_cmap('viridis_capped')
|
|
21
|
+
aspect = choose_aspect('auto')
|
|
22
|
+
frames = matrix.shape[1]
|
|
23
|
+
if frames <= 100:
|
|
24
|
+
x_step = 10
|
|
25
|
+
elif frames <= 500:
|
|
26
|
+
x_step = 50
|
|
27
|
+
else:
|
|
28
|
+
x_step = 100
|
|
29
|
+
|
|
30
|
+
fig, ax = plt.subplots(figsize=(12, 8))
|
|
31
|
+
im = ax.imshow(matrix, cmap=cmap, vmin=vmin, vmax=vmax, aspect=aspect)
|
|
32
|
+
ax.invert_yaxis()
|
|
33
|
+
|
|
34
|
+
ax.set_xticks(np.arange(0, frames + 1, step=x_step))
|
|
35
|
+
ax.tick_params(axis='x', labelsize=14)
|
|
36
|
+
ax.set_yticks(np.arange(residue_start, residue_end, step=y_major),)
|
|
37
|
+
ax.tick_params(axis='y', labelsize=14)
|
|
38
|
+
ax.yaxis.set_minor_locator(MultipleLocator(y_minor))
|
|
39
|
+
ax.xaxis.set_minor_locator(MultipleLocator(x_minor))
|
|
40
|
+
for tick in ax.get_xticklabels():
|
|
41
|
+
tick.set_fontweight("bold")
|
|
42
|
+
for tick in ax.get_yticklabels():
|
|
43
|
+
tick.set_fontweight("bold")
|
|
44
|
+
|
|
45
|
+
ax.set_title("Trajectory Map", fontsize=20, fontweight="bold")
|
|
46
|
+
ax.set_ylabel("Residue", fontsize=18, fontweight="bold")
|
|
47
|
+
ax.set_xlabel("Frames", fontsize=18, fontweight="bold")
|
|
48
|
+
cbar = fig.colorbar(im, fraction=0.029, pad=0.028)
|
|
49
|
+
cbar.set_label("Shift / Å", fontsize=12, fontweight="bold")
|
|
50
|
+
fig.tight_layout()
|
|
51
|
+
plt.close(fig)
|
|
52
|
+
return fig
|
tmq/utils/__init__.py
ADDED
tmq/utils/utils.py
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
""" This contains all the utility functions used in the core modules and CLI wrappers
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pandas as pd
|
|
7
|
+
import mdtraj as mdt
|
|
8
|
+
from Bio.PDB import PDBParser
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def ensure_pdb_extension(name):
|
|
13
|
+
return name if name.endswith(".pdb") else name + ".pdb"
|
|
14
|
+
|
|
15
|
+
def load_traj(topology, trajectories, stride):
|
|
16
|
+
return mdt.load(trajectories, top=topology, stride=stride)
|
|
17
|
+
|
|
18
|
+
def select_backbone(traj):
|
|
19
|
+
idx = traj.topology.select("backbone")
|
|
20
|
+
return traj.atom_slice(idx)
|
|
21
|
+
|
|
22
|
+
def align_to_first_frame(traj):
|
|
23
|
+
return traj.superpose(traj, 0)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def load_pdb_models(file):
|
|
27
|
+
parser = PDBParser(QUIET=True)
|
|
28
|
+
structure = parser.get_structure("prot", file)
|
|
29
|
+
return list(structure.get_models())
|
|
30
|
+
|
|
31
|
+
def build_residue_lookup(model):
|
|
32
|
+
lookup = {}
|
|
33
|
+
for residue in model.get_residues():
|
|
34
|
+
het, resnum, icode = residue.get_id()
|
|
35
|
+
if het == " ":
|
|
36
|
+
lookup[resnum] = residue
|
|
37
|
+
return lookup
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_ca_coord(residue):
|
|
41
|
+
if "CA" in residue:
|
|
42
|
+
return np.array(residue["CA"].coord, dtype=float)
|
|
43
|
+
return np.zeros(3)
|
|
44
|
+
|
|
45
|
+
def get_backbone_com(residue, weights):
|
|
46
|
+
coords = []
|
|
47
|
+
w = []
|
|
48
|
+
for atom_name in ["N", "CA", "C", "O"]:
|
|
49
|
+
if atom_name in residue:
|
|
50
|
+
atom = residue[atom_name]
|
|
51
|
+
coords.append(atom.coord * weights[atom_name])
|
|
52
|
+
w.append(weights[atom_name])
|
|
53
|
+
if coords:
|
|
54
|
+
return np.sum(coords, axis=0) / np.sum(w)
|
|
55
|
+
return np.zeros(3)
|
|
56
|
+
|
|
57
|
+
def distance(a, b):
|
|
58
|
+
return np.linalg.norm(a - b)
|
|
59
|
+
|
|
60
|
+
def ensure_csv_extension(name):
|
|
61
|
+
return name if name.endswith(".csv") else name + ".csv"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def csv2matrix(csv_file):
|
|
65
|
+
matrix = pd.read_csv(csv_file, index_col=0)
|
|
66
|
+
return matrix
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def matrix2shift(matrix, params):
|
|
70
|
+
|
|
71
|
+
matrix_array = np.array(matrix)
|
|
72
|
+
|
|
73
|
+
res1 = params[0]
|
|
74
|
+
res2 = params[1]
|
|
75
|
+
time1 = params[2]
|
|
76
|
+
time2 = params[3]
|
|
77
|
+
|
|
78
|
+
output = pd.DataFrame(data=np.arange(time1, time2, 1, dtype=float))
|
|
79
|
+
|
|
80
|
+
if res1 == res2:
|
|
81
|
+
output = matrix_array[res1]
|
|
82
|
+
else:
|
|
83
|
+
y = matrix_array[res1:res2, time1:time2]
|
|
84
|
+
|
|
85
|
+
i = 0
|
|
86
|
+
while i < len(output):
|
|
87
|
+
output.iloc[i] = np.average(y[:, i])
|
|
88
|
+
i = i + 1
|
|
89
|
+
|
|
90
|
+
return output
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def csv2matrix(file):
|
|
94
|
+
return pd.read_csv(file, index_col=None)
|
|
95
|
+
|
|
96
|
+
def choose_cmap(cmap_choice):
|
|
97
|
+
if cmap_choice == 0:
|
|
98
|
+
return "magma"
|
|
99
|
+
elif cmap_choice == 1:
|
|
100
|
+
return "seismic"
|
|
101
|
+
elif cmap_choice == 5:
|
|
102
|
+
return "Greys"
|
|
103
|
+
elif cmap_choice == 6:
|
|
104
|
+
return "turbo"
|
|
105
|
+
return "viridis"
|
|
106
|
+
|
|
107
|
+
def choose_aspect(aspect):
|
|
108
|
+
if aspect == 0:
|
|
109
|
+
return "auto"
|
|
110
|
+
elif aspect == 1:
|
|
111
|
+
return "equal"
|
|
112
|
+
return "auto"
|
|
113
|
+
|
|
114
|
+
def ensure_png_extension(name):
|
|
115
|
+
return name if name.endswith(".png") else name + ".png"
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def shift_to_dataframe(shift_data):
|
|
119
|
+
df = pd.read_csv(shift_data, header=None)
|
|
120
|
+
df = df.astype(float)
|
|
121
|
+
df = df.iloc[2:].reset_index(drop=True)
|
|
122
|
+
return df
|
|
123
|
+
|
|
124
|
+
def rolling_average(df, window):
|
|
125
|
+
return df.rolling(window).mean()
|
|
126
|
+
|
|
127
|
+
def auto_limits(df):
|
|
128
|
+
col = df.iloc[:, 0]
|
|
129
|
+
y_min = float(col.min())
|
|
130
|
+
y_max = float(col.max() + 0.5)
|
|
131
|
+
x_min = 0
|
|
132
|
+
x_max = len(df) - 1
|
|
133
|
+
return x_min, x_max, y_min, y_max
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def parse_range(s):
|
|
138
|
+
"""
|
|
139
|
+
Parse a residue range string like '110-125' into (110, 125).
|
|
140
|
+
"""
|
|
141
|
+
try:
|
|
142
|
+
start, end = map(int, s.split("-"))
|
|
143
|
+
return start, end
|
|
144
|
+
except Exception:
|
|
145
|
+
raise ValueError("Residue range must be in the form start-end, e.g., 110-125")
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
import numpy as np
|
|
149
|
+
import pandas as pd
|
|
150
|
+
from scipy.signal import find_peaks
|
|
151
|
+
|
|
152
|
+
def detect(matrix, window, mindist, hotspot):
|
|
153
|
+
return extract_residue_shift_regions(
|
|
154
|
+
matrix,
|
|
155
|
+
window,
|
|
156
|
+
mindist,
|
|
157
|
+
hotspot,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def extract_residue_shift_regions(matrix, window, min_distance, top_n):
|
|
162
|
+
|
|
163
|
+
mat = np.array(matrix, dtype=float)
|
|
164
|
+
|
|
165
|
+
mean_shift = mat.mean(axis=1)
|
|
166
|
+
|
|
167
|
+
smooth = pd.Series(mean_shift).rolling(window, center=True).mean()
|
|
168
|
+
smooth = smooth.bfill().ffill()
|
|
169
|
+
|
|
170
|
+
peaks, props = find_peaks(smooth, distance=min_distance, prominence=0.1)
|
|
171
|
+
|
|
172
|
+
if len(peaks) == 0:
|
|
173
|
+
return []
|
|
174
|
+
|
|
175
|
+
prominences = props["prominences"]
|
|
176
|
+
ranked = np.argsort(prominences)[::-1]
|
|
177
|
+
top_peaks = peaks[ranked][:top_n]
|
|
178
|
+
|
|
179
|
+
regions = []
|
|
180
|
+
for p in top_peaks:
|
|
181
|
+
left = p
|
|
182
|
+
while left > 0 and smooth[left] > smooth[p] * 0.5:
|
|
183
|
+
left -= 1
|
|
184
|
+
|
|
185
|
+
right = p
|
|
186
|
+
while right < len(smooth)-1 and smooth[right] > smooth[p] * 0.5:
|
|
187
|
+
right += 1
|
|
188
|
+
|
|
189
|
+
regions.append((left, p, right))
|
|
190
|
+
|
|
191
|
+
return regions, mean_shift, smooth
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def extract_region_submatrices(matrix, regions_only):
|
|
195
|
+
mat = matrix.values
|
|
196
|
+
submatrices = []
|
|
197
|
+
for start, end in regions_only:
|
|
198
|
+
sub = mat[start:end+1, :]
|
|
199
|
+
submatrices.append((start, end, sub))
|
|
200
|
+
return submatrices
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def slice_matrix2shift(matrix, params):
|
|
204
|
+
|
|
205
|
+
matrix_array = np.array(matrix)
|
|
206
|
+
|
|
207
|
+
res1 = params[0]
|
|
208
|
+
res2 = params[1]
|
|
209
|
+
time1 = params[2]
|
|
210
|
+
time2 = params[3]
|
|
211
|
+
|
|
212
|
+
output = np.zeros(time2 - time1)
|
|
213
|
+
|
|
214
|
+
if res1 == res2:
|
|
215
|
+
output[:] = matrix_array[res1, time1:time2]
|
|
216
|
+
else:
|
|
217
|
+
y = matrix_array[res1:res2+1, time1:time2]
|
|
218
|
+
for i in range(output.shape[0]):
|
|
219
|
+
output[i] = np.mean(y[:, i])
|
|
220
|
+
|
|
221
|
+
return output
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def load_matrix_4_diff_map(csv_path):
|
|
226
|
+
"""Load a CSV file into a numeric matrix."""
|
|
227
|
+
return pd.read_csv(csv_path, header=None).values
|
|
228
|
+
|
|
229
|
+
def compute_mat_diff(A, B):
|
|
230
|
+
"""Compute A - B with NaN mask."""
|
|
231
|
+
if A.shape != B.shape:
|
|
232
|
+
raise ValueError(f"Matrix shapes do not match: {A.shape} vs {B.shape}")
|
|
233
|
+
return A - B
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tmquick
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: TrajMapQuick: Towards Fast Trajectory Map Analysis and Visualization
|
|
5
|
+
Author: Wande M. Oluyemi (PhD), Adeniyi T. Adewumi (PhD), Shadrach C. Eze, Stephen C. Nnemolisa, @ Research Laboratory for Rational Design of Drugs and Biomaterials (ResLaR Labs), Afe Babalola University, Ado-Ekiti Nigeria
|
|
6
|
+
Project-URL: Homepage, https://github.com/SHEDOOMTC/TrajMapQuick.git
|
|
7
|
+
Project-URL: Documentation, https://github.com/SHEDOOMTC/TrajMapQuick/blob/7997b0076a6c31bd725dba3ccdca4349b43250d4/README.md
|
|
8
|
+
Project-URL: DOI, https://github.com/SHEDOOMTC/TrajMapQuick.git
|
|
9
|
+
Requires-Python: >=3.9
|
|
10
|
+
License-File: LICENSE.md
|
|
11
|
+
Requires-Dist: numpy
|
|
12
|
+
Requires-Dist: pandas
|
|
13
|
+
Requires-Dist: matplotlib
|
|
14
|
+
Requires-Dist: seaborn
|
|
15
|
+
Requires-Dist: tqdm
|
|
16
|
+
Requires-Dist: biopython
|
|
17
|
+
Requires-Dist: scipy
|
|
18
|
+
Requires-Dist: netcdf4
|
|
19
|
+
Requires-Dist: colorama
|
|
20
|
+
Provides-Extra: md
|
|
21
|
+
Requires-Dist: mdtraj; extra == "md"
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
Dynamic: requires-python
|