devansh152 commited on
Commit
65d4bb3
Β·
verified Β·
1 Parent(s): 37d3919

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +160 -74
app.py CHANGED
@@ -1,33 +1,80 @@
1
- from transformers import pipeline
2
- import gradio as gr
3
  import networkx as nx
4
  import sympy as sp
5
- from collections import defaultdict
6
  import re
7
- from dotenv import load_dotenv
8
- import google.generativeai as genai
9
- import os
10
  from gradio.themes import Ocean
 
 
 
 
11
 
12
- load_dotenv()
13
- API_KEY = os.getenv("GEMINI_API")
14
- genai.configure(api_key=API_KEY)
15
 
16
- # Initialize the Gemini Flash Model
17
- model = genai.GenerativeModel('gemini-2.5-flash-lite-preview-06-17')
18
 
 
 
 
 
 
 
19
 
20
- # Gradio App with Support for Multi-Reactant Networks (e.g. A + B -> AB)
 
 
 
 
21
 
22
- # --- Parsing Functions ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  def parse_species(expr):
24
- # e.g., "A + B" -> ["A", "B"]
25
  return [s.strip() for s in re.split(r'\s*[\+\-]\s*', expr)]
26
 
27
  def parse_network(input_string):
28
- edges = []
29
- reversible_edges = []
30
-
31
  for part in input_string.split(','):
32
  part = part.strip()
33
  if '<->' in part:
@@ -40,7 +87,6 @@ def parse_network(input_string):
40
  lhs_species = parse_species(lhs)
41
  rhs_species = parse_species(rhs)
42
  edges.append((lhs_species, rhs_species))
43
-
44
  return edges, reversible_edges
45
 
46
  def build_graph(edges, reversible_edges):
@@ -65,7 +111,6 @@ def analyze_graph(G):
65
  "is_cyclic": not nx.is_directed_acyclic_graph(G)
66
  }
67
 
68
- # --- ODE Generator for Complex Reactions ---
69
  def mass_action_odes(edges, reversible_edges):
70
  species = set()
71
  odes = defaultdict(lambda: 0)
@@ -82,7 +127,6 @@ def mass_action_odes(edges, reversible_edges):
82
  for lhs_species, rhs_species in edges:
83
  k = sp.symbols(f'k{rate_counter}')
84
  rate_counter += 1
85
-
86
  flux = k * term(lhs_species)
87
  for s in lhs_species:
88
  sym = sp.symbols(s)
@@ -92,15 +136,12 @@ def mass_action_odes(edges, reversible_edges):
92
  odes[sym] += flux
93
 
94
  for lhs_species, rhs_species in reversible_edges:
95
-
96
  kf = sp.symbols(f'k{rate_counter}')
97
  rate_counter += 1
98
  kr = sp.symbols(f'k{rate_counter}')
99
  rate_counter += 1
100
-
101
  forward_flux = kf * term(lhs_species)
102
  reverse_flux = kr * term(rhs_species)
103
-
104
  for s in lhs_species:
105
  sym = sp.symbols(s)
106
  odes[sym] -= forward_flux
@@ -121,30 +162,7 @@ def compute_jacobian(odes):
121
  J = F.jacobian(variables)
122
  return sp.pretty(J)
123
 
124
- def extract_network_from_image(image):
125
- prompt = (
126
- "Analyze this network diagram and list the network only. "
127
- "Use reaction format like 'A + B -> C' or 'X <-> Y'. "
128
- "List multiple reactions separated by commas."
129
- )
130
- gemini_response = model.generate_content([prompt, image])
131
- return gemini_response.text.strip()
132
-
133
-
134
- def full_process(image, text_input, query):
135
- if text_input.strip(): # If text is given, use it
136
- network_description = text_input.strip()
137
- elif image is not None: # Else if image is given, extract network from image
138
- network_description = extract_network_from_image(image)
139
- else:
140
- return "❌ Please provide either a network image or a textual description."
141
-
142
- # Step 2: Process extracted/generated network
143
- return process_network(network_description, query)
144
-
145
-
146
- qa = pipeline("text2text-generation", model="google/flan-t5-base")
147
- def process_network(input_string, query):
148
  edges, reversible_edges = parse_network(input_string)
149
  G = build_graph(edges, reversible_edges)
150
  info = analyze_graph(G)
@@ -152,42 +170,110 @@ def process_network(input_string, query):
152
  if 'ode' in query.lower():
153
  ode_sys = mass_action_odes(edges, reversible_edges)
154
  return format_odes(ode_sys)
155
-
156
  elif 'jacobian' in query.lower():
157
  ode_sys = mass_action_odes(edges, reversible_edges)
158
  return f"Jacobian Matrix:\n{compute_jacobian(ode_sys)}"
159
-
160
  elif 'variables' in query.lower():
161
  return f"There are {info['num_nodes']} variables: {info['nodes']}"
162
-
163
  elif 'edges' in query.lower():
164
  return f"Edges: {info['edges']}"
165
-
166
- elif 'cyclic' or 'cycle' in query.lower():
167
  cycles = list(nx.simple_cycles(G))
168
- if cycles:
169
- cycles_str = "\n".join([" -> ".join(cycle + [cycle[0]]) for cycle in cycles])
170
- return f"Cycles found:\n{cycles_str}"
171
- else:
172
- return "No cycles found."
173
 
 
174
  else:
175
- prompt = f"Given the network with nodes: {info['nodes']} and edges: {info['edges']}, answer the query: {query}"
176
- answer = qa(prompt, max_length=128)[0]['generated_text']
177
- return answer
178
-
179
-
180
- iface = gr.Interface(
181
- fn=full_process,
182
- inputs=[
183
- gr.Image(type="pil", label="Upload Network Image (optional)"),
184
- gr.Textbox(label="Text Input (optional)", placeholder="Or paste network: A + B -> C, X <-> Y"),
185
- gr.Textbox(label="Query", placeholder="Ask about ODEs, Jacobian, edges, etc.")
186
- ],
187
- outputs="text",
188
- title="Biological Network Analyzer",
189
- description="Upload an image or enter network text. Then ask a query like 'Give ODEs' or 'Is it cyclic?'.",
190
- theme=Ocean()
191
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
 
193
  iface.launch(share=True)
 
1
+ import os
2
+ import tempfile
3
  import networkx as nx
4
  import sympy as sp
 
5
  import re
6
+ from collections import defaultdict
7
+ import gradio as gr
 
8
  from gradio.themes import Ocean
9
+ from huggingface_hub import InferenceClient
10
+ import requests
11
+ from PIL import Image
12
+ from io import BytesIO
13
 
14
+ # --- Set your HF token ---
15
+ # Either hardcode here or use Colab's userdata like:
16
+ HF_TOKEN = os.environ.get("HF_TOKEN")
17
 
18
+ if not HF_TOKEN:
19
+ raise RuntimeError("HF_TOKEN environment variable not set. Please add it in your Space's secrets.")
20
 
21
+ from huggingface_hub import InferenceClient
22
+
23
+ client = InferenceClient(
24
+ provider="featherless-ai",
25
+ api_key=os.environ["HF_TOKEN"],
26
+ )
27
 
28
+ # --- Helper: Save PIL image to URL-accessible temp file ---
29
+ def image_to_temp_url(image):
30
+ temp_path = tempfile.NamedTemporaryFile(delete=False, suffix=".png")
31
+ image.save(temp_path.name)
32
+ return "https://your-server.com/temporary-image-support.png" # placeholder (host image externally if needed)
33
 
34
+ # --- OR upload image to Hugging Face Space / GDrive and return a public URL instead
35
+ # You can use this for production use
36
+
37
+ def extract_network_from_image(image):
38
+ # Upload image to temp path
39
+ image_path = tempfile.NamedTemporaryFile(suffix=".png", delete=False).name
40
+ image.save(image_path)
41
+
42
+ # Upload manually or serve image online if needed
43
+ # For now, simulate by loading image into bytes and re-uploading to HF or GDrive
44
+ # Instead: In Colab, just use direct GDrive URLs
45
+
46
+ # Placeholder: Manually put a URL here for now (from GDrive or HF Spaces or web)
47
+ raise NotImplementedError("Replace this with your public image URL logic.")
48
+
49
+ # New: Directly send the URL to Unsloth Mistral + get output
50
+ def extract_network_from_url(image_url):
51
+ prompt = (
52
+ "Analyze this network diagram and list the network only, e.g. Q + W -> R. Do not print any other sentence except the network."
53
+ "The arrows represent reactions. If there are multiple reactions, give them comma separated like A -> B, B -> C, etc."
54
+ )
55
+
56
+ completion = client.chat.completions.create(
57
+ model="unsloth/Mistral-Small-3.2-24B-Instruct-2506",
58
+ messages=[
59
+ {
60
+ "role": "user",
61
+ "content": [
62
+ {"type": "text", "text": prompt},
63
+ {"type": "image_url", "image_url": {"url": image_url}},
64
+ ]
65
+ }
66
+ ]
67
+ )
68
+
69
+ return completion.choices[0].message.content.strip()
70
+
71
+
72
+ # --- Network Analysis Functions ---
73
  def parse_species(expr):
 
74
  return [s.strip() for s in re.split(r'\s*[\+\-]\s*', expr)]
75
 
76
  def parse_network(input_string):
77
+ edges, reversible_edges = [], []
 
 
78
  for part in input_string.split(','):
79
  part = part.strip()
80
  if '<->' in part:
 
87
  lhs_species = parse_species(lhs)
88
  rhs_species = parse_species(rhs)
89
  edges.append((lhs_species, rhs_species))
 
90
  return edges, reversible_edges
91
 
92
  def build_graph(edges, reversible_edges):
 
111
  "is_cyclic": not nx.is_directed_acyclic_graph(G)
112
  }
113
 
 
114
  def mass_action_odes(edges, reversible_edges):
115
  species = set()
116
  odes = defaultdict(lambda: 0)
 
127
  for lhs_species, rhs_species in edges:
128
  k = sp.symbols(f'k{rate_counter}')
129
  rate_counter += 1
 
130
  flux = k * term(lhs_species)
131
  for s in lhs_species:
132
  sym = sp.symbols(s)
 
136
  odes[sym] += flux
137
 
138
  for lhs_species, rhs_species in reversible_edges:
 
139
  kf = sp.symbols(f'k{rate_counter}')
140
  rate_counter += 1
141
  kr = sp.symbols(f'k{rate_counter}')
142
  rate_counter += 1
 
143
  forward_flux = kf * term(lhs_species)
144
  reverse_flux = kr * term(rhs_species)
 
145
  for s in lhs_species:
146
  sym = sp.symbols(s)
147
  odes[sym] -= forward_flux
 
162
  J = F.jacobian(variables)
163
  return sp.pretty(J)
164
 
165
+ def process_network(input_string, query, image_url=None):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
  edges, reversible_edges = parse_network(input_string)
167
  G = build_graph(edges, reversible_edges)
168
  info = analyze_graph(G)
 
170
  if 'ode' in query.lower():
171
  ode_sys = mass_action_odes(edges, reversible_edges)
172
  return format_odes(ode_sys)
 
173
  elif 'jacobian' in query.lower():
174
  ode_sys = mass_action_odes(edges, reversible_edges)
175
  return f"Jacobian Matrix:\n{compute_jacobian(ode_sys)}"
 
176
  elif 'variables' in query.lower():
177
  return f"There are {info['num_nodes']} variables: {info['nodes']}"
 
178
  elif 'edges' in query.lower():
179
  return f"Edges: {info['edges']}"
180
+ elif 'cyclic' in query.lower() or 'cycle' in query.lower():
 
181
  cycles = list(nx.simple_cycles(G))
182
+ return "Cycles found:\n" + "\n".join([" -> ".join(cycle + [cycle[0]]) for cycle in cycles]) if cycles else "No cycles found."
 
 
 
 
183
 
184
+ # Fallback: Use LLM on both image and parsed network
185
  else:
186
+ content = [
187
+ {
188
+ "type": "text",
189
+ "text": (
190
+ "You are given a biological network with the following structure:\n"
191
+ f"β€’ Nodes: {info['nodes']}\n"
192
+ f"β€’ Reactions (edges): {info['edges']}\n\n"
193
+ f"Answer the following query based on this structure and the image:"
194
+ f"\n\n{query}"
195
+ ),
196
+ }
197
+ ]
198
+ if image_url:
199
+ content.append({
200
+ "type": "image_url",
201
+ "image_url": {"url": image_url}
202
+ })
203
+
204
+ response = client.chat.completions.create(
205
+ model="unsloth/Mistral-Small-3.2-24B-Instruct-2506",
206
+ messages=[{"role": "user", "content": content}],
207
+ )
208
+ return response.choices[0].message.content.strip()
209
+
210
+ # --- Full Gradio Handler ---
211
+ def full_process(text_input, image_url, query):
212
+ image_preview = None
213
+ network_description = ""
214
+ result = ""
215
+
216
+ if text_input.strip():
217
+ network_description = text_input.strip()
218
+ elif image_url.strip():
219
+ # Display image from URL
220
+ try:
221
+ response = requests.get(image_url)
222
+ image_preview = Image.open(BytesIO(response.content))
223
+ except:
224
+ return None, "", "❌ Invalid image URL"
225
+
226
+ # Extract network
227
+ network_description = extract_network_from_url(image_url)
228
+ else:
229
+ return None, "", "❌ Provide text or image URL."
230
+
231
+ # Answer query
232
+ result = process_network(network_description, query, image_url=image_url if image_url.strip() else None)
233
+ return image_preview, network_description, result
234
+
235
+ import gradio as gr
236
+ from gradio.themes.utils import sizes
237
+ from gradio.themes.base import Base
238
+ from gradio.themes.utils import colors
239
+
240
+ # Optional: Keep your theme
241
+ theme = gr.themes.Ocean()
242
+
243
+ with gr.Blocks(theme=theme, css="#footer-link {text-align: center; font-size: 14px; color: #555;}") as iface:
244
+
245
+ gr.Markdown("## πŸ”¬ Biological Network Analyzer (Multimodal Mistral via Unsloth)")
246
+ gr.Markdown("Paste a network OR provide a public image URL. Then ask a query like **'Give ODEs'** or **'Is it cyclic?'**")
247
+
248
+ with gr.Row():
249
+ with gr.Column():
250
+ # img_input = gr.Image(type="pil", label="Upload Network Image (❌ Not supported unless image is hosted online)")
251
+ text_input = gr.Textbox(label="Text Input (optional)", placeholder="Or paste network: A + B -> C, X <-> Y")
252
+ url_input = gr.Textbox(label="πŸ”— Public Image URL (e.g., from GDrive)", placeholder="https://... (must be accessible)")
253
+ query_input = gr.Textbox(label="Query", placeholder="Ask about ODEs, Jacobian, edges, etc.")
254
+
255
+ with gr.Column():
256
+ img_output = gr.Image(label="πŸ–ΌοΈ Image Preview")
257
+ network_text = gr.Textbox(label="πŸ§ͺ Extracted Network")
258
+ result_box = gr.Textbox(label="πŸ“˜ Answer")
259
+
260
+
261
+
262
+
263
+ # Link logic to function
264
+ inputs = [text_input, url_input, query_input]
265
+ outputs = [img_output, network_text, result_box]
266
+ iface_fn = gr.Interface(fn=full_process, inputs=inputs, outputs=outputs)
267
+
268
+ # Footer GitHub link
269
+ gr.Markdown("""
270
+ <footer style='text-align:center; margin-top:20px; color:#aaa;'>
271
+ Built using Gradio, Hugging Face & Mistral |
272
+ <a href="https://github.com/kumardevansh/network_analyzer" target="_blank" style="color:#aaa; text-decoration:underline;">
273
+ View on GitHub
274
+ </a>
275
+ </footer>
276
+ """)
277
+
278
 
279
  iface.launch(share=True)