Nanny7 commited on
Commit
2780791
·
1 Parent(s): 6b65aed

Replace Plotly with Matplotlib for reliable orbital visualization rendering

Browse files
Files changed (3) hide show
  1. app.py +93 -90
  2. force_rebuild.txt +1 -1
  3. 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 meshgrid
253
- X, Y, Z = np.meshgrid(x, y, z, indexing='ij')
254
 
255
- # Create plotly figure with isosurface
256
- fig = go.Figure()
 
257
 
258
- # Add positive isosurface (blue)
259
- fig.add_trace(go.Isosurface(
260
- x=X.flatten(),
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
- atom_x.append(pos.x)
297
- atom_y.append(pos.y)
298
- atom_z.append(pos.z)
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
- fig.add_trace(go.Scatter3d(
313
- x=atom_x, y=atom_y, z=atom_z,
314
- mode='markers+text',
315
- marker=dict(size=10, color=atom_colors, line=dict(width=1, color='white')),
316
- text=atom_text,
317
- textposition='top center',
318
- textfont=dict(size=8),
319
- name='Atoms'
320
- ))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
321
 
322
- fig.update_layout(
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
- # Generate HTML with config
341
- import plotly.io as pio
342
- config = {
343
- 'displayModeBar': True,
344
- 'displaylogo': False,
345
- 'responsive': True
346
- }
347
- plot_html = pio.to_html(fig, full_html=False, include_plotlyjs='cdn', config=config)
348
- html_sections.append(f"<div style='margin: 20px 0; padding: 10px; border: 1px solid #ddd; border-radius: 5px;'>{plot_html}</div>")
 
 
 
 
 
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 v9
 
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