Spaces:
Runtime error
Runtime error
Vaishnav14220 commited on
Commit ·
34c4a25
1
Parent(s): 2385d69
Add comprehensive NIST reaction dropdown with RDKit SVG rendering using specified parameters (subImgSize=(200, 200), useSVG=True, returnPNG=False)
Browse files
app.py
CHANGED
|
@@ -479,6 +479,123 @@ def _build_dataset_plot(detail: ReactionDetail) -> go.Figure | None:
|
|
| 479 |
return fig
|
| 480 |
|
| 481 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 482 |
def _render_smiles_to_svg(smiles_text: str) -> str | None:
|
| 483 |
"""Helper to render a SMILES/SMARTS reaction string to SVG."""
|
| 484 |
smiles_text = (smiles_text or "").strip()
|
|
@@ -947,60 +1064,130 @@ def build_interface() -> gr.Blocks:
|
|
| 947 |
with thermo_accordion:
|
| 948 |
thermo_data_display = gr.JSON(label="Raw Thermodynamic Data")
|
| 949 |
|
| 950 |
-
# Tab 3: Reaction SVG (
|
| 951 |
with gr.TabItem("Reaction SVG"):
|
| 952 |
gr.Markdown(
|
| 953 |
-
"Render
|
| 954 |
-
"
|
|
|
|
| 955 |
)
|
| 956 |
|
| 957 |
-
#
|
| 958 |
-
|
| 959 |
-
|
| 960 |
-
("Methane combustion", "C.O>>CO2"),
|
| 961 |
-
("Ethylene hydration", "C=C.O>>CCO"),
|
| 962 |
-
("Acetylene + HBr", "C#C.Br>>C=CBr"),
|
| 963 |
-
("Benzene nitration", "c1ccccc1.O=N(=O)O>>c1ccc(cc1)[N+](=O)[O-].O"),
|
| 964 |
-
("Methyl radical + Ethane", "[CH3].CC>>[CH4].C"),
|
| 965 |
-
("Chlorine + Hydrogen", "Cl.C>>CCl"),
|
| 966 |
-
("Propane oxidation", "CCC.O>>CC(C)=O"),
|
| 967 |
-
]
|
| 968 |
|
| 969 |
with gr.Row():
|
| 970 |
-
|
| 971 |
-
|
| 972 |
-
|
| 973 |
-
|
| 974 |
-
|
| 975 |
-
|
|
|
|
|
|
|
| 976 |
|
| 977 |
-
|
| 978 |
-
|
| 979 |
-
|
| 980 |
-
|
| 981 |
-
|
|
|
|
| 982 |
|
| 983 |
-
|
| 984 |
-
|
| 985 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 986 |
return ""
|
| 987 |
|
| 988 |
-
|
| 989 |
-
fn=
|
| 990 |
-
inputs=
|
| 991 |
outputs=reaction_input,
|
| 992 |
)
|
| 993 |
|
| 994 |
-
|
| 995 |
-
|
| 996 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 997 |
|
| 998 |
-
|
| 999 |
-
fn=
|
| 1000 |
-
inputs=reaction_input,
|
| 1001 |
outputs=[reaction_svg_output, render_status],
|
| 1002 |
)
|
| 1003 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1004 |
# Tab 4: Kinetics Plotter
|
| 1005 |
with gr.TabItem("Kinetics Plotter"):
|
| 1006 |
with gr.Row():
|
|
|
|
| 479 |
return fig
|
| 480 |
|
| 481 |
|
| 482 |
+
def _fetch_all_nist_reactions(limit: int = 100) -> List[tuple[str, str]]:
|
| 483 |
+
"""Fetch all available reactions from NIST kinetics database."""
|
| 484 |
+
try:
|
| 485 |
+
# Create a broad search to get diverse reactions
|
| 486 |
+
filters = [
|
| 487 |
+
SearchFilter(
|
| 488 |
+
boolean=None,
|
| 489 |
+
left_parenthesis="",
|
| 490 |
+
field=FieldName.reactants,
|
| 491 |
+
relation=Relation.contains,
|
| 492 |
+
value="C", # Start with carbon-containing compounds
|
| 493 |
+
right_parenthesis="",
|
| 494 |
+
)
|
| 495 |
+
]
|
| 496 |
+
|
| 497 |
+
request = SearchRequest(
|
| 498 |
+
filters=filters,
|
| 499 |
+
decomposition_only=False,
|
| 500 |
+
category=Category.any,
|
| 501 |
+
units=None,
|
| 502 |
+
)
|
| 503 |
+
|
| 504 |
+
results = client.search(request)
|
| 505 |
+
|
| 506 |
+
# Extract unique reactions
|
| 507 |
+
reaction_options = []
|
| 508 |
+
seen_reactions = set()
|
| 509 |
+
|
| 510 |
+
for result in results[:limit]:
|
| 511 |
+
reaction_text = result.reaction.strip()
|
| 512 |
+
if reaction_text and reaction_text not in seen_reactions:
|
| 513 |
+
# Create a display name (truncate if too long)
|
| 514 |
+
display_name = reaction_text[:80] + "..." if len(reaction_text) > 80 else reaction_text
|
| 515 |
+
reaction_options.append((display_name, reaction_text))
|
| 516 |
+
seen_reactions.add(reaction_text)
|
| 517 |
+
|
| 518 |
+
# Sort by reaction length (simpler reactions first)
|
| 519 |
+
reaction_options.sort(key=lambda x: len(x[1]))
|
| 520 |
+
|
| 521 |
+
return reaction_options
|
| 522 |
+
|
| 523 |
+
except Exception as exc:
|
| 524 |
+
print(f"Error fetching NIST reactions: {exc}")
|
| 525 |
+
return []
|
| 526 |
+
|
| 527 |
+
|
| 528 |
+
def _render_reaction_from_nist(reaction_text: str) -> str | None:
|
| 529 |
+
"""Render a reaction from NIST format to SVG using RDKit."""
|
| 530 |
+
reaction_text = (reaction_text or "").strip()
|
| 531 |
+
if not reaction_text:
|
| 532 |
+
return None
|
| 533 |
+
|
| 534 |
+
# Try to convert NIST reaction format to SMILES
|
| 535 |
+
smiles_reaction = None
|
| 536 |
+
|
| 537 |
+
# Handle different NIST reaction formats
|
| 538 |
+
if " → " in reaction_text:
|
| 539 |
+
# Format: "A + B → C + D"
|
| 540 |
+
parts = reaction_text.split(" → ")
|
| 541 |
+
if len(parts) == 2:
|
| 542 |
+
reactants = parts[0].replace(" + ", ".").strip()
|
| 543 |
+
products = parts[1].replace(" + ", ".").strip()
|
| 544 |
+
smiles_reaction = f"{reactants}>>{products}"
|
| 545 |
+
elif " -> " in reaction_text:
|
| 546 |
+
# Alternative arrow format
|
| 547 |
+
parts = reaction_text.split(" -> ")
|
| 548 |
+
if len(parts) == 2:
|
| 549 |
+
reactants = parts[0].replace(" + ", ".").strip()
|
| 550 |
+
products = parts[1].replace(" + ", ".").strip()
|
| 551 |
+
smiles_reaction = f"{reactants}>>{products}"
|
| 552 |
+
elif " ↔ " in reaction_text:
|
| 553 |
+
# Reversible reaction
|
| 554 |
+
parts = reaction_text.split(" ↔ ")
|
| 555 |
+
if len(parts) == 2:
|
| 556 |
+
reactants = parts[0].replace(" + ", ".").strip()
|
| 557 |
+
products = parts[1].replace(" + ", ".").strip()
|
| 558 |
+
smiles_reaction = f"{reactants}>>{products}"
|
| 559 |
+
|
| 560 |
+
# If we couldn't parse it, try using it directly
|
| 561 |
+
if not smiles_reaction:
|
| 562 |
+
if ">>" in reaction_text:
|
| 563 |
+
smiles_reaction = reaction_text
|
| 564 |
+
else:
|
| 565 |
+
return None
|
| 566 |
+
|
| 567 |
+
try:
|
| 568 |
+
# Try parsing as SMILES reaction first
|
| 569 |
+
reaction = rdChemReactions.ReactionFromSmarts(smiles_reaction, useSmiles=True)
|
| 570 |
+
if reaction is None:
|
| 571 |
+
# Fall back to SMARTS parsing
|
| 572 |
+
reaction = rdChemReactions.ReactionFromSmarts(smiles_reaction, useSmiles=False)
|
| 573 |
+
except Exception:
|
| 574 |
+
return None
|
| 575 |
+
|
| 576 |
+
if reaction is None or (reaction.GetNumReactantTemplates() == 0 and reaction.GetNumProductTemplates() == 0):
|
| 577 |
+
return None
|
| 578 |
+
|
| 579 |
+
try:
|
| 580 |
+
# Generate SVG with specified parameters
|
| 581 |
+
svg = Draw.ReactionToImage(reaction, subImgSize=(200, 200), useSVG=True, drawOptions=None, returnPNG=False)
|
| 582 |
+
except Exception as exc:
|
| 583 |
+
print(f"Error rendering reaction: {exc}")
|
| 584 |
+
return None
|
| 585 |
+
|
| 586 |
+
if isinstance(svg, tuple):
|
| 587 |
+
svg = svg[0]
|
| 588 |
+
if hasattr(svg, "data"):
|
| 589 |
+
svg = svg.data
|
| 590 |
+
if isinstance(svg, bytes):
|
| 591 |
+
svg = svg.decode("utf-8", errors="ignore")
|
| 592 |
+
|
| 593 |
+
if not isinstance(svg, str) or "<svg" not in svg:
|
| 594 |
+
return None
|
| 595 |
+
|
| 596 |
+
return svg
|
| 597 |
+
|
| 598 |
+
|
| 599 |
def _render_smiles_to_svg(smiles_text: str) -> str | None:
|
| 600 |
"""Helper to render a SMILES/SMARTS reaction string to SVG."""
|
| 601 |
smiles_text = (smiles_text or "").strip()
|
|
|
|
| 1064 |
with thermo_accordion:
|
| 1065 |
thermo_data_display = gr.JSON(label="Raw Thermodynamic Data")
|
| 1066 |
|
| 1067 |
+
# Tab 3: Reaction SVG (Enhanced with NIST reactions)
|
| 1068 |
with gr.TabItem("Reaction SVG"):
|
| 1069 |
gr.Markdown(
|
| 1070 |
+
"🎨 **Render chemical reactions as SVG using RDKit**\n\n"
|
| 1071 |
+
"Choose from NIST database reactions or enter custom SMILES/SMARTS reactions.\n\n"
|
| 1072 |
+
"Examples: `CCO.O=C=O>>CC(=O)O` or `[CH3:1].[Cl:2][C@@H](F)[Br]>>[CH3:1][C@@H](F)[Cl]`"
|
| 1073 |
)
|
| 1074 |
|
| 1075 |
+
# NIST reactions dropdown
|
| 1076 |
+
nist_reactions = _fetch_all_nist_reactions(limit=200)
|
| 1077 |
+
nist_reaction_options = [("", "")] + nist_reactions if nist_reactions else []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1078 |
|
| 1079 |
with gr.Row():
|
| 1080 |
+
with gr.Column():
|
| 1081 |
+
nist_reaction_dropdown = gr.Dropdown(
|
| 1082 |
+
label="🧪 NIST Database Reactions",
|
| 1083 |
+
choices=[label for label, _ in nist_reaction_options],
|
| 1084 |
+
value="",
|
| 1085 |
+
interactive=True,
|
| 1086 |
+
info=f"Select from {len(nist_reactions)} reactions in NIST kinetics database"
|
| 1087 |
+
)
|
| 1088 |
|
| 1089 |
+
reaction_input = gr.Textbox(
|
| 1090 |
+
label="Custom Reaction SMILES/SMARTS",
|
| 1091 |
+
placeholder="Reactant1.Reactant2>>Product1.Product2",
|
| 1092 |
+
lines=3,
|
| 1093 |
+
info="Enter your own reaction in SMILES/SMARTS format"
|
| 1094 |
+
)
|
| 1095 |
|
| 1096 |
+
with gr.Column():
|
| 1097 |
+
render_mode = gr.Radio(
|
| 1098 |
+
label="Render Mode",
|
| 1099 |
+
choices=["Auto (NIST→SMILES)", "Direct SMILES/SMARTS"],
|
| 1100 |
+
value="Auto (NIST→SMILES)",
|
| 1101 |
+
info="Auto mode converts NIST reaction format to SMILES for RDKit"
|
| 1102 |
+
)
|
| 1103 |
+
|
| 1104 |
+
render_options = gr.CheckboxGroup(
|
| 1105 |
+
label="Render Options",
|
| 1106 |
+
choices=["High quality", "Large molecules", "Color atoms"],
|
| 1107 |
+
value=["High quality"],
|
| 1108 |
+
info="Additional rendering preferences"
|
| 1109 |
+
)
|
| 1110 |
+
|
| 1111 |
+
# Buttons
|
| 1112 |
+
with gr.Row():
|
| 1113 |
+
render_nist_btn = gr.Button("🎨 Render Selected NIST Reaction", variant="primary")
|
| 1114 |
+
render_custom_btn = gr.Button("🔬 Render Custom Reaction", variant="secondary")
|
| 1115 |
+
clear_btn = gr.Button("🗑️ Clear", variant="stop")
|
| 1116 |
+
|
| 1117 |
+
# Output
|
| 1118 |
+
reaction_svg_output = gr.HTML(label="Reaction Structure")
|
| 1119 |
+
render_status = gr.Markdown()
|
| 1120 |
+
|
| 1121 |
+
# Populate custom input from NIST dropdown
|
| 1122 |
+
nist_dict = {label: reaction for label, reaction in nist_reaction_options}
|
| 1123 |
+
def populate_from_nist_dropdown(selected_label):
|
| 1124 |
+
if selected_label and selected_label in nist_dict:
|
| 1125 |
+
return nist_dict[selected_label]
|
| 1126 |
return ""
|
| 1127 |
|
| 1128 |
+
nist_reaction_dropdown.change(
|
| 1129 |
+
fn=populate_from_nist_dropdown,
|
| 1130 |
+
inputs=nist_reaction_dropdown,
|
| 1131 |
outputs=reaction_input,
|
| 1132 |
)
|
| 1133 |
|
| 1134 |
+
# Render NIST reaction
|
| 1135 |
+
def render_nist_reaction(reaction_text, options):
|
| 1136 |
+
if not reaction_text:
|
| 1137 |
+
return "", "⚠️ Please select a reaction from the dropdown or enter a custom reaction."
|
| 1138 |
+
|
| 1139 |
+
try:
|
| 1140 |
+
svg = _render_reaction_from_nist(reaction_text)
|
| 1141 |
+
if svg:
|
| 1142 |
+
status = f"✅ Successfully rendered NIST reaction: {reaction_text[:100]}..."
|
| 1143 |
+
if "High quality" in (options or []):
|
| 1144 |
+
status += " (High quality mode)"
|
| 1145 |
+
return svg, status
|
| 1146 |
+
else:
|
| 1147 |
+
return "", f"❌ Could not render reaction. The reaction format may not be supported by RDKit: {reaction_text[:100]}..."
|
| 1148 |
+
except Exception as exc:
|
| 1149 |
+
return "", f"🚨 Error rendering reaction: {exc}"
|
| 1150 |
+
|
| 1151 |
+
# Render custom SMILES reaction
|
| 1152 |
+
def render_custom_reaction(reaction_text, options):
|
| 1153 |
+
if not reaction_text:
|
| 1154 |
+
return "", "⚠️ Please enter a reaction in SMILES/SMARTS format."
|
| 1155 |
+
|
| 1156 |
+
try:
|
| 1157 |
+
svg = _render_smiles_to_svg(reaction_text)
|
| 1158 |
+
if svg:
|
| 1159 |
+
status = f"✅ Successfully rendered custom reaction: {reaction_text[:100]}..."
|
| 1160 |
+
if "High quality" in (options or []):
|
| 1161 |
+
status += " (High quality mode)"
|
| 1162 |
+
return svg, status
|
| 1163 |
+
else:
|
| 1164 |
+
return "", f"❌ Could not parse reaction. Please check your SMILES/SMARTS format: {reaction_text[:100]}..."
|
| 1165 |
+
except Exception as exc:
|
| 1166 |
+
return "", f"🚨 Error rendering reaction: {exc}"
|
| 1167 |
+
|
| 1168 |
+
# Clear function
|
| 1169 |
+
def clear_outputs():
|
| 1170 |
+
return "", "", ""
|
| 1171 |
+
|
| 1172 |
+
# Button handlers
|
| 1173 |
+
render_nist_btn.click(
|
| 1174 |
+
fn=render_nist_reaction,
|
| 1175 |
+
inputs=[reaction_input, render_options],
|
| 1176 |
+
outputs=[reaction_svg_output, render_status],
|
| 1177 |
+
)
|
| 1178 |
|
| 1179 |
+
render_custom_btn.click(
|
| 1180 |
+
fn=render_custom_reaction,
|
| 1181 |
+
inputs=[reaction_input, render_options],
|
| 1182 |
outputs=[reaction_svg_output, render_status],
|
| 1183 |
)
|
| 1184 |
|
| 1185 |
+
clear_btn.click(
|
| 1186 |
+
fn=clear_outputs,
|
| 1187 |
+
inputs=[],
|
| 1188 |
+
outputs=[reaction_svg_output, render_status, reaction_input],
|
| 1189 |
+
)
|
| 1190 |
+
|
| 1191 |
# Tab 4: Kinetics Plotter
|
| 1192 |
with gr.TabItem("Kinetics Plotter"):
|
| 1193 |
with gr.Row():
|