ProfRom commited on
Commit
29be286
·
verified ·
1 Parent(s): 4065ff9

Poudel - Unit 8 Assignment - Final Submission

Browse files
Files changed (2) hide show
  1. app.py +14 -399
  2. requirements.txt +2 -9
app.py CHANGED
@@ -1,407 +1,22 @@
1
- # app.py — Lazy Loaded Multimodal AI System
2
- #
3
- # Models load ONLY when needed to avoid memory overflow
4
- # Works on Hugging Face free CPU Spaces
5
-
6
- import torch
7
  import gradio as gr
 
8
 
9
- device = torch.device("cpu")
10
-
11
-
12
- # ---------------------------------------------------------
13
- # LAZY MODEL LOADERS
14
- # ---------------------------------------------------------
15
-
16
- def load_caption_model():
17
- """
18
- Quick breakdown of what this function does:
19
- - Loads the BLIP image captioning model
20
- - Loads the BLIP processor (handles image preprocessing)
21
- - Moves the model to CPU
22
- - Returns both so we can generate captions later
23
- """
24
- from transformers import BlipProcessor, BlipForConditionalGeneration
25
- model_name = "Salesforce/blip-image-captioning-base"
26
- processor = BlipProcessor.from_pretrained(model_name)
27
- model = BlipForConditionalGeneration.from_pretrained(model_name).to(device)
28
- return processor, model
29
-
30
-
31
- def load_sentiment_model():
32
- """
33
- What this function does:
34
- - Loads a ready-to-use Hugging Face pipeline for sentiment analysis
35
- - Uses DistilBERT, which is lightweight and fast
36
- """
37
- from transformers import pipeline
38
- return pipeline(
39
- "sentiment-analysis",
40
- model="distilbert-base-uncased-finetuned-sst-2-english"
41
- )
42
-
43
-
44
- def load_vqa_model():
45
- """
46
- What this VQA loader does:
47
- - Loads BLIP again, but the VQA version this time
48
- - Processes both images and text questions
49
- - Lets the model "answer" natural language questions about an image
50
- """
51
- from transformers import BlipProcessor, BlipForQuestionAnswering
52
- model_name = "Salesforce/blip-vqa-base"
53
- processor = BlipProcessor.from_pretrained(model_name)
54
- model = BlipForQuestionAnswering.from_pretrained(model_name).to(device)
55
- return processor, model
56
-
57
-
58
- def load_detr_model():
59
- """
60
- What this model loader does:
61
- - Loads DETR, a transformer-based object detector
62
- - Prepares the image processor so DETR can understand pictures
63
- - Returns both objects so we can run object detection later
64
- """
65
- from transformers import DetrImageProcessor, DetrForObjectDetection
66
- processor = DetrImageProcessor.from_pretrained("facebook/detr-resnet-50")
67
- model = DetrForObjectDetection.from_pretrained("facebook/detr-resnet-50").to(device)
68
- return processor, model
69
-
70
-
71
- def load_vit_model():
72
- """
73
- What this function does:
74
- - Loads a Vision Transformer (ViT) model
75
- - Used for scene classification
76
- - Outputs a general "type" of environment (forest, kitchen, street, etc.)
77
- """
78
- from transformers import ViTImageProcessor, ViTForImageClassification
79
- model_name = "google/vit-base-patch16-224"
80
- processor = ViTImageProcessor.from_pretrained(model_name)
81
- model = ViTForImageClassification.from_pretrained(model_name).to(device)
82
- return processor, model
83
-
84
-
85
- def load_llm():
86
- """
87
- What this language model loader does:
88
- - Loads the FLAN T5 Large model for rewriting captions
89
- - Handles different rewrite styles like creative, technical, humorous, etc.
90
- - Moves the model to CPU since HF Spaces do not have GPUs by default
91
- """
92
- from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
93
- name = "google/flan-t5-large"
94
- tokenizer = AutoTokenizer.from_pretrained(name)
95
- model = AutoModelForSeq2SeqLM.from_pretrained(name).to(device)
96
- return tokenizer, model
97
-
98
-
99
- # ---------------------------------------------------------
100
- # TASKS
101
- # ---------------------------------------------------------
102
 
103
  def generate_caption(image):
104
- """
105
- What this function does:
106
- - Takes the uploaded image
107
- - Runs it through BLIP to produce an automatic caption
108
- - Returns a plain English description of whatever is in the image
109
- """
110
- processor, model = load_caption_model()
111
- inputs = processor(images=image, return_tensors="pt").to(device)
112
- with torch.no_grad():
113
- out_ids = model.generate(**inputs, max_new_tokens=30)
114
- return processor.decode(out_ids[0], skip_special_tokens=True)
115
-
116
-
117
- def analyze_sentiment(text):
118
- """
119
- What this function does:
120
- - Uses DistilBERT to classify the caption as positive, negative, or neutral
121
- - Also returns a confidence score as a percentage
122
- """
123
- sentiment = load_sentiment_model()
124
- out = sentiment(text)[0]
125
- return out["label"], round(out["score"] * 100, 2)
126
-
127
-
128
- def vqa_answer(image, question):
129
- """
130
- What this function does:
131
- - Takes an image and a user-typed question
132
- - Runs them through BLIP VQA to produce an answer
133
- - Works best with concrete questions like "What color is the car?"
134
- """
135
- processor, model = load_vqa_model()
136
- inputs = processor(images=image, text=question, return_tensors="pt").to(device)
137
- with torch.no_grad():
138
- out = model.generate(**inputs)
139
- return processor.decode(out[0], skip_special_tokens=True)
140
-
141
-
142
- def detect_objects(image):
143
- """
144
- What this function does:
145
- - Runs the image through DETR
146
- - Returns a list of detected objects with confidence scores
147
- - Falls back to a friendly message if nothing is detected clearly
148
- """
149
- processor, model = load_detr_model()
150
- inputs = processor(images=image, return_tensors="pt").to(device)
151
-
152
- with torch.no_grad():
153
- outputs = model(**inputs)
154
-
155
- target_sizes = torch.tensor([image.size[::-1]])
156
- results = processor.post_process_object_detection(outputs, target_sizes=target_sizes)[0]
157
-
158
- detections = []
159
- for score, label, box in zip(results["scores"], results["labels"], results["boxes"]):
160
- if score > 0.3:
161
- detections.append(
162
- f"{model.config.id2label[label.item()]} (score {round(score.item(), 2)})"
163
- )
164
- if len(detections) == 0:
165
- return ["No high-confidence objects detected"]
166
- return detections
167
-
168
-
169
- def classify_scene(image):
170
- """
171
- What this function does:
172
- - Sends the image through a Vision Transformer (ViT)
173
- - Figures out the general type of scene (indoors, outdoors, nature, etc.)
174
- - Returns the scene label predicted by the model
175
- """
176
- processor, model = load_vit_model()
177
- inputs = processor(images=image, return_tensors="pt").to(device)
178
- with torch.no_grad():
179
- logits = model(**inputs).logits
180
- label = logits.argmax(-1).item()
181
- return model.config.id2label[label]
182
-
183
-
184
- # ---------------------------------------------------------
185
- # REWRITE CAPTIONS (8 STYLE SYSTEM + LENGTH SLIDER)
186
- # ---------------------------------------------------------
187
-
188
- def _build_style_prompt(caption, style):
189
- """
190
- What this helper function does:
191
- - Builds a custom prompt for the rewrite model
192
- - Each style (creative, technical, poetic, etc.) has its own instructions
193
- - Helps FLAN T5 understand what kind of rewrite we want
194
- """
195
- base = (
196
- "Rewrite the following image caption. "
197
- "Keep the original meaning and important details, "
198
- "but change the wording significantly and avoid repeating sentences verbatim. "
199
- "Do not just copy the original text.\n\n"
200
- f"Original caption:\n{caption}\n\n"
201
- )
202
-
203
- if style == "Short":
204
- return (
205
- base
206
- + "Now produce a shorter, compact version in one or two sentences."
207
- )
208
- elif style == "Creative":
209
- return (
210
- base
211
- + "Rewrite it in a colorful, imaginative, and richly descriptive style."
212
- )
213
- elif style == "Technical":
214
- return (
215
- base
216
- + "Rewrite it in a highly technical, analytical style using precise visual terminology."
217
- )
218
- elif style == "Humorous":
219
- return (
220
- base
221
- + "Rewrite it with a fun, humorous, witty tone while keeping the meaning."
222
- )
223
- elif style == "Poetic":
224
- return (
225
- base
226
- + "Rewrite it in a poetic, rhythmic, metaphorical style using sensory language."
227
- )
228
- elif style == "Cinematic":
229
- return (
230
- base
231
- + "Rewrite it as if describing an epic cinematic movie scene with dramatic, vivid imagery."
232
- )
233
- elif style == "Journalistic":
234
- return (
235
- base
236
- + "Rewrite it in a factual, neutral, journalistic news-reporting style."
237
- )
238
- elif style == "Academic":
239
- return (
240
- base
241
- + "Rewrite it in a formal, academic style with clear, analytical phrasing."
242
- )
243
- else:
244
- # Fallback: treat unknown style as creative rewrite
245
- return (
246
- base
247
- + "Rewrite it in a natural, descriptive style."
248
- )
249
-
250
-
251
- def rewrite_caption(caption, style, length):
252
- """
253
- What this function does:
254
- - Uses FLAN T5 to rewrite the caption in a chosen style
255
- - Uses a token limit slider so the user controls how long the rewrite is
256
- - Falls back to a second rewrite attempt if the model just echoes the caption
257
- """
258
- tokenizer, model = load_llm()
259
-
260
- prompt = _build_style_prompt(caption, style)
261
-
262
- # Tokenize
263
- inputs = tokenizer(prompt, return_tensors="pt").to(device)
264
-
265
- # First pass: normal creative decoding
266
- with torch.no_grad():
267
- outputs = model.generate(
268
- **inputs,
269
- max_new_tokens=length,
270
- do_sample=True,
271
- temperature=0.9,
272
- top_p=0.9,
273
- no_repeat_ngram_size=3,
274
- repetition_penalty=1.2,
275
- )
276
-
277
- rewritten = tokenizer.decode(outputs[0], skip_special_tokens=True).strip()
278
-
279
- # If the model basically echoed the caption, try a second, more forceful pass.
280
- if rewritten.lower().strip() == caption.lower().strip():
281
- strong_prompt = (
282
- "Paraphrase and expand the following caption. "
283
- "Use different wording and add extra detail, but keep the meaning. "
284
- "Do not repeat the original sentence exactly.\n\n"
285
- f"Original caption:\n{caption}"
286
- )
287
- strong_inputs = tokenizer(strong_prompt, return_tensors="pt").to(device)
288
-
289
- with torch.no_grad():
290
- outputs2 = model.generate(
291
- **strong_inputs,
292
- max_new_tokens=length,
293
- do_sample=True,
294
- temperature=1.0,
295
- top_p=0.95,
296
- no_repeat_ngram_size=3,
297
- repetition_penalty=1.3,
298
- )
299
- rewritten2 = tokenizer.decode(outputs2[0], skip_special_tokens=True).strip()
300
-
301
- # Only replace if it actually changed something
302
- if rewritten2 and rewritten2.lower().strip() != caption.lower().strip():
303
- rewritten = rewritten2
304
-
305
- return rewritten
306
-
307
-
308
- def extract_metadata(image):
309
- """
310
- What this function does:
311
- - Pulls basic metadata from the image
312
- - Reports width, height, and whether EXIF info exists
313
- """
314
- width, height = image.size
315
- meta = f"Dimensions: {width} x {height}\n"
316
- meta += "EXIF data detected\n" if "exif" in image.info else "No EXIF data available\n"
317
- return meta
318
-
319
-
320
- # ---------------------------------------------------------
321
- # MAIN LOOP
322
- # ---------------------------------------------------------
323
-
324
- def process_all(image, question, style, length):
325
- """
326
- What this main function does:
327
- - Acts as the central pipeline for the whole app
328
- - Calls every other function and collects their outputs
329
- - Returns all results for display in the Gradio UI
330
- """
331
  if image is None:
332
- return ["No image"] * 8
333
-
334
- caption = generate_caption(image)
335
- sentiment_label, sentiment_score = analyze_sentiment(caption)
336
- vqa = vqa_answer(image, question) if question else "No question asked"
337
- objects = detect_objects(image)
338
- scene = classify_scene(image)
339
- rewritten = rewrite_caption(caption, style, length)
340
- metadata = extract_metadata(image)
341
-
342
- return caption, sentiment_label, sentiment_score, vqa, objects, scene, rewritten, metadata
343
-
344
-
345
- # ---------------------------------------------------------
346
- # GRADIO UI
347
- # ---------------------------------------------------------
348
-
349
- with gr.Blocks(title="Multimodal AI System (Lazy Loaded)") as demo:
350
- gr.Markdown("# **Multimodal AI System**")
351
-
352
- with gr.Row():
353
- image_input = gr.Image(type="pil", label="Upload Image")
354
- question_input = gr.Textbox(label="Ask a Question")
355
-
356
- style_input = gr.Dropdown(
357
- [
358
- "Short",
359
- "Creative",
360
- "Technical",
361
- "Humorous",
362
- "Poetic",
363
- "Cinematic",
364
- "Journalistic",
365
- "Academic"
366
- ],
367
- label="Rewrite Style"
368
- )
369
-
370
- # New: length slider
371
- length_slider = gr.Slider(
372
- minimum=20,
373
- maximum=200,
374
- value=80,
375
- step=10,
376
- label="Rewrite Length (Max Tokens)"
377
- )
378
-
379
- run_btn = gr.Button("Run All Tools")
380
-
381
- caption = gr.Textbox(label="Generated Caption")
382
- sentiment_label = gr.Textbox(label="Sentiment Label")
383
- sentiment_score = gr.Number(label="Sentiment Score")
384
- vqa_output = gr.Textbox(label="VQA Answer")
385
- objects_output = gr.JSON(label="Detected Objects")
386
- scene_output = gr.Textbox(label="Scene Classification")
387
- rewritten_output = gr.Textbox(label="Rewritten Caption")
388
- metadata_output = gr.Textbox(label="Image Metadata")
389
-
390
- run_btn.click(
391
- process_all,
392
- [image_input, question_input, style_input, length_slider],
393
- [
394
- caption,
395
- sentiment_label,
396
- sentiment_score,
397
- vqa_output,
398
- objects_output,
399
- scene_output,
400
- rewritten_output,
401
- metadata_output
402
- ]
403
- )
404
-
405
 
406
  if __name__ == "__main__":
407
  demo.launch()
 
 
 
 
 
 
 
1
  import gradio as gr
2
+ from transformers import pipeline
3
 
4
+ # Load image captioning pipeline
5
+ captioner = pipeline("image-to-text", model="nlpconnect/vit-gpt2-image-captioning")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
 
7
  def generate_caption(image):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  if image is None:
9
+ return "Please upload an image."
10
+ result = captioner(image)
11
+ return result[0]['generated_text']
12
+
13
+ demo = gr.Interface(
14
+ fn=generate_caption,
15
+ inputs=gr.Image(type="pil", label="Upload an image"),
16
+ outputs=gr.Textbox(label="Generated Caption"),
17
+ title="Image Captioning Demo",
18
+ description="Multimodal model: Vision → Language"
19
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
  if __name__ == "__main__":
22
  demo.launch()
requirements.txt CHANGED
@@ -1,11 +1,4 @@
1
- torch
2
- torchvision
3
- transformers
4
- timm
5
  gradio
 
 
6
  Pillow
7
- numpy
8
- scipy
9
- accelerate
10
- pycocotools
11
- exifread
 
 
 
 
 
1
  gradio
2
+ transformers
3
+ torch
4
  Pillow