DYDYLAN commited on
Commit
b547608
·
verified ·
1 Parent(s): 61eea38

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +117 -170
app.py CHANGED
@@ -1,60 +1,62 @@
1
  import os
 
2
  import gradio as gr
3
  from transformers import pipeline
4
  import torch
5
  from openai import OpenAI
6
- from PIL import Image, ImageEnhance
7
  from pypdf import PdfReader
8
 
 
9
  # =========================
10
- # 1. 设备 & Vision 模型
11
  # =========================
12
 
13
  device = "cuda" if torch.cuda.is_available() else "cpu"
14
 
 
15
  vision_pipe = pipeline(
16
  "image-to-text",
17
  model="nlpconnect/vit-gpt2-image-captioning",
18
- device=0 if device == "cuda" else -1,
19
  )
20
 
21
  # =========================
22
- # 2. 云雾 / DeepSeek API 设置
23
  # =========================
24
 
25
  YUNWU_API_KEY = os.environ.get("YUNWU_API_KEY")
26
-
27
  if not YUNWU_API_KEY:
28
  raise RuntimeError(
29
- "环境变量 YUNWU_API_KEY 未设置。请在 Hugging Face Space "
30
- "Settings → Variables and secrets 中添加:Name=YUNWU_API_KEY, Type=Secret。"
31
  )
32
 
33
- # 注意:这里用的是云雾文档里的 openai 兼容端点
34
  client = OpenAI(
35
  api_key=YUNWU_API_KEY,
36
- base_url="https://yunwu.ai/v1",
37
  )
38
 
39
- # 你可以根据需要调不同模型,例如:
40
- MODEL_EXPLANATION = "deepseek-chat" # Step 1:解释图
41
- MODEL_ANNOTATION = "deepseek-chat" # Step 2:标注建议
42
- MODEL_PARAPHRASE = "deepseek-chat" # Step 3:改写
 
 
43
 
44
 
45
  def call_llm(prompt: str,
46
  model: str,
47
- temperature: float = 0.25,
48
- max_tokens: int = 600) -> str:
49
- """统一封装一次聊天调用。"""
50
  resp = client.chat.completions.create(
51
  model=model,
52
  messages=[
53
  {
54
  "role": "system",
55
  "content": (
56
- "You are an academic writing assistant for scientific figures. "
57
- "Always reply in clear, professional English suitable for university-level work."
 
58
  ),
59
  },
60
  {"role": "user", "content": prompt},
@@ -66,17 +68,15 @@ def call_llm(prompt: str,
66
 
67
 
68
  # =========================
69
- # 3. 工具函数:PDF 文本、图像增强
70
  # =========================
71
 
72
  def extract_pdf_snippet(pdf_path: str | None, max_chars: int = 2500) -> str:
73
- """读取 PDF,抽取前几页文本作为上下文。"""
74
  if not pdf_path:
75
  return ""
76
-
77
  try:
78
  reader = PdfReader(pdf_path)
79
- texts: list[str] = []
80
  for page in reader.pages:
81
  txt = page.extract_text() or ""
82
  texts.append(txt)
@@ -88,227 +88,174 @@ def extract_pdf_snippet(pdf_path: str | None, max_chars: int = 2500) -> str:
88
  return ""
89
 
90
 
91
- def enhance_image_simple(image: Image.Image | None) -> Image.Image | None:
92
- """简单增强亮度&对比度(现在只是备选,不再作为单独一步展示)。"""
93
- if image is None:
94
- return None
95
- try:
96
- img = image.convert("RGB")
97
- img = ImageEnhance.Brightness(img).enhance(1.05)
98
- img = ImageEnhance.Contrast(img).enhance(1.15)
99
- return img
100
- except Exception:
101
- return image
 
 
 
 
 
 
 
102
 
103
 
104
  # =========================
105
- # 4. 主逻辑:3 大输出
106
  # =========================
107
 
108
  def run_workflow(image, keywords, style, pdf_path):
109
- """
110
- 返回 4 个东西:
111
- 1. 原始图像
112
- 2. Step 1: 图表含义解释(结合论文)
113
- 3. Step 2: 如何 annotate 的建议
114
- 4. Step 3: 论文风格解释 + 根据 style 改写的版本
115
- """
116
  if image is None:
117
- return None, "Please upload a figure first.", "", "", ""
118
 
119
- # -------- Vision caption ----------
120
- try:
121
- vision_raw = vision_pipe(image)[0]["generated_text"]
122
- except Exception as e:
123
- vision_raw = f"(Vision model failed: {e})"
124
 
 
125
  kw_text = keywords.strip() if keywords else ""
126
  pdf_context = extract_pdf_snippet(pdf_path)
127
 
128
- # -------- Step 1: 解释图表含义 ----------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
  step1_prompt = f"""
130
- You are given a scientific figure from a research paper.
131
 
132
- Rough description from a vision model:
133
  \"\"\"{vision_raw}\"\"\"
134
 
135
- User-provided figure keywords / variables (comma-separated):
136
  \"\"\"{kw_text}\"\"\"
137
 
138
- Context from the paper (may be noisy or partial):
139
  \"\"\"{pdf_context}\"\"\"
140
 
141
  Task:
142
- In about 5–8 sentences of clear academic English:
143
- - Explain what the figure is about.
144
- - State what the x-axis represents (in generic terms, e.g. temperature treatments, species, experimental groups).
145
- - State what the y-axis represents (e.g. relative bleaching, response intensity).
146
- - Describe the main patterns and comparisons (which treatments are higher / lower, more or less sensitive).
147
- - Connect the pattern to the scientific meaning (e.g. higher temperature + high CO2 leads to more bleaching).
148
-
149
- Do NOT invent exact numbers or p-values.
150
- Do NOT describe details of the full experimental protocol (no sample size, location, etc.).
151
- """
152
- explanation_en = call_llm(
153
- prompt=step1_prompt,
154
- model=MODEL_EXPLANATION,
155
- max_tokens=550,
156
- )
157
 
158
- # -------- Step 2: 标注建议 ----------
159
- step2_prompt = f"""
160
- Here is an explanation of a scientific figure:
161
-
162
- \"\"\"{explanation_en}\"\"\"
163
-
164
- The figure is a chart/diagram used in a research paper.
165
- Suggest how a student should annotate this figure to make it clearer for presentations or homework.
166
-
167
- In 4–7 bullet points of concise English:
168
- - Propose specific labels, arrows, or text boxes to add.
169
- - Indicate WHAT to label (e.g. treatment groups, axes, key contrasts).
170
- - Indicate WHERE to place the annotations (e.g. near bars with highest bleaching, next to control group, above x-axis groups).
171
- - Mention any colour or symbol conventions that would help (e.g. “use red arrows for stress conditions”).
172
-
173
- Write only the bullet-point suggestions.
174
  """
175
- annotation_suggestions = call_llm(
176
- prompt=step2_prompt,
177
- model=MODEL_ANNOTATION,
178
- max_tokens=420,
179
- )
180
-
181
- # -------- Step 3A: 论文风格解释 ----------
182
- step3_paper_prompt = f"""
183
- Using the same figure, write a short text that could appear in the Results section of a scientific paper.
184
 
185
- Base it on the following explanation and context:
186
-
187
- Current explanation:
188
- \"\"\"{explanation_en}\"\"\"
189
 
190
- Paper context:
191
- \"\"\"{pdf_context}\"\"\"
192
 
193
  Task:
194
- - Write a concise paragraph (4–6 sentences) in formal academic English.
195
- - Describe what is on the x-axis and y-axis in generic terms.
196
- - Describe the main trends and key contrasts shown in the figure.
197
- - End with one sentence that interprets what these patterns imply for the biological / scientific question.
198
 
199
- Do NOT include exact numeric values or statistics.
200
- """
201
- paper_style_explanation = call_llm(
202
- prompt=step3_paper_prompt,
203
- model=MODEL_PARAPHRASE,
204
- max_tokens=450,
205
- )
206
 
207
- # -------- Step 3B: 根据用户选择风格改写 ----------
208
- if style == "formal":
209
- style_instruction = (
210
- "Make it slightly more concise and polished, but keep a formal academic tone "
211
- "suitable for a written assignment or report."
212
- )
213
- elif style == "fluency":
214
- style_instruction = (
215
- "Make it smoother and more speech-like, as if the student is explaining the figure "
216
- "in an oral presentation, while still sounding professional."
217
- )
218
- else: # "simple"
219
- style_instruction = (
220
- "Rewrite it in simpler English for a non-expert audience, such as classmates, "
221
- "while keeping the scientific meaning accurate."
222
- )
223
 
224
- step3_para_prompt = f"""
225
- Here is a paper-style explanation of a figure:
 
226
 
227
- \"\"\"{paper_style_explanation}\"\"\"
 
228
 
229
- Rewrite this explanation according to the following requirement:
230
- {style_instruction}
 
 
 
231
 
232
- Keep the main scientific meaning and relationships between conditions.
233
- Keep it in English. Output a single coherent paragraph.
234
  """
235
- paraphrased_explanation = call_llm(
236
- prompt=step3_para_prompt,
237
- model=MODEL_PARAPHRASE,
238
- max_tokens=350,
239
- )
240
 
241
- # 返回顺序一定要和 outputs 一致!
242
- return (
243
- image, # 原图预览
244
- explanation_en, # Step 1
245
- annotation_suggestions, # Step 2
246
- paper_style_explanation, # Step 3A
247
- paraphrased_explanation, # Step 3B
248
- )
249
 
250
 
251
  # =========================
252
- # 5. Gradio UI
253
  # =========================
254
 
255
  with gr.Blocks() as demo:
256
  gr.Markdown("## ChartSmith – AI Figure Explainer (multi-model workflow)")
257
 
258
  with gr.Row():
259
- # 左边:输入
260
  with gr.Column():
261
- img_in = gr.Image(
262
- type="pil",
263
- label="Upload your scientific figure (screenshot is fine)"
264
- )
265
 
266
  keywords = gr.Textbox(
267
- label="Figure keywords / variables (English, comma-separated, e.g. bleaching, coral, temperature, CO2)",
268
- placeholder="bleaching, coral, temperature, CO2",
269
  )
270
 
271
  style = gr.Radio(
272
  ["formal", "fluency", "simple"],
273
  value="formal",
274
- label="Step 3: Explanation style",
275
  )
276
 
277
  pdf_in = gr.File(
278
  label="Upload the paper PDF (for context, optional)",
279
- type="filepath",
280
  )
281
 
282
  run_btn = gr.Button("Run workflow", variant="primary")
283
 
284
- # 右边:输出
285
  with gr.Column():
286
- orig_img = gr.Image(label="Figure preview")
287
-
288
  step1_box = gr.Textbox(
289
- label="Step 1: Explanation of what the figure shows (English)",
290
- lines=9,
291
  )
292
 
293
  step2_box = gr.Textbox(
294
  label="Step 2: Suggestions for annotating the figure",
295
- lines=8,
296
- )
297
-
298
- step3_paper_box = gr.Textbox(
299
- label="Step 3A: Paper-style explanation of the figure (Results-style)",
300
- lines=8,
301
  )
302
 
303
- step3_para_box = gr.Textbox(
304
- label="Step 3B: Paraphrased explanation for presentations / assignments",
305
- lines=8,
306
  )
307
 
308
  run_btn.click(
309
  run_workflow,
310
  inputs=[img_in, keywords, style, pdf_in],
311
- outputs=[orig_img, step1_box, step2_box, step3_paper_box, step3_para_box],
312
  )
313
 
314
 
 
1
  import os
2
+ import re
3
  import gradio as gr
4
  from transformers import pipeline
5
  import torch
6
  from openai import OpenAI
7
+ from PIL import Image
8
  from pypdf import PdfReader
9
 
10
+
11
  # =========================
12
+ # 1. Device & local vision model
13
  # =========================
14
 
15
  device = "cuda" if torch.cuda.is_available() else "cpu"
16
 
17
+ # local image->text (fast rough caption)
18
  vision_pipe = pipeline(
19
  "image-to-text",
20
  model="nlpconnect/vit-gpt2-image-captioning",
21
+ device=0 if device == "cuda" else -1
22
  )
23
 
24
  # =========================
25
+ # 2. Yunwu / OpenAI-compatible client
26
  # =========================
27
 
28
  YUNWU_API_KEY = os.environ.get("YUNWU_API_KEY")
 
29
  if not YUNWU_API_KEY:
30
  raise RuntimeError(
31
+ "YUNWU_API_KEY not set. Add it in HF Space Settings → Variables and secrets."
 
32
  )
33
 
 
34
  client = OpenAI(
35
  api_key=YUNWU_API_KEY,
36
+ base_url="https://yunwu.ai/v1"
37
  )
38
 
39
+ # =========================
40
+ # 3. Per-step model routing (edit here)
41
+ # =========================
42
+ MODEL_STEP1 = "gpt-5.1" # strongest reasoning for paper+figure meaning
43
+ MODEL_STEP2 = "deepseek-chat" # cheap/fast for annotation suggestions
44
+ MODEL_STEP3B = "gpt-4o" # good fluency for plain-language explanation
45
 
46
 
47
  def call_llm(prompt: str,
48
  model: str,
49
+ temperature: float = 0.2,
50
+ max_tokens: int = 512) -> str:
 
51
  resp = client.chat.completions.create(
52
  model=model,
53
  messages=[
54
  {
55
  "role": "system",
56
  "content": (
57
+ "You are a helpful academic assistant. "
58
+ "Be accurate, concrete, and avoid hallucinating numbers. "
59
+ "Do not use markdown unless asked."
60
  ),
61
  },
62
  {"role": "user", "content": prompt},
 
68
 
69
 
70
  # =========================
71
+ # 4. Utilities: PDF context + output cleaning
72
  # =========================
73
 
74
  def extract_pdf_snippet(pdf_path: str | None, max_chars: int = 2500) -> str:
 
75
  if not pdf_path:
76
  return ""
 
77
  try:
78
  reader = PdfReader(pdf_path)
79
+ texts = []
80
  for page in reader.pages:
81
  txt = page.extract_text() or ""
82
  texts.append(txt)
 
88
  return ""
89
 
90
 
91
+ def clean_text(text: str) -> str:
92
+ """
93
+ Remove markdown-ish / AI-ish formatting and obvious repetition.
94
+ """
95
+ if not text:
96
+ return text
97
+
98
+ # remove bold/italic markers
99
+ text = text.replace("**", "").replace("__", "")
100
+
101
+ # remove leading markdown bullets like "- ", "* ", "• "
102
+ text = re.sub(r"(?m)^\s*[-•*]\s+", "", text)
103
+
104
+ # collapse repeated whitespace
105
+ text = re.sub(r"\n{3,}", "\n\n", text)
106
+ text = re.sub(r"[ \t]{2,}", " ", text)
107
+
108
+ return text.strip()
109
 
110
 
111
  # =========================
112
+ # 5. Main workflow
113
  # =========================
114
 
115
  def run_workflow(image, keywords, style, pdf_path):
 
 
 
 
 
 
 
116
  if image is None:
117
+ return "", "", ""
118
 
119
+ # Step 0: rough vision caption
120
+ vision_raw = vision_pipe(image)[0]["generated_text"]
 
 
 
121
 
122
+ # contexts
123
  kw_text = keywords.strip() if keywords else ""
124
  pdf_context = extract_pdf_snippet(pdf_path)
125
 
126
+ # style control for Step1 only
127
+ if style == "formal":
128
+ style_instruction = (
129
+ "Write in formal academic English suitable for a Results/Figure explanation."
130
+ )
131
+ elif style == "fluency":
132
+ style_instruction = (
133
+ "Write in smooth, natural academic English, clear and readable."
134
+ )
135
+ else: # simple
136
+ style_instruction = (
137
+ "Write clearly with simpler wording, but still accurate."
138
+ )
139
+
140
+ # -------- Step 1: Explanation of what the figure shows (paper+figure) --------
141
  step1_prompt = f"""
142
+ You are given a scientific figure and some related paper text.
143
 
144
+ Rough vision caption of the image:
145
  \"\"\"{vision_raw}\"\"\"
146
 
147
+ Figure keywords:
148
  \"\"\"{kw_text}\"\"\"
149
 
150
+ Relevant paper context (may be partial/noisy):
151
  \"\"\"{pdf_context}\"\"\"
152
 
153
  Task:
154
+ - Explain what this figure means in 6–8 sentences.
155
+ - Say what is compared on the x-axis (or panels) and what the y-axis measures.
156
+ - Describe the main trends and the scientific takeaway.
157
+ - If the paper context implies a specific mechanism, mention it briefly.
158
+ - Do NOT invent exact numbers, statistics, or p-values.
159
+ - Do NOT describe unrelated parts of the paper.
 
 
 
 
 
 
 
 
 
160
 
161
+ {style_instruction}
162
+ Return plain text only.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
  """
164
+ step1_text = call_llm(step1_prompt, model=MODEL_STEP1, max_tokens=520)
165
+ step1_text = clean_text(step1_text)
 
 
 
 
 
 
 
166
 
167
+ # -------- Step 2: Suggestions for annotating the figure (less AI-ish) --------
168
+ step2_prompt = f"""
169
+ You are helping someone improve a scientific figure for a presentation.
 
170
 
171
+ Figure meaning (for context):
172
+ \"\"\"{step1_text}\"\"\"
173
 
174
  Task:
175
+ Give 5–7 practical, specific suggestions for how to annotate this figure directly on the image
176
+ (e.g., labels, arrows, callouts, highlights), so the key message is immediately obvious.
 
 
177
 
178
+ Rules:
179
+ - Write like a human TA giving advice.
180
+ - Avoid fancy formatting, no markdown, no bold, no bullet symbols like "-" or "•".
181
+ - Each suggestion should be one short sentence.
182
+ - Do not invent values; only suggest how to visually emphasize real trends.
 
 
183
 
184
+ Return plain text, one suggestion per line.
185
+ """
186
+ step2_text = call_llm(step2_prompt, model=MODEL_STEP2, temperature=0.3, max_tokens=260)
187
+ step2_text = clean_text(step2_text)
 
 
 
 
 
 
 
 
 
 
 
 
188
 
189
+ # -------- Step 3B: Plain-language explanation (replaces old paraphrase) --------
190
+ step3b_prompt = f"""
191
+ Explain this same figure to a smart high-school or first-year university student.
192
 
193
+ Figure meaning:
194
+ \"\"\"{step1_text}\"\"\"
195
 
196
+ Rules:
197
+ - 4–6 sentences.
198
+ - Use simple, conversational English.
199
+ - Keep the science correct but avoid jargon unless necessary.
200
+ - No numbers or stats unless they were explicitly in the meaning above.
201
 
202
+ Return plain text only.
 
203
  """
204
+ step3b_text = call_llm(step3b_prompt, model=MODEL_STEP3B, temperature=0.4, max_tokens=240)
205
+ step3b_text = clean_text(step3b_text)
 
 
 
206
 
207
+ return step1_text, step2_text, step3b_text
 
 
 
 
 
 
 
208
 
209
 
210
  # =========================
211
+ # 6. Gradio UI
212
  # =========================
213
 
214
  with gr.Blocks() as demo:
215
  gr.Markdown("## ChartSmith – AI Figure Explainer (multi-model workflow)")
216
 
217
  with gr.Row():
 
218
  with gr.Column():
219
+ img_in = gr.Image(type="pil", label="Upload your scientific figure (screenshot is fine)")
 
 
 
220
 
221
  keywords = gr.Textbox(
222
+ label="Figure keywords / variables (English, comma-separated)",
223
+ placeholder="bleaching, coral, temperature, CO2"
224
  )
225
 
226
  style = gr.Radio(
227
  ["formal", "fluency", "simple"],
228
  value="formal",
229
+ label="Explanation style (for Step 1)"
230
  )
231
 
232
  pdf_in = gr.File(
233
  label="Upload the paper PDF (for context, optional)",
234
+ type="filepath"
235
  )
236
 
237
  run_btn = gr.Button("Run workflow", variant="primary")
238
 
 
239
  with gr.Column():
 
 
240
  step1_box = gr.Textbox(
241
+ label="Step 1: Explanation of what the figure shows (paper-aware)",
242
+ lines=10
243
  )
244
 
245
  step2_box = gr.Textbox(
246
  label="Step 2: Suggestions for annotating the figure",
247
+ lines=8
 
 
 
 
 
248
  )
249
 
250
+ step3b_box = gr.Textbox(
251
+ label="Step 3: Plain-language explanation (for class/presentation)",
252
+ lines=6
253
  )
254
 
255
  run_btn.click(
256
  run_workflow,
257
  inputs=[img_in, keywords, style, pdf_in],
258
+ outputs=[step1_box, step2_box, step3b_box],
259
  )
260
 
261