emdbva 0.0.1.dev152__tar.gz → 0.0.1.dev155__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.
- {emdbva-0.0.1.dev152/emdbva.egg-info → emdbva-0.0.1.dev155}/PKG-INFO +1 -1
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155/emdbva.egg-info}/PKG-INFO +1 -1
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/emdbva.egg-info/SOURCES.txt +2 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/mainva.py +12 -7
- emdbva-0.0.1.dev155/va/metrics/phenix_locres.py +324 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/metrics/residue_locres.py +86 -1
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/metrics/resmap.py +7 -4
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/preparation.py +4 -1
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/utils/ChimeraxViews.py +20 -20
- emdbva-0.0.1.dev155/va/utils/chimerax_combined_locres.py +80 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/validationanalysis.py +97 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/version.py +1 -1
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/LICENSE +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/MANIFEST.in +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/README.rst +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/emdbva.egg-info/dependency_links.txt +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/emdbva.egg-info/entry_points.txt +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/emdbva.egg-info/requires.txt +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/emdbva.egg-info/top_level.txt +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/setup.cfg +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/setup.py +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/PATHS_codon_airflow.py +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/__init__.py +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/audit/__init__.py +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/audit/cif_revision.py +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/metrics/__init__.py +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/metrics/bars.py +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/metrics/connected_percentage.py +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/metrics/contour_level_predicator.py +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/metrics/emda_mmcc.py +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/metrics/emringer.py +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/metrics/inclusion.py +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/metrics/map_data_validation.py +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/metrics/map_map_cc.py +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/metrics/overlap_percentage.py +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/metrics/phaserandomization.py +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/metrics/phenix_cc.py +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/metrics/phenix_mm.py +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/metrics/projections.py +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/metrics/qscore.py +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/metrics/smoc.py +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/metrics/strudel.py +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/metrics/surfaces.py +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/metrics/threedfsc.py +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/prepareandrun_codon_airflow.py +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/qscores.csv +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/schemas/__init__.py +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/schemas/emdb_entry_full.schema.json +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/schemas/loader.py +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/utils/Checker.py +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/utils/MapProcessor.py +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/utils/Model.py +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/utils/__init__.py +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/utils/cl_weights.pth +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/utils/log_utils.py +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/utils/misc.py +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/utils/rescolor.py +0 -0
- {emdbva-0.0.1.dev152 → emdbva-0.0.1.dev155}/va/utils/stars.py +0 -0
|
@@ -30,6 +30,7 @@ va/metrics/map_map_cc.py
|
|
|
30
30
|
va/metrics/overlap_percentage.py
|
|
31
31
|
va/metrics/phaserandomization.py
|
|
32
32
|
va/metrics/phenix_cc.py
|
|
33
|
+
va/metrics/phenix_locres.py
|
|
33
34
|
va/metrics/phenix_mm.py
|
|
34
35
|
va/metrics/projections.py
|
|
35
36
|
va/metrics/qscore.py
|
|
@@ -47,6 +48,7 @@ va/utils/ChimeraxViews.py
|
|
|
47
48
|
va/utils/MapProcessor.py
|
|
48
49
|
va/utils/Model.py
|
|
49
50
|
va/utils/__init__.py
|
|
51
|
+
va/utils/chimerax_combined_locres.py
|
|
50
52
|
va/utils/cl_weights.pth
|
|
51
53
|
va/utils/log_utils.py
|
|
52
54
|
va/utils/misc.py
|
|
@@ -151,13 +151,18 @@ def allruns(validationobj, runs):
|
|
|
151
151
|
# validationobj.phrand()
|
|
152
152
|
|
|
153
153
|
# Resmap local resolution
|
|
154
|
-
if 'locres' in runs:
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
154
|
+
# if 'locres' in runs:
|
|
155
|
+
# validationobj.mask_volume()
|
|
156
|
+
# validationobj.locres_resmap()
|
|
157
|
+
# validationobj.locres_histo()
|
|
158
|
+
# validationobj.locres_residue()
|
|
159
|
+
# validationobj.model_map_ration()
|
|
160
|
+
# validationobj.locres_map()
|
|
161
|
+
|
|
162
|
+
if 'locres_phe' in runs:
|
|
163
|
+
validationobj.phenix_local_resolution(run_phenix=True)
|
|
164
|
+
elif 'locres_phe_results' in runs:
|
|
165
|
+
validationobj.phenix_local_resolution(run_phenix=False)
|
|
161
166
|
|
|
162
167
|
# EMringer
|
|
163
168
|
if 'emringer' in runs:
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
"""Run PHENIX local-resolution calculations and serialize their results."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
from distutils.spawn import find_executable
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
EXECUTABLE = "phenix.local_resolution"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def find_local_resolution_executable():
|
|
15
|
+
"""Find the PHENIX local-resolution executable on ``PATH``."""
|
|
16
|
+
return find_executable(EXECUTABLE)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def validate_inputs(half_maps):
|
|
20
|
+
"""Return validation errors for the two required half maps."""
|
|
21
|
+
errors = []
|
|
22
|
+
if len(half_maps) != 2:
|
|
23
|
+
return ["PHENIX local resolution requires exactly two half maps."]
|
|
24
|
+
for label, path in (("half map 1", half_maps[0]), ("half map 2", half_maps[1])):
|
|
25
|
+
if not path or not os.path.isfile(path):
|
|
26
|
+
errors.append("Required {} is missing: {}".format(label, path))
|
|
27
|
+
return errors
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def build_command(executable, half_maps, output_dir, resolution=None):
|
|
31
|
+
"""Build a PHENIX command with both documented half-map PHIL inputs."""
|
|
32
|
+
command = [
|
|
33
|
+
executable,
|
|
34
|
+
"input_files.map_model.half_map={}".format(os.path.abspath(half_maps[0])),
|
|
35
|
+
"input_files.map_model.half_map={}".format(os.path.abspath(half_maps[1])),
|
|
36
|
+
"output.prefix={}".format(os.path.join(os.path.abspath(output_dir), "phenix_locres")),
|
|
37
|
+
"output.overwrite=True",
|
|
38
|
+
]
|
|
39
|
+
if resolution is not None:
|
|
40
|
+
command.append("crystal_info.resolution={}".format(resolution))
|
|
41
|
+
return command
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def find_output_map(output_dir, half_maps, stdout=""):
|
|
45
|
+
"""Locate the local-resolution map reported by PHENIX, excluding full maps."""
|
|
46
|
+
match = re.search(r"^\s*local_resolution_map\s*:\s*(\S+)\s*$", stdout, re.MULTILINE)
|
|
47
|
+
if match:
|
|
48
|
+
reported_path = match.group(1)
|
|
49
|
+
candidate = reported_path if os.path.isabs(reported_path) else os.path.join(output_dir, reported_path)
|
|
50
|
+
if os.path.isfile(candidate):
|
|
51
|
+
return candidate
|
|
52
|
+
|
|
53
|
+
candidates = []
|
|
54
|
+
for half_map in half_maps:
|
|
55
|
+
half_map_stem = os.path.splitext(os.path.basename(half_map))[0]
|
|
56
|
+
candidate = os.path.join(output_dir, "{}_local_resolution.ccp4".format(half_map_stem))
|
|
57
|
+
if os.path.isfile(candidate) and candidate not in candidates:
|
|
58
|
+
candidates.append(candidate)
|
|
59
|
+
return candidates[0] if len(candidates) == 1 else None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def output_map_paths(output_dir, half_maps):
|
|
63
|
+
"""Return the expected local-resolution and full-map output paths."""
|
|
64
|
+
pair_name = "{}_{}".format(
|
|
65
|
+
os.path.basename(half_maps[0]), os.path.basename(half_maps[1])
|
|
66
|
+
)
|
|
67
|
+
return (os.path.join(output_dir, "{}_local_resolution.map".format(pair_name)),
|
|
68
|
+
os.path.join(output_dir, "{}_full.map".format(pair_name)))
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def rename_output_maps(output_dir, half_maps, stdout=""):
|
|
72
|
+
"""Rename PHENIX local-resolution and sharpened maps using both input names."""
|
|
73
|
+
local_map = find_output_map(output_dir, half_maps, stdout)
|
|
74
|
+
if local_map is None:
|
|
75
|
+
return None, None
|
|
76
|
+
|
|
77
|
+
full_match = re.search(r"^\s*full_map\s*:\s*(\S+)\s*$", stdout, re.MULTILINE)
|
|
78
|
+
full_map = None
|
|
79
|
+
if full_match:
|
|
80
|
+
reported_path = full_match.group(1)
|
|
81
|
+
candidate = reported_path if os.path.isabs(reported_path) else os.path.join(output_dir, reported_path)
|
|
82
|
+
if os.path.isfile(candidate):
|
|
83
|
+
full_map = candidate
|
|
84
|
+
|
|
85
|
+
local_destination, full_destination = output_map_paths(output_dir, half_maps)
|
|
86
|
+
os.replace(local_map, local_destination)
|
|
87
|
+
|
|
88
|
+
if full_map is not None:
|
|
89
|
+
os.replace(full_map, full_destination)
|
|
90
|
+
else:
|
|
91
|
+
full_destination = None
|
|
92
|
+
return local_destination, full_destination
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def read_map_statistics(map_path):
|
|
96
|
+
"""Read basic statistics from a PHENIX local-resolution map."""
|
|
97
|
+
try:
|
|
98
|
+
import mrcfile
|
|
99
|
+
import numpy as np
|
|
100
|
+
except ImportError as exc:
|
|
101
|
+
raise RuntimeError("mrcfile and numpy are required to read the PHENIX map: {}".format(exc))
|
|
102
|
+
|
|
103
|
+
with mrcfile.open(map_path, permissive=False) as map_file:
|
|
104
|
+
values = np.asarray(map_file.data, dtype=float)
|
|
105
|
+
values = values[np.isfinite(values)]
|
|
106
|
+
if values.size == 0:
|
|
107
|
+
raise ValueError("Local-resolution map contains no finite values.")
|
|
108
|
+
return {
|
|
109
|
+
"min": float(values.min()),
|
|
110
|
+
"max": float(values.max()),
|
|
111
|
+
"mean": float(values.mean()),
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def reuse_local_resolution(half_maps, output_dir):
|
|
116
|
+
"""Load an existing PHENIX output map without invoking PHENIX.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
dict or None: A result suitable for post-processing, or None if the
|
|
120
|
+
expected map is missing or unreadable.
|
|
121
|
+
"""
|
|
122
|
+
if len(half_maps) != 2 or not all(half_maps):
|
|
123
|
+
print('Cannot reuse PHENIX local resolution: both half-map names are required.')
|
|
124
|
+
return None
|
|
125
|
+
output_map, full_map = output_map_paths(output_dir, half_maps)
|
|
126
|
+
if not os.path.isfile(output_map):
|
|
127
|
+
print('No existing PHENIX local-resolution map found: {}'.format(output_map))
|
|
128
|
+
return None
|
|
129
|
+
try:
|
|
130
|
+
statistics = read_map_statistics(output_map)
|
|
131
|
+
except Exception as exc:
|
|
132
|
+
print('Cannot read existing PHENIX local-resolution map {}: {}'.format(output_map, exc))
|
|
133
|
+
return None
|
|
134
|
+
return {
|
|
135
|
+
'status': 'success',
|
|
136
|
+
'method': 'phenix',
|
|
137
|
+
'output_map': os.path.relpath(output_map, output_dir),
|
|
138
|
+
'full_map': os.path.relpath(full_map, output_dir) if os.path.isfile(full_map) else None,
|
|
139
|
+
'statistics': statistics,
|
|
140
|
+
'reused': True,
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def run_local_resolution(half_maps, output_dir, resolution=None):
|
|
145
|
+
"""Run PHENIX and return a structured success or error result."""
|
|
146
|
+
result = {"status": "error", "method": "phenix", "errors": []}
|
|
147
|
+
errors = validate_inputs(half_maps)
|
|
148
|
+
if errors:
|
|
149
|
+
result["errors"] = errors
|
|
150
|
+
return result
|
|
151
|
+
|
|
152
|
+
executable = find_local_resolution_executable()
|
|
153
|
+
if not executable:
|
|
154
|
+
result["errors"] = ["PHENIX executable {} is not available.".format(EXECUTABLE)]
|
|
155
|
+
return result
|
|
156
|
+
|
|
157
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
158
|
+
command = build_command(executable, half_maps, output_dir, resolution)
|
|
159
|
+
print(f"Phenix local resolution command: {' '.join(command)}")
|
|
160
|
+
try:
|
|
161
|
+
process = subprocess.Popen(
|
|
162
|
+
command,
|
|
163
|
+
cwd=output_dir,
|
|
164
|
+
stdout=subprocess.PIPE,
|
|
165
|
+
stderr=subprocess.STDOUT,
|
|
166
|
+
text=True,
|
|
167
|
+
bufsize=1,
|
|
168
|
+
)
|
|
169
|
+
output_lines = []
|
|
170
|
+
for line in process.stdout:
|
|
171
|
+
print(line, end="", flush=True)
|
|
172
|
+
output_lines.append(line)
|
|
173
|
+
return_code = process.wait()
|
|
174
|
+
except OSError as exc:
|
|
175
|
+
result["errors"] = ["Could not start {}: {}".format(EXECUTABLE, exc)]
|
|
176
|
+
return result
|
|
177
|
+
|
|
178
|
+
result["stdout"] = "".join(output_lines)
|
|
179
|
+
result["return_code"] = return_code
|
|
180
|
+
if return_code != 0:
|
|
181
|
+
result["errors"] = ["{} exited with code {}.".format(EXECUTABLE, return_code)]
|
|
182
|
+
return result
|
|
183
|
+
|
|
184
|
+
output_map, full_map = rename_output_maps(output_dir, half_maps, result["stdout"])
|
|
185
|
+
if output_map is None or not os.path.isfile(output_map):
|
|
186
|
+
result["errors"] = ["PHENIX did not produce one identifiable local-resolution map."]
|
|
187
|
+
return result
|
|
188
|
+
try:
|
|
189
|
+
statistics = read_map_statistics(output_map)
|
|
190
|
+
except Exception as exc:
|
|
191
|
+
result["errors"] = ["Could not read PHENIX local-resolution map: {}".format(exc)]
|
|
192
|
+
return result
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
"status": "success",
|
|
196
|
+
"method": "phenix",
|
|
197
|
+
"output_map": os.path.relpath(output_map, output_dir),
|
|
198
|
+
"full_map": os.path.relpath(full_map, output_dir) if full_map else None,
|
|
199
|
+
"statistics": statistics,
|
|
200
|
+
"return_code": return_code,
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def write_result(output_path, result):
|
|
205
|
+
"""Write a PHENIX local-resolution JSON fragment."""
|
|
206
|
+
with open(output_path, "w", encoding="utf-8") as output_file:
|
|
207
|
+
json.dump({"phenix_local_resolution": result}, output_file, indent=2)
|
|
208
|
+
return output_path
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def create_local_resolution_views(chimerax, map_path, resolution_map, contour, output_dir, prefix):
|
|
212
|
+
"""Generate three ChimeraX surface views colored by local resolution."""
|
|
213
|
+
if not chimerax:
|
|
214
|
+
raise RuntimeError("ChimeraX executable is not available.")
|
|
215
|
+
for path in (map_path, resolution_map):
|
|
216
|
+
if not path or not os.path.isfile(path):
|
|
217
|
+
raise FileNotFoundError("Map file does not exist: {}".format(path))
|
|
218
|
+
if contour is None:
|
|
219
|
+
raise ValueError("A contour level is required for the surface view.")
|
|
220
|
+
|
|
221
|
+
def quoted(path):
|
|
222
|
+
return '"{}"'.format(os.path.abspath(path).replace('\\', '\\\\').replace('"', '\\"'))
|
|
223
|
+
|
|
224
|
+
script_path = os.path.join(output_dir, prefix + '_chimerax.cxc')
|
|
225
|
+
images = {
|
|
226
|
+
'original': {axis: prefix + '_' + axis.upper() + '.png' for axis in ('x', 'y', 'z')},
|
|
227
|
+
'scaled': {axis: prefix + '_scaled_' + axis.upper() + '.png' for axis in ('x', 'y', 'z')},
|
|
228
|
+
}
|
|
229
|
+
with open(script_path, 'w', encoding='utf-8') as script:
|
|
230
|
+
script.write('open {}\nvolume #1 style surface\nvolume #1 step 1\n'.format(quoted(map_path)))
|
|
231
|
+
script.write('volume #1 level {}\n'.format(float(contour)))
|
|
232
|
+
script.write('open {}\nvolume #2 style surface\nvolume #2 step 1\n'.format(quoted(resolution_map)))
|
|
233
|
+
script.write('hide #2 models\nset bgColor #B8B8B8\ncolor sample #1 map #2 palette blue:white:red range full key true\n')
|
|
234
|
+
script.write('key pos 0.25,0.05 size 0.53,0.015 labelSide right/bottom fontSize 9 border false labelColor black\n')
|
|
235
|
+
for axis, rotation in (('z', ''), ('x', 'turn y 90\n'), ('y', 'turn x -90\n')):
|
|
236
|
+
script.write('view orient\n' + rotation)
|
|
237
|
+
script.write('view #1 pad 0.25 cofr true\n')
|
|
238
|
+
script.write('save {} width 1200 height 1200\n'.format(
|
|
239
|
+
quoted(os.path.join(output_dir, images['original'][axis]))))
|
|
240
|
+
script.write('save {} width 300 height 300\n'.format(
|
|
241
|
+
quoted(os.path.join(output_dir, images['scaled'][axis]))))
|
|
242
|
+
script.write('exit\n')
|
|
243
|
+
has_display = bool(os.environ.get('DISPLAY') or os.environ.get('WAYLAND_DISPLAY'))
|
|
244
|
+
if sys.platform == 'darwin' and not os.environ.get('SSH_CONNECTION'):
|
|
245
|
+
has_display = True
|
|
246
|
+
command = [chimerax]
|
|
247
|
+
if not has_display:
|
|
248
|
+
command.extend(['--offscreen', '--nogui'])
|
|
249
|
+
command.append(script_path)
|
|
250
|
+
subprocess.run(command, cwd=output_dir, check=True)
|
|
251
|
+
missing = [
|
|
252
|
+
name for size_images in images.values() for name in size_images.values()
|
|
253
|
+
if not os.path.isfile(os.path.join(output_dir, name))
|
|
254
|
+
]
|
|
255
|
+
if missing:
|
|
256
|
+
raise RuntimeError('ChimeraX did not produce images: {}'.format(', '.join(missing)))
|
|
257
|
+
return images
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def create_combined_local_resolution_views(chimerax, map_path, resolution_map, model_path,
|
|
261
|
+
contour, residue_data, output_dir, prefix):
|
|
262
|
+
"""Render a transparent map and residue-colored model with a shared scale."""
|
|
263
|
+
if not chimerax:
|
|
264
|
+
raise RuntimeError('ChimeraX executable is not available.')
|
|
265
|
+
for path in (map_path, resolution_map, model_path):
|
|
266
|
+
if not path or not os.path.isfile(path):
|
|
267
|
+
raise FileNotFoundError('Input file does not exist: {}'.format(path))
|
|
268
|
+
if contour is None:
|
|
269
|
+
raise ValueError('An author contour level is required for the primary map.')
|
|
270
|
+
values = residue_data['localResolution']
|
|
271
|
+
if not values:
|
|
272
|
+
raise ValueError('No residue local-resolution values are available.')
|
|
273
|
+
def quoted(path):
|
|
274
|
+
return '"{}"'.format(os.path.abspath(path).replace('\\', '\\\\').replace('"', '\\"'))
|
|
275
|
+
|
|
276
|
+
script_path = os.path.join(output_dir, prefix + '_chimerax.cxc')
|
|
277
|
+
config_path = os.path.join(output_dir, prefix + '_color_config.txt')
|
|
278
|
+
residue_specs = []
|
|
279
|
+
for residue in residue_data['residue']:
|
|
280
|
+
match = re.fullmatch(r'([^:\s/]+):(-?\d+)\s+\S+', residue)
|
|
281
|
+
if not match:
|
|
282
|
+
raise ValueError('Unsupported residue identifier: {}'.format(residue))
|
|
283
|
+
residue_specs.append('/{}:{}'.format(match.group(1), match.group(2)))
|
|
284
|
+
if len(residue_specs) != len(values):
|
|
285
|
+
raise ValueError('Residue identifiers and local-resolution values do not match.')
|
|
286
|
+
with open(config_path, 'w', encoding='utf-8') as config_file:
|
|
287
|
+
json.dump({'residues': residue_specs, 'values': values}, config_file, allow_nan=False)
|
|
288
|
+
images = {
|
|
289
|
+
'original': {axis: prefix + '_' + axis.upper() + '.png' for axis in ('x', 'y', 'z')},
|
|
290
|
+
'scaled': {axis: prefix + '_scaled_' + axis.upper() + '.png' for axis in ('x', 'y', 'z')},
|
|
291
|
+
}
|
|
292
|
+
with open(script_path, 'w', encoding='utf-8') as script:
|
|
293
|
+
script.write('open {}\nvolume #1 style surface\nvolume #1 step 1\n'.format(quoted(map_path)))
|
|
294
|
+
script.write('volume #1 level {}\n'.format(float(contour)))
|
|
295
|
+
script.write('open {}\nhide #2 models\n'.format(quoted(resolution_map)))
|
|
296
|
+
rescolor_script = os.path.join(os.path.dirname(__file__), '..', 'utils', 'rescolor.py')
|
|
297
|
+
color_script = os.path.join(os.path.dirname(__file__), '..', 'utils', 'chimerax_combined_locres.py')
|
|
298
|
+
script.write('open {} id #10\nopen {}\n'.format(quoted(model_path), quoted(rescolor_script)))
|
|
299
|
+
script.write('show #10 ribbons\nhide #10 atoms\n')
|
|
300
|
+
script.write('runscript {} {}\n'.format(quoted(color_script), quoted(config_path)))
|
|
301
|
+
script.write('transparency #1 65 target s\n')
|
|
302
|
+
script.write('lighting soft\n')
|
|
303
|
+
script.write('set bgColor #B8B8B8\n')
|
|
304
|
+
for axis, rotation in (('z', ''), ('x', 'turn y 90\n'), ('y', 'turn x -90\n')):
|
|
305
|
+
script.write('view orient\n' + rotation + 'view #1 pad 0.25 cofr true\n')
|
|
306
|
+
script.write('save {} width 1200 height 1200\n'.format(
|
|
307
|
+
quoted(os.path.join(output_dir, images['original'][axis]))))
|
|
308
|
+
script.write('save {} width 300 height 300\n'.format(
|
|
309
|
+
quoted(os.path.join(output_dir, images['scaled'][axis]))))
|
|
310
|
+
script.write('exit\n')
|
|
311
|
+
has_display = bool(os.environ.get('DISPLAY') or os.environ.get('WAYLAND_DISPLAY'))
|
|
312
|
+
if sys.platform == 'darwin' and not os.environ.get('SSH_CONNECTION'):
|
|
313
|
+
has_display = True
|
|
314
|
+
command = [chimerax]
|
|
315
|
+
if not has_display:
|
|
316
|
+
command.extend(['--offscreen', '--nogui'])
|
|
317
|
+
subprocess.run(command + [script_path], cwd=output_dir, check=True)
|
|
318
|
+
missing = [
|
|
319
|
+
name for size_images in images.values() for name in size_images.values()
|
|
320
|
+
if not os.path.isfile(os.path.join(output_dir, name))
|
|
321
|
+
]
|
|
322
|
+
if missing:
|
|
323
|
+
raise RuntimeError('ChimeraX did not produce images: {}'.format(', '.join(missing)))
|
|
324
|
+
return images
|
|
@@ -2,6 +2,90 @@ from va.utils.misc import floattohex, scale_values, scale_value, float_to_hex
|
|
|
2
2
|
from va.utils.MapProcessor import MapProcessor
|
|
3
3
|
from va.utils.ChimeraxViews import ChimeraxViews
|
|
4
4
|
import numpy as np
|
|
5
|
+
import os
|
|
6
|
+
import mrcfile
|
|
7
|
+
|
|
8
|
+
LOCAL_RESOLUTION_PALETTE = [(0, 0, 255), (255, 255, 255), (255, 0, 0)]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def interpolate_atom_local_resolutions(map_path, inmodels):
|
|
12
|
+
"""Interpolate map values at atom positions and summarize them per residue.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
map_path (str): Local-resolution MRC/CCP4 map path.
|
|
16
|
+
inmodels: Model or list of models with Bio.PDB-style atom coordinates.
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
tuple: Model-indexed atom and residue dictionaries. Outside-map and
|
|
20
|
+
non-finite samples have a null atom value and are excluded from means.
|
|
21
|
+
"""
|
|
22
|
+
from scipy.ndimage import map_coordinates
|
|
23
|
+
|
|
24
|
+
models = inmodels if isinstance(inmodels, list) else [inmodels]
|
|
25
|
+
atom_results, residue_results = {}, {}
|
|
26
|
+
with mrcfile.open(map_path, permissive=False) as local_map:
|
|
27
|
+
header = local_map.header
|
|
28
|
+
axis_order = [int(header.mapc), int(header.mapr), int(header.maps)]
|
|
29
|
+
if sorted(axis_order) != [1, 2, 3]:
|
|
30
|
+
raise ValueError('Invalid local-resolution map axis order: {}'.format(axis_order))
|
|
31
|
+
voxel_sizes = local_map.voxel_size.tolist()
|
|
32
|
+
angles = [float(header.cellb.alpha), float(header.cellb.beta), float(header.cellb.gamma)]
|
|
33
|
+
transform = MapProcessor.map_matrix(voxel_sizes, angles)
|
|
34
|
+
origin = np.array([header.origin.x, header.origin.y, header.origin.z], dtype=float)
|
|
35
|
+
starts = np.array([header.nxstart, header.nystart, header.nzstart], dtype=float)
|
|
36
|
+
shape = np.array(local_map.data.shape)
|
|
37
|
+
|
|
38
|
+
for model_index, model in enumerate(models):
|
|
39
|
+
atoms, residue_names, residue_values, chain_values = [], [], [], {}
|
|
40
|
+
for chain in model.get_chains():
|
|
41
|
+
for residue in chain.get_residues():
|
|
42
|
+
sampled_values = []
|
|
43
|
+
for atom in residue.get_atoms():
|
|
44
|
+
element = (getattr(atom, 'element', '') or '').strip().upper()
|
|
45
|
+
xyz_index = transform.dot(np.asarray(atom.coord, dtype=float) - origin)
|
|
46
|
+
crs_index = xyz_index[np.array(axis_order) - 1] - starts
|
|
47
|
+
zyx_index = crs_index[::-1]
|
|
48
|
+
value = None
|
|
49
|
+
if np.all(np.isfinite(zyx_index)) and np.all(zyx_index >= 0) and np.all(zyx_index <= shape - 1):
|
|
50
|
+
sampled = float(map_coordinates(local_map.data, zyx_index[:, None], order=1, mode='nearest', prefilter=False)[0])
|
|
51
|
+
if np.isfinite(sampled):
|
|
52
|
+
value = sampled
|
|
53
|
+
sampled_values.append(value)
|
|
54
|
+
atoms.append({
|
|
55
|
+
'chain': str(chain.id), 'residue_number': int(residue.id[1]),
|
|
56
|
+
'insertion_code': str(residue.id[2]).strip(),
|
|
57
|
+
'hetero_flag': str(residue.id[0]).strip(),
|
|
58
|
+
'residue_name': str(residue.resname), 'atom_name': str(atom.name),
|
|
59
|
+
'altloc': str(atom.get_altloc()).strip(), 'element': element,
|
|
60
|
+
'serial_number': getattr(atom, 'serial_number', None),
|
|
61
|
+
'local_resolution': value,
|
|
62
|
+
})
|
|
63
|
+
if sampled_values:
|
|
64
|
+
value = round(float(np.mean(sampled_values)), 4)
|
|
65
|
+
residue_names.append('{}:{} {}'.format(chain.id, residue.id[1], residue.resname))
|
|
66
|
+
residue_values.append(value)
|
|
67
|
+
chain_values.setdefault(str(chain.id), []).append(value)
|
|
68
|
+
|
|
69
|
+
model_name = os.path.basename(model.filename)
|
|
70
|
+
atom_results[str(model_index)] = {'name': model_name, 'atoms': atoms}
|
|
71
|
+
if not residue_values:
|
|
72
|
+
continue
|
|
73
|
+
lower, upper = min(residue_values), max(residue_values)
|
|
74
|
+
colors = [float_to_hex((value - lower) / (upper - lower) if upper > lower else 0.5,
|
|
75
|
+
LOCAL_RESOLUTION_PALETTE)
|
|
76
|
+
for value in residue_values]
|
|
77
|
+
chain_summary = {}
|
|
78
|
+
for chain, values in chain_values.items():
|
|
79
|
+
mean = round(float(np.mean(values)), 3)
|
|
80
|
+
color_value = (mean - lower) / (upper - lower) if upper > lower else 0.5
|
|
81
|
+
chain_summary[chain] = {'value': mean, 'color': float_to_hex(max(0, min(1, color_value)), LOCAL_RESOLUTION_PALETTE),
|
|
82
|
+
'numberOfResidues': len(values)}
|
|
83
|
+
residue_results[str(len(residue_results))] = {
|
|
84
|
+
'name': model_name,
|
|
85
|
+
'data': {'residue': residue_names, 'localResolution': residue_values,
|
|
86
|
+
'color': colors, 'chainResolution': chain_summary},
|
|
87
|
+
}
|
|
88
|
+
return atom_results, residue_results
|
|
5
89
|
|
|
6
90
|
|
|
7
91
|
def local_resolution_json(map, inmodels, radius, value_range):
|
|
@@ -98,7 +182,8 @@ def local_resolution_json(map, inmodels, radius, value_range):
|
|
|
98
182
|
|
|
99
183
|
data_result['residue'] = allkeys
|
|
100
184
|
data_result['localResolution'] = allvalues
|
|
101
|
-
data_result['color'] = [float_to_hex(i
|
|
185
|
+
data_result['color'] = [float_to_hex(i, LOCAL_RESOLUTION_PALETTE)
|
|
186
|
+
for i in scale_values(allvalues, new_min, new_max)]
|
|
102
187
|
# data_result['color'] = [float_to_hex(i) for i in scale_values(allvalues, 0, 1)]
|
|
103
188
|
data_result['chainResolution'] = chainaiscore
|
|
104
189
|
result[str(modelcount-1)] = {'name': modelname, 'data': data_result}
|
|
@@ -412,16 +412,19 @@ def get_masked_indices(input_mask):
|
|
|
412
412
|
return None
|
|
413
413
|
|
|
414
414
|
|
|
415
|
-
def localres_histogram(mapone, primary_mapname, resolution=None):
|
|
415
|
+
def localres_histogram(mapone, primary_mapname, resolution=None, local_resolution_path=None):
|
|
416
416
|
"""
|
|
417
417
|
local resolution histogram
|
|
418
418
|
"""
|
|
419
419
|
|
|
420
420
|
mapname = os.path.basename(mapone)
|
|
421
421
|
output_path = os.path.dirname(mapone)
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
422
|
+
if local_resolution_path is None:
|
|
423
|
+
file_pattern = '{}/{}_relion/*_locres.mrc'.format(output_path, primary_mapname)
|
|
424
|
+
resmapname_glob = glob.glob(file_pattern)
|
|
425
|
+
resmapname = resmapname_glob[0] if len(resmapname_glob) > 0 else None
|
|
426
|
+
else:
|
|
427
|
+
resmapname = local_resolution_path
|
|
425
428
|
mask_file = f'{output_path}/{primary_mapname}_relion/mask/{primary_mapname}_mask.mrc'
|
|
426
429
|
bin_mask = get_masked_indices(mask_file)
|
|
427
430
|
final_dict = {}
|
|
@@ -910,6 +910,7 @@ class PreParation:
|
|
|
910
910
|
'volume': False, 'fsc': False, 'raps': False, 'mapmodel': False, 'inclusion': False,
|
|
911
911
|
'largestvariance': False, 'mask': False, 'symmetry': False, 'rmmcc': False, 'smoc': False,
|
|
912
912
|
'resccc': False, 'emringer': False, 'strudel': False, '3dfsc': False, 'locres': False,
|
|
913
|
+
'locres_phe': False, 'locres_phe_results': False,
|
|
913
914
|
'phrand': False, 'predictcontour': False, 'mapmapcc': False}
|
|
914
915
|
for key in resdict.keys():
|
|
915
916
|
if key in runs:
|
|
@@ -924,8 +925,10 @@ class PreParation:
|
|
|
924
925
|
if self.mapname is not None:
|
|
925
926
|
runlist.extend(['projection', 'central', 'surface', 'volume', 'density', 'raps', 'largestvariance',
|
|
926
927
|
'mask', 'fsc', 'mmfsc', 'rmmcc', 'symmetry', 'qscore', 'strudel', 'emringer', '3dfsc',
|
|
927
|
-
'smoc', 'resccc', 'locres', 'phrand', 'predictcontour', 'mapmapcc',
|
|
928
|
+
'smoc', 'resccc', 'locres', 'locres_phe', 'phrand', 'predictcontour', 'mapmapcc',
|
|
928
929
|
])
|
|
930
|
+
if 'locres_phe_results' in runs:
|
|
931
|
+
runlist.append('locres_phe_results')
|
|
929
932
|
|
|
930
933
|
if self.masks is None:
|
|
931
934
|
runlist.remove('mask')
|
|
@@ -162,7 +162,8 @@ class ChimeraxViews:
|
|
|
162
162
|
surface_file_name = '{}/{}_{}'.format(self.va_dir, model_name, map_name)
|
|
163
163
|
model = f'{self.va_dir}/{model_name}'
|
|
164
164
|
if data_type == 'residue_local_resolution':
|
|
165
|
-
|
|
165
|
+
items = min_median_max.items() if isinstance(min_median_max, dict) else min_median_max
|
|
166
|
+
list_items = list(itertools.islice(items, 3))
|
|
166
167
|
min_value, min_color = list_items[0]
|
|
167
168
|
median_value, median_color = list_items[1]
|
|
168
169
|
max_value, max_color = list_items[2]
|
|
@@ -187,7 +188,7 @@ class ChimeraxViews:
|
|
|
187
188
|
# )
|
|
188
189
|
if count == 0:
|
|
189
190
|
count += 1
|
|
190
|
-
fp.write(f'rescolors /{chain}:{res} {color} ')
|
|
191
|
+
fp.write(f'rescolors /{chain}:{res} {color}' + ('\n' if number_of_item == 1 else ' '))
|
|
191
192
|
elif count == number_of_item - 1:
|
|
192
193
|
fp.write(f'/{chain}:{res} {color}\n')
|
|
193
194
|
else:
|
|
@@ -197,29 +198,33 @@ class ChimeraxViews:
|
|
|
197
198
|
if data_type == 'residue_local_resolution':
|
|
198
199
|
fp.write(
|
|
199
200
|
'key showTool true\n'
|
|
200
|
-
'key size 0.
|
|
201
|
-
'key pos 0.
|
|
202
|
-
|
|
203
|
-
|
|
201
|
+
'key size 0.50000,0.02000\n'
|
|
202
|
+
'key pos 0.25000,0.05000\n'
|
|
203
|
+
'key numericLabelSpacing equal\n'
|
|
204
|
+
'key border false\n'
|
|
205
|
+
'key labelColor black\n'
|
|
206
|
+
f'key blue:{min_value:.2f} white:{median_value:.2f} red:{max_value:.2f}\n'
|
|
204
207
|
)
|
|
208
|
+
framing = 'view pad 0.25 cofr true\n' if data_type == 'residue_local_resolution' else 'view cofr True\n'
|
|
209
|
+
background = 'set bgColor #B8B8B8\n' if data_type == 'residue_local_resolution' else 'set bgColor white\n'
|
|
205
210
|
fp.write(
|
|
206
211
|
# 'key showTool true\n'
|
|
207
212
|
# 'key size 0.25000,0.02000\n'
|
|
208
213
|
# 'key pos 0.72000,0.05000\n'
|
|
209
214
|
# f'key blue-white-red :{round(min, 2)} :{round((min+max)/2, 2)} :{round(max, 2)}\n'
|
|
210
215
|
# f'key {min_color}:{round(min_value, 1)} {median_color}:{round(median_value, 1)} {max_color}:{round(max_value, 1)}\n'
|
|
211
|
-
|
|
216
|
+
background +
|
|
212
217
|
'lighting soft\n'
|
|
213
|
-
|
|
218
|
+
+ framing +
|
|
214
219
|
f'save {str(surface_file_name)}_z{cur_type}.jpeg supersample 1 width 1200 height 1200\n'
|
|
215
220
|
'turn x -90\n'
|
|
216
221
|
'turn y -90\n'
|
|
217
|
-
|
|
222
|
+
+ framing +
|
|
218
223
|
f'save {str(surface_file_name)}_y{cur_type}.jpeg supersample 1 width 1200 height 1200\n'
|
|
219
224
|
'view orient\n'
|
|
220
225
|
'turn x 90\n'
|
|
221
226
|
'turn z 90\n'
|
|
222
|
-
|
|
227
|
+
+ framing +
|
|
223
228
|
f'save {str(surface_file_name)}_x{cur_type}.jpeg supersample 1 width 1200 height 1200\n'
|
|
224
229
|
'close all\n'
|
|
225
230
|
'exit'
|
|
@@ -421,22 +426,17 @@ class ChimeraxViews:
|
|
|
421
426
|
model_name = cur_model[key]
|
|
422
427
|
min_middle_max = None
|
|
423
428
|
if data_type == 'residue_local_resolution':
|
|
424
|
-
min_middle_max =
|
|
429
|
+
min_middle_max = []
|
|
425
430
|
min_value = min(values)
|
|
426
431
|
max_value = max(values)
|
|
427
|
-
|
|
428
|
-
difference = [abs(value - real_middle) for value in values]
|
|
429
|
-
middle_index = difference.index(min(difference))
|
|
430
|
-
middle_value = values[middle_index]
|
|
431
|
-
min_value, middle_value, max_value = (min(values), middle_value, max(values))
|
|
432
|
+
middle_value = (min_value + max_value) / 2
|
|
432
433
|
min_index = values.index(min_value)
|
|
433
434
|
max_index = values.index(max_value)
|
|
434
435
|
min_colour = colors[min_index]
|
|
435
|
-
middle_colour =
|
|
436
|
+
middle_colour = '#FFFFFF'
|
|
436
437
|
max_colour = colors[max_index]
|
|
437
|
-
min_middle_max[min_value
|
|
438
|
-
|
|
439
|
-
min_middle_max[max_value] = max_colour
|
|
438
|
+
min_middle_max.extend([(min_value, min_colour), (middle_value, middle_colour),
|
|
439
|
+
(max_value, max_colour)])
|
|
440
440
|
# result = self.json_to_attributes(data_type)
|
|
441
441
|
chimerax_file_name = self.write_residue_cxc(colors, residues, map_name, model_name, data_type, min_middle_max)
|
|
442
442
|
# Use attributes file for colouring
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Color a combined map/model view using its surface and residue values.
|
|
2
|
+
|
|
3
|
+
This script is run by ChimeraX with a configuration filename as its argument.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
from chimerax.core.commands import run
|
|
11
|
+
from chimerax.core.errors import UserError
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def color_combined_view(session, config):
|
|
15
|
+
"""Sample the contoured map surface and apply one shared color range."""
|
|
16
|
+
primary = session.models.list(model_id=(1,))
|
|
17
|
+
resolution = session.models.list(model_id=(2,))
|
|
18
|
+
if len(primary) != 1 or len(resolution) != 1:
|
|
19
|
+
raise UserError('Expected primary and local-resolution maps #1 and #2.')
|
|
20
|
+
primary_map, resolution_map = primary[0], resolution[0]
|
|
21
|
+
primary_map.update_drawings()
|
|
22
|
+
all_values = list(config['values'])
|
|
23
|
+
sampled_count = 0
|
|
24
|
+
map_lower, map_upper = None, None
|
|
25
|
+
for surface in primary_map.surfaces:
|
|
26
|
+
if not surface.display or surface.vertices is None or not len(surface.vertices):
|
|
27
|
+
continue
|
|
28
|
+
values, outside = resolution_map.interpolated_values(
|
|
29
|
+
surface.vertices, surface.scene_position, out_of_bounds_list=True
|
|
30
|
+
)
|
|
31
|
+
inside = np.ones(len(values), dtype=bool)
|
|
32
|
+
inside[outside] = False
|
|
33
|
+
sampled = values[inside & np.isfinite(values)]
|
|
34
|
+
if len(sampled):
|
|
35
|
+
sample_lower, sample_upper = float(sampled.min()), float(sampled.max())
|
|
36
|
+
map_lower = sample_lower if map_lower is None else min(map_lower, sample_lower)
|
|
37
|
+
map_upper = sample_upper if map_upper is None else max(map_upper, sample_upper)
|
|
38
|
+
sampled_count += len(sampled)
|
|
39
|
+
if not sampled_count:
|
|
40
|
+
raise UserError('No valid local-resolution values on the displayed map surface.')
|
|
41
|
+
model_lower, model_upper = min(all_values), max(all_values)
|
|
42
|
+
lower, upper = min(map_lower, model_lower), max(map_upper, model_upper)
|
|
43
|
+
if lower == upper:
|
|
44
|
+
lower, upper = lower - 0.01, upper + 0.01
|
|
45
|
+
|
|
46
|
+
run(session, 'color sample #1 map #2 palette blue:white:red range {},{} key true'.format(lower, upper))
|
|
47
|
+
run(session, 'key blue: white: red: pos 0.25,0.08 size 0.5,0.02 border false')
|
|
48
|
+
for label, limits, text_y, arrow_start, arrow_end in (
|
|
49
|
+
('Map', (map_lower, map_upper), 0.073, 0.074, 0.08),
|
|
50
|
+
('Model', (model_lower, model_upper), 0.110, 0.110, 0.10),
|
|
51
|
+
):
|
|
52
|
+
run(session, '2dlabels text "{}" color black size 9 xpos 0.16 ypos {}'.format(label, text_y))
|
|
53
|
+
values = (limits[0], (limits[0] + limits[1]) / 2, limits[1])
|
|
54
|
+
for value in sorted(set(values)):
|
|
55
|
+
xpos = 0.25 + 0.5 * (value - lower) / (upper - lower)
|
|
56
|
+
run(session, '2dlabels arrow start {},{} end {},{} color black weight 0.3 headStyle pointer'.format(
|
|
57
|
+
xpos, arrow_start, xpos, arrow_end))
|
|
58
|
+
label_x = xpos
|
|
59
|
+
if label == 'Map' and value == map_lower:
|
|
60
|
+
label_x = 0.25 + 0.5 * (model_lower - lower) / (upper - lower)
|
|
61
|
+
run(session, '2dlabels text "{:.2f}" color black size 9 xpos {} ypos {}'.format(
|
|
62
|
+
value, max(0.0, label_x - 0.012), text_y))
|
|
63
|
+
colors = []
|
|
64
|
+
for spec, value in zip(config['residues'], config['values']):
|
|
65
|
+
position = (value - lower) / (upper - lower)
|
|
66
|
+
palette = ((0, 0, 255), (255, 255, 255), (255, 0, 0))
|
|
67
|
+
index = min(int(position * 2), 1)
|
|
68
|
+
fraction = position * 2 - index
|
|
69
|
+
rgb = tuple(int(start + fraction * (end - start))
|
|
70
|
+
for start, end in zip(palette[index], palette[index + 1]))
|
|
71
|
+
colors.append('{} #{:02x}{:02x}{:02x}'.format(spec, *rgb))
|
|
72
|
+
run(session, 'rescolors ' + ' '.join(colors))
|
|
73
|
+
session.logger.info('Combined local-resolution ranges: map {:.4f}-{:.4f} Å, model {:.4f}-{:.4f} Å '
|
|
74
|
+
'(shared {:.4f}-{:.4f} Å; {} surface samples).'.format(
|
|
75
|
+
map_lower, map_upper, model_lower, model_upper, lower, upper, sampled_count))
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
if 'session' in globals():
|
|
79
|
+
with open(sys.argv[1], encoding='utf-8') as config_file:
|
|
80
|
+
color_combined_view(session, json.load(config_file))
|
|
@@ -62,6 +62,9 @@ from va.metrics.threedfsc import run_threedfsc
|
|
|
62
62
|
from va.metrics.smoc import run_smoc
|
|
63
63
|
from va.metrics.phenix_cc import *
|
|
64
64
|
from va.metrics.phenix_mm import *
|
|
65
|
+
from va.metrics.phenix_locres import (run_local_resolution, reuse_local_resolution, create_local_resolution_views,
|
|
66
|
+
create_combined_local_resolution_views,
|
|
67
|
+
write_result as write_phenix_locres_result)
|
|
65
68
|
from va.metrics.resmap import *
|
|
66
69
|
from va.metrics.bars import *
|
|
67
70
|
from va.metrics.phaserandomization import *
|
|
@@ -7269,6 +7272,100 @@ class ValidationAnalysis:
|
|
|
7269
7272
|
print('Local resolution views time: %s' % (end - start))
|
|
7270
7273
|
print('------------------------------------')
|
|
7271
7274
|
|
|
7275
|
+
def phenix_local_resolution(self, run_phenix=True):
|
|
7276
|
+
"""Run PHENIX or reuse its existing map and generate result artifacts."""
|
|
7277
|
+
output_dir = self.workdir
|
|
7278
|
+
output_json = os.path.join(self.workdir, '{}_phenix_local_resolution.json'.format(self.mapname))
|
|
7279
|
+
half_maps = [
|
|
7280
|
+
getattr(self.hmodd, 'fullname', None),
|
|
7281
|
+
getattr(self.hmeven, 'fullname', None),
|
|
7282
|
+
]
|
|
7283
|
+
if run_phenix:
|
|
7284
|
+
result = run_local_resolution(half_maps, output_dir, self.resolution)
|
|
7285
|
+
else:
|
|
7286
|
+
result = reuse_local_resolution(half_maps, output_dir)
|
|
7287
|
+
if result is None:
|
|
7288
|
+
return None
|
|
7289
|
+
if result.get('status') == 'success':
|
|
7290
|
+
resolution_map = os.path.join(output_dir, result['output_map'])
|
|
7291
|
+
if self.models:
|
|
7292
|
+
try:
|
|
7293
|
+
atom_data, residue_data = interpolate_atom_local_resolutions(resolution_map, self.models)
|
|
7294
|
+
atom_json = os.path.join(output_dir, self.mapname + '_atom_local_resolution.json')
|
|
7295
|
+
with open(atom_json, 'w', encoding='utf-8') as output_file:
|
|
7296
|
+
json.dump({'atom_local_resolution': atom_data}, output_file, indent=2, allow_nan=False)
|
|
7297
|
+
if residue_data:
|
|
7298
|
+
residue_json = os.path.join(output_dir, self.mapname + '_residue_local_resolution.json')
|
|
7299
|
+
with open(residue_json, 'w', encoding='utf-8') as output_file:
|
|
7300
|
+
json.dump({'residue_local_resolution': residue_data}, output_file, indent=2, allow_nan=False)
|
|
7301
|
+
_, model_chimerax = self.surface_envcheck()
|
|
7302
|
+
if model_chimerax:
|
|
7303
|
+
model_local_resolution_views(residue_json, self.mapname, 'residue_local_resolution', model_chimerax)
|
|
7304
|
+
combined_views = {}
|
|
7305
|
+
for model_result in residue_data.values():
|
|
7306
|
+
model_name = model_result['name']
|
|
7307
|
+
try:
|
|
7308
|
+
prefix = '{}_{}_phenix_local_resolution_combined'.format(self.mapname, model_name)
|
|
7309
|
+
combined_views[model_name] = create_combined_local_resolution_views(
|
|
7310
|
+
model_chimerax, self.map.fullname, resolution_map,
|
|
7311
|
+
os.path.join(output_dir, model_name), self.cl,
|
|
7312
|
+
model_result['data'], output_dir, prefix)
|
|
7313
|
+
except Exception as exc:
|
|
7314
|
+
sys.stderr.write('Combined local-resolution views for {} failed: {}.\n'.format(model_name, exc))
|
|
7315
|
+
if combined_views:
|
|
7316
|
+
combined_json = os.path.join(output_dir, self.mapname + '_modelmap_localresolutionviews.json')
|
|
7317
|
+
with open(combined_json, 'w', encoding='utf-8') as output_file:
|
|
7318
|
+
json.dump({'model_map_local_resolution_views': combined_views}, output_file, indent=2)
|
|
7319
|
+
else:
|
|
7320
|
+
sys.stderr.write('ChimeraX unavailable; skipping residue local-resolution views.\n')
|
|
7321
|
+
else:
|
|
7322
|
+
sys.stderr.write('No model residues had valid local-resolution samples.\n')
|
|
7323
|
+
except Exception as exc:
|
|
7324
|
+
sys.stderr.write('PHENIX atom/residue local resolution failed: {}.\n'.format(exc))
|
|
7325
|
+
try:
|
|
7326
|
+
resolution = float(self.resolution) if self.resolution is not None else None
|
|
7327
|
+
except (TypeError, ValueError):
|
|
7328
|
+
resolution = None
|
|
7329
|
+
try:
|
|
7330
|
+
histogram = localres_histogram(
|
|
7331
|
+
half_maps[0], self.mapname, resolution, local_resolution_path=resolution_map
|
|
7332
|
+
)
|
|
7333
|
+
if histogram:
|
|
7334
|
+
histogram_json = os.path.join(output_dir, self.mapname + '_localres_histo.json')
|
|
7335
|
+
with open(histogram_json, 'w', encoding='utf-8') as output_file:
|
|
7336
|
+
json.dump({'local_res_histogram': histogram}, output_file)
|
|
7337
|
+
except Exception as exc:
|
|
7338
|
+
sys.stderr.write('PHENIX local-resolution histogram failed: {}.\n'.format(exc))
|
|
7339
|
+
_, chimerax = self.surface_envcheck()
|
|
7340
|
+
views = {}
|
|
7341
|
+
for label, map_obj in (('primary', self.map), ('raw', self.rawmap)):
|
|
7342
|
+
if map_obj is None:
|
|
7343
|
+
print('Skipping PHENIX local-resolution {} views: map is missing.'.format(label))
|
|
7344
|
+
continue
|
|
7345
|
+
try:
|
|
7346
|
+
contour = self.cl if label == 'primary' else calc_level_dev(map_obj.data)[0]
|
|
7347
|
+
map_basename = os.path.basename(map_obj.fullname)
|
|
7348
|
+
prefix = '{}_phenix_local_resolution'.format(map_basename)
|
|
7349
|
+
views[label] = create_local_resolution_views(
|
|
7350
|
+
chimerax, map_obj.fullname, resolution_map, contour, output_dir, prefix
|
|
7351
|
+
)
|
|
7352
|
+
view_key = 'local_resolution_views' if label == 'primary' else 'rawmap_local_resolution_views'
|
|
7353
|
+
view_json = os.path.join(output_dir, '{}_localresolutionviews.json'.format(map_basename))
|
|
7354
|
+
with open(view_json, 'w', encoding='utf-8') as output_file:
|
|
7355
|
+
json.dump({view_key: views[label]}, output_file, indent=2)
|
|
7356
|
+
except Exception as exc:
|
|
7357
|
+
sys.stderr.write('PHENIX local-resolution {} views failed: {}.\n'.format(label, exc))
|
|
7358
|
+
if views:
|
|
7359
|
+
result['views'] = views
|
|
7360
|
+
try:
|
|
7361
|
+
write_phenix_locres_result(output_json, result)
|
|
7362
|
+
except Exception as exc:
|
|
7363
|
+
sys.stderr.write('Saving PHENIX local-resolution JSON failed: {}.\n'.format(exc))
|
|
7364
|
+
if result.get('status') != 'success':
|
|
7365
|
+
for error in result.get('errors', []):
|
|
7366
|
+
sys.stderr.write('PHENIX local-resolution error: {}\n'.format(error))
|
|
7367
|
+
return result
|
|
7368
|
+
|
|
7272
7369
|
@profile_peak_memory()
|
|
7273
7370
|
def emringer(self):
|
|
7274
7371
|
"""
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|