Update app.py
Browse files
app.py
CHANGED
@@ -9,6 +9,7 @@ import matplotlib.pyplot as plt
|
|
9 |
import seaborn as sns
|
10 |
import requests
|
11 |
import io
|
|
|
12 |
from PIL import Image
|
13 |
import base64
|
14 |
|
@@ -320,6 +321,8 @@ def predict_admet_properties(smiles_dict: dict):
|
|
320 |
log += f"✅ Predicted ADMET properties for {len(df)} molecules.\n"
|
321 |
return df, log
|
322 |
|
|
|
|
|
323 |
def visualize_molecule_2d_3d(smiles: str, name: str):
|
324 |
"""Generates a side-by-side 2D SVG and 3D py3Dmol HTML view for a single molecule."""
|
325 |
log = ""
|
@@ -328,16 +331,21 @@ def visualize_molecule_2d_3d(smiles: str, name: str):
|
|
328 |
if not mol: return f"<p>Invalid SMILES for {name}</p>", f"❌ Invalid SMILES for {name}"
|
329 |
|
330 |
drawer = Draw.rdMolDraw2D.MolDraw2DSVG(400, 300)
|
|
|
331 |
drawer.drawOptions().clearBackground = False
|
332 |
drawer.drawOptions().addStereoAnnotation = True
|
333 |
drawer.drawOptions().baseFontSize = 0.8
|
334 |
drawer.drawOptions().circleAtoms = False
|
335 |
-
drawer.drawOptions().highlightColour = (1, 0.5, 0)
|
336 |
-
|
337 |
-
|
338 |
-
drawer.drawOptions().
|
|
|
|
|
|
|
|
|
339 |
try:
|
340 |
-
drawer.drawOptions().annotationColour = (1, 1, 1)
|
341 |
except:
|
342 |
pass
|
343 |
|
@@ -345,13 +353,51 @@ def visualize_molecule_2d_3d(smiles: str, name: str):
|
|
345 |
drawer.FinishDrawing()
|
346 |
svg_2d = drawer.GetDrawingText().replace('svg:', '')
|
347 |
|
348 |
-
|
|
|
|
|
349 |
svg_2d = svg_2d.replace('stroke="black"', 'stroke="white"')
|
350 |
svg_2d = svg_2d.replace('fill="black"', 'fill="white"')
|
351 |
svg_2d = svg_2d.replace('stroke="#000000"', 'stroke="#FFFFFF"')
|
352 |
svg_2d = svg_2d.replace('fill="#000000"', 'fill="#FFFFFF"')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
353 |
svg_2d = re.sub(r'<text(?![^>]*fill=)([^>]*?)>', r'<text fill="white"\1>', svg_2d)
|
354 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
355 |
svg_2d = f'<div style="background-color: #1C1C1C; padding: 10px; border-radius: 5px;">{svg_2d}</div>'
|
356 |
|
357 |
mol_3d = Chem.AddHs(mol)
|
@@ -546,18 +592,14 @@ with tab1:
|
|
546 |
props_df, log_props = calculate_molecular_properties(smiles_list)
|
547 |
full_log += log_props
|
548 |
|
549 |
-
# --- MODIFIED LOGIC ---
|
550 |
-
# assess_drug_likeness now returns two dataframes: one for analysis/plotting, one for display
|
551 |
analysis_df, display_df, log_lipinski = assess_drug_likeness(props_df)
|
552 |
full_log += log_lipinski
|
553 |
|
554 |
-
# The plotting function uses the 'analysis_df' with the boolean 'Drug_Like' column
|
555 |
props_plot, log_plot = plot_properties_dashboard(analysis_df)
|
556 |
full_log += log_plot
|
557 |
full_log += "\n--- Phase 1 Analysis Complete ---"
|
558 |
st.session_state.log_p1 = full_log
|
559 |
|
560 |
-
# The display dataframe has the pretty emojis
|
561 |
lipinski_cols = ['Molecule', 'MW', 'LogP', 'HBD', 'HBA', 'Lipinski_Violations', 'Drug_Like']
|
562 |
lipinski_subset_df = display_df[lipinski_cols] if not display_df.empty else pd.DataFrame(columns=lipinski_cols)
|
563 |
|
|
|
9 |
import seaborn as sns
|
10 |
import requests
|
11 |
import io
|
12 |
+
import re
|
13 |
from PIL import Image
|
14 |
import base64
|
15 |
|
|
|
321 |
log += f"✅ Predicted ADMET properties for {len(df)} molecules.\n"
|
322 |
return df, log
|
323 |
|
324 |
+
# --- MODIFIED FUNCTION ---
|
325 |
+
# This is the updated function from the old app to correctly render 2D molecules.
|
326 |
def visualize_molecule_2d_3d(smiles: str, name: str):
|
327 |
"""Generates a side-by-side 2D SVG and 3D py3Dmol HTML view for a single molecule."""
|
328 |
log = ""
|
|
|
331 |
if not mol: return f"<p>Invalid SMILES for {name}</p>", f"❌ Invalid SMILES for {name}"
|
332 |
|
333 |
drawer = Draw.rdMolDraw2D.MolDraw2DSVG(400, 300)
|
334 |
+
# Set dark theme colors for 2D drawing
|
335 |
drawer.drawOptions().clearBackground = False
|
336 |
drawer.drawOptions().addStereoAnnotation = True
|
337 |
drawer.drawOptions().baseFontSize = 0.8
|
338 |
drawer.drawOptions().circleAtoms = False
|
339 |
+
drawer.drawOptions().highlightColour = (1, 0.5, 0) # Orange for highlights
|
340 |
+
|
341 |
+
# Set colors for dark background visibility
|
342 |
+
drawer.drawOptions().backgroundColour = (0.11, 0.11, 0.11) # Dark background
|
343 |
+
drawer.drawOptions().symbolColour = (1, 1, 1) # White symbols
|
344 |
+
drawer.drawOptions().defaultColour = (1, 1, 1) # White default color
|
345 |
+
|
346 |
+
# Try to set annotation color (this might help with (R)/(S) labels)
|
347 |
try:
|
348 |
+
drawer.drawOptions().annotationColour = (1, 1, 1) # White annotations
|
349 |
except:
|
350 |
pass
|
351 |
|
|
|
353 |
drawer.FinishDrawing()
|
354 |
svg_2d = drawer.GetDrawingText().replace('svg:', '')
|
355 |
|
356 |
+
# More aggressive SVG text color fixes - target all possible black text variations
|
357 |
+
|
358 |
+
# First, comprehensive string replacements
|
359 |
svg_2d = svg_2d.replace('stroke="black"', 'stroke="white"')
|
360 |
svg_2d = svg_2d.replace('fill="black"', 'fill="white"')
|
361 |
svg_2d = svg_2d.replace('stroke="#000000"', 'stroke="#FFFFFF"')
|
362 |
svg_2d = svg_2d.replace('fill="#000000"', 'fill="#FFFFFF"')
|
363 |
+
svg_2d = svg_2d.replace('stroke="#000"', 'stroke="#FFF"')
|
364 |
+
svg_2d = svg_2d.replace('fill="#000"', 'fill="#FFF"')
|
365 |
+
svg_2d = svg_2d.replace('stroke:black', 'stroke:white')
|
366 |
+
svg_2d = svg_2d.replace('fill:black', 'fill:white')
|
367 |
+
svg_2d = svg_2d.replace('stroke:#000000', 'stroke:#FFFFFF')
|
368 |
+
svg_2d = svg_2d.replace('fill:#000000', 'fill:#FFFFFF')
|
369 |
+
svg_2d = svg_2d.replace('stroke:#000', 'stroke:#FFF')
|
370 |
+
svg_2d = svg_2d.replace('fill:#000', 'fill:#FFF')
|
371 |
+
svg_2d = svg_2d.replace('stroke="rgb(0,0,0)"', 'stroke="rgb(255,255,255)"')
|
372 |
+
svg_2d = svg_2d.replace('fill="rgb(0,0,0)"', 'fill="rgb(255,255,255)"')
|
373 |
+
svg_2d = svg_2d.replace('stroke:rgb(0,0,0)', 'stroke:rgb(255,255,255)')
|
374 |
+
svg_2d = svg_2d.replace('fill:rgb(0,0,0)', 'fill:rgb(255,255,255)')
|
375 |
+
svg_2d = svg_2d.replace('color="black"', 'color="white"')
|
376 |
+
svg_2d = svg_2d.replace('color:#000000', 'color:#FFFFFF')
|
377 |
+
svg_2d = svg_2d.replace('color:#000', 'color:#FFF')
|
378 |
+
|
379 |
+
# Aggressive regex-based fixes for all text elements
|
380 |
+
# Remove any existing fill attributes from text elements and add white fill
|
381 |
+
svg_2d = re.sub(r'<text([^>]*?)\s+fill="[^"]*"([^>]*?)>', r'<text\1\2 fill="white">', svg_2d)
|
382 |
+
svg_2d = re.sub(r'<text([^>]*?)(?<!fill="white")>', r'<text\1 fill="white">', svg_2d)
|
383 |
+
|
384 |
+
# Fix style attributes in text elements
|
385 |
+
svg_2d = re.sub(r'<text([^>]*?)style="([^"]*?)fill:\s*(?:black|#000000|#000|rgb\(0,0,0\))([^"]*?)"([^>]*?)>',
|
386 |
+
r'<text\1style="\2fill:white\3"\4>', svg_2d)
|
387 |
+
|
388 |
+
# If text elements don't have any fill specified, ensure they get white
|
389 |
svg_2d = re.sub(r'<text(?![^>]*fill=)([^>]*?)>', r'<text fill="white"\1>', svg_2d)
|
390 |
|
391 |
+
# Clean up any duplicate fill attributes
|
392 |
+
svg_2d = re.sub(r'fill="white"\s+fill="white"', 'fill="white"', svg_2d)
|
393 |
+
|
394 |
+
# Final catch-all: replace any remaining black in the entire SVG
|
395 |
+
svg_2d = re.sub(r'\bblack\b', 'white', svg_2d)
|
396 |
+
svg_2d = re.sub(r'#000000', '#FFFFFF', svg_2d)
|
397 |
+
svg_2d = re.sub(r'#000\b', '#FFF', svg_2d)
|
398 |
+
svg_2d = re.sub(r'rgb\(0,\s*0,\s*0\)', 'rgb(255,255,255)', svg_2d)
|
399 |
+
|
400 |
+
# Embed the SVG within a div with a dark background for consistency
|
401 |
svg_2d = f'<div style="background-color: #1C1C1C; padding: 10px; border-radius: 5px;">{svg_2d}</div>'
|
402 |
|
403 |
mol_3d = Chem.AddHs(mol)
|
|
|
592 |
props_df, log_props = calculate_molecular_properties(smiles_list)
|
593 |
full_log += log_props
|
594 |
|
|
|
|
|
595 |
analysis_df, display_df, log_lipinski = assess_drug_likeness(props_df)
|
596 |
full_log += log_lipinski
|
597 |
|
|
|
598 |
props_plot, log_plot = plot_properties_dashboard(analysis_df)
|
599 |
full_log += log_plot
|
600 |
full_log += "\n--- Phase 1 Analysis Complete ---"
|
601 |
st.session_state.log_p1 = full_log
|
602 |
|
|
|
603 |
lipinski_cols = ['Molecule', 'MW', 'LogP', 'HBD', 'HBA', 'Lipinski_Violations', 'Drug_Like']
|
604 |
lipinski_subset_df = display_df[lipinski_cols] if not display_df.empty else pd.DataFrame(columns=lipinski_cols)
|
605 |
|