1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import { Hono } from "https://deno.land/x/hono@v3.11.7/mod.ts";
import { marked } from "npm:marked";
const intro = `
# Gemini AI Object Detection and Bounding Box Visualizer
This application visualizes object detection results by drawing bounding boxes on images using the [Google's Gemini 1.5 Pro AI model](https://ai.google.dev/).
Try using this image of 8 bananas, with 1 row and 1/2/4 columns: [https://f2.phage.directory/blogalog/bananas-8.png](https://f2.phage.directory/blogalog/bananas-8.png)
Or this image of a phage plaque assay, with 3x3 grid and high contrast turned on: [https://f2.phage.directory/blogalog/pae7.png](https://f2.phage.directory/blogalog/pae7.png)
This app is an adaptation of Simon Willison's idea, which you can read more about here: [https://simonwillison.net/2024/Aug/26/gemini-bounding-box-visualization/](https://simonwillison.net/2024/Aug/26/gemini-bounding-box-visualization/)
- API keys are only stored in your browser's local storage.
- Source code: [https://www.val.town/v/yawnxyz/geminiBbox/](https://www.val.town/v/yawnxyz/geminiBbox/)
- Original Source: https://github.com/simonw/tools/blob/main/gemini-bbox.html
`;
const markdownJsonRegex = /```json\n([\s\S]*?)```/;
export const module = `<script type="module">
import { GoogleGenerativeAI } from "https://esm.run/@google/generative-ai";
import { marked } from "https://esm.run/marked";
function getApiKey() {
let apiKey = localStorage.getItem("GEMINI_API_KEY");
if (!apiKey) {
apiKey = prompt("Please enter your Gemini API key:");
if (apiKey) {
localStorage.setItem("GEMINI_API_KEY", apiKey);
}
}
return apiKey;
}
async function getGenerativeModel(params) {
const API_KEY = getApiKey();
const genAI = new GoogleGenerativeAI(API_KEY);
return genAI.getGenerativeModel(params);
}
async function fileToGenerativePart(file) {
return new Promise((resolve) => {
const reader = new FileReader();
reader.onloadend = () => resolve({
inlineData: {
data: reader.result.split(",")[1],
mimeType: file.type
}
});
reader.readAsDataURL(file);
});
}
function applyHighContrast(canvas) {
const ctx = canvas.getContext('2d');
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
const darkThreshold = parseInt(document.getElementById('darkThresholdInput').value);
const lightThreshold = parseInt(document.getElementById('lightThresholdInput').value);
for (let i = 0; i < data.length; i += 4) {
const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
if (avg < darkThreshold) {
// Make dark gray things more black
data[i] = data[i + 1] = data[i + 2] = 0;
} else if (avg > lightThreshold) {
// Make light gray things go away (white)
data[i] = data[i + 1] = data[i + 2] = 255;
}
// Leave mid-range values unchanged
}
ctx.putImageData(imageData, 0, 0);
}
function resizeAndCompressImage(file, maxWidth = 1000) {
return new Promise((resolve) => {
const reader = new FileReader();
reader.onload = function(event) {
const img = new Image();
img.onload = function() {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
let width = img.width;
let height = img.height;
if (width > maxWidth) {
height = Math.round((height * maxWidth) / width);
width = maxWidth;
}
canvas.width = width;
canvas.height = height;
ctx.drawImage(img, 0, 0, width, height);