173 lines
6.0 KiB
Python
173 lines
6.0 KiB
Python
import molfoundry as mf
|
|
import streamlit as st
|
|
import pandas as pd
|
|
import requests
|
|
from rdkit import Chem
|
|
from rdkit.Chem import Draw
|
|
|
|
import json
|
|
from pathlib import Path
|
|
import re
|
|
|
|
|
|
def parse_rules():
|
|
rules = {}
|
|
folder = Path("../2026-OverlayGraphs/learning_exports/aams/ogs")
|
|
MAX = 30
|
|
count = 0
|
|
for p in folder.glob("*.gml"):
|
|
if count>=MAX: break
|
|
count+=1 # only .gml files in this folder
|
|
text = p.read_text()
|
|
rule_gml = text
|
|
m = re.search(r"https?://[^\s<>\"')\]]+", text)
|
|
url = m.group(0).rstrip(".,;:!?") if m else None
|
|
m = re.search(r"/entry/(\d+)", url)
|
|
mcsa_id = m.group(1) if m else None
|
|
m = re.search(r"ruleID \"(.*)\"", text)
|
|
rule_id = m.group(1) if m else None
|
|
rules[int(mcsa_id)] = {'mcsa_id': mcsa_id, 'rule_id': rule_id, 'url': url, 'gml_string': rule_gml}
|
|
return rules
|
|
|
|
def parse_smiles():
|
|
smiles = []
|
|
mols = []
|
|
with open("./output/mcsa-smiles.json", 'r') as f:
|
|
data = json.load(f)
|
|
|
|
for entry in data:
|
|
#g = mf.smiles(data[entry]['smiles'], entry, add=False)
|
|
smiles.append(data[entry]['smiles'])
|
|
mols.append(Chem.MolFromSmiles(data[entry]['smiles']))
|
|
return (smiles, mols)
|
|
|
|
def get_compounds_mcsa(mcsa_id):
|
|
BASE = "https://www.ebi.ac.uk/thornton-srv/m-csa/api"
|
|
r = requests.get(f"{BASE}/entries/{mcsa_id}/", params={"format": "json"}, timeout=60)
|
|
r.raise_for_status()
|
|
entry = r.json()
|
|
reaction = entry.get("reaction")
|
|
compounds = reaction["compounds"]
|
|
|
|
reactants=[]
|
|
products = []
|
|
for comp in compounds:
|
|
cnt = comp['count']
|
|
comp_type = comp['type']
|
|
name = comp['name']
|
|
mol_url = comp['mol_file']
|
|
if "reactant" in comp_type:
|
|
reactants.append(comp)
|
|
else:
|
|
products.append(comp)
|
|
|
|
return (reactants, products)
|
|
|
|
def draw_molfile(url):
|
|
url = f"https://{url}"
|
|
r = requests.get(url, timeout=30)
|
|
r.raise_for_status()
|
|
molblock = r.text
|
|
|
|
mol = Chem.MolFromMolBlock(molblock)
|
|
if mol is None:
|
|
raise ValueError("RDKit could not parse the MOL file")
|
|
|
|
return Draw.MolToImage(mol, size=(200, 150))
|
|
|
|
def main():
|
|
rules = parse_rules()
|
|
mcsa_ids = sorted([int(rules[r]['mcsa_id']) for r in rules])
|
|
|
|
st.set_page_config(layout="wide")
|
|
|
|
df = pd.DataFrame(mcsa_ids, columns=["mcsa_id"])
|
|
|
|
if "selected" not in st.session_state:
|
|
st.session_state.selected = None
|
|
|
|
if "op_selected" not in st.session_state:
|
|
st.session_state.op_selected = None
|
|
|
|
def select(mcsa_id):
|
|
st.session_state.selected = mcsa_id
|
|
|
|
def back():
|
|
st.session_state.selected = None
|
|
st.session_state.op_selected = None
|
|
|
|
|
|
# ---------- full-screen view ----------
|
|
if st.session_state.selected is not None:
|
|
sel = st.session_state.selected
|
|
st.button("← Back to grid", on_click=back)
|
|
st.subheader("Reaction Comparison")
|
|
st.write(f"**{rules[sel]['rule_id']}**")
|
|
st.markdown(f"[{rules[sel]['url']}]({rules[sel]['url']})")
|
|
(reactants, products) = get_compounds_mcsa(sel)
|
|
|
|
# split into reactants and products
|
|
left, right = st.columns(2)
|
|
with left:
|
|
st.subheader("Reactants")
|
|
for r in reactants:
|
|
box = st.container()
|
|
with box:
|
|
st.image(draw_molfile(r['mol_file']))
|
|
st.write(f"**Count:** {r['count']}, **Name:** {r['name']}, **Chebi:** {r['chebi_id']}")
|
|
with right:
|
|
st.subheader("Products")
|
|
for p in products:
|
|
box = st.container()
|
|
with box:
|
|
st.image(draw_molfile(p['mol_file']))
|
|
st.write(f"**Count:** {p['count']}, **Name:** {p['name']}, **Chebi:** {p['chebi_id']}")
|
|
|
|
mfbox = st.container()
|
|
with mfbox:
|
|
st.subheader("OG products")
|
|
left, right = st.columns([1,10])
|
|
with left:
|
|
options = [Path(p).name for p in Path(f"output/mf_products/{sel}").glob("*")]
|
|
for op in options:
|
|
if left.button(op, key=f"btn_{op}", use_container_width=True):
|
|
st.session_state.op_selected = op
|
|
with right:
|
|
if st.session_state.op_selected:
|
|
op = st.session_state.op_selected
|
|
if "match" in op:
|
|
with open(f"output/mf_products/{sel}/match.txt", 'r') as f:
|
|
data = f.read()
|
|
st.code(data)
|
|
elif "reaction" in op:
|
|
with open(f"output/mf_products/{sel}/reaction.svg", 'r') as f:
|
|
svg = f.read()
|
|
st.image(svg, width=1000)
|
|
else:
|
|
folder = Path(f"output/mf_products/{sel}/{op}")
|
|
for p in folder.glob("*.svg"):
|
|
with open(p, 'r') as f:
|
|
svg = f.read()
|
|
st.image(svg, width=300)
|
|
else:
|
|
st.write("Choose a derivation to see products.")
|
|
st.stop() # nothing below this line runs in this view
|
|
|
|
# ---------- grid view ----------
|
|
st.title("M-CSA IDs")
|
|
N_COLS = 9
|
|
ids = df["mcsa_id"].tolist()
|
|
for start in range(0, len(ids), N_COLS):
|
|
cols = st.columns(N_COLS)
|
|
for col, cid in zip(cols, ids[start:start + N_COLS]):
|
|
if not Path(f"output/mf_products/{cid}/match.txt").exists(): continue
|
|
with open(f"output/mf_products/{cid}/match.txt", 'r') as f:
|
|
green = f.read()
|
|
if "TRUE" in green:
|
|
if col.button(f":green[{cid}]", key=f"btn_{cid}", use_container_width=True):
|
|
st.session_state.selected = cid
|
|
else:
|
|
if col.button(f":red[{cid}]", key=f"btn_{cid}", use_container_width=True):
|
|
st.session_state.selected = cid
|
|
|
|
main() |