Afsha001 commited on
Commit
4b0137c
·
verified ·
1 Parent(s): cfb72d8

update app.py

Browse files
Files changed (1) hide show
  1. app.py +92 -99
app.py CHANGED
@@ -21,12 +21,6 @@ st.set_page_config(
21
  HF_TOKEN = os.environ.get("HF_TOKEN", "")
22
  JINA_KEY = os.environ.get("JINA_KEY", "")
23
 
24
- QWEN_URL = "https://api-inference.huggingface.co/models/Qwen/Qwen2.5-1.5B-Instruct/v1/chat/completions"
25
- HF_HEADERS = {
26
- "Authorization": f"Bearer {HF_TOKEN}",
27
- "Content-Type": "application/json"
28
- }
29
-
30
  JINA_URL = "https://api.jina.ai/v1/rerank"
31
  JINA_HEADERS = {
32
  "Authorization": f"Bearer {JINA_KEY}",
@@ -42,25 +36,30 @@ DETECT_PROMPT = (
42
  "jacket . dress . shirt . hat . bag ."
43
  )
44
 
45
- if not HF_TOKEN:
46
- st.error("HF_TOKEN missing. Go to Space Settings → Secrets and add it.")
47
- st.stop()
48
-
49
  if not JINA_KEY:
50
  st.error("JINA_KEY missing. Go to Space Settings → Secrets and add it.")
51
  st.stop()
52
 
 
 
 
 
 
 
 
53
  @st.cache_resource
54
  def load_local_models():
55
  from transformers import (
56
  AutoProcessor,
57
  AutoModelForCausalLM,
 
58
  BlipProcessor,
59
  BlipForImageTextRetrieval,
60
  AutoModelForZeroShotObjectDetection
61
  )
62
  gc.collect()
63
 
 
64
  git_processor = AutoProcessor.from_pretrained("microsoft/git-large-coco")
65
  git_model = AutoModelForCausalLM.from_pretrained(
66
  "microsoft/git-large-coco",
@@ -68,6 +67,7 @@ def load_local_models():
68
  )
69
  git_model.eval()
70
 
 
71
  blip_processor = BlipProcessor.from_pretrained(
72
  "Salesforce/blip-image-captioning-large"
73
  )
@@ -77,6 +77,7 @@ def load_local_models():
77
  )
78
  blip_itm_model.eval()
79
 
 
80
  dino_processor = AutoProcessor.from_pretrained(
81
  "IDEA-Research/grounding-dino-base"
82
  )
@@ -86,7 +87,22 @@ def load_local_models():
86
  )
87
  dino_model.eval()
88
 
89
- return git_processor, git_model, blip_processor, blip_itm_model, dino_processor, dino_model
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
 
91
  def image_to_bytes(image: Image.Image) -> bytes:
92
  buf = BytesIO()
@@ -98,45 +114,14 @@ def image_to_data_uri(image: Image.Image) -> str:
98
  b64 = base64.b64encode(raw).decode()
99
  return f"data:image/jpeg;base64,{b64}"
100
 
101
- # ============================================================================
102
- # ONLY CHANGE: generate_captions_git
103
- # Fix: 5 different generation strategies instead of just max_new_tokens
104
- # Greedy / beam search / sampling with different temperatures
105
- # ============================================================================
106
  def generate_captions_git(image: Image.Image, git_proc, git_mod) -> list:
107
 
108
  strategies = [
109
- # Greedy — short deterministic baseline
110
- {
111
- "max_new_tokens": 30
112
- },
113
- # Beam search explores multiple decode paths
114
- {
115
- "max_new_tokens": 50,
116
- "num_beams": 5,
117
- "early_stopping": True
118
- },
119
- # Sampling — low temperature, focused output
120
- {
121
- "max_new_tokens": 60,
122
- "do_sample": True,
123
- "temperature": 0.7,
124
- "top_k": 50
125
- },
126
- # Sampling — high temperature, creative output
127
- {
128
- "max_new_tokens": 70,
129
- "do_sample": True,
130
- "temperature": 1.3,
131
- "top_k": 100
132
- },
133
- # Nucleus sampling — top-p based
134
- {
135
- "max_new_tokens": 55,
136
- "do_sample": True,
137
- "top_p": 0.9,
138
- "temperature": 1.0
139
- },
140
  ]
141
 
142
  captions = []
@@ -149,26 +134,20 @@ def generate_captions_git(image: Image.Image, git_proc, git_mod) -> list:
149
  pixel_values=pixel_values,
150
  **strategy
151
  )
152
-
153
  cap = git_proc.batch_decode(
154
- generated_ids,
155
- skip_special_tokens=True
156
  )[0].strip().lower()
157
-
158
  captions.append(cap if cap else "a scene shown in the image")
159
-
160
  except Exception as e:
161
  st.warning(f"GIT error: {str(e)[:80]}")
162
  captions.append("a scene shown in the image")
163
 
164
- # Deduplicate while keeping order
165
  seen, unique = set(), []
166
  for c in captions:
167
  if c not in seen:
168
  seen.add(c)
169
  unique.append(c)
170
 
171
- # If model still returns all duplicates keep originals so voting has input
172
  if len(unique) < 2:
173
  unique = captions
174
 
@@ -199,7 +178,6 @@ def compute_itm_scores(image, captions, blip_proc, blip_itm) -> list:
199
  def compute_jina_scores(image: Image.Image, captions: list) -> list:
200
  img_data_uri = image_to_data_uri(image)
201
  scores = []
202
-
203
  for cap in captions:
204
  try:
205
  payload = {
@@ -209,10 +187,8 @@ def compute_jina_scores(image: Image.Image, captions: list) -> list:
209
  "top_n": 1
210
  }
211
  response = requests.post(
212
- JINA_URL,
213
- headers=JINA_HEADERS,
214
- json=payload,
215
- timeout=30
216
  )
217
  if response.status_code == 200:
218
  result = response.json()
@@ -251,7 +227,6 @@ def compute_cosine_scores(image, captions, blip_proc, blip_itm) -> list:
251
 
252
  sims = cosine_similarity(img_feat, cap_feat)[0]
253
  return [round(float(s), 4) for s in sims]
254
-
255
  except Exception as e:
256
  st.warning(f"Cosine error: {str(e)[:60]}")
257
  return [0.0] * len(captions)
@@ -283,9 +258,7 @@ def detect_objects(image, dino_proc, dino_mod, threshold=0.3) -> tuple:
283
 
284
  target_sizes = torch.tensor([image.size[::-1]])
285
  results = dino_proc.post_process_grounded_object_detection(
286
- outputs,
287
- inputs.input_ids,
288
- target_sizes=target_sizes
289
  )[0]
290
 
291
  scores = results["scores"]
@@ -310,51 +283,66 @@ def detect_objects(image, dino_proc, dino_mod, threshold=0.3) -> tuple:
310
  ]
311
  formatted = "Detected objects: [" + ", ".join(sorted_labels) + "]"
312
  return formatted, sorted_labels
313
-
314
  except Exception as e:
315
  st.warning(f"DINO error: {str(e)[:80]}")
316
  return "Object detection unavailable", []
317
 
318
- def fuse_captions(cap1: str, cap2: str, objects: str) -> str:
 
 
 
 
 
 
 
319
  system_prompt = (
320
  "You are an expert image captioning assistant. "
321
- "Write ONE natural, fluent, descriptive caption combining the best details. "
322
- "Return ONLY the caption, no explanation or prefix."
 
323
  )
324
  user_prompt = (
325
  f"Caption A: {cap1}\n"
326
  f"Caption B: {cap2}\n"
327
  f"{objects}\n\n"
328
- "Fused caption:"
329
  )
 
330
  try:
331
- payload = {
332
- "model": "Qwen/Qwen2.5-1.5B-Instruct",
333
- "messages": [
334
- {"role": "system", "content": system_prompt},
335
- {"role": "user", "content": user_prompt}
336
- ],
337
- "max_tokens": 100,
338
- "temperature": 0.3,
339
- "top_p": 0.9
340
- }
341
- response = requests.post(
342
- QWEN_URL,
343
- headers=HF_HEADERS,
344
- json=payload,
345
- timeout=40
346
  )
347
- if response.status_code == 200:
348
- fused = response.json()["choices"][0]["message"]["content"].strip()
349
- for prefix in ["Fused caption:", "Caption:", "Result:"]:
350
- if fused.lower().startswith(prefix.lower()):
351
- fused = fused[len(prefix):].strip()
352
- return fused if fused else cap1
353
- else:
354
- st.warning(f"Qwen API error {response.status_code}")
355
- return cap1
 
 
 
 
 
 
 
 
 
 
 
 
 
356
  except Exception as e:
357
- st.warning(f"Qwen exception: {str(e)[:60]}")
358
  return cap1
359
 
360
  with st.sidebar:
@@ -380,12 +368,12 @@ Best 2 captions selected
380
  **6. Grounding DINO** (Local)
381
  Object detection
382
 
383
- **7. Qwen2.5-1.5B** (API)
384
  Caption fusion
385
  """)
386
  st.markdown("---")
387
- st.markdown("**Local:** GIT-Large, BLIP ITM, DINO")
388
- st.markdown("**API:** Jina, Qwen2.5")
389
 
390
  st.title("Image Caption Fusion System")
391
  st.markdown("Upload an image to generate a refined, grounded caption.")
@@ -407,8 +395,13 @@ if uploaded_file is not None:
407
  with col_run:
408
  if st.button("Generate Caption", type="primary", use_container_width=True):
409
 
410
- with st.spinner("Loading local models (first run takes 2-3 min)..."):
411
- git_proc, git_mod, blip_proc, blip_itm, dino_proc, dino_mod = load_local_models()
 
 
 
 
 
412
 
413
  progress = st.progress(0)
414
  status = st.empty()
@@ -463,7 +456,7 @@ if uploaded_file is not None:
463
  st.write(" | ".join(obj_list) if obj_list else obj_str)
464
 
465
  status.info("Step 7/7: Fusing captions with Qwen2.5-1.5B...")
466
- final = fuse_captions(best_1, best_2, obj_str)
467
  progress.progress(100)
468
  status.success("Pipeline complete!")
469
 
 
21
  HF_TOKEN = os.environ.get("HF_TOKEN", "")
22
  JINA_KEY = os.environ.get("JINA_KEY", "")
23
 
 
 
 
 
 
 
24
  JINA_URL = "https://api.jina.ai/v1/rerank"
25
  JINA_HEADERS = {
26
  "Authorization": f"Bearer {JINA_KEY}",
 
36
  "jacket . dress . shirt . hat . bag ."
37
  )
38
 
 
 
 
 
39
  if not JINA_KEY:
40
  st.error("JINA_KEY missing. Go to Space Settings → Secrets and add it.")
41
  st.stop()
42
 
43
+ # ============================================================================
44
+ # LOAD LOCAL MODELS
45
+ # GIT-Large-COCO: caption generation
46
+ # BLIP ITM: image-text matching + cosine similarity
47
+ # DINO: object detection
48
+ # Qwen2.5-1.5B: caption fusion (moved local — API was returning 404)
49
+ # ============================================================================
50
  @st.cache_resource
51
  def load_local_models():
52
  from transformers import (
53
  AutoProcessor,
54
  AutoModelForCausalLM,
55
+ AutoTokenizer,
56
  BlipProcessor,
57
  BlipForImageTextRetrieval,
58
  AutoModelForZeroShotObjectDetection
59
  )
60
  gc.collect()
61
 
62
+ # GIT-Large-COCO — caption generation
63
  git_processor = AutoProcessor.from_pretrained("microsoft/git-large-coco")
64
  git_model = AutoModelForCausalLM.from_pretrained(
65
  "microsoft/git-large-coco",
 
67
  )
68
  git_model.eval()
69
 
70
+ # BLIP — ITM scoring and cosine similarity
71
  blip_processor = BlipProcessor.from_pretrained(
72
  "Salesforce/blip-image-captioning-large"
73
  )
 
77
  )
78
  blip_itm_model.eval()
79
 
80
+ # DINO — object detection
81
  dino_processor = AutoProcessor.from_pretrained(
82
  "IDEA-Research/grounding-dino-base"
83
  )
 
87
  )
88
  dino_model.eval()
89
 
90
+ # Qwen2.5-1.5B caption fusion (local, no API)
91
+ qwen_tokenizer = AutoTokenizer.from_pretrained(
92
+ "Qwen/Qwen2.5-1.5B-Instruct"
93
+ )
94
+ qwen_model = AutoModelForCausalLM.from_pretrained(
95
+ "Qwen/Qwen2.5-1.5B-Instruct",
96
+ torch_dtype=torch.float32
97
+ )
98
+ qwen_model.eval()
99
+
100
+ return (
101
+ git_processor, git_model,
102
+ blip_processor, blip_itm_model,
103
+ dino_processor, dino_model,
104
+ qwen_tokenizer, qwen_model
105
+ )
106
 
107
  def image_to_bytes(image: Image.Image) -> bytes:
108
  buf = BytesIO()
 
114
  b64 = base64.b64encode(raw).decode()
115
  return f"data:image/jpeg;base64,{b64}"
116
 
 
 
 
 
 
117
  def generate_captions_git(image: Image.Image, git_proc, git_mod) -> list:
118
 
119
  strategies = [
120
+ {"max_new_tokens": 30},
121
+ {"max_new_tokens": 50, "num_beams": 5, "early_stopping": True},
122
+ {"max_new_tokens": 60, "do_sample": True, "temperature": 0.7, "top_k": 50},
123
+ {"max_new_tokens": 70, "do_sample": True, "temperature": 1.3, "top_k": 100},
124
+ {"max_new_tokens": 55, "do_sample": True, "top_p": 0.9, "temperature": 1.0},
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  ]
126
 
127
  captions = []
 
134
  pixel_values=pixel_values,
135
  **strategy
136
  )
 
137
  cap = git_proc.batch_decode(
138
+ generated_ids, skip_special_tokens=True
 
139
  )[0].strip().lower()
 
140
  captions.append(cap if cap else "a scene shown in the image")
 
141
  except Exception as e:
142
  st.warning(f"GIT error: {str(e)[:80]}")
143
  captions.append("a scene shown in the image")
144
 
 
145
  seen, unique = set(), []
146
  for c in captions:
147
  if c not in seen:
148
  seen.add(c)
149
  unique.append(c)
150
 
 
151
  if len(unique) < 2:
152
  unique = captions
153
 
 
178
  def compute_jina_scores(image: Image.Image, captions: list) -> list:
179
  img_data_uri = image_to_data_uri(image)
180
  scores = []
 
181
  for cap in captions:
182
  try:
183
  payload = {
 
187
  "top_n": 1
188
  }
189
  response = requests.post(
190
+ JINA_URL, headers=JINA_HEADERS,
191
+ json=payload, timeout=30
 
 
192
  )
193
  if response.status_code == 200:
194
  result = response.json()
 
227
 
228
  sims = cosine_similarity(img_feat, cap_feat)[0]
229
  return [round(float(s), 4) for s in sims]
 
230
  except Exception as e:
231
  st.warning(f"Cosine error: {str(e)[:60]}")
232
  return [0.0] * len(captions)
 
258
 
259
  target_sizes = torch.tensor([image.size[::-1]])
260
  results = dino_proc.post_process_grounded_object_detection(
261
+ outputs, inputs.input_ids, target_sizes=target_sizes
 
 
262
  )[0]
263
 
264
  scores = results["scores"]
 
283
  ]
284
  formatted = "Detected objects: [" + ", ".join(sorted_labels) + "]"
285
  return formatted, sorted_labels
 
286
  except Exception as e:
287
  st.warning(f"DINO error: {str(e)[:80]}")
288
  return "Object detection unavailable", []
289
 
290
+ # ============================================================================
291
+ # STEP 7 — QWEN2.5-1.5B (LOCAL): CAPTION FUSION
292
+ # Moved from API to local — API was consistently returning 404
293
+ # Uses chat template for proper instruct format
294
+ # Prompt asks Qwen to enrich and add detail using detected objects
295
+ # ============================================================================
296
+ def fuse_captions(cap1: str, cap2: str, objects: str, qwen_tok, qwen_mod) -> str:
297
+
298
  system_prompt = (
299
  "You are an expert image captioning assistant. "
300
+ "Write ONE natural, fluent, detailed and descriptive caption. "
301
+ "Combine the best details from both captions and incorporate the detected objects. "
302
+ "Return ONLY the final caption, no explanation or prefix."
303
  )
304
  user_prompt = (
305
  f"Caption A: {cap1}\n"
306
  f"Caption B: {cap2}\n"
307
  f"{objects}\n\n"
308
+ "Write a detailed fused caption:"
309
  )
310
+
311
  try:
312
+ messages = [
313
+ {"role": "system", "content": system_prompt},
314
+ {"role": "user", "content": user_prompt}
315
+ ]
316
+
317
+ text = qwen_tok.apply_chat_template(
318
+ messages,
319
+ tokenize=False,
320
+ add_generation_prompt=True
 
 
 
 
 
 
321
  )
322
+
323
+ model_inputs = qwen_tok([text], return_tensors="pt")
324
+
325
+ with torch.no_grad():
326
+ generated_ids = qwen_mod.generate(
327
+ **model_inputs,
328
+ max_new_tokens=120,
329
+ temperature=0.3,
330
+ do_sample=True,
331
+ top_p=0.9
332
+ )
333
+
334
+ # Strip input tokens from output
335
+ output_ids = generated_ids[0][len(model_inputs.input_ids[0]):]
336
+ fused = qwen_tok.decode(output_ids, skip_special_tokens=True).strip()
337
+
338
+ for prefix in ["Fused caption:", "Caption:", "Result:", "Answer:"]:
339
+ if fused.lower().startswith(prefix.lower()):
340
+ fused = fused[len(prefix):].strip()
341
+
342
+ return fused if fused else cap1
343
+
344
  except Exception as e:
345
+ st.warning(f"Qwen fusion error: {str(e)[:80]}")
346
  return cap1
347
 
348
  with st.sidebar:
 
368
  **6. Grounding DINO** (Local)
369
  Object detection
370
 
371
+ **7. Qwen2.5-1.5B** (Local)
372
  Caption fusion
373
  """)
374
  st.markdown("---")
375
+ st.markdown("**Local:** GIT-Large, BLIP ITM, DINO, Qwen2.5")
376
+ st.markdown("**API:** Jina")
377
 
378
  st.title("Image Caption Fusion System")
379
  st.markdown("Upload an image to generate a refined, grounded caption.")
 
395
  with col_run:
396
  if st.button("Generate Caption", type="primary", use_container_width=True):
397
 
398
+ with st.spinner("Loading local models (first run takes 3-4 min)..."):
399
+ (
400
+ git_proc, git_mod,
401
+ blip_proc, blip_itm,
402
+ dino_proc, dino_mod,
403
+ qwen_tok, qwen_mod
404
+ ) = load_local_models()
405
 
406
  progress = st.progress(0)
407
  status = st.empty()
 
456
  st.write(" | ".join(obj_list) if obj_list else obj_str)
457
 
458
  status.info("Step 7/7: Fusing captions with Qwen2.5-1.5B...")
459
+ final = fuse_captions(best_1, best_2, obj_str, qwen_tok, qwen_mod)
460
  progress.progress(100)
461
  status.success("Pipeline complete!")
462