csa3d 0.2.0__tar.gz → 0.2.2__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.
- {csa3d-0.2.0 → csa3d-0.2.2}/PKG-INFO +30 -14
- {csa3d-0.2.0 → csa3d-0.2.2}/README.md +26 -11
- csa3d-0.2.2/csa3d/__init__.py +12 -0
- csa3d-0.2.2/csa3d/_vendor/__init__.py +7 -0
- csa3d-0.2.2/csa3d/_vendor/dendrogram_cut.py +469 -0
- csa3d-0.2.2/csa3d/data_setup.py +224 -0
- {csa3d-0.2.0 → csa3d-0.2.2}/csa3d.egg-info/PKG-INFO +30 -14
- {csa3d-0.2.0 → csa3d-0.2.2}/csa3d.egg-info/SOURCES.txt +4 -0
- {csa3d-0.2.0 → csa3d-0.2.2}/csa3d.egg-info/requires.txt +4 -2
- {csa3d-0.2.0 → csa3d-0.2.2}/pyproject.toml +6 -4
- {csa3d-0.2.0 → csa3d-0.2.2}/LICENSE +0 -0
- {csa3d-0.2.0 → csa3d-0.2.2}/csa3d/config/__init__.py +0 -0
- {csa3d-0.2.0 → csa3d-0.2.2}/csa3d/config/loader.py +0 -0
- {csa3d-0.2.0 → csa3d-0.2.2}/csa3d/config/settings.py +0 -0
- {csa3d-0.2.0 → csa3d-0.2.2}/csa3d.egg-info/dependency_links.txt +0 -0
- {csa3d-0.2.0 → csa3d-0.2.2}/csa3d.egg-info/top_level.txt +0 -0
- {csa3d-0.2.0 → csa3d-0.2.2}/setup.cfg +0 -0
- {csa3d-0.2.0 → csa3d-0.2.2}/tests/test_code_quality.py +0 -0
- {csa3d-0.2.0 → csa3d-0.2.2}/tests/test_config.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: csa3d
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Extract and analyze enzyme catalytic sites in 3D from the Mechanism and Catalytic Site Atlas (M-CSA)
|
|
5
5
|
Author-email: Ioannis Riziotis <riziotis@ebi.ac.uk>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -30,9 +30,10 @@ Requires-Dist: scipy>=1.7.0
|
|
|
30
30
|
Requires-Dist: scikit-learn>=0.24.0
|
|
31
31
|
Requires-Dist: rdkit>=2021.09.0
|
|
32
32
|
Requires-Dist: natsort>=8.0.0
|
|
33
|
-
Requires-Dist: dendrogram-cut>=0.1.0
|
|
34
|
-
Requires-Dist: parity-core>=0.5.0
|
|
35
33
|
Requires-Dist: rmsd>=1.4.0
|
|
34
|
+
Provides-Extra: clustering
|
|
35
|
+
Requires-Dist: matplotlib>=3.5; extra == "clustering"
|
|
36
|
+
Requires-Dist: plotly>=5.0; extra == "clustering"
|
|
36
37
|
Provides-Extra: dev
|
|
37
38
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
38
39
|
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
@@ -105,28 +106,43 @@ pip install -e ".[dev,docs]"
|
|
|
105
106
|
|
|
106
107
|
## Quick Start
|
|
107
108
|
|
|
108
|
-
### 1.
|
|
109
|
+
### 1. Automatic Data Setup (Recommended)
|
|
109
110
|
|
|
110
|
-
|
|
111
|
+
Download and setup M-CSA data automatically:
|
|
111
112
|
|
|
112
|
-
**Option A: Download automatically (recommended)**
|
|
113
113
|
```python
|
|
114
|
-
from csa3d import
|
|
115
|
-
|
|
114
|
+
from csa3d import setup_data
|
|
115
|
+
|
|
116
|
+
# Downloads ~50 GB of data to ~/.csa3d/data
|
|
117
|
+
# Set up specific location with:
|
|
118
|
+
# setup_data("/path/to/csa3d_data")
|
|
119
|
+
success = setup_data()
|
|
120
|
+
|
|
121
|
+
if success:
|
|
122
|
+
print("✓ Data setup complete!")
|
|
116
123
|
```
|
|
117
124
|
|
|
118
|
-
|
|
125
|
+
Or via command line:
|
|
126
|
+
```bash
|
|
127
|
+
python -m csa3d.data_setup /path/to/csa3d_data
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### 2. Manual Data Configuration
|
|
131
|
+
|
|
132
|
+
If you prefer to download data manually:
|
|
133
|
+
|
|
134
|
+
**Option A: Environment variable**
|
|
119
135
|
```bash
|
|
120
136
|
export CSA3D_DATA_DIR="/path/to/csa3d_data"
|
|
121
137
|
```
|
|
122
138
|
|
|
123
|
-
**Option
|
|
139
|
+
**Option B: Set in code**
|
|
124
140
|
```python
|
|
125
|
-
from csa3d import
|
|
126
|
-
|
|
141
|
+
from csa3d import set_data_dir
|
|
142
|
+
set_data_dir("/path/to/csa3d_data")
|
|
127
143
|
```
|
|
128
144
|
|
|
129
|
-
###
|
|
145
|
+
### 3. Load and Inspect Active Sites
|
|
130
146
|
|
|
131
147
|
```python
|
|
132
148
|
from csa3d import Mcsa
|
|
@@ -151,7 +167,7 @@ for entry in db:
|
|
|
151
167
|
print(f" Resolution: {site.resolution} Å")
|
|
152
168
|
```
|
|
153
169
|
|
|
154
|
-
###
|
|
170
|
+
### 4. Clustering and Template Creation
|
|
155
171
|
|
|
156
172
|
```python
|
|
157
173
|
# Build multiple entries
|
|
@@ -51,28 +51,43 @@ pip install -e ".[dev,docs]"
|
|
|
51
51
|
|
|
52
52
|
## Quick Start
|
|
53
53
|
|
|
54
|
-
### 1.
|
|
54
|
+
### 1. Automatic Data Setup (Recommended)
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
Download and setup M-CSA data automatically:
|
|
57
57
|
|
|
58
|
-
**Option A: Download automatically (recommended)**
|
|
59
58
|
```python
|
|
60
|
-
from csa3d import
|
|
61
|
-
|
|
59
|
+
from csa3d import setup_data
|
|
60
|
+
|
|
61
|
+
# Downloads ~50 GB of data to ~/.csa3d/data
|
|
62
|
+
# Set up specific location with:
|
|
63
|
+
# setup_data("/path/to/csa3d_data")
|
|
64
|
+
success = setup_data()
|
|
65
|
+
|
|
66
|
+
if success:
|
|
67
|
+
print("✓ Data setup complete!")
|
|
62
68
|
```
|
|
63
69
|
|
|
64
|
-
|
|
70
|
+
Or via command line:
|
|
71
|
+
```bash
|
|
72
|
+
python -m csa3d.data_setup /path/to/csa3d_data
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 2. Manual Data Configuration
|
|
76
|
+
|
|
77
|
+
If you prefer to download data manually:
|
|
78
|
+
|
|
79
|
+
**Option A: Environment variable**
|
|
65
80
|
```bash
|
|
66
81
|
export CSA3D_DATA_DIR="/path/to/csa3d_data"
|
|
67
82
|
```
|
|
68
83
|
|
|
69
|
-
**Option
|
|
84
|
+
**Option B: Set in code**
|
|
70
85
|
```python
|
|
71
|
-
from csa3d import
|
|
72
|
-
|
|
86
|
+
from csa3d import set_data_dir
|
|
87
|
+
set_data_dir("/path/to/csa3d_data")
|
|
73
88
|
```
|
|
74
89
|
|
|
75
|
-
###
|
|
90
|
+
### 3. Load and Inspect Active Sites
|
|
76
91
|
|
|
77
92
|
```python
|
|
78
93
|
from csa3d import Mcsa
|
|
@@ -97,7 +112,7 @@ for entry in db:
|
|
|
97
112
|
print(f" Resolution: {site.resolution} Å")
|
|
98
113
|
```
|
|
99
114
|
|
|
100
|
-
###
|
|
115
|
+
### 4. Clustering and Template Creation
|
|
101
116
|
|
|
102
117
|
```python
|
|
103
118
|
# Build multiple entries
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""CSA-3D: Extract and analyze enzyme catalytic sites from M-CSA."""
|
|
2
|
+
|
|
3
|
+
from .data_setup import setup_data
|
|
4
|
+
from .config import set_data_dir, get_data_dir
|
|
5
|
+
|
|
6
|
+
__version__ = "0.2.2"
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"setup_data",
|
|
10
|
+
"set_data_dir",
|
|
11
|
+
"get_data_dir",
|
|
12
|
+
]
|
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
import itertools
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import scipy.spatial
|
|
5
|
+
import scipy.cluster
|
|
6
|
+
import matplotlib.cm
|
|
7
|
+
import plotly.graph_objs as go
|
|
8
|
+
from sklearn.metrics import silhouette_score
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DendrogramCut:
|
|
12
|
+
def __init__(self, k_max, method='average'):
|
|
13
|
+
self.k_max = k_max
|
|
14
|
+
self.method = method
|
|
15
|
+
|
|
16
|
+
def fit(self, distance_matrix):
|
|
17
|
+
'''
|
|
18
|
+
Build linkage_stats
|
|
19
|
+
css: cross sum of square when merging c1 and c2
|
|
20
|
+
tss: total sum of square of merged cluster
|
|
21
|
+
'''
|
|
22
|
+
# Handle both condensed and square distance matrices
|
|
23
|
+
if distance_matrix.ndim == 1:
|
|
24
|
+
self.distance_matrix = scipy.spatial.distance.squareform(distance_matrix)
|
|
25
|
+
self.linkage = scipy.cluster.hierarchy.linkage(distance_matrix, method=self.method, optimal_ordering=True)
|
|
26
|
+
else:
|
|
27
|
+
self.distance_matrix = distance_matrix
|
|
28
|
+
self.linkage = scipy.cluster.hierarchy.linkage(scipy.spatial.distance.pdist(distance_matrix), method=self.method, optimal_ordering=True)
|
|
29
|
+
|
|
30
|
+
self.n_data = self.distance_matrix.shape[0]
|
|
31
|
+
self.linkage_stats = [{'c1': 0, 'c2': 0, 'css': 0, 'tss': 0, 'indices': set()} for _ in range(2 * self.n_data - 1)]
|
|
32
|
+
|
|
33
|
+
for i in range(self.n_data):
|
|
34
|
+
self.linkage_stats[i]['c1'] = i
|
|
35
|
+
self.linkage_stats[i]['c2'] = i
|
|
36
|
+
self.linkage_stats[i]['indices'].add(i)
|
|
37
|
+
|
|
38
|
+
for i, (c1, c2, _, _) in enumerate(self.linkage):
|
|
39
|
+
c1 = int(c1)
|
|
40
|
+
c2 = int(c2)
|
|
41
|
+
self.linkage_stats[i + self.n_data]['c1'] = c1
|
|
42
|
+
self.linkage_stats[i + self.n_data]['c2'] = c2
|
|
43
|
+
self.linkage_stats[i + self.n_data]['indices'].update(self.linkage_stats[c1]['indices'])
|
|
44
|
+
self.linkage_stats[i + self.n_data]['indices'].update(self.linkage_stats[c2]['indices'])
|
|
45
|
+
|
|
46
|
+
for i in range(self.n_data, 2 * self.n_data - 1):
|
|
47
|
+
c1 = self.linkage_stats[i]['c1']
|
|
48
|
+
c2 = self.linkage_stats[i]['c2']
|
|
49
|
+
c1_indices = np.asarray(list(self.linkage_stats[c1]['indices']))
|
|
50
|
+
c2_indices = np.asarray(list(self.linkage_stats[c2]['indices']))
|
|
51
|
+
|
|
52
|
+
sample_distances = self.distance_matrix[c1_indices, :][:, c2_indices]
|
|
53
|
+
self.linkage_stats[i]['css'] = np.sum(sample_distances ** 2)
|
|
54
|
+
self.linkage_stats[i]['tss'] = self.linkage_stats[i]['css'] + self.linkage_stats[c1]['tss'] + self.linkage_stats[c2]['tss']
|
|
55
|
+
|
|
56
|
+
### dynamic programming ###
|
|
57
|
+
'''
|
|
58
|
+
Dynamic programming
|
|
59
|
+
kl_mat[i, k]: the number of clusters in the left branch of linkage_stat[$i], maximal cluster $k
|
|
60
|
+
mss_mat[i, k]: the optimal mean square error achieved at linkage_stat[$i], maximal cluster $k
|
|
61
|
+
'''
|
|
62
|
+
self.kl_mat = np.zeros((self.n_data * 2 - 1, self.k_max + 1), dtype=int)
|
|
63
|
+
self.mss_mat = np.zeros((self.n_data * 2 - 1, self.k_max + 1), dtype=float) + np.inf
|
|
64
|
+
|
|
65
|
+
for i in range(self.n_data):
|
|
66
|
+
self.mss_mat[i, 1] = 0
|
|
67
|
+
|
|
68
|
+
for i in range(self.n_data, 2 * self.n_data - 1):
|
|
69
|
+
self.mss_mat[i, 1] = self.linkage_stats[i]['tss'] / len(self.linkage_stats[i]['indices'])
|
|
70
|
+
|
|
71
|
+
for i in range(self.n_data, 2 * self.n_data - 1):
|
|
72
|
+
for k in range(2, self.k_max + 1):
|
|
73
|
+
kl_min = 0
|
|
74
|
+
mss_min = np.inf
|
|
75
|
+
for kl in range(1, k):
|
|
76
|
+
tss = self.mss_mat[self.linkage_stats[i]['c1'], kl] + self.mss_mat[self.linkage_stats[i]['c2'], k - kl]
|
|
77
|
+
if tss < mss_min:
|
|
78
|
+
kl_min = kl
|
|
79
|
+
mss_min = tss
|
|
80
|
+
self.kl_mat[i, k] = kl_min
|
|
81
|
+
self.mss_mat[i, k] = mss_min
|
|
82
|
+
|
|
83
|
+
return self
|
|
84
|
+
|
|
85
|
+
def _get_cut_nodes(self, v, k):
|
|
86
|
+
if k == 1:
|
|
87
|
+
yield v
|
|
88
|
+
else:
|
|
89
|
+
yield from self._get_cut_nodes(self.linkage_stats[v]['c1'], self.kl_mat[v, k])
|
|
90
|
+
yield from self._get_cut_nodes(self.linkage_stats[v]['c2'], k - self.kl_mat[v, k])
|
|
91
|
+
|
|
92
|
+
def get_cluster_mss(self, k):
|
|
93
|
+
total_mss = 0.
|
|
94
|
+
for cid in self._get_cut_nodes(2 * self.n_data - 2, k):
|
|
95
|
+
total_mss += self.linkage_stats[cid]['tss'] / len(self.linkage_stats[cid]['indices'])
|
|
96
|
+
|
|
97
|
+
return total_mss
|
|
98
|
+
|
|
99
|
+
def get_cluster_label(self, k):
|
|
100
|
+
### get flat clusters ###
|
|
101
|
+
z = np.zeros(self.n_data, dtype=int) - 1
|
|
102
|
+
for c, cid in enumerate(self._get_cut_nodes(2 * self.n_data - 2, k)):
|
|
103
|
+
for i in self.linkage_stats[cid]['indices']:
|
|
104
|
+
if z[i] != -1:
|
|
105
|
+
print(i)
|
|
106
|
+
z[i] = c
|
|
107
|
+
|
|
108
|
+
return z
|
|
109
|
+
|
|
110
|
+
def _dirichlet_process_kl(self, n_list, alpha_):
|
|
111
|
+
out = 0.
|
|
112
|
+
count = alpha_
|
|
113
|
+
for n in n_list:
|
|
114
|
+
out -= np.log(alpha_ / count)
|
|
115
|
+
count += 1
|
|
116
|
+
for i in range(1, n):
|
|
117
|
+
out -= np.log(i / count)
|
|
118
|
+
count += 1
|
|
119
|
+
return out
|
|
120
|
+
|
|
121
|
+
def pac_bayesian_cut(self, alpha_=1., lambda_=1.):
|
|
122
|
+
min_loss = np.inf
|
|
123
|
+
min_loss_k = None
|
|
124
|
+
|
|
125
|
+
for k in range(1, self.k_max + 1):
|
|
126
|
+
n_list = [len(self.linkage_stats[c]['indices']) for c in self._get_cut_nodes(2 * self.n_data - 2, k)]
|
|
127
|
+
total_mss = self.get_cluster_mss(k)
|
|
128
|
+
loss = total_mss + self._dirichlet_process_kl(n_list, alpha_) / lambda_
|
|
129
|
+
if loss < min_loss:
|
|
130
|
+
min_loss = loss
|
|
131
|
+
min_loss_k = k
|
|
132
|
+
|
|
133
|
+
return min_loss_k
|
|
134
|
+
|
|
135
|
+
def dendrogram_plot(self, k=None, label=None):
|
|
136
|
+
cmap = matplotlib.cm.get_cmap('viridis')
|
|
137
|
+
|
|
138
|
+
order = scipy.cluster.hierarchy.leaves_list(self.linkage)
|
|
139
|
+
|
|
140
|
+
dendrogram_stat = dict((v, {'x': i, 'y': 0}) for i, v in enumerate(order))
|
|
141
|
+
|
|
142
|
+
data = []
|
|
143
|
+
|
|
144
|
+
### plot dendrogram ###
|
|
145
|
+
if k is None:
|
|
146
|
+
norm = np.max(self.linkage[:, 2])
|
|
147
|
+
|
|
148
|
+
for res in self.linkage:
|
|
149
|
+
new_cluster = len(dendrogram_stat)
|
|
150
|
+
dendrogram_stat[new_cluster] = {'x': (dendrogram_stat[res[0]]['x'] + dendrogram_stat[res[1]]['x']) / 2, 'y': res[2]}
|
|
151
|
+
x_range = np.array([dendrogram_stat[res[0]]['x'], dendrogram_stat[res[0]]['x'], dendrogram_stat[res[1]]['x'], dendrogram_stat[res[1]]['x']])
|
|
152
|
+
y_range = np.array([dendrogram_stat[res[0]]['y'], dendrogram_stat[new_cluster]['y'], dendrogram_stat[new_cluster]['y'], dendrogram_stat[res[1]]['y']])
|
|
153
|
+
|
|
154
|
+
data.append(
|
|
155
|
+
{
|
|
156
|
+
'hoverinfo': 'text',
|
|
157
|
+
'marker': {'color': 'rgb({}, {}, {})'.format(*cmap(res[2] / norm, bytes=True)[:3])},
|
|
158
|
+
'mode': 'lines',
|
|
159
|
+
'type': 'scatter',
|
|
160
|
+
'x': x_range,
|
|
161
|
+
'xaxis': 'x',
|
|
162
|
+
'y': y_range,
|
|
163
|
+
'yaxis': 'y'
|
|
164
|
+
}
|
|
165
|
+
)
|
|
166
|
+
else:
|
|
167
|
+
pivot = set(self._get_cut_nodes(2 * self.n_data - 2, k))
|
|
168
|
+
z = self.get_cluster_label(k)
|
|
169
|
+
colorscale = dict((i, z[i] / (k - 1)) for i in range(self.n_data))
|
|
170
|
+
|
|
171
|
+
for res in self.linkage:
|
|
172
|
+
new_cluster = len(dendrogram_stat)
|
|
173
|
+
colorscale[new_cluster] = (colorscale[int(res[0])] + colorscale[int(res[1])]) / 2
|
|
174
|
+
dendrogram_stat[new_cluster] = {'x': (dendrogram_stat[res[0]]['x'] + dendrogram_stat[res[1]]['x']) / 2, 'y': res[2]}
|
|
175
|
+
x_range = np.array([dendrogram_stat[res[0]]['x'], dendrogram_stat[res[0]]['x'], dendrogram_stat[res[1]]['x'], dendrogram_stat[res[1]]['x']])
|
|
176
|
+
y_range = np.array([dendrogram_stat[res[0]]['y'], dendrogram_stat[new_cluster]['y'], dendrogram_stat[new_cluster]['y'], dendrogram_stat[res[1]]['y']])
|
|
177
|
+
|
|
178
|
+
if new_cluster in pivot:
|
|
179
|
+
data.append(
|
|
180
|
+
{
|
|
181
|
+
'x': [dendrogram_stat[new_cluster]['x']],
|
|
182
|
+
'y': [dendrogram_stat[new_cluster]['y']],
|
|
183
|
+
'type': 'scatter',
|
|
184
|
+
'mode': 'markers',
|
|
185
|
+
'xaxis': 'x',
|
|
186
|
+
'yaxis': 'y',
|
|
187
|
+
'marker': {'size': [20], 'color': 'rgb({}, {}, {})'.format(*cmap(colorscale[new_cluster], bytes=True)[:3])}
|
|
188
|
+
}
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
data.append(
|
|
192
|
+
{
|
|
193
|
+
'hoverinfo': 'text',
|
|
194
|
+
'marker': {'color': 'rgb({}, {}, {})'.format(*cmap(colorscale[new_cluster], bytes=True)[:3])},
|
|
195
|
+
'mode': 'lines',
|
|
196
|
+
'type': 'scatter',
|
|
197
|
+
'x': x_range,
|
|
198
|
+
'xaxis': 'x',
|
|
199
|
+
'y': y_range,
|
|
200
|
+
'yaxis': 'y'
|
|
201
|
+
}
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
fig = go.Figure(data=data)
|
|
205
|
+
|
|
206
|
+
### edit layout ###
|
|
207
|
+
if label is not None:
|
|
208
|
+
fig.update_layout(
|
|
209
|
+
{
|
|
210
|
+
'xaxis': {
|
|
211
|
+
'tickmode': 'array',
|
|
212
|
+
'tickvals': np.arange(len(label)),
|
|
213
|
+
'ticktext': label[order]
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
fig.update_layout(
|
|
219
|
+
{
|
|
220
|
+
'width': 800,
|
|
221
|
+
'height': 800,
|
|
222
|
+
'showlegend': False,
|
|
223
|
+
'hovermode': 'closest',
|
|
224
|
+
'xaxis': {
|
|
225
|
+
'domain': [0, 1],
|
|
226
|
+
'rangemode': 'tozero',
|
|
227
|
+
'mirror': False,
|
|
228
|
+
'showgrid': False,
|
|
229
|
+
'showline': False,
|
|
230
|
+
'zeroline': False,
|
|
231
|
+
'showticklabels': True,
|
|
232
|
+
'ticks': ""
|
|
233
|
+
},
|
|
234
|
+
'yaxis': {
|
|
235
|
+
'domain': [0, 1],
|
|
236
|
+
'rangemode': 'tozero',
|
|
237
|
+
'mirror': False,
|
|
238
|
+
'showgrid': False,
|
|
239
|
+
'showline': False,
|
|
240
|
+
'zeroline': False,
|
|
241
|
+
'showticklabels': True,
|
|
242
|
+
'ticks': ""
|
|
243
|
+
},
|
|
244
|
+
'paper_bgcolor': 'rgba(0,0,0,0)',
|
|
245
|
+
'plot_bgcolor': 'rgba(0,0,0,0)'
|
|
246
|
+
}
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
fig.show()
|
|
250
|
+
|
|
251
|
+
def heatmap_with_dendrogram_plot(self, k=None, label=None):
|
|
252
|
+
cmap = matplotlib.cm.get_cmap('viridis')
|
|
253
|
+
|
|
254
|
+
order = scipy.cluster.hierarchy.leaves_list(self.linkage)
|
|
255
|
+
|
|
256
|
+
dendrogram_stat = dict((v, {'x': i, 'y': 0}) for i, v in enumerate(order))
|
|
257
|
+
|
|
258
|
+
data = []
|
|
259
|
+
|
|
260
|
+
### plot dendrogram ###
|
|
261
|
+
if k is None:
|
|
262
|
+
norm = np.max(self.linkage[:, 2])
|
|
263
|
+
|
|
264
|
+
for res in self.linkage:
|
|
265
|
+
new_cluster = len(dendrogram_stat)
|
|
266
|
+
dendrogram_stat[new_cluster] = {'x': (dendrogram_stat[res[0]]['x'] + dendrogram_stat[res[1]]['x']) / 2, 'y': res[2]}
|
|
267
|
+
x_range = np.array([dendrogram_stat[res[0]]['x'], dendrogram_stat[res[0]]['x'], dendrogram_stat[res[1]]['x'], dendrogram_stat[res[1]]['x']])
|
|
268
|
+
y_range = np.array([dendrogram_stat[res[0]]['y'], dendrogram_stat[new_cluster]['y'], dendrogram_stat[new_cluster]['y'], dendrogram_stat[res[1]]['y']])
|
|
269
|
+
|
|
270
|
+
data.append(
|
|
271
|
+
{
|
|
272
|
+
'hoverinfo': 'text',
|
|
273
|
+
'marker': {'color': 'rgb({}, {}, {})'.format(*cmap(res[2] / norm, bytes=True)[:3])},
|
|
274
|
+
'mode': 'lines',
|
|
275
|
+
'type': 'scatter',
|
|
276
|
+
'x': x_range,
|
|
277
|
+
'xaxis': 'x',
|
|
278
|
+
'y': y_range,
|
|
279
|
+
'yaxis': 'y2'
|
|
280
|
+
}
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
data.append(
|
|
284
|
+
{
|
|
285
|
+
'hoverinfo': 'text',
|
|
286
|
+
'marker': {'color': 'rgb({}, {}, {})'.format(*cmap(res[2] / norm, bytes=True)[:3])},
|
|
287
|
+
'mode': 'lines',
|
|
288
|
+
'type': 'scatter',
|
|
289
|
+
'x': -y_range,
|
|
290
|
+
'xaxis': 'x2',
|
|
291
|
+
'y': x_range,
|
|
292
|
+
'yaxis': 'y'
|
|
293
|
+
}
|
|
294
|
+
)
|
|
295
|
+
else:
|
|
296
|
+
pivot = set(self._get_cut_nodes(2 * self.n_data - 2, k))
|
|
297
|
+
z = self.get_cluster_label(k)
|
|
298
|
+
colorscale = dict((i, z[i] / (k - 1)) for i in range(self.n_data))
|
|
299
|
+
|
|
300
|
+
for res in self.linkage:
|
|
301
|
+
new_cluster = len(dendrogram_stat)
|
|
302
|
+
colorscale[new_cluster] = (colorscale[int(res[0])] + colorscale[int(res[1])]) / 2
|
|
303
|
+
dendrogram_stat[new_cluster] = {'x': (dendrogram_stat[res[0]]['x'] + dendrogram_stat[res[1]]['x']) / 2, 'y': res[2]}
|
|
304
|
+
x_range = np.array([dendrogram_stat[res[0]]['x'], dendrogram_stat[res[0]]['x'], dendrogram_stat[res[1]]['x'], dendrogram_stat[res[1]]['x']])
|
|
305
|
+
y_range = np.array([dendrogram_stat[res[0]]['y'], dendrogram_stat[new_cluster]['y'], dendrogram_stat[new_cluster]['y'], dendrogram_stat[res[1]]['y']])
|
|
306
|
+
|
|
307
|
+
if new_cluster in pivot:
|
|
308
|
+
data.append(
|
|
309
|
+
{
|
|
310
|
+
'x': [dendrogram_stat[new_cluster]['x']],
|
|
311
|
+
'y': [dendrogram_stat[new_cluster]['y']],
|
|
312
|
+
'type': 'scatter',
|
|
313
|
+
'mode': 'markers',
|
|
314
|
+
'xaxis': 'x',
|
|
315
|
+
'yaxis': 'y2',
|
|
316
|
+
'marker': {'size': [10], 'color': 'rgb({}, {}, {})'.format(*cmap(colorscale[new_cluster], bytes=True)[:3])}
|
|
317
|
+
}
|
|
318
|
+
)
|
|
319
|
+
data.append(
|
|
320
|
+
{
|
|
321
|
+
'x': [-dendrogram_stat[new_cluster]['y']],
|
|
322
|
+
'y': [dendrogram_stat[new_cluster]['x']],
|
|
323
|
+
'type': 'scatter',
|
|
324
|
+
'mode': 'markers',
|
|
325
|
+
'xaxis': 'x2',
|
|
326
|
+
'yaxis': 'y',
|
|
327
|
+
'marker': {'size': [10], 'color': 'rgb({}, {}, {})'.format(*cmap(colorscale[new_cluster], bytes=True)[:3])}
|
|
328
|
+
}
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
data.append(
|
|
332
|
+
{
|
|
333
|
+
'hoverinfo': 'text',
|
|
334
|
+
'marker': {'color': 'rgb({}, {}, {})'.format(*cmap(colorscale[new_cluster], bytes=True)[:3])},
|
|
335
|
+
'mode': 'lines',
|
|
336
|
+
'type': 'scatter',
|
|
337
|
+
'x': x_range,
|
|
338
|
+
'xaxis': 'x',
|
|
339
|
+
'y': y_range,
|
|
340
|
+
'yaxis': 'y2'
|
|
341
|
+
}
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
data.append(
|
|
345
|
+
{
|
|
346
|
+
'hoverinfo': 'text',
|
|
347
|
+
'marker': {'color': 'rgb({}, {}, {})'.format(*cmap(colorscale[new_cluster], bytes=True)[:3])},
|
|
348
|
+
'mode': 'lines',
|
|
349
|
+
'type': 'scatter',
|
|
350
|
+
'x': -y_range,
|
|
351
|
+
'xaxis': 'x2',
|
|
352
|
+
'y': x_range,
|
|
353
|
+
'yaxis': 'y'
|
|
354
|
+
}
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
### plot hard cluster region ###
|
|
358
|
+
|
|
359
|
+
ordered_z = z[order]
|
|
360
|
+
block_size = [sum(1 for __ in g) for _, g in itertools.groupby(ordered_z)]
|
|
361
|
+
start = -0.5
|
|
362
|
+
for i in range(len(block_size)):
|
|
363
|
+
data.append(
|
|
364
|
+
go.Scatter(
|
|
365
|
+
x=[start, start, start+block_size[i], start+block_size[i], start],
|
|
366
|
+
y=[start, start+block_size[i], start+block_size[i], start, start],
|
|
367
|
+
mode='lines',
|
|
368
|
+
hoverinfo='none',
|
|
369
|
+
line=dict(color='#FF0000')
|
|
370
|
+
)
|
|
371
|
+
)
|
|
372
|
+
start += block_size[i]
|
|
373
|
+
|
|
374
|
+
### plot heatmap ###
|
|
375
|
+
data.append(
|
|
376
|
+
go.Heatmap(
|
|
377
|
+
z=self.distance_matrix[order, :][:, order],
|
|
378
|
+
colorscale='Blues',
|
|
379
|
+
reversescale=True,
|
|
380
|
+
hoverinfo='x+y+z'
|
|
381
|
+
)
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
fig = go.Figure(data=data)
|
|
385
|
+
|
|
386
|
+
### edit layout ###
|
|
387
|
+
if label is not None:
|
|
388
|
+
fig.update_layout(
|
|
389
|
+
{
|
|
390
|
+
'xaxis': {
|
|
391
|
+
'tickmode': 'array',
|
|
392
|
+
'tickvals': np.arange(len(label)),
|
|
393
|
+
'ticktext': label[order]
|
|
394
|
+
},
|
|
395
|
+
'yaxis': {
|
|
396
|
+
'tickmode': 'array',
|
|
397
|
+
'tickvals': np.arange(len(label)),
|
|
398
|
+
'ticktext': label[order],
|
|
399
|
+
'autorange': 'reversed'
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
fig.update_layout(
|
|
405
|
+
{
|
|
406
|
+
'width': 800,
|
|
407
|
+
'height': 800,
|
|
408
|
+
'showlegend': False,
|
|
409
|
+
'hovermode': 'closest',
|
|
410
|
+
'xaxis': {
|
|
411
|
+
'domain': [.15, 1],
|
|
412
|
+
'rangemode': 'tozero',
|
|
413
|
+
'mirror': False,
|
|
414
|
+
'showgrid': False,
|
|
415
|
+
'showline': False,
|
|
416
|
+
'zeroline': False,
|
|
417
|
+
'showticklabels': False,
|
|
418
|
+
'ticks': ""
|
|
419
|
+
},
|
|
420
|
+
'xaxis2': {
|
|
421
|
+
'domain': [0, .15],
|
|
422
|
+
'rangemode': 'tozero',
|
|
423
|
+
'mirror': False,
|
|
424
|
+
'showgrid': False,
|
|
425
|
+
'showline': False,
|
|
426
|
+
'zeroline': False,
|
|
427
|
+
'showticklabels': False,
|
|
428
|
+
'ticks': ""
|
|
429
|
+
},
|
|
430
|
+
'yaxis': {
|
|
431
|
+
'domain': [0, .85],
|
|
432
|
+
'rangemode': 'tozero',
|
|
433
|
+
'mirror': False,
|
|
434
|
+
'showgrid': False,
|
|
435
|
+
'showline': False,
|
|
436
|
+
'zeroline': False,
|
|
437
|
+
'showticklabels': False,
|
|
438
|
+
'ticks': ""
|
|
439
|
+
},
|
|
440
|
+
'yaxis2': {
|
|
441
|
+
'domain':[.825, .975],
|
|
442
|
+
'rangemode': 'tozero',
|
|
443
|
+
'mirror': False,
|
|
444
|
+
'showgrid': False,
|
|
445
|
+
'showline': False,
|
|
446
|
+
'zeroline': False,
|
|
447
|
+
'showticklabels': False,
|
|
448
|
+
'ticks': ""
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
fig.show()
|
|
454
|
+
|
|
455
|
+
def elbow_plot(self):
|
|
456
|
+
x = np.arange(self.k_max) + 1
|
|
457
|
+
|
|
458
|
+
y = np.asarray([sum(self.linkage_stats[i]['tss'] / len(self.linkage_stats[i]['indices']) for i in self._get_cut_nodes(-1, k)) for k in range(1, self.k_max + 1)])
|
|
459
|
+
y = (y[0] - y) / y[0]
|
|
460
|
+
|
|
461
|
+
fig = go.Figure(data=[go.Scatter(x=x, y=y, mode='lines')])
|
|
462
|
+
fig.show()
|
|
463
|
+
|
|
464
|
+
def silhouette_score_plot(self):
|
|
465
|
+
x = np.arange(2, self.k_max + 1)
|
|
466
|
+
y = [silhouette_score(self.distance_matrix, self.get_cluster_label(k), metric='precomputed') for k in x]
|
|
467
|
+
|
|
468
|
+
fig = go.Figure(data=go.Scatter(x=x, y=y, mode='lines'))
|
|
469
|
+
fig.show()
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"""Automatic data setup and download for CSA-3D.
|
|
2
|
+
|
|
3
|
+
Downloads and organizes required data files from official sources.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Optional
|
|
10
|
+
import urllib.request
|
|
11
|
+
import urllib.error
|
|
12
|
+
from urllib.parse import urljoin
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class DataDownloader:
|
|
18
|
+
"""Download and setup CSA-3D data files."""
|
|
19
|
+
|
|
20
|
+
# Data source URLs
|
|
21
|
+
SIFTS_URL = "https://www.ebi.ac.uk/pdbe/download/sifts/csv/"
|
|
22
|
+
KEGG_URL = "https://rest.kegg.jp/get/"
|
|
23
|
+
RHEA_URL = "https://www.rhea-db.org/download/rhea/"
|
|
24
|
+
PDBE_URL = "https://www.ebi.ac.uk/pdbe/download/"
|
|
25
|
+
MCSA_URL = "https://www.ebi.ac.uk/thornton-srv/m-csa/download/"
|
|
26
|
+
|
|
27
|
+
def __init__(self, data_dir: Path):
|
|
28
|
+
"""Initialize data downloader.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
data_dir: Root directory for CSA-3D data
|
|
32
|
+
"""
|
|
33
|
+
self.data_dir = Path(data_dir)
|
|
34
|
+
self.data_dir.mkdir(parents=True, exist_ok=True)
|
|
35
|
+
|
|
36
|
+
def download_file(self, url: str, output_path: Path) -> bool:
|
|
37
|
+
"""Download a file from URL.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
url: URL to download from
|
|
41
|
+
output_path: Path to save file
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
True if successful, False otherwise
|
|
45
|
+
"""
|
|
46
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
logger.info(f"Downloading {url} → {output_path}")
|
|
50
|
+
urllib.request.urlretrieve(url, output_path)
|
|
51
|
+
logger.info(f"✓ Downloaded {output_path.name}")
|
|
52
|
+
return True
|
|
53
|
+
except urllib.error.URLError as e:
|
|
54
|
+
logger.warning(f"✗ Failed to download {url}: {e}")
|
|
55
|
+
return False
|
|
56
|
+
except Exception as e:
|
|
57
|
+
logger.error(f"Unexpected error downloading {url}: {e}")
|
|
58
|
+
return False
|
|
59
|
+
|
|
60
|
+
def setup_sifts(self) -> bool:
|
|
61
|
+
"""Setup SIFTS (PDB-UniProt mappings).
|
|
62
|
+
|
|
63
|
+
Downloads from EBI PDBe SIFTS data.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
True if successful
|
|
67
|
+
"""
|
|
68
|
+
logger.info("Setting up SIFTS data...")
|
|
69
|
+
sifts_dir = self.data_dir / "sifts"
|
|
70
|
+
sifts_dir.mkdir(parents=True, exist_ok=True)
|
|
71
|
+
|
|
72
|
+
files = {
|
|
73
|
+
"uniprot_pdb.csv": "uniprot_pdb.csv",
|
|
74
|
+
"pdb_chain_uniprot.csv": "pdb_chain_uniprot.csv",
|
|
75
|
+
"pdb_chain_enzyme.csv": "pdb_chain_enzyme.csv",
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
success = True
|
|
79
|
+
for filename, remote_name in files.items():
|
|
80
|
+
url = urljoin(self.SIFTS_URL, remote_name)
|
|
81
|
+
output = sifts_dir / filename
|
|
82
|
+
if not self.download_file(url, output):
|
|
83
|
+
success = False
|
|
84
|
+
|
|
85
|
+
return success
|
|
86
|
+
|
|
87
|
+
def setup_kegg(self) -> bool:
|
|
88
|
+
"""Setup KEGG reaction data.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
True if successful
|
|
92
|
+
"""
|
|
93
|
+
logger.info("Setting up KEGG data...")
|
|
94
|
+
kegg_dir = self.data_dir / "kegg"
|
|
95
|
+
kegg_dir.mkdir(parents=True, exist_ok=True)
|
|
96
|
+
|
|
97
|
+
# KEGG reactions endpoint
|
|
98
|
+
output = kegg_dir / "kegg_reaction_components.csv"
|
|
99
|
+
logger.info("Note: KEGG data requires special API handling")
|
|
100
|
+
logger.info("Please obtain KEGG reaction data from: https://www.kegg.jp/")
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
def setup_rhea(self) -> bool:
|
|
104
|
+
"""Setup RHEA reaction data.
|
|
105
|
+
|
|
106
|
+
Downloads from RHEA database.
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
True if successful
|
|
110
|
+
"""
|
|
111
|
+
logger.info("Setting up RHEA data...")
|
|
112
|
+
rhea_dir = self.data_dir / "rhea"
|
|
113
|
+
rhea_dir.mkdir(parents=True, exist_ok=True)
|
|
114
|
+
|
|
115
|
+
url = urljoin(self.RHEA_URL, "rhea_reaction_components.csv")
|
|
116
|
+
output = rhea_dir / "rhea_reaction_components.csv"
|
|
117
|
+
return self.download_file(url, output)
|
|
118
|
+
|
|
119
|
+
def setup_pdbe(self) -> bool:
|
|
120
|
+
"""Setup PDBe data (ions, cofactors, resolution).
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
True if successful
|
|
124
|
+
"""
|
|
125
|
+
logger.info("Setting up PDBe data...")
|
|
126
|
+
pdbe_dir = self.data_dir / "pdbe"
|
|
127
|
+
pdbe_dir.mkdir(parents=True, exist_ok=True)
|
|
128
|
+
|
|
129
|
+
files = {
|
|
130
|
+
"pdb_ions.csv": "pdb_ions.csv",
|
|
131
|
+
"pdb_cofactors.csv": "pdb_cofactors.csv",
|
|
132
|
+
"resolu.idx": "resolu.idx",
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
success = True
|
|
136
|
+
for filename, remote_name in files.items():
|
|
137
|
+
url = urljoin(self.PDBE_URL, remote_name)
|
|
138
|
+
output = pdbe_dir / filename
|
|
139
|
+
if not self.download_file(url, output):
|
|
140
|
+
success = False
|
|
141
|
+
|
|
142
|
+
return success
|
|
143
|
+
|
|
144
|
+
def setup_mcsa(self) -> bool:
|
|
145
|
+
"""Setup M-CSA catalytic site data.
|
|
146
|
+
|
|
147
|
+
Downloads from M-CSA database.
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
True if successful
|
|
151
|
+
"""
|
|
152
|
+
logger.info("Setting up M-CSA data...")
|
|
153
|
+
mcsa_dir = self.data_dir / "mcsa"
|
|
154
|
+
mcsa_dir.mkdir(parents=True, exist_ok=True)
|
|
155
|
+
|
|
156
|
+
url = urljoin(self.MCSA_URL, "mcsa_cofactors.csv")
|
|
157
|
+
output = mcsa_dir / "mcsa_cofactors.csv"
|
|
158
|
+
return self.download_file(url, output)
|
|
159
|
+
|
|
160
|
+
def setup_all(self, skip_optional: bool = False) -> dict:
|
|
161
|
+
"""Setup all data files.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
skip_optional: Skip optional data sources (KEGG)
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
Dictionary with setup status for each source
|
|
168
|
+
"""
|
|
169
|
+
logger.info(f"Starting automatic CSA-3D data setup to {self.data_dir}")
|
|
170
|
+
|
|
171
|
+
results = {}
|
|
172
|
+
results["sifts"] = self.setup_sifts()
|
|
173
|
+
results["rhea"] = self.setup_rhea()
|
|
174
|
+
results["pdbe"] = self.setup_pdbe()
|
|
175
|
+
results["mcsa"] = self.setup_mcsa()
|
|
176
|
+
|
|
177
|
+
if not skip_optional:
|
|
178
|
+
results["kegg"] = self.setup_kegg()
|
|
179
|
+
|
|
180
|
+
# Summary
|
|
181
|
+
logger.info("\n" + "=" * 60)
|
|
182
|
+
logger.info("Data Setup Summary:")
|
|
183
|
+
for source, success in results.items():
|
|
184
|
+
status = "✓ Complete" if success else "✗ Failed/Skipped"
|
|
185
|
+
logger.info(f" {source.upper()}: {status}")
|
|
186
|
+
logger.info("=" * 60)
|
|
187
|
+
|
|
188
|
+
return results
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def setup_data(data_dir: Optional[str] = None) -> bool:
|
|
192
|
+
"""Setup CSA-3D data automatically.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
data_dir: Directory for data files. Defaults to ~/.csa3d/data
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
True if setup successful
|
|
199
|
+
"""
|
|
200
|
+
if data_dir is None:
|
|
201
|
+
data_dir = str(Path.home() / ".csa3d" / "data")
|
|
202
|
+
|
|
203
|
+
logger.info(f"Setting up CSA-3D data in: {data_dir}")
|
|
204
|
+
|
|
205
|
+
downloader = DataDownloader(Path(data_dir))
|
|
206
|
+
results = downloader.setup_all()
|
|
207
|
+
|
|
208
|
+
success = all(results.values())
|
|
209
|
+
if success:
|
|
210
|
+
logger.info(f"\n✓ Data setup complete!")
|
|
211
|
+
logger.info(f"Set environment variable: export CSA3D_DATA_DIR={data_dir}")
|
|
212
|
+
else:
|
|
213
|
+
logger.warning("\n⚠ Some data sources failed to download")
|
|
214
|
+
logger.info("Please check logs above and retry")
|
|
215
|
+
|
|
216
|
+
return success
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
if __name__ == "__main__":
|
|
220
|
+
logging.basicConfig(
|
|
221
|
+
level=logging.INFO,
|
|
222
|
+
format="%(levelname)s: %(message)s"
|
|
223
|
+
)
|
|
224
|
+
setup_data()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: csa3d
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Extract and analyze enzyme catalytic sites in 3D from the Mechanism and Catalytic Site Atlas (M-CSA)
|
|
5
5
|
Author-email: Ioannis Riziotis <riziotis@ebi.ac.uk>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -30,9 +30,10 @@ Requires-Dist: scipy>=1.7.0
|
|
|
30
30
|
Requires-Dist: scikit-learn>=0.24.0
|
|
31
31
|
Requires-Dist: rdkit>=2021.09.0
|
|
32
32
|
Requires-Dist: natsort>=8.0.0
|
|
33
|
-
Requires-Dist: dendrogram-cut>=0.1.0
|
|
34
|
-
Requires-Dist: parity-core>=0.5.0
|
|
35
33
|
Requires-Dist: rmsd>=1.4.0
|
|
34
|
+
Provides-Extra: clustering
|
|
35
|
+
Requires-Dist: matplotlib>=3.5; extra == "clustering"
|
|
36
|
+
Requires-Dist: plotly>=5.0; extra == "clustering"
|
|
36
37
|
Provides-Extra: dev
|
|
37
38
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
38
39
|
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
@@ -105,28 +106,43 @@ pip install -e ".[dev,docs]"
|
|
|
105
106
|
|
|
106
107
|
## Quick Start
|
|
107
108
|
|
|
108
|
-
### 1.
|
|
109
|
+
### 1. Automatic Data Setup (Recommended)
|
|
109
110
|
|
|
110
|
-
|
|
111
|
+
Download and setup M-CSA data automatically:
|
|
111
112
|
|
|
112
|
-
**Option A: Download automatically (recommended)**
|
|
113
113
|
```python
|
|
114
|
-
from csa3d import
|
|
115
|
-
|
|
114
|
+
from csa3d import setup_data
|
|
115
|
+
|
|
116
|
+
# Downloads ~50 GB of data to ~/.csa3d/data
|
|
117
|
+
# Set up specific location with:
|
|
118
|
+
# setup_data("/path/to/csa3d_data")
|
|
119
|
+
success = setup_data()
|
|
120
|
+
|
|
121
|
+
if success:
|
|
122
|
+
print("✓ Data setup complete!")
|
|
116
123
|
```
|
|
117
124
|
|
|
118
|
-
|
|
125
|
+
Or via command line:
|
|
126
|
+
```bash
|
|
127
|
+
python -m csa3d.data_setup /path/to/csa3d_data
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### 2. Manual Data Configuration
|
|
131
|
+
|
|
132
|
+
If you prefer to download data manually:
|
|
133
|
+
|
|
134
|
+
**Option A: Environment variable**
|
|
119
135
|
```bash
|
|
120
136
|
export CSA3D_DATA_DIR="/path/to/csa3d_data"
|
|
121
137
|
```
|
|
122
138
|
|
|
123
|
-
**Option
|
|
139
|
+
**Option B: Set in code**
|
|
124
140
|
```python
|
|
125
|
-
from csa3d import
|
|
126
|
-
|
|
141
|
+
from csa3d import set_data_dir
|
|
142
|
+
set_data_dir("/path/to/csa3d_data")
|
|
127
143
|
```
|
|
128
144
|
|
|
129
|
-
###
|
|
145
|
+
### 3. Load and Inspect Active Sites
|
|
130
146
|
|
|
131
147
|
```python
|
|
132
148
|
from csa3d import Mcsa
|
|
@@ -151,7 +167,7 @@ for entry in db:
|
|
|
151
167
|
print(f" Resolution: {site.resolution} Å")
|
|
152
168
|
```
|
|
153
169
|
|
|
154
|
-
###
|
|
170
|
+
### 4. Clustering and Template Creation
|
|
155
171
|
|
|
156
172
|
```python
|
|
157
173
|
# Build multiple entries
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
LICENSE
|
|
2
2
|
README.md
|
|
3
3
|
pyproject.toml
|
|
4
|
+
csa3d/__init__.py
|
|
5
|
+
csa3d/data_setup.py
|
|
4
6
|
csa3d.egg-info/PKG-INFO
|
|
5
7
|
csa3d.egg-info/SOURCES.txt
|
|
6
8
|
csa3d.egg-info/dependency_links.txt
|
|
7
9
|
csa3d.egg-info/requires.txt
|
|
8
10
|
csa3d.egg-info/top_level.txt
|
|
11
|
+
csa3d/_vendor/__init__.py
|
|
12
|
+
csa3d/_vendor/dendrogram_cut.py
|
|
9
13
|
csa3d/config/__init__.py
|
|
10
14
|
csa3d/config/loader.py
|
|
11
15
|
csa3d/config/settings.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "csa3d"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.2"
|
|
8
8
|
description = "Extract and analyze enzyme catalytic sites in 3D from the Mechanism and Catalytic Site Atlas (M-CSA)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "Apache-2.0"}
|
|
@@ -34,12 +34,14 @@ dependencies = [
|
|
|
34
34
|
"scikit-learn>=0.24.0",
|
|
35
35
|
"rdkit>=2021.09.0",
|
|
36
36
|
"natsort>=8.0.0",
|
|
37
|
-
"dendrogram-cut>=0.1.0",
|
|
38
|
-
"parity-core>=0.5.0",
|
|
39
37
|
"rmsd>=1.4.0",
|
|
40
38
|
]
|
|
41
39
|
|
|
42
40
|
[project.optional-dependencies]
|
|
41
|
+
clustering = [
|
|
42
|
+
"matplotlib>=3.5",
|
|
43
|
+
"plotly>=5.0",
|
|
44
|
+
]
|
|
43
45
|
dev = [
|
|
44
46
|
"pytest>=7.0",
|
|
45
47
|
"pytest-cov>=4.0",
|
|
@@ -68,7 +70,7 @@ Repository = "https://github.com/your-username/csa3d"
|
|
|
68
70
|
"Issue Tracker" = "https://github.com/your-username/csa3d/issues"
|
|
69
71
|
|
|
70
72
|
[tool.setuptools]
|
|
71
|
-
packages = ["csa3d", "csa3d.config"]
|
|
73
|
+
packages = ["csa3d", "csa3d.config", "csa3d._vendor"]
|
|
72
74
|
|
|
73
75
|
[tool.black]
|
|
74
76
|
line-length = 100
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|