techresearchspace commited on
Commit
b8bc0f0
·
verified ·
1 Parent(s): 8605ade

Upload 2 files

Browse files

Fixed issue with Q&A

Files changed (2) hide show
  1. README.md +15 -5
  2. index.html +111 -32
README.md CHANGED
@@ -8,7 +8,7 @@ pinned: false
8
  license: mit
9
  models:
10
  - Xenova/vit-gpt2-image-captioning
11
- - Xenova/blip-vqa-base
12
  - Xenova/clip-vit-base-patch32
13
  - Xenova/trocr-small-printed
14
  ---
@@ -28,7 +28,7 @@ acceleration when the browser supports it.
28
  | Tab | Task | Model |
29
  |---|---|---|
30
  | Caption | Image → natural-language description | `Xenova/vit-gpt2-image-captioning` |
31
- | Visual Q&A | Image + free-form question answer | `Xenova/blip-vqa-base` |
32
  | Classify | Zero-shot classification against any labels you define | `Xenova/clip-vit-base-patch32` |
33
  | Read Text | OCR on printed text in an image | `Xenova/trocr-small-printed` |
34
  | Semantic Search | Rank a gallery of images against a natural-language query | `Xenova/clip-vit-base-patch32` (shared) |
@@ -66,6 +66,16 @@ python -m http.server 8000
66
  ## Extending it
67
 
68
  Ideas for going further: add a webcam capture option alongside file upload,
69
- swap in a larger VQA or captioning model for better quality (trading off
70
- download size), or add an "export gallery as JSON" button to persist search
71
- results across sessions.
 
 
 
 
 
 
 
 
 
 
 
8
  license: mit
9
  models:
10
  - Xenova/vit-gpt2-image-captioning
11
+ - Xenova/owlvit-base-patch32
12
  - Xenova/clip-vit-base-patch32
13
  - Xenova/trocr-small-printed
14
  ---
 
28
  | Tab | Task | Model |
29
  |---|---|---|
30
  | Caption | Image → natural-language description | `Xenova/vit-gpt2-image-captioning` |
31
+ | Detect Objects | Draws bounding boxes for any labels you define, even ones the model wasn't explicitly trained to detect | `Xenova/owlvit-base-patch32` |
32
  | Classify | Zero-shot classification against any labels you define | `Xenova/clip-vit-base-patch32` |
33
  | Read Text | OCR on printed text in an image | `Xenova/trocr-small-printed` |
34
  | Semantic Search | Rank a gallery of images against a natural-language query | `Xenova/clip-vit-base-patch32` (shared) |
 
66
  ## Extending it
67
 
68
  Ideas for going further: add a webcam capture option alongside file upload,
69
+ swap in a larger captioning model for better quality (trading off download
70
+ size), or add an "export gallery as JSON" button to persist search results
71
+ across sessions.
72
+
73
+ ## A note on transformers.js task coverage
74
+
75
+ Not every task in the Python `transformers` library has a browser
76
+ equivalent. Visual question answering, for instance, isn't currently a
77
+ supported transformers.js pipeline — only its listed NLP, vision, audio,
78
+ and multimodal tasks (embeddings, zero-shot classification/detection) run
79
+ client-side. Object detection was used here instead of VQA for exactly that
80
+ reason: it's a confirmed, supported multimodal task that still combines
81
+ free-form text labels with an image.
index.html CHANGED
@@ -301,6 +301,32 @@
301
  100% { top: 4%; }
302
  }
303
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
304
  .vf-replace {
305
  position: absolute;
306
  bottom: 10px;
@@ -599,7 +625,7 @@
599
  <div class="layout">
600
  <nav class="tabs" id="tabNav">
601
  <button class="tab-btn active" data-tab="caption"><span class="idx">01</span> Caption</button>
602
- <button class="tab-btn" data-tab="vqa"><span class="idx">02</span> Visual Q&amp;A</button>
603
  <button class="tab-btn" data-tab="classify"><span class="idx">03</span> Classify</button>
604
  <button class="tab-btn" data-tab="ocr"><span class="idx">04</span> Read Text</button>
605
  <button class="tab-btn" data-tab="search"><span class="idx">05</span> Semantic Search</button>
@@ -622,6 +648,7 @@
622
  <div class="bracket bl"></div>
623
  <div class="bracket br"></div>
624
  <div class="scanline" id="scanline"></div>
 
625
  <div class="vf-replace">click to replace</div>
626
  </div>
627
  </div>
@@ -645,21 +672,21 @@
645
  </div>
646
  </section>
647
 
648
- <!-- Panel: VQA -->
649
- <section class="panel" data-panel="vqa">
650
  <div class="panel-head">
651
- <h2>Visual Question Answering</h2>
652
- <p>Ask a free-form question about the image; the model fuses the visual and text encoders to produce an answer.</p>
653
- <span class="model-tag">model: Xenova/blip-vqa-base</span>
654
  </div>
655
  <div class="controls">
656
- <input type="text" id="vqaQuestion" placeholder="What color is the object in the center?" />
657
- <button class="run" id="btnVqa" disabled>Ask</button>
658
  </div>
659
  <div class="output">
660
- <div class="output-card" id="outVqa">
661
- <div class="output-label">Answer</div>
662
- <div class="output-text" id="vqaText"></div>
663
  </div>
664
  </div>
665
  </section>
@@ -795,7 +822,7 @@
795
  /* ---------------- Model registry ---------------- */
796
  const MODEL_SPECS = {
797
  caption: { task: "image-to-text", model: "Xenova/vit-gpt2-image-captioning" },
798
- vqa: { task: "visual-question-answering", model: "Xenova/blip-vqa-base" },
799
  classify: { task: "zero-shot-image-classification", model: "Xenova/clip-vit-base-patch32" },
800
  ocr: { task: "image-to-text", model: "Xenova/trocr-small-printed" },
801
  };
@@ -865,7 +892,7 @@
865
  let currentImageURL = null;
866
  const actionButtons = [
867
  document.getElementById("btnCaption"),
868
- document.getElementById("btnVqa"),
869
  document.getElementById("btnClassify"),
870
  document.getElementById("btnOcr"),
871
  ];
@@ -876,6 +903,7 @@
876
  vfEmpty.style.display = "none";
877
  vfImageWrap.style.display = "block";
878
  actionButtons.forEach((b) => (b.disabled = false));
 
879
  log("Image loaded into viewfinder");
880
  }
881
 
@@ -943,31 +971,82 @@
943
  }
944
  });
945
 
946
- /* ---------------- VQA ---------------- */
947
- const btnVqa = document.getElementById("btnVqa");
948
- const vqaQuestion = document.getElementById("vqaQuestion");
949
- const outVqa = document.getElementById("outVqa");
950
- const vqaText = document.getElementById("vqaText");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
951
 
952
- btnVqa.addEventListener("click", async () => {
 
 
 
 
 
 
953
  if (!currentImageURL) return;
954
- const question = vqaQuestion.value.trim() || "What is in this image?";
955
- btnVqa.disabled = true;
 
 
 
 
956
  withScan(true);
957
- setBusy(true, "answering…");
958
  try {
959
- const vqa = await getPipeline("vqa");
960
- const result = await vqa(currentImageURL, question);
961
- const answer = Array.isArray(result) ? result[0]?.answer : result?.answer;
962
- vqaText.textContent = answer || "(no answer produced)";
963
- outVqa.classList.add("show");
964
- log(`VQA answered: "${question}"`, "ok");
 
 
 
 
 
 
 
965
  } catch (err) {
966
- vqaText.textContent = "Error answering question — see console.";
967
- outVqa.classList.add("show");
968
- log(`VQA error: ${err.message || err}`, "err");
969
  } finally {
970
- btnVqa.disabled = false;
971
  withScan(false);
972
  setBusy(false);
973
  }
 
301
  100% { top: 4%; }
302
  }
303
 
304
+ .detect-boxes {
305
+ position: absolute;
306
+ inset: 0;
307
+ pointer-events: none;
308
+ }
309
+ .detect-box {
310
+ position: absolute;
311
+ border: 2px solid var(--coral);
312
+ box-shadow: 0 0 0 1px rgba(0,0,0,0.5);
313
+ border-radius: 2px;
314
+ }
315
+ .detect-box .tag {
316
+ position: absolute;
317
+ top: -20px;
318
+ left: -2px;
319
+ background: var(--coral);
320
+ color: #1a0a06;
321
+ font-family: var(--mono);
322
+ font-size: 10px;
323
+ font-weight: 600;
324
+ padding: 1px 6px;
325
+ border-radius: 3px;
326
+ white-space: nowrap;
327
+ }
328
+ .detect-box.tag-inside .tag { top: 2px; left: 2px; }
329
+
330
  .vf-replace {
331
  position: absolute;
332
  bottom: 10px;
 
625
  <div class="layout">
626
  <nav class="tabs" id="tabNav">
627
  <button class="tab-btn active" data-tab="caption"><span class="idx">01</span> Caption</button>
628
+ <button class="tab-btn" data-tab="detect"><span class="idx">02</span> Detect Objects</button>
629
  <button class="tab-btn" data-tab="classify"><span class="idx">03</span> Classify</button>
630
  <button class="tab-btn" data-tab="ocr"><span class="idx">04</span> Read Text</button>
631
  <button class="tab-btn" data-tab="search"><span class="idx">05</span> Semantic Search</button>
 
648
  <div class="bracket bl"></div>
649
  <div class="bracket br"></div>
650
  <div class="scanline" id="scanline"></div>
651
+ <div class="detect-boxes" id="detectBoxes"></div>
652
  <div class="vf-replace">click to replace</div>
653
  </div>
654
  </div>
 
672
  </div>
673
  </section>
674
 
675
+ <!-- Panel: Detect -->
676
+ <section class="panel" data-panel="detect">
677
  <div class="panel-head">
678
+ <h2>Zero-Shot Object Detection</h2>
679
+ <p>Define any set of objects to look for — the model locates and draws bounding boxes around them in the image above, even for categories it wasn't explicitly trained to detect.</p>
680
+ <span class="model-tag">model: Xenova/owlvit-base-patch32</span>
681
  </div>
682
  <div class="controls">
683
+ <input type="text" id="detectLabels" placeholder="person, dog, car, tree, building" value="person, dog, car, tree, building" />
684
+ <button class="run" id="btnDetect" disabled>Detect objects</button>
685
  </div>
686
  <div class="output">
687
+ <div class="output-card" id="outDetect">
688
+ <div class="output-label">Detections</div>
689
+ <div id="detectList"></div>
690
  </div>
691
  </div>
692
  </section>
 
822
  /* ---------------- Model registry ---------------- */
823
  const MODEL_SPECS = {
824
  caption: { task: "image-to-text", model: "Xenova/vit-gpt2-image-captioning" },
825
+ detect: { task: "zero-shot-object-detection", model: "Xenova/owlvit-base-patch32" },
826
  classify: { task: "zero-shot-image-classification", model: "Xenova/clip-vit-base-patch32" },
827
  ocr: { task: "image-to-text", model: "Xenova/trocr-small-printed" },
828
  };
 
892
  let currentImageURL = null;
893
  const actionButtons = [
894
  document.getElementById("btnCaption"),
895
+ document.getElementById("btnDetect"),
896
  document.getElementById("btnClassify"),
897
  document.getElementById("btnOcr"),
898
  ];
 
903
  vfEmpty.style.display = "none";
904
  vfImageWrap.style.display = "block";
905
  actionButtons.forEach((b) => (b.disabled = false));
906
+ clearDetectionBoxes();
907
  log("Image loaded into viewfinder");
908
  }
909
 
 
971
  }
972
  });
973
 
974
+ /* ---------------- Detect (zero-shot object detection) ---------------- */
975
+ const btnDetect = document.getElementById("btnDetect");
976
+ const detectLabels = document.getElementById("detectLabels");
977
+ const outDetect = document.getElementById("outDetect");
978
+ const detectList = document.getElementById("detectList");
979
+ const detectBoxesEl = document.getElementById("detectBoxes");
980
+ let lastDetections = [];
981
+
982
+ function clearDetectionBoxes() {
983
+ lastDetections = [];
984
+ detectBoxesEl.innerHTML = "";
985
+ }
986
+
987
+ function renderDetectionBoxes() {
988
+ detectBoxesEl.innerHTML = "";
989
+ if (!lastDetections.length || !vfImage.naturalWidth) return;
990
+ const containerW = vfImageWrap.clientWidth;
991
+ const containerH = vfImageWrap.clientHeight;
992
+ const naturalW = vfImage.naturalWidth;
993
+ const naturalH = vfImage.naturalHeight;
994
+ const scale = Math.min(containerW / naturalW, containerH / naturalH);
995
+ const renderW = naturalW * scale;
996
+ const renderH = naturalH * scale;
997
+ const offsetX = (containerW - renderW) / 2;
998
+ const offsetY = (containerH - renderH) / 2;
999
+
1000
+ lastDetections.forEach((d) => {
1001
+ const box = document.createElement("div");
1002
+ const top = offsetY + d.box.ymin * scale;
1003
+ box.className = "detect-box" + (top < 22 ? " tag-inside" : "");
1004
+ box.style.left = (offsetX + d.box.xmin * scale) + "px";
1005
+ box.style.top = top + "px";
1006
+ box.style.width = ((d.box.xmax - d.box.xmin) * scale) + "px";
1007
+ box.style.height = ((d.box.ymax - d.box.ymin) * scale) + "px";
1008
+ const tag = document.createElement("div");
1009
+ tag.className = "tag";
1010
+ tag.textContent = `${d.label} ${(d.score * 100).toFixed(0)}%`;
1011
+ box.appendChild(tag);
1012
+ detectBoxesEl.appendChild(box);
1013
+ });
1014
+ }
1015
 
1016
+ let resizeTimer;
1017
+ window.addEventListener("resize", () => {
1018
+ clearTimeout(resizeTimer);
1019
+ resizeTimer = setTimeout(renderDetectionBoxes, 120);
1020
+ });
1021
+
1022
+ btnDetect.addEventListener("click", async () => {
1023
  if (!currentImageURL) return;
1024
+ const labels = detectLabels.value.split(",").map((s) => s.trim()).filter(Boolean);
1025
+ if (!labels.length) {
1026
+ log("Enter at least one label to detect", "warn");
1027
+ return;
1028
+ }
1029
+ btnDetect.disabled = true;
1030
  withScan(true);
1031
+ setBusy(true, "detecting objects…");
1032
  try {
1033
+ const detector = await getPipeline("detect");
1034
+ const output = await detector(currentImageURL, labels, { threshold: 0.12, top_k: 12 });
1035
+ lastDetections = output;
1036
+ renderDetectionBoxes();
1037
+
1038
+ detectList.innerHTML = "";
1039
+ if (!output.length) {
1040
+ detectList.innerHTML = `<div class="output-text" style="font-size:14px; color: var(--ink-dim);">No objects matched those labels above the confidence threshold.</div>`;
1041
+ } else {
1042
+ renderBars(detectList, [...output].sort((a, b) => b.score - a.score));
1043
+ }
1044
+ outDetect.classList.add("show");
1045
+ log(`Detected ${output.length} object(s) across ${labels.length} label(s)`, "ok");
1046
  } catch (err) {
1047
+ log(`Detect error: ${err.message || err}`, "err");
 
 
1048
  } finally {
1049
+ btnDetect.disabled = false;
1050
  withScan(false);
1051
  setBusy(false);
1052
  }