Update app.py
Browse files
app.py
CHANGED
@@ -43,7 +43,7 @@ def apply_custom_styling():
|
|
43 |
<style>
|
44 |
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap');
|
45 |
|
46 |
-
html, body, [class
|
47 |
font-family: 'Roboto', sans-serif;
|
48 |
}
|
49 |
|
@@ -53,11 +53,11 @@ def apply_custom_styling():
|
|
53 |
}
|
54 |
|
55 |
/* Tab styles */
|
56 |
-
.stTabs [data-baseweb
|
57 |
gap: 24px;
|
58 |
}
|
59 |
|
60 |
-
.stTabs [data-baseweb
|
61 |
height: 50px;
|
62 |
white-space: pre-wrap;
|
63 |
background: none;
|
@@ -67,12 +67,12 @@ def apply_custom_styling():
|
|
67 |
color: #AAA;
|
68 |
}
|
69 |
|
70 |
-
.stTabs [data-baseweb
|
71 |
background: #222;
|
72 |
color: #FFF;
|
73 |
}
|
74 |
|
75 |
-
.stTabs [aria-selected
|
76 |
border-bottom: 2px solid #00A0FF; /* Highlight color for active tab */
|
77 |
color: #FFF;
|
78 |
}
|
@@ -154,7 +154,6 @@ def visualize_protein_3d(pdb_data: str, title="Protein 3D Structure"):
|
|
154 |
return None, "Cannot generate 3D view: No PDB data provided."
|
155 |
try:
|
156 |
viewer = py3Dmol.view(width='100%', height=600)
|
157 |
-
# MODIFIED: Changed background color
|
158 |
viewer.setBackgroundColor('#1C1C1C')
|
159 |
viewer.addModel(pdb_data, "pdb")
|
160 |
viewer.setStyle({'cartoon': {'color': 'spectrum', 'thickness': 0.8}})
|
@@ -206,28 +205,46 @@ def calculate_molecular_properties(smiles_list: list):
|
|
206 |
|
207 |
def assess_drug_likeness(df: pd.DataFrame):
|
208 |
"""
|
209 |
-
Assesses drug-likeness based on Lipinski's Rule of Five.
|
|
|
210 |
"""
|
211 |
if df.empty:
|
212 |
-
return pd.DataFrame(), "Cannot assess drug-likeness: No properties data."
|
213 |
-
df_copy = df.copy()
|
214 |
-
df_copy['MW_OK'] = df_copy['MW'] <= 500
|
215 |
-
df_copy['LogP_OK'] = df_copy['LogP'] <= 5
|
216 |
-
df_copy['HBD_OK'] = df_copy['HBD'] <= 5
|
217 |
-
df_copy['HBA_OK'] = df_copy['HBA'] <= 10
|
218 |
-
df_copy['Lipinski_Violations'] = (~df_copy[['MW_OK', 'LogP_OK', 'HBD_OK', 'HBA_OK']]).sum(axis=1)
|
219 |
-
# Fixed: Use proper colored emojis instead of boolean values
|
220 |
-
df_copy['Drug_Like'] = df_copy['Lipinski_Violations'].apply(lambda x: 'β
Yes' if x <= 1 else 'β No')
|
221 |
-
log = "β
Assessed drug-likeness using Lipinski's Rule of Five.\n" [cite: 27]
|
222 |
-
return df_copy, log [cite: 27]
|
223 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
224 |
def plot_properties_dashboard(df: pd.DataFrame):
|
225 |
"""
|
226 |
-
Creates a 2x2 dashboard of molecular property visualizations.
|
|
|
227 |
"""
|
228 |
-
if df.empty:
|
229 |
-
return None, "Cannot plot: No analysis data."
|
230 |
|
|
|
|
|
|
|
|
|
231 |
plt.style.use('dark_background')
|
232 |
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
|
233 |
fig.suptitle("Molecular Properties Analysis", fontsize=16)
|
@@ -237,17 +254,18 @@ def plot_properties_dashboard(df: pd.DataFrame):
|
|
237 |
for ax in ax_row:
|
238 |
ax.set_facecolor('none')
|
239 |
|
240 |
-
#
|
241 |
-
|
|
|
|
|
242 |
axes[0,0].set_title('Molecular Weight vs LogP')
|
243 |
-
axes[0,0].set_xlabel('Molecular Weight (Da)')
|
244 |
-
axes[0,0].set_ylabel('LogP')
|
245 |
-
axes[0,0].axvline(500, color='r', linestyle='--', alpha=0.6, label='MW < 500')
|
246 |
-
axes[0,0].axhline(5, color='r', linestyle='--', alpha=0.6, label='LogP < 5')
|
247 |
axes[0,0].legend()
|
248 |
|
249 |
-
|
250 |
-
axes[0,1].scatter(df['HBD'], df['HBA'], c=df['Drug_Like'].map({'β
Yes': 'green', 'β No': 'red'}), s=80, alpha=0.7)
|
251 |
axes[0,1].set_title('Hydrogen Bonding Properties')
|
252 |
axes[0,1].set_xlabel('Hydrogen Bond Donors')
|
253 |
axes[0,1].set_ylabel('Hydrogen Bond Acceptors')
|
@@ -255,22 +273,20 @@ def plot_properties_dashboard(df: pd.DataFrame):
|
|
255 |
axes[0,1].axhline(10, color='r', linestyle='--', alpha=0.6, label='HBA < 10')
|
256 |
axes[0,1].legend()
|
257 |
|
258 |
-
|
259 |
-
axes[1,0].
|
260 |
-
axes[1,0].
|
261 |
-
axes[1,0].
|
262 |
-
axes[1,0].set_ylabel('Rotatable Bonds') [cite: 30]
|
263 |
|
264 |
drug_like_counts = df['Drug_Like'].value_counts()
|
265 |
-
|
266 |
-
|
267 |
-
colors = ['green' if i == 'β
Yes' else 'red' for i in drug_like_counts.index]
|
268 |
axes[1,1].pie(drug_like_counts.values, labels=labels, colors=colors, autopct='%1.1f%%', startangle=90)
|
269 |
axes[1,1].set_title('Drug-likeness Distribution')
|
270 |
|
271 |
plt.tight_layout(rect=[0, 0, 1, 0.96])
|
272 |
return fig, "β
Generated properties dashboard."
|
273 |
-
|
274 |
# ===== Phase 2 Functions =====
|
275 |
def get_phase2_molecules():
|
276 |
return {
|
@@ -312,21 +328,16 @@ def visualize_molecule_2d_3d(smiles: str, name: str):
|
|
312 |
if not mol: return f"<p>Invalid SMILES for {name}</p>", f"β Invalid SMILES for {name}"
|
313 |
|
314 |
drawer = Draw.rdMolDraw2D.MolDraw2DSVG(400, 300)
|
315 |
-
# Set dark theme colors for 2D drawing
|
316 |
drawer.drawOptions().clearBackground = False
|
317 |
drawer.drawOptions().addStereoAnnotation = True
|
318 |
drawer.drawOptions().baseFontSize = 0.8
|
319 |
drawer.drawOptions().circleAtoms = False
|
320 |
-
drawer.drawOptions().highlightColour = (1, 0.5, 0)
|
321 |
-
|
322 |
-
|
323 |
-
drawer.drawOptions().
|
324 |
-
drawer.drawOptions().symbolColour = (1, 1, 1) # White symbols
|
325 |
-
drawer.drawOptions().defaultColour = (1, 1, 1) # White default color
|
326 |
-
|
327 |
-
# Try to set annotation color (this might help with (R)/(S) labels)
|
328 |
try:
|
329 |
-
drawer.drawOptions().annotationColour = (1, 1, 1)
|
330 |
except:
|
331 |
pass
|
332 |
|
@@ -334,52 +345,13 @@ def visualize_molecule_2d_3d(smiles: str, name: str):
|
|
334 |
drawer.FinishDrawing()
|
335 |
svg_2d = drawer.GetDrawingText().replace('svg:', '')
|
336 |
|
337 |
-
# More aggressive SVG text color fixes - target all possible black text variations
|
338 |
import re
|
339 |
-
|
340 |
-
# First, comprehensive string replacements
|
341 |
svg_2d = svg_2d.replace('stroke="black"', 'stroke="white"')
|
342 |
svg_2d = svg_2d.replace('fill="black"', 'fill="white"')
|
343 |
svg_2d = svg_2d.replace('stroke="#000000"', 'stroke="#FFFFFF"')
|
344 |
svg_2d = svg_2d.replace('fill="#000000"', 'fill="#FFFFFF"')
|
345 |
-
svg_2d = svg_2d.replace('stroke="#000"', 'stroke="#FFF"')
|
346 |
-
svg_2d = svg_2d.replace('fill="#000"', 'fill="#FFF"')
|
347 |
-
svg_2d = svg_2d.replace('stroke:black', 'stroke:white')
|
348 |
-
svg_2d = svg_2d.replace('fill:black', 'fill:white')
|
349 |
-
svg_2d = svg_2d.replace('stroke:#000000', 'stroke:#FFFFFF')
|
350 |
-
svg_2d = svg_2d.replace('fill:#000000', 'fill:#FFFFFF')
|
351 |
-
svg_2d = svg_2d.replace('stroke:#000', 'stroke:#FFF')
|
352 |
-
svg_2d = svg_2d.replace('fill:#000', 'fill:#FFF')
|
353 |
-
svg_2d = svg_2d.replace('stroke="rgb(0,0,0)"', 'stroke="rgb(255,255,255)"')
|
354 |
-
svg_2d = svg_2d.replace('fill="rgb(0,0,0)"', 'fill="rgb(255,255,255)"')
|
355 |
-
svg_2d = svg_2d.replace('stroke:rgb(0,0,0)', 'stroke:rgb(255,255,255)')
|
356 |
-
svg_2d = svg_2d.replace('fill:rgb(0,0,0)', 'fill:rgb(255,255,255)')
|
357 |
-
svg_2d = svg_2d.replace('color="black"', 'color="white"')
|
358 |
-
svg_2d = svg_2d.replace('color:#000000', 'color:#FFFFFF')
|
359 |
-
svg_2d = svg_2d.replace('color:#000', 'color:#FFF')
|
360 |
-
|
361 |
-
# Aggressive regex-based fixes for all text elements
|
362 |
-
# Remove any existing fill attributes from text elements and add white fill
|
363 |
-
svg_2d = re.sub(r'<text([^>]*?)\s+fill="[^"]*"([^>]*?)>', r'<text\1\2 fill="white">', svg_2d)
|
364 |
-
svg_2d = re.sub(r'<text([^>]*?)(?<!fill="white")>', r'<text\1 fill="white">', svg_2d)
|
365 |
-
|
366 |
-
# Fix style attributes in text elements
|
367 |
-
svg_2d = re.sub(r'<text([^>]*?)style="([^"]*?)fill:\s*(?:black|#000000|#000|rgb\(0,0,0\))([^"]*?)"([^>]*?)>',
|
368 |
-
r'<text\1style="\2fill:white\3"\4>', svg_2d)
|
369 |
-
|
370 |
-
# If text elements don't have any fill specified, ensure they get white
|
371 |
svg_2d = re.sub(r'<text(?![^>]*fill=)([^>]*?)>', r'<text fill="white"\1>', svg_2d)
|
372 |
|
373 |
-
# Clean up any duplicate fill attributes
|
374 |
-
svg_2d = re.sub(r'fill="white"\s+fill="white"', 'fill="white"', svg_2d)
|
375 |
-
|
376 |
-
# Final catch-all: replace any remaining black in the entire SVG
|
377 |
-
svg_2d = re.sub(r'\bblack\b', 'white', svg_2d)
|
378 |
-
svg_2d = re.sub(r'#000000', '#FFFFFF', svg_2d)
|
379 |
-
svg_2d = re.sub(r'#000\b', '#FFF', svg_2d)
|
380 |
-
svg_2d = re.sub(r'rgb\(0,\s*0,\s*0\)', 'rgb(255,255,255)', svg_2d)
|
381 |
-
|
382 |
-
# Embed the SVG within a div with a dark background for consistency
|
383 |
svg_2d = f'<div style="background-color: #1C1C1C; padding: 10px; border-radius: 5px;">{svg_2d}</div>'
|
384 |
|
385 |
mol_3d = Chem.AddHs(mol)
|
@@ -416,7 +388,6 @@ def visualize_protein_ligand_interaction(pdb_data: str, pdb_id: str, ligand_resn
|
|
416 |
if not pdb_data: return None, "Cannot generate view: No PDB data provided."
|
417 |
try:
|
418 |
viewer = py3Dmol.view(width='100%', height=700)
|
419 |
-
# MODIFIED: Changed background color
|
420 |
viewer.setBackgroundColor('#1C1C1C')
|
421 |
viewer.addModel(pdb_data, "pdb")
|
422 |
viewer.setStyle({'cartoon': {'color': 'spectrum', 'thickness': 0.8}})
|
@@ -453,7 +424,6 @@ def calculate_comprehensive_properties(smiles_dict: dict):
|
|
453 |
'TPSA': Descriptors.TPSA(mol), 'Rotatable_Bonds': Descriptors.NumRotatableBonds(mol),
|
454 |
'Aromatic_Rings': Descriptors.NumAromaticRings(mol),
|
455 |
'Lipinski_Violations': violations,
|
456 |
-
# Fixed: Use proper colored emojis instead of text
|
457 |
'Drug_Like': 'β
Yes' if violations <= 1 else 'β No'})
|
458 |
df = pd.DataFrame(analysis).round(2)
|
459 |
log += f"β
Calculated comprehensive properties for {len(df)} compounds.\n"
|
@@ -478,7 +448,6 @@ def predict_toxicity(properties_df: pd.DataFrame):
|
|
478 |
toxicity_prob = rf_model.predict_proba(X_pred)[:, 1]
|
479 |
results_df = properties_df[['Compound']].copy()
|
480 |
results_df['Toxicity_Probability'] = np.round(toxicity_prob, 3)
|
481 |
-
# Fixed: Use proper colored emojis that will display correctly
|
482 |
results_df['Predicted_Risk'] = ["π’ LOW" if p < 0.3 else "π‘ MODERATE" if p < 0.7 else "π΄ HIGH" for p in toxicity_prob]
|
483 |
return results_df, "β
Predicted toxicity using a pre-trained simulation model.\n"
|
484 |
|
@@ -576,15 +545,21 @@ with tab1:
|
|
576 |
smiles_list = [s.strip() for s in smiles_input_p1.split('\n') if s.strip()]
|
577 |
props_df, log_props = calculate_molecular_properties(smiles_list)
|
578 |
full_log += log_props
|
579 |
-
|
|
|
|
|
|
|
580 |
full_log += log_lipinski
|
|
|
|
|
581 |
props_plot, log_plot = plot_properties_dashboard(analysis_df)
|
582 |
full_log += log_plot
|
583 |
full_log += "\n--- Phase 1 Analysis Complete ---"
|
584 |
st.session_state.log_p1 = full_log
|
585 |
|
|
|
586 |
lipinski_cols = ['Molecule', 'MW', 'LogP', 'HBD', 'HBA', 'Lipinski_Violations', 'Drug_Like']
|
587 |
-
lipinski_subset_df =
|
588 |
|
589 |
st.session_state.results_p1 = {
|
590 |
'protein_view_html': protein_view_html,
|
@@ -601,7 +576,8 @@ with tab1:
|
|
601 |
p1_tabs = st.tabs(["Protein Information", "Molecule Analysis", "Analysis Plots"])
|
602 |
with p1_tabs[0]:
|
603 |
st.subheader("Protein 3D Structure (Interactive)")
|
604 |
-
|
|
|
605 |
st.subheader("FASTA Sequence Information")
|
606 |
st.text_area("", res1.get('fasta_log', 'No data'), height=200, key="fasta_info_area")
|
607 |
with p1_tabs[1]:
|
@@ -613,6 +589,8 @@ with tab1:
|
|
613 |
st.subheader("Molecular Properties Dashboard")
|
614 |
if res1.get('props_plot'):
|
615 |
st.pyplot(res1['props_plot'])
|
|
|
|
|
616 |
|
617 |
# ===== TAB 2: LEAD GENERATION & OPTIMIZATION =====
|
618 |
with tab2:
|
@@ -668,10 +646,12 @@ with tab2:
|
|
668 |
st.dataframe(res2.get('admet_df', pd.DataFrame()), use_container_width=True)
|
669 |
with p2_tabs[1]:
|
670 |
st.subheader("Interactive 2D and 3D views of candidate molecules.")
|
671 |
-
|
|
|
672 |
with p2_tabs[2]:
|
673 |
st.subheader("Detailed view of the top candidate binding to the protein.")
|
674 |
-
|
|
|
675 |
|
676 |
|
677 |
# ===== TAB 3: PRECLINICAL DEVELOPMENT =====
|
@@ -717,7 +697,8 @@ with tab3:
|
|
717 |
st.dataframe(res3.get('tox_df', pd.DataFrame()), use_container_width=True)
|
718 |
with p3_tabs[1]:
|
719 |
st.subheader("Interactive 3D gallery of the compounds under analysis.")
|
720 |
-
|
|
|
721 |
|
722 |
|
723 |
# ===== TAB 4: POST-MARKET SURVEILLANCE =====
|
@@ -756,7 +737,8 @@ with tab4:
|
|
756 |
p4_tabs = st.tabs(["Pharmacovigilance Analysis", "Regulatory & Ethical Frameworks"])
|
757 |
with p4_tabs[0]:
|
758 |
st.subheader("Simulated Adverse Event Analysis")
|
759 |
-
|
|
|
760 |
st.dataframe(res4['rwd_df'], use_container_width=True)
|
761 |
|
762 |
with p4_tabs[1]:
|
@@ -767,3 +749,4 @@ with tab4:
|
|
767 |
with col2:
|
768 |
st.subheader("Ethical Framework for AI in Healthcare")
|
769 |
st.dataframe(res4.get('eth_df', pd.DataFrame()), use_container_width=True)
|
|
|
|
43 |
<style>
|
44 |
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap');
|
45 |
|
46 |
+
html, body, [class*="st-"] {
|
47 |
font-family: 'Roboto', sans-serif;
|
48 |
}
|
49 |
|
|
|
53 |
}
|
54 |
|
55 |
/* Tab styles */
|
56 |
+
.stTabs [data-baseweb="tab-list"] {
|
57 |
gap: 24px;
|
58 |
}
|
59 |
|
60 |
+
.stTabs [data-baseweb="tab"] {
|
61 |
height: 50px;
|
62 |
white-space: pre-wrap;
|
63 |
background: none;
|
|
|
67 |
color: #AAA;
|
68 |
}
|
69 |
|
70 |
+
.stTabs [data-baseweb="tab"]:hover {
|
71 |
background: #222;
|
72 |
color: #FFF;
|
73 |
}
|
74 |
|
75 |
+
.stTabs [aria-selected="true"] {
|
76 |
border-bottom: 2px solid #00A0FF; /* Highlight color for active tab */
|
77 |
color: #FFF;
|
78 |
}
|
|
|
154 |
return None, "Cannot generate 3D view: No PDB data provided."
|
155 |
try:
|
156 |
viewer = py3Dmol.view(width='100%', height=600)
|
|
|
157 |
viewer.setBackgroundColor('#1C1C1C')
|
158 |
viewer.addModel(pdb_data, "pdb")
|
159 |
viewer.setStyle({'cartoon': {'color': 'spectrum', 'thickness': 0.8}})
|
|
|
205 |
|
206 |
def assess_drug_likeness(df: pd.DataFrame):
|
207 |
"""
|
208 |
+
Assesses drug-likeness based on Lipinski's Rule of Five.
|
209 |
+
This version returns a boolean for plotting and a formatted string for display.
|
210 |
"""
|
211 |
if df.empty:
|
212 |
+
return pd.DataFrame(), pd.DataFrame(), "Cannot assess drug-likeness: No properties data."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
213 |
|
214 |
+
# Create a copy for analysis to avoid modifying the original dataframe
|
215 |
+
analysis_df = df.copy()
|
216 |
+
analysis_df['MW_OK'] = analysis_df['MW'] <= 500
|
217 |
+
analysis_df['LogP_OK'] = analysis_df['LogP'] <= 5
|
218 |
+
analysis_df['HBD_OK'] = analysis_df['HBD'] <= 5
|
219 |
+
analysis_df['HBA_OK'] = analysis_df['HBA'] <= 10
|
220 |
+
analysis_df['Lipinski_Violations'] = (~analysis_df[['MW_OK', 'LogP_OK', 'HBD_OK', 'HBA_OK']]).sum(axis=1)
|
221 |
+
|
222 |
+
# This boolean column is for the plotting function
|
223 |
+
analysis_df['Drug_Like'] = analysis_df['Lipinski_Violations'] <= 1
|
224 |
+
|
225 |
+
# Create a separate DataFrame for display purposes with emojis
|
226 |
+
display_df = df.copy()
|
227 |
+
display_df['Lipinski_Violations'] = analysis_df['Lipinski_Violations']
|
228 |
+
display_df['Drug_Like'] = analysis_df['Drug_Like'].apply(lambda x: 'β
Yes' if x else 'β No')
|
229 |
+
|
230 |
+
log = "β
Assessed drug-likeness using Lipinski's Rule of Five.\n"
|
231 |
+
|
232 |
+
# Return both the analysis_df (for plotting) and display_df (for tables)
|
233 |
+
return analysis_df, display_df, log
|
234 |
+
|
235 |
+
|
236 |
def plot_properties_dashboard(df: pd.DataFrame):
|
237 |
"""
|
238 |
+
Creates a 2x2 dashboard of molecular property visualizations.
|
239 |
+
This version expects a boolean 'Drug_Like' column.
|
240 |
"""
|
241 |
+
if df.empty or 'Drug_Like' not in df.columns:
|
242 |
+
return None, "Cannot plot: No analysis data or 'Drug_Like' column missing."
|
243 |
|
244 |
+
# Ensure 'Drug_Like' is boolean for correct mapping
|
245 |
+
if df['Drug_Like'].dtype != bool:
|
246 |
+
return None, f"Cannot plot: 'Drug_Like' column must be boolean, but it is {df['Drug_Like'].dtype}."
|
247 |
+
|
248 |
plt.style.use('dark_background')
|
249 |
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
|
250 |
fig.suptitle("Molecular Properties Analysis", fontsize=16)
|
|
|
254 |
for ax in ax_row:
|
255 |
ax.set_facecolor('none')
|
256 |
|
257 |
+
# Color mapping now correctly uses boolean values
|
258 |
+
color_map = {True: 'green', False: 'red'}
|
259 |
+
|
260 |
+
axes[0,0].scatter(df['MW'], df['LogP'], c=df['Drug_Like'].map(color_map), s=80, alpha=0.7)
|
261 |
axes[0,0].set_title('Molecular Weight vs LogP')
|
262 |
+
axes[0,0].set_xlabel('Molecular Weight (Da)')
|
263 |
+
axes[0,0].set_ylabel('LogP')
|
264 |
+
axes[0,0].axvline(500, color='r', linestyle='--', alpha=0.6, label='MW < 500')
|
265 |
+
axes[0,0].axhline(5, color='r', linestyle='--', alpha=0.6, label='LogP < 5')
|
266 |
axes[0,0].legend()
|
267 |
|
268 |
+
axes[0,1].scatter(df['HBD'], df['HBA'], c=df['Drug_Like'].map(color_map), s=80, alpha=0.7)
|
|
|
269 |
axes[0,1].set_title('Hydrogen Bonding Properties')
|
270 |
axes[0,1].set_xlabel('Hydrogen Bond Donors')
|
271 |
axes[0,1].set_ylabel('Hydrogen Bond Acceptors')
|
|
|
273 |
axes[0,1].axhline(10, color='r', linestyle='--', alpha=0.6, label='HBA < 10')
|
274 |
axes[0,1].legend()
|
275 |
|
276 |
+
axes[1,0].scatter(df['TPSA'], df['RotBonds'], c=df['Drug_Like'].map(color_map), s=80, alpha=0.7)
|
277 |
+
axes[1,0].set_title('TPSA vs Flexibility')
|
278 |
+
axes[1,0].set_xlabel('Topological Polar Surface Area (Γ
Β²)')
|
279 |
+
axes[1,0].set_ylabel('Rotatable Bonds')
|
|
|
280 |
|
281 |
drug_like_counts = df['Drug_Like'].value_counts()
|
282 |
+
labels = ['Drug-like' if i else 'Non-drug-like' for i in drug_like_counts.index]
|
283 |
+
colors = ['green' if i else 'red' for i in drug_like_counts.index]
|
|
|
284 |
axes[1,1].pie(drug_like_counts.values, labels=labels, colors=colors, autopct='%1.1f%%', startangle=90)
|
285 |
axes[1,1].set_title('Drug-likeness Distribution')
|
286 |
|
287 |
plt.tight_layout(rect=[0, 0, 1, 0.96])
|
288 |
return fig, "β
Generated properties dashboard."
|
289 |
+
|
290 |
# ===== Phase 2 Functions =====
|
291 |
def get_phase2_molecules():
|
292 |
return {
|
|
|
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 |
+
drawer.drawOptions().backgroundColour = (0.11, 0.11, 0.11)
|
337 |
+
drawer.drawOptions().symbolColour = (1, 1, 1)
|
338 |
+
drawer.drawOptions().defaultColour = (1, 1, 1)
|
|
|
|
|
|
|
|
|
339 |
try:
|
340 |
+
drawer.drawOptions().annotationColour = (1, 1, 1)
|
341 |
except:
|
342 |
pass
|
343 |
|
|
|
345 |
drawer.FinishDrawing()
|
346 |
svg_2d = drawer.GetDrawingText().replace('svg:', '')
|
347 |
|
|
|
348 |
import re
|
|
|
|
|
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)
|
|
|
388 |
if not pdb_data: return None, "Cannot generate view: No PDB data provided."
|
389 |
try:
|
390 |
viewer = py3Dmol.view(width='100%', height=700)
|
|
|
391 |
viewer.setBackgroundColor('#1C1C1C')
|
392 |
viewer.addModel(pdb_data, "pdb")
|
393 |
viewer.setStyle({'cartoon': {'color': 'spectrum', 'thickness': 0.8}})
|
|
|
424 |
'TPSA': Descriptors.TPSA(mol), 'Rotatable_Bonds': Descriptors.NumRotatableBonds(mol),
|
425 |
'Aromatic_Rings': Descriptors.NumAromaticRings(mol),
|
426 |
'Lipinski_Violations': violations,
|
|
|
427 |
'Drug_Like': 'β
Yes' if violations <= 1 else 'β No'})
|
428 |
df = pd.DataFrame(analysis).round(2)
|
429 |
log += f"β
Calculated comprehensive properties for {len(df)} compounds.\n"
|
|
|
448 |
toxicity_prob = rf_model.predict_proba(X_pred)[:, 1]
|
449 |
results_df = properties_df[['Compound']].copy()
|
450 |
results_df['Toxicity_Probability'] = np.round(toxicity_prob, 3)
|
|
|
451 |
results_df['Predicted_Risk'] = ["π’ LOW" if p < 0.3 else "π‘ MODERATE" if p < 0.7 else "π΄ HIGH" for p in toxicity_prob]
|
452 |
return results_df, "β
Predicted toxicity using a pre-trained simulation model.\n"
|
453 |
|
|
|
545 |
smiles_list = [s.strip() for s in smiles_input_p1.split('\n') if s.strip()]
|
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 |
|
564 |
st.session_state.results_p1 = {
|
565 |
'protein_view_html': protein_view_html,
|
|
|
576 |
p1_tabs = st.tabs(["Protein Information", "Molecule Analysis", "Analysis Plots"])
|
577 |
with p1_tabs[0]:
|
578 |
st.subheader("Protein 3D Structure (Interactive)")
|
579 |
+
if res1.get('protein_view_html'):
|
580 |
+
st.components.v1.html(res1['protein_view_html'], height=600, scrolling=False)
|
581 |
st.subheader("FASTA Sequence Information")
|
582 |
st.text_area("", res1.get('fasta_log', 'No data'), height=200, key="fasta_info_area")
|
583 |
with p1_tabs[1]:
|
|
|
589 |
st.subheader("Molecular Properties Dashboard")
|
590 |
if res1.get('props_plot'):
|
591 |
st.pyplot(res1['props_plot'])
|
592 |
+
else:
|
593 |
+
st.warning("Could not generate plots. Please check the logs for more details.")
|
594 |
|
595 |
# ===== TAB 2: LEAD GENERATION & OPTIMIZATION =====
|
596 |
with tab2:
|
|
|
646 |
st.dataframe(res2.get('admet_df', pd.DataFrame()), use_container_width=True)
|
647 |
with p2_tabs[1]:
|
648 |
st.subheader("Interactive 2D and 3D views of candidate molecules.")
|
649 |
+
if res2.get('combined_viz_html'):
|
650 |
+
st.components.v1.html(res2.get('combined_viz_html'), height=700, scrolling=True)
|
651 |
with p2_tabs[2]:
|
652 |
st.subheader("Detailed view of the top candidate binding to the protein.")
|
653 |
+
if res2.get('interaction_html'):
|
654 |
+
st.components.v1.html(res2.get('interaction_html'), height=700, scrolling=False)
|
655 |
|
656 |
|
657 |
# ===== TAB 3: PRECLINICAL DEVELOPMENT =====
|
|
|
697 |
st.dataframe(res3.get('tox_df', pd.DataFrame()), use_container_width=True)
|
698 |
with p3_tabs[1]:
|
699 |
st.subheader("Interactive 3D gallery of the compounds under analysis.")
|
700 |
+
if res3.get('combined_viz_html'):
|
701 |
+
st.components.v1.html(res3.get('combined_viz_html'), height=1000, scrolling=True)
|
702 |
|
703 |
|
704 |
# ===== TAB 4: POST-MARKET SURVEILLANCE =====
|
|
|
737 |
p4_tabs = st.tabs(["Pharmacovigilance Analysis", "Regulatory & Ethical Frameworks"])
|
738 |
with p4_tabs[0]:
|
739 |
st.subheader("Simulated Adverse Event Analysis")
|
740 |
+
if res4.get('plot_bar'):
|
741 |
+
st.pyplot(res4['plot_bar'])
|
742 |
st.dataframe(res4['rwd_df'], use_container_width=True)
|
743 |
|
744 |
with p4_tabs[1]:
|
|
|
749 |
with col2:
|
750 |
st.subheader("Ethical Framework for AI in Healthcare")
|
751 |
st.dataframe(res4.get('eth_df', pd.DataFrame()), use_container_width=True)
|
752 |
+
|