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
Files changed (1) hide show
  1. app.py +224 -37
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 (Original functionality)
951
  with gr.TabItem("Reaction SVG"):
952
  gr.Markdown(
953
- "Render an RDKit reaction sketch from reaction SMILES/SMARTS. "
954
- "Example: `CCO.O=C=O>>CC(=O)O` or `[CH3:1].[Cl:2][C@@H](F)[Br]>>[CH3:1][C@@H](F)[Cl]`."
 
955
  )
956
 
957
- # Common reaction examples
958
- common_reactions = [
959
- ("Ethanol esterification", "CCO.CC(=O)O>>CC(=O)OCC.O"),
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
- reaction_preset = gr.Dropdown(
971
- label="Common Reactions",
972
- choices=[label for label, _ in common_reactions],
973
- interactive=True
974
- )
975
- preset_dict = {label: smiles for label, smiles in common_reactions}
 
 
976
 
977
- reaction_input = gr.Textbox(
978
- label="Reaction SMILES/SMARTS",
979
- placeholder="Reactant1.Reactant2>>Product1.Product2",
980
- lines=2,
981
- )
 
982
 
983
- def populate_from_preset(preset_name):
984
- if preset_name and preset_name in preset_dict:
985
- return preset_dict[preset_name]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
986
  return ""
987
 
988
- reaction_preset.change(
989
- fn=populate_from_preset,
990
- inputs=reaction_preset,
991
  outputs=reaction_input,
992
  )
993
 
994
- render_button = gr.Button("Render Reaction", variant="secondary")
995
- reaction_svg_output = gr.HTML()
996
- render_status = gr.Markdown()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
997
 
998
- render_button.click(
999
- fn=render_reaction_svg,
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():