diff --git a/package-lock.json b/package-lock.json index 2ee60df..8f6844e 100755 --- a/package-lock.json +++ b/package-lock.json @@ -11,8 +11,9 @@ "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-progress": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", + "@tensorflow-models/coco-ssd": "^2.2.3", "@tensorflow-models/mobilenet": "^2.1.1", - "@tensorflow/tfjs": "^4.21.0", + "@tensorflow/tfjs": "^4.22.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "lucide-react": "^0.452.0", @@ -538,6 +539,15 @@ "tslib": "^2.4.0" } }, + "node_modules/@tensorflow-models/coco-ssd": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@tensorflow-models/coco-ssd/-/coco-ssd-2.2.3.tgz", + "integrity": "sha512-iCLGktG/XhHbP6h2FWxqCKMp/Px0lCp6MZU1fjNhjDHeaWEC9G7S7cZrnPXsfH+NewCM53YShlrHnknxU3SQig==", + "peerDependencies": { + "@tensorflow/tfjs-converter": "^4.10.0", + "@tensorflow/tfjs-core": "^4.10.0" + } + }, "node_modules/@tensorflow-models/mobilenet": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@tensorflow-models/mobilenet/-/mobilenet-2.1.1.tgz", @@ -548,16 +558,16 @@ } }, "node_modules/@tensorflow/tfjs": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs/-/tfjs-4.21.0.tgz", - "integrity": "sha512-7D/+H150ptvt+POMbsME3WlIvLiuBR2rCC2Z0hOKKb/5Ygkj7xsp/K2HzOvUj0g0yjk+utkU45QEYhnhjnbHRA==", - "dependencies": { - "@tensorflow/tfjs-backend-cpu": "4.21.0", - "@tensorflow/tfjs-backend-webgl": "4.21.0", - "@tensorflow/tfjs-converter": "4.21.0", - "@tensorflow/tfjs-core": "4.21.0", - "@tensorflow/tfjs-data": "4.21.0", - "@tensorflow/tfjs-layers": "4.21.0", + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs/-/tfjs-4.22.0.tgz", + "integrity": "sha512-0TrIrXs6/b7FLhLVNmfh8Sah6JgjBPH4mZ8JGb7NU6WW+cx00qK5BcAZxw7NCzxj6N8MRAIfHq+oNbPUNG5VAg==", + "dependencies": { + "@tensorflow/tfjs-backend-cpu": "4.22.0", + "@tensorflow/tfjs-backend-webgl": "4.22.0", + "@tensorflow/tfjs-converter": "4.22.0", + "@tensorflow/tfjs-core": "4.22.0", + "@tensorflow/tfjs-data": "4.22.0", + "@tensorflow/tfjs-layers": "4.22.0", "argparse": "^1.0.10", "chalk": "^4.1.0", "core-js": "3.29.1", @@ -568,10 +578,43 @@ "tfjs-custom-module": "dist/tools/custom_module/cli.js" } }, - "node_modules/@tensorflow/tfjs-backend-cpu": { + "node_modules/@tensorflow/tfjs-converter": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-4.21.0.tgz", + "integrity": "sha512-cUhU+F1lGx2qnKk/gRy8odBh0PZlFz0Dl71TG8LVnj0/g352DqiNrKXlKO/po9aWzP8x0KUGC3gNMSMJW+T0DA==", + "peer": true, + "peerDependencies": { + "@tensorflow/tfjs-core": "4.21.0" + } + }, + "node_modules/@tensorflow/tfjs-core": { "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-4.21.0.tgz", - "integrity": "sha512-yS9Oisg4L48N7ML6677ilv1eP5Jt59S74skSU1cCsM4yBAtH4DAn9b89/JtqBISh6JadanfX26b4HCWQvMvqFg==", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-4.21.0.tgz", + "integrity": "sha512-ZbECwXps5wb9XXcGq4ZXvZDVjr5okc3I0+i/vU6bpQ+nVApyIrMiyEudP8f6vracVTvNmnlN62vUXoEsQb2F8g==", + "peer": true, + "dependencies": { + "@types/long": "^4.0.1", + "@types/offscreencanvas": "~2019.7.0", + "@types/seedrandom": "^2.4.28", + "@webgpu/types": "0.1.38", + "long": "4.0.0", + "node-fetch": "~2.6.1", + "seedrandom": "^3.0.5" + }, + "engines": { + "yarn": ">= 1.3.2" + } + }, + "node_modules/@tensorflow/tfjs-core/node_modules/@types/offscreencanvas": { + "version": "2019.7.3", + "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz", + "integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==", + "peer": true + }, + "node_modules/@tensorflow/tfjs/node_modules/@tensorflow/tfjs-backend-cpu": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-4.22.0.tgz", + "integrity": "sha512-1u0FmuLGuRAi8D2c3cocHTASGXOmHc/4OvoVDENJayjYkS119fcTcQf4iHrtLthWyDIPy3JiPhRrZQC9EwnhLw==", "dependencies": { "@types/seedrandom": "^2.4.28", "seedrandom": "^3.0.5" @@ -580,15 +623,15 @@ "yarn": ">= 1.3.2" }, "peerDependencies": { - "@tensorflow/tfjs-core": "4.21.0" + "@tensorflow/tfjs-core": "4.22.0" } }, - "node_modules/@tensorflow/tfjs-backend-webgl": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-4.21.0.tgz", - "integrity": "sha512-7k6mb7dd0uF9jI51iunF3rhEXjvR/a613kjWZ0Rj3o1COFrneyku2C7cRMZERWPhbgXZ+dF+j9MdpGIpgtShIQ==", + "node_modules/@tensorflow/tfjs/node_modules/@tensorflow/tfjs-backend-webgl": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-4.22.0.tgz", + "integrity": "sha512-H535XtZWnWgNwSzv538czjVlbJebDl5QTMOth4RXr2p/kJ1qSIXE0vZvEtO+5EC9b00SvhplECny2yDewQb/Yg==", "dependencies": { - "@tensorflow/tfjs-backend-cpu": "4.21.0", + "@tensorflow/tfjs-backend-cpu": "4.22.0", "@types/offscreencanvas": "~2019.3.0", "@types/seedrandom": "^2.4.28", "seedrandom": "^3.0.5" @@ -597,21 +640,21 @@ "yarn": ">= 1.3.2" }, "peerDependencies": { - "@tensorflow/tfjs-core": "4.21.0" + "@tensorflow/tfjs-core": "4.22.0" } }, - "node_modules/@tensorflow/tfjs-converter": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-4.21.0.tgz", - "integrity": "sha512-cUhU+F1lGx2qnKk/gRy8odBh0PZlFz0Dl71TG8LVnj0/g352DqiNrKXlKO/po9aWzP8x0KUGC3gNMSMJW+T0DA==", + "node_modules/@tensorflow/tfjs/node_modules/@tensorflow/tfjs-converter": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-4.22.0.tgz", + "integrity": "sha512-PT43MGlnzIo+YfbsjM79Lxk9lOq6uUwZuCc8rrp0hfpLjF6Jv8jS84u2jFb+WpUeuF4K33ZDNx8CjiYrGQ2trQ==", "peerDependencies": { - "@tensorflow/tfjs-core": "4.21.0" + "@tensorflow/tfjs-core": "4.22.0" } }, - "node_modules/@tensorflow/tfjs-core": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-4.21.0.tgz", - "integrity": "sha512-ZbECwXps5wb9XXcGq4ZXvZDVjr5okc3I0+i/vU6bpQ+nVApyIrMiyEudP8f6vracVTvNmnlN62vUXoEsQb2F8g==", + "node_modules/@tensorflow/tfjs/node_modules/@tensorflow/tfjs-core": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-4.22.0.tgz", + "integrity": "sha512-LEkOyzbknKFoWUwfkr59vSB68DMJ4cjwwHgicXN0DUi3a0Vh1Er3JQqCI1Hl86GGZQvY8ezVrtDIvqR1ZFW55A==", "dependencies": { "@types/long": "^4.0.1", "@types/offscreencanvas": "~2019.7.0", @@ -625,31 +668,31 @@ "yarn": ">= 1.3.2" } }, - "node_modules/@tensorflow/tfjs-core/node_modules/@types/offscreencanvas": { + "node_modules/@tensorflow/tfjs/node_modules/@tensorflow/tfjs-core/node_modules/@types/offscreencanvas": { "version": "2019.7.3", "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz", "integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==" }, - "node_modules/@tensorflow/tfjs-data": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-data/-/tfjs-data-4.21.0.tgz", - "integrity": "sha512-LpJ/vyQMwYHkcVCqIRg7IVVw13VBY7rNAiuhmKP9S5NP/2ye4KA8BJ4XwDIDgjCVQM7glK9L8bMav++xCDf7xA==", + "node_modules/@tensorflow/tfjs/node_modules/@tensorflow/tfjs-data": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-data/-/tfjs-data-4.22.0.tgz", + "integrity": "sha512-dYmF3LihQIGvtgJrt382hSRH4S0QuAp2w1hXJI2+kOaEqo5HnUPG0k5KA6va+S1yUhx7UBToUKCBHeLHFQRV4w==", "dependencies": { "@types/node-fetch": "^2.1.2", "node-fetch": "~2.6.1", "string_decoder": "^1.3.0" }, "peerDependencies": { - "@tensorflow/tfjs-core": "4.21.0", + "@tensorflow/tfjs-core": "4.22.0", "seedrandom": "^3.0.5" } }, - "node_modules/@tensorflow/tfjs-layers": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-layers/-/tfjs-layers-4.21.0.tgz", - "integrity": "sha512-a8KaMYlY3+llvE9079nvASKpaaf8xpCMdOjbgn+eGhdOGOcY7QuFUkd/2odvnXDG8fK/jffE1LoNOlfYoBHC4w==", + "node_modules/@tensorflow/tfjs/node_modules/@tensorflow/tfjs-layers": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-layers/-/tfjs-layers-4.22.0.tgz", + "integrity": "sha512-lybPj4ZNj9iIAPUj7a8ZW1hg8KQGfqWLlCZDi9eM/oNKCCAgchiyzx8OrYoWmRrB+AM6VNEeIT+2gZKg5ReihA==", "peerDependencies": { - "@tensorflow/tfjs-core": "4.21.0" + "@tensorflow/tfjs-core": "4.22.0" } }, "node_modules/@tensorflow/tfjs/node_modules/argparse": { @@ -672,11 +715,11 @@ "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" }, "node_modules/@types/node": { - "version": "22.7.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", - "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", + "version": "22.8.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.6.tgz", + "integrity": "sha512-tosuJYKrIqjQIlVCM4PEGxOmyg3FCPa/fViuJChnGeEIhjA46oy8FMVoF9su1/v8PNs2a8Q0iFNyOx0uOF91nw==", "dependencies": { - "undici-types": "~6.19.2" + "undici-types": "~6.19.8" } }, "node_modules/@types/node-fetch": { diff --git a/package.json b/package.json index 5291d9e..6d9f202 100755 --- a/package.json +++ b/package.json @@ -12,8 +12,9 @@ "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-progress": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", + "@tensorflow-models/coco-ssd": "^2.2.3", "@tensorflow-models/mobilenet": "^2.1.1", - "@tensorflow/tfjs": "^4.21.0", + "@tensorflow/tfjs": "^4.22.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "lucide-react": "^0.452.0", diff --git a/src/app/coco-ssd/layout.js b/src/app/coco-ssd/layout.js new file mode 100644 index 0000000..a0bb1b0 --- /dev/null +++ b/src/app/coco-ssd/layout.js @@ -0,0 +1,13 @@ +export const metadata = { + title: "Tensor Hub", + description: + "A central hub for AI-powered analysis using TensorFlow.js models", +}; + +export default function RootLayout({ children }) { + return ( + + {children} + + ); +} diff --git a/src/app/coco-ssd/page.js b/src/app/coco-ssd/page.js new file mode 100644 index 0000000..cd9a0f9 --- /dev/null +++ b/src/app/coco-ssd/page.js @@ -0,0 +1,195 @@ +"use client"; + +import { useState, useEffect, useRef } from "react"; +import * as cocoSsd from "@tensorflow-models/coco-ssd"; +import "@tensorflow/tfjs"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { Progress } from "@/components/ui/progress"; +import { Upload, Zap } from "lucide-react"; + +export default function ObjectDetection() { + const canvasRef = useRef(null); + const imageRef = useRef(null); + const [model, setModel] = useState(null); + const [uploadedImage, setUploadedImage] = useState(null); + const [detectedObjects, setDetectedObjects] = useState([]); + const [loadingState, setLoadingState] = useState({ + loading: true, + progress: 0, + }); + const [error, setError] = useState(null); + + useEffect(() => { + const loadModel = async () => { + try { + setLoadingState({ loading: true, progress: 10 }); + const loadedModel = await cocoSsd.load(); + setModel(loadedModel); + setLoadingState({ loading: false, progress: 100 }); + } catch (err) { + setError("Failed to load model."); + setLoadingState({ loading: false, progress: 100 }); + } + }; + loadModel(); + }, []); + + const handleFileChange = (event) => { + const file = event.target.files?.[0]; + if (file) { + const reader = new FileReader(); + reader.onload = () => setUploadedImage(reader.result); + reader.readAsDataURL(file); + } + }; + + const drawDetections = (ctx, detections) => { + ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height); + ctx.drawImage( + imageRef.current, + 0, + 0, + canvasRef.current.width, + canvasRef.current.height + ); + + detections.forEach(({ bbox: [x, y, width, height], class: className }) => { + ctx.strokeStyle = "green"; + ctx.lineWidth = 2; + ctx.strokeRect(x, y, width, height); + ctx.fillStyle = "green"; + ctx.font = "16px Arial"; + ctx.fillText(className, x, y > 10 ? y - 5 : 10); + }); + }; + + const startDetection = async () => { + if (!model) return setError("Model not loaded yet."); + if (!imageRef.current) return setError("No image selected."); + + const ctx = canvasRef.current.getContext("2d"); + if (!ctx) return; + + setLoadingState({ loading: true, progress: 0 }); + try { + const detections = await model.detect(imageRef.current); + setDetectedObjects(detections); + drawDetections(ctx, detections); + } catch (err) { + setError("Failed to perform detection."); + } finally { + setLoadingState({ loading: false, progress: 100 }); + } + }; + + return ( + + + + Object Detection + + + + {loadingState.loading && loadingState.progress < 100 && ( +
+

Loading model...

+ +
+ )} + + {error && ( + + Error + {error} + + )} + +
+ + +
+ {uploadedImage && ( + <> + Uploaded + + + )} +
+
+ + {detectedObjects.length > 0 && ( + + )} +
+
+ ); +} + +const ImageUploader = ({ handleFileChange, startDetection, isDisabled }) => ( +
+
+ +
+ + +
+); + +const DetectedObjectsList = ({ detectedObjects }) => ( +
+

Detected Objects:

+ +
+); diff --git a/src/components/ui/alert.jsx b/src/components/ui/alert.jsx new file mode 100644 index 0000000..28597e8 --- /dev/null +++ b/src/components/ui/alert.jsx @@ -0,0 +1,47 @@ +import * as React from "react" +import { cva } from "class-variance-authority"; + +import { cn } from "@/lib/utils" + +const alertVariants = cva( + "relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7", + { + variants: { + variant: { + default: "bg-background text-foreground", + destructive: + "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +const Alert = React.forwardRef(({ className, variant, ...props }, ref) => ( +
+)) +Alert.displayName = "Alert" + +const AlertTitle = React.forwardRef(({ className, ...props }, ref) => ( +
+)) +AlertTitle.displayName = "AlertTitle" + +const AlertDescription = React.forwardRef(({ className, ...props }, ref) => ( +
+)) +AlertDescription.displayName = "AlertDescription" + +export { Alert, AlertTitle, AlertDescription }