Spaces:
Runtime error
Runtime error
Replace Plotly with Matplotlib for reliable orbital visualization rendering
Browse files- app.py +93 -90
- force_rebuild.txt +1 -1
- requirements.txt +3 -1
app.py
CHANGED
|
@@ -71,6 +71,13 @@ from urllib.error import HTTPError, URLError
|
|
| 71 |
import os
|
| 72 |
import tempfile
|
| 73 |
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
|
| 75 |
|
| 76 |
# RDKit API with multiple endpoints
|
|
@@ -249,103 +256,99 @@ def smiles_to_molecular_orbitals(smiles_input: str, name_input: str) -> str:
|
|
| 249 |
# Parse cube file
|
| 250 |
x, y, z, values = parse_cube_file(cube_file)
|
| 251 |
|
| 252 |
-
# Create 3D
|
| 253 |
-
|
| 254 |
|
| 255 |
-
# Create
|
| 256 |
-
|
|
|
|
| 257 |
|
| 258 |
-
#
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
y=Y.flatten(),
|
| 262 |
-
z=Z.flatten(),
|
| 263 |
-
value=values.flatten(),
|
| 264 |
-
isomin=0.02,
|
| 265 |
-
isomax=values.max(),
|
| 266 |
-
surface_count=1,
|
| 267 |
-
colorscale=[[0, 'blue'], [1, 'blue']],
|
| 268 |
-
showscale=False,
|
| 269 |
-
caps=dict(x_show=False, y_show=False, z_show=False),
|
| 270 |
-
opacity=0.7,
|
| 271 |
-
name='Positive'
|
| 272 |
-
))
|
| 273 |
-
|
| 274 |
-
# Add negative isosurface (red)
|
| 275 |
-
fig.add_trace(go.Isosurface(
|
| 276 |
-
x=X.flatten(),
|
| 277 |
-
y=Y.flatten(),
|
| 278 |
-
z=Z.flatten(),
|
| 279 |
-
value=values.flatten(),
|
| 280 |
-
isomin=values.min(),
|
| 281 |
-
isomax=-0.02,
|
| 282 |
-
surface_count=1,
|
| 283 |
-
colorscale=[[0, 'red'], [1, 'red']],
|
| 284 |
-
showscale=False,
|
| 285 |
-
caps=dict(x_show=False, y_show=False, z_show=False),
|
| 286 |
-
opacity=0.7,
|
| 287 |
-
name='Negative'
|
| 288 |
-
))
|
| 289 |
-
|
| 290 |
-
# Add molecular structure
|
| 291 |
-
atom_x, atom_y, atom_z = [], [], []
|
| 292 |
-
atom_colors = []
|
| 293 |
-
atom_text = []
|
| 294 |
for atom in mol_3d.GetAtoms():
|
| 295 |
pos = mol_3d.GetConformer().GetAtomPosition(atom.GetIdx())
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
atom_text.append(atom.GetSymbol())
|
| 300 |
-
# Color by atom type
|
| 301 |
-
if atom.GetSymbol() == 'C':
|
| 302 |
-
atom_colors.append('gray')
|
| 303 |
-
elif atom.GetSymbol() == 'N':
|
| 304 |
-
atom_colors.append('darkblue')
|
| 305 |
-
elif atom.GetSymbol() == 'O':
|
| 306 |
-
atom_colors.append('darkred')
|
| 307 |
-
elif atom.GetSymbol() == 'H':
|
| 308 |
-
atom_colors.append('lightgray')
|
| 309 |
-
else:
|
| 310 |
-
atom_colors.append('purple')
|
| 311 |
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 321 |
|
| 322 |
-
|
| 323 |
-
title=dict(text=f'{label} Orbital', x=0.5, xanchor='center'),
|
| 324 |
-
width=700,
|
| 325 |
-
height=600,
|
| 326 |
-
scene=dict(
|
| 327 |
-
xaxis_title='X (Å)',
|
| 328 |
-
yaxis_title='Y (Å)',
|
| 329 |
-
zaxis_title='Z (Å)',
|
| 330 |
-
aspectmode='data',
|
| 331 |
-
camera=dict(
|
| 332 |
-
eye=dict(x=1.5, y=1.5, z=1.5)
|
| 333 |
-
)
|
| 334 |
-
),
|
| 335 |
-
margin=dict(l=0, r=0, t=40, b=0),
|
| 336 |
-
showlegend=True,
|
| 337 |
-
legend=dict(x=0.02, y=0.98)
|
| 338 |
-
)
|
| 339 |
|
| 340 |
-
#
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 349 |
|
| 350 |
if not html_sections:
|
| 351 |
return "<p>Could not prepare HOMO/LUMO visualizations.</p>"
|
|
|
|
| 71 |
import os
|
| 72 |
import tempfile
|
| 73 |
from pathlib import Path
|
| 74 |
+
import matplotlib
|
| 75 |
+
matplotlib.use('Agg') # Use non-interactive backend
|
| 76 |
+
import matplotlib.pyplot as plt
|
| 77 |
+
from mpl_toolkits.mplot3d import Axes3D
|
| 78 |
+
from PIL import Image
|
| 79 |
+
import io
|
| 80 |
+
import base64
|
| 81 |
|
| 82 |
|
| 83 |
# RDKit API with multiple endpoints
|
|
|
|
| 256 |
# Parse cube file
|
| 257 |
x, y, z, values = parse_cube_file(cube_file)
|
| 258 |
|
| 259 |
+
# Create matplotlib 3D visualization
|
| 260 |
+
fig = plt.figure(figsize=(12, 5))
|
| 261 |
|
| 262 |
+
# Create two subplots side by side
|
| 263 |
+
ax1 = fig.add_subplot(121, projection='3d')
|
| 264 |
+
ax2 = fig.add_subplot(122, projection='3d')
|
| 265 |
|
| 266 |
+
# Get atom positions
|
| 267 |
+
atom_coords = []
|
| 268 |
+
atom_symbols = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 269 |
for atom in mol_3d.GetAtoms():
|
| 270 |
pos = mol_3d.GetConformer().GetAtomPosition(atom.GetIdx())
|
| 271 |
+
atom_coords.append([pos.x, pos.y, pos.z])
|
| 272 |
+
atom_symbols.append(atom.GetSymbol())
|
| 273 |
+
atom_coords = np.array(atom_coords)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 274 |
|
| 275 |
+
# Plot atoms on both subplots
|
| 276 |
+
for ax, title in [(ax1, f'{label} - View 1'), (ax2, f'{label} - View 2')]:
|
| 277 |
+
# Color atoms by element
|
| 278 |
+
for i, (coord, symbol) in enumerate(zip(atom_coords, atom_symbols)):
|
| 279 |
+
if symbol == 'C':
|
| 280 |
+
color = 'gray'
|
| 281 |
+
size = 100
|
| 282 |
+
elif symbol == 'O':
|
| 283 |
+
color = 'red'
|
| 284 |
+
size = 120
|
| 285 |
+
elif symbol == 'N':
|
| 286 |
+
color = 'blue'
|
| 287 |
+
size = 120
|
| 288 |
+
elif symbol == 'H':
|
| 289 |
+
color = 'lightgray'
|
| 290 |
+
size = 60
|
| 291 |
+
else:
|
| 292 |
+
color = 'purple'
|
| 293 |
+
size = 100
|
| 294 |
+
ax.scatter(*coord, c=color, s=size, alpha=0.9, edgecolors='black', linewidth=0.5)
|
| 295 |
+
ax.text(*coord, symbol, fontsize=8)
|
| 296 |
+
|
| 297 |
+
# Sample the cube data for contour visualization
|
| 298 |
+
iso_val = 0.02
|
| 299 |
+
# Find where values exceed threshold
|
| 300 |
+
pos_mask = values > iso_val
|
| 301 |
+
neg_mask = values < -iso_val
|
| 302 |
+
|
| 303 |
+
# Create 3D mesh grid
|
| 304 |
+
X, Y, Z = np.meshgrid(x, y, z, indexing='ij')
|
| 305 |
+
|
| 306 |
+
# Plot positive regions (blue)
|
| 307 |
+
if np.any(pos_mask):
|
| 308 |
+
pos_points = np.column_stack((X[pos_mask], Y[pos_mask], Z[pos_mask]))
|
| 309 |
+
if len(pos_points) > 0:
|
| 310 |
+
sample_idx = np.random.choice(len(pos_points), min(1000, len(pos_points)), replace=False)
|
| 311 |
+
ax.scatter(pos_points[sample_idx, 0],
|
| 312 |
+
pos_points[sample_idx, 1],
|
| 313 |
+
pos_points[sample_idx, 2],
|
| 314 |
+
c='blue', alpha=0.3, s=1, label='Positive lobe')
|
| 315 |
+
|
| 316 |
+
# Plot negative regions (red)
|
| 317 |
+
if np.any(neg_mask):
|
| 318 |
+
neg_points = np.column_stack((X[neg_mask], Y[neg_mask], Z[neg_mask]))
|
| 319 |
+
if len(neg_points) > 0:
|
| 320 |
+
sample_idx = np.random.choice(len(neg_points), min(1000, len(neg_points)), replace=False)
|
| 321 |
+
ax.scatter(neg_points[sample_idx, 0],
|
| 322 |
+
neg_points[sample_idx, 1],
|
| 323 |
+
neg_points[sample_idx, 2],
|
| 324 |
+
c='red', alpha=0.3, s=1, label='Negative lobe')
|
| 325 |
+
|
| 326 |
+
ax.set_xlabel('X (Å)')
|
| 327 |
+
ax.set_ylabel('Y (Å)')
|
| 328 |
+
ax.set_zlabel('Z (Å)')
|
| 329 |
+
ax.set_title(title)
|
| 330 |
+
ax.legend(fontsize=8)
|
| 331 |
+
|
| 332 |
+
# Set different viewing angles
|
| 333 |
+
ax1.view_init(elev=20, azim=45)
|
| 334 |
+
ax2.view_init(elev=20, azim=135)
|
| 335 |
|
| 336 |
+
plt.tight_layout()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 337 |
|
| 338 |
+
# Convert to base64 image
|
| 339 |
+
buf = io.BytesIO()
|
| 340 |
+
plt.savefig(buf, format='png', dpi=100, bbox_inches='tight')
|
| 341 |
+
buf.seek(0)
|
| 342 |
+
img_base64 = base64.b64encode(buf.read()).decode('utf-8')
|
| 343 |
+
plt.close(fig)
|
| 344 |
+
|
| 345 |
+
html_sections.append(
|
| 346 |
+
f"<div style='margin: 20px 0;'>"
|
| 347 |
+
f"<h3>{label} Orbital</h3>"
|
| 348 |
+
f"<img src='data:image/png;base64,{img_base64}' style='max-width: 100%; height: auto;'/>"
|
| 349 |
+
f"<p><small>Blue = positive orbital lobe, Red = negative orbital lobe</small></p>"
|
| 350 |
+
f"</div>"
|
| 351 |
+
)
|
| 352 |
|
| 353 |
if not html_sections:
|
| 354 |
return "<p>Could not prepare HOMO/LUMO visualizations.</p>"
|
force_rebuild.txt
CHANGED
|
@@ -1,2 +1,2 @@
|
|
| 1 |
# Force rebuild
|
| 2 |
-
2025-11-08
|
|
|
|
| 1 |
# Force rebuild
|
| 2 |
+
2025-11-08 v10
|
requirements.txt
CHANGED
|
@@ -7,4 +7,6 @@ pubchempy
|
|
| 7 |
psikit
|
| 8 |
py3Dmol
|
| 9 |
pyscf
|
| 10 |
-
plotly
|
|
|
|
|
|
|
|
|
| 7 |
psikit
|
| 8 |
py3Dmol
|
| 9 |
pyscf
|
| 10 |
+
plotly
|
| 11 |
+
matplotlib
|
| 12 |
+
pillow
|