Spaces:
Sleeping
Sleeping
Upload visualization.py
Browse files- visualization.py +140 -4
visualization.py
CHANGED
|
@@ -4,6 +4,7 @@ Uses py3Dmol for interactive molecular visualization
|
|
| 4 |
"""
|
| 5 |
|
| 6 |
import numpy as np
|
|
|
|
| 7 |
from rmsd_utils import (
|
| 8 |
parse_residue_atoms,
|
| 9 |
translate_rotate_coords,
|
|
@@ -13,7 +14,8 @@ from rmsd_utils import (
|
|
| 13 |
|
| 14 |
|
| 15 |
def create_structure_visualization(ref_path, query_path, ref_window_indices, query_window_indices,
|
| 16 |
-
rotation_matrix, ref_com, query_com, rmsd=None
|
|
|
|
| 17 |
"""
|
| 18 |
Create an interactive 3D visualization of aligned structures.
|
| 19 |
|
|
@@ -26,11 +28,21 @@ def create_structure_visualization(ref_path, query_path, ref_window_indices, que
|
|
| 26 |
ref_com: Center of mass of reference window
|
| 27 |
query_com: Center of mass of query window
|
| 28 |
rmsd: RMSD value (optional, for display)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
Returns:
|
| 31 |
HTML string containing the py3Dmol visualization
|
| 32 |
"""
|
| 33 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
# Read PDB files
|
| 35 |
with open(ref_path) as f:
|
| 36 |
ref_pdb = f.read()
|
|
@@ -84,6 +96,7 @@ def create_structure_visualization(ref_path, query_path, ref_window_indices, que
|
|
| 84 |
<html>
|
| 85 |
<head>
|
| 86 |
<script src="https://3Dmol.csb.pitt.edu/build/3Dmol-min.js"></script>
|
|
|
|
| 87 |
<style>
|
| 88 |
#container {{
|
| 89 |
width: 100%;
|
|
@@ -167,12 +180,25 @@ def create_structure_visualization(ref_path, query_path, ref_window_indices, que
|
|
| 167 |
bottom: 10px;
|
| 168 |
left: 10px;
|
| 169 |
background: rgba(255, 255, 255, 0.95);
|
| 170 |
-
padding:
|
| 171 |
border-radius: 8px;
|
| 172 |
font-family: Arial, sans-serif;
|
| 173 |
font-size: 13px;
|
| 174 |
z-index: 1000;
|
| 175 |
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 176 |
}}
|
| 177 |
.section-title {{
|
| 178 |
font-weight: bold;
|
|
@@ -181,6 +207,30 @@ def create_structure_visualization(ref_path, query_path, ref_window_indices, que
|
|
| 181 |
font-size: 12px;
|
| 182 |
text-transform: uppercase;
|
| 183 |
}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
</style>
|
| 185 |
</head>
|
| 186 |
<body>
|
|
@@ -268,7 +318,28 @@ def create_structure_visualization(ref_path, query_path, ref_window_indices, que
|
|
| 268 |
</div>
|
| 269 |
|
| 270 |
<div class="rmsd-info">
|
| 271 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 272 |
</div>
|
| 273 |
|
| 274 |
<script>
|
|
@@ -530,6 +601,71 @@ def create_structure_visualization(ref_path, query_path, ref_window_indices, que
|
|
| 530 |
}});
|
| 531 |
}}
|
| 532 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 533 |
// Initialize on load
|
| 534 |
initViewer();
|
| 535 |
</script>
|
|
@@ -670,4 +806,4 @@ def transform_pdb_string(pdb_string, rotation_matrix, query_com, ref_com=None):
|
|
| 670 |
else:
|
| 671 |
transformed_lines.append(line)
|
| 672 |
|
| 673 |
-
return '\n'.join(transformed_lines)
|
|
|
|
| 4 |
"""
|
| 5 |
|
| 6 |
import numpy as np
|
| 7 |
+
from pathlib import Path
|
| 8 |
from rmsd_utils import (
|
| 9 |
parse_residue_atoms,
|
| 10 |
translate_rotate_coords,
|
|
|
|
| 14 |
|
| 15 |
|
| 16 |
def create_structure_visualization(ref_path, query_path, ref_window_indices, query_window_indices,
|
| 17 |
+
rotation_matrix, ref_com, query_com, rmsd=None,
|
| 18 |
+
ref_name=None, query_name=None, ref_sequence=None, query_sequence=None):
|
| 19 |
"""
|
| 20 |
Create an interactive 3D visualization of aligned structures.
|
| 21 |
|
|
|
|
| 28 |
ref_com: Center of mass of reference window
|
| 29 |
query_com: Center of mass of query window
|
| 30 |
rmsd: RMSD value (optional, for display)
|
| 31 |
+
ref_name: Reference structure name (optional, for display)
|
| 32 |
+
query_name: Query structure name (optional, for display)
|
| 33 |
+
ref_sequence: Reference sequence (optional, for display)
|
| 34 |
+
query_sequence: Query sequence (optional, for display)
|
| 35 |
|
| 36 |
Returns:
|
| 37 |
HTML string containing the py3Dmol visualization
|
| 38 |
"""
|
| 39 |
|
| 40 |
+
# Extract simple names if not provided
|
| 41 |
+
if ref_name is None:
|
| 42 |
+
ref_name = Path(ref_path).stem
|
| 43 |
+
if query_name is None:
|
| 44 |
+
query_name = Path(query_path).stem
|
| 45 |
+
|
| 46 |
# Read PDB files
|
| 47 |
with open(ref_path) as f:
|
| 48 |
ref_pdb = f.read()
|
|
|
|
| 96 |
<html>
|
| 97 |
<head>
|
| 98 |
<script src="https://3Dmol.csb.pitt.edu/build/3Dmol-min.js"></script>
|
| 99 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
|
| 100 |
<style>
|
| 101 |
#container {{
|
| 102 |
width: 100%;
|
|
|
|
| 180 |
bottom: 10px;
|
| 181 |
left: 10px;
|
| 182 |
background: rgba(255, 255, 255, 0.95);
|
| 183 |
+
padding: 12px 15px;
|
| 184 |
border-radius: 8px;
|
| 185 |
font-family: Arial, sans-serif;
|
| 186 |
font-size: 13px;
|
| 187 |
z-index: 1000;
|
| 188 |
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
| 189 |
+
max-width: 450px;
|
| 190 |
+
}}
|
| 191 |
+
.info-row {{
|
| 192 |
+
margin: 4px 0;
|
| 193 |
+
line-height: 1.4;
|
| 194 |
+
}}
|
| 195 |
+
.info-label {{
|
| 196 |
+
font-weight: bold;
|
| 197 |
+
color: #555;
|
| 198 |
+
}}
|
| 199 |
+
.info-value {{
|
| 200 |
+
color: #333;
|
| 201 |
+
font-family: 'Courier New', monospace;
|
| 202 |
}}
|
| 203 |
.section-title {{
|
| 204 |
font-weight: bold;
|
|
|
|
| 207 |
font-size: 12px;
|
| 208 |
text-transform: uppercase;
|
| 209 |
}}
|
| 210 |
+
.download-section {{
|
| 211 |
+
position: absolute;
|
| 212 |
+
bottom: 10px;
|
| 213 |
+
right: 10px;
|
| 214 |
+
background: rgba(255, 255, 255, 0.95);
|
| 215 |
+
padding: 10px;
|
| 216 |
+
border-radius: 8px;
|
| 217 |
+
font-family: Arial, sans-serif;
|
| 218 |
+
z-index: 1000;
|
| 219 |
+
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
| 220 |
+
}}
|
| 221 |
+
.download-btn {{
|
| 222 |
+
background: #4A90E2;
|
| 223 |
+
color: white;
|
| 224 |
+
border: none;
|
| 225 |
+
padding: 8px 16px;
|
| 226 |
+
border-radius: 4px;
|
| 227 |
+
cursor: pointer;
|
| 228 |
+
font-size: 13px;
|
| 229 |
+
font-weight: bold;
|
| 230 |
+
}}
|
| 231 |
+
.download-btn:hover {{
|
| 232 |
+
background: #357ABD;
|
| 233 |
+
}}
|
| 234 |
</style>
|
| 235 |
</head>
|
| 236 |
<body>
|
|
|
|
| 318 |
</div>
|
| 319 |
|
| 320 |
<div class="rmsd-info">
|
| 321 |
+
<div class="info-row">
|
| 322 |
+
<span class="info-label">RMSD:</span>
|
| 323 |
+
<span style="color: #E94B3C; font-weight: bold; font-size: 14px;">{f"{rmsd:.3f}" if rmsd is not None else "N/A"} Å</span>
|
| 324 |
+
</div>
|
| 325 |
+
<div style="margin-top: 8px; padding-top: 8px; border-top: 1px solid #ddd;">
|
| 326 |
+
<div class="info-row">
|
| 327 |
+
<span class="info-label">Reference:</span>
|
| 328 |
+
<span class="info-value">{ref_name}</span>
|
| 329 |
+
</div>
|
| 330 |
+
{f'<div class="info-row" style="margin-left: 15px; font-size: 12px;"><span class="info-label">Seq:</span> <span class="info-value">{ref_sequence}</span></div>' if ref_sequence else ''}
|
| 331 |
+
</div>
|
| 332 |
+
<div style="margin-top: 6px;">
|
| 333 |
+
<div class="info-row">
|
| 334 |
+
<span class="info-label">Query:</span>
|
| 335 |
+
<span class="info-value">{query_name}</span>
|
| 336 |
+
</div>
|
| 337 |
+
{f'<div class="info-row" style="margin-left: 15px; font-size: 12px;"><span class="info-label">Seq:</span> <span class="info-value">{query_sequence}</span></div>' if query_sequence else ''}
|
| 338 |
+
</div>
|
| 339 |
+
</div>
|
| 340 |
+
|
| 341 |
+
<div class="download-section">
|
| 342 |
+
<button class="download-btn" onclick="downloadImage()">📷 Download PNG</button>
|
| 343 |
</div>
|
| 344 |
|
| 345 |
<script>
|
|
|
|
| 601 |
}});
|
| 602 |
}}
|
| 603 |
|
| 604 |
+
function downloadImage() {{
|
| 605 |
+
try {{
|
| 606 |
+
// Generate filename with metadata
|
| 607 |
+
var refName = "{ref_name}".replace('.pdb', '');
|
| 608 |
+
var queryName = "{query_name}".replace('.pdb', '');
|
| 609 |
+
var rmsdValue = "{f'{rmsd:.3f}' if rmsd is not None else 'NA'}";
|
| 610 |
+
var filename = 'alignment_' + refName + '_' + queryName + '_RMSD_' + rmsdValue + '.png';
|
| 611 |
+
|
| 612 |
+
// Ensure viewer is rendered
|
| 613 |
+
if (viewer) {{
|
| 614 |
+
viewer.render();
|
| 615 |
+
}}
|
| 616 |
+
|
| 617 |
+
// Get the container element (includes canvas + all overlays)
|
| 618 |
+
const container = document.getElementById('container');
|
| 619 |
+
|
| 620 |
+
if (!container) {{
|
| 621 |
+
alert('Container not ready. Please wait and try again.');
|
| 622 |
+
return;
|
| 623 |
+
}}
|
| 624 |
+
|
| 625 |
+
// Use html2canvas to capture the entire container with overlays
|
| 626 |
+
html2canvas(container, {{
|
| 627 |
+
backgroundColor: '#ffffff',
|
| 628 |
+
scale: 2, // Higher resolution
|
| 629 |
+
logging: false,
|
| 630 |
+
useCORS: true,
|
| 631 |
+
allowTaint: true
|
| 632 |
+
}}).then(function(canvas) {{
|
| 633 |
+
// Convert to PNG
|
| 634 |
+
const dataURL = canvas.toDataURL('image/png');
|
| 635 |
+
|
| 636 |
+
// Create download link
|
| 637 |
+
const link = document.createElement('a');
|
| 638 |
+
link.download = filename;
|
| 639 |
+
link.href = dataURL;
|
| 640 |
+
|
| 641 |
+
// Trigger download
|
| 642 |
+
document.body.appendChild(link);
|
| 643 |
+
link.click();
|
| 644 |
+
document.body.removeChild(link);
|
| 645 |
+
}}).catch(function(error) {{
|
| 646 |
+
console.error('html2canvas error:', error);
|
| 647 |
+
|
| 648 |
+
// Fallback: just capture the canvas without overlays
|
| 649 |
+
const canvas = document.querySelector('#container canvas');
|
| 650 |
+
if (canvas) {{
|
| 651 |
+
const dataURL = canvas.toDataURL('image/png');
|
| 652 |
+
const link = document.createElement('a');
|
| 653 |
+
link.download = filename;
|
| 654 |
+
link.href = dataURL;
|
| 655 |
+
document.body.appendChild(link);
|
| 656 |
+
link.click();
|
| 657 |
+
document.body.removeChild(link);
|
| 658 |
+
}} else {{
|
| 659 |
+
alert('Download failed. Please try again.');
|
| 660 |
+
}}
|
| 661 |
+
}});
|
| 662 |
+
|
| 663 |
+
}} catch (error) {{
|
| 664 |
+
console.error('PNG download error:', error);
|
| 665 |
+
alert('Error downloading PNG: ' + error.message);
|
| 666 |
+
}}
|
| 667 |
+
}}
|
| 668 |
+
|
| 669 |
// Initialize on load
|
| 670 |
initViewer();
|
| 671 |
</script>
|
|
|
|
| 806 |
else:
|
| 807 |
transformed_lines.append(line)
|
| 808 |
|
| 809 |
+
return '\n'.join(transformed_lines)
|