diff --git a/api/src/routes/imageRoutes.ts b/api/src/routes/imageRoutes.ts index efbea34..6dbc862 100644 --- a/api/src/routes/imageRoutes.ts +++ b/api/src/routes/imageRoutes.ts @@ -1,6 +1,7 @@ import { Router } from "express"; import { ImageController } from "../controllers/imageController"; import { upload } from "../middlewares/multerConfig"; +import { ImageService } from "../services/imageService"; const router = Router(); @@ -12,4 +13,14 @@ router.post("/upload", upload.single("image"), async (req, res) => { } }); +// In your imageRoutes.ts +router.get("/", async (req, res) => { + try { + const images = await ImageService.getAllImages(); // Implement this method in your ImageService + res.status(200).json(images); + } catch (error) { + res.status(500).json({ message: "Error fetching images", error: error }); + } +}); + export default router; \ No newline at end of file diff --git a/api/src/services/imageService.ts b/api/src/services/imageService.ts index 38987af..447cf1d 100644 --- a/api/src/services/imageService.ts +++ b/api/src/services/imageService.ts @@ -24,4 +24,18 @@ export class ImageService { } // Optional: You can create other methods for fetching or managing images. + + static async getAllImages(): Promise { + const query = `SELECT * FROM images ORDER BY created_at DESC`; // Adjust the query as necessary + try { + const result = await pool.query(query); + return result.rows; // Return the array of images + } catch (error: unknown) { + if (error instanceof Error) { + throw new Error("Error fetching images from the database: " + error.message); + } else { + throw new Error("Unknown error occurred"); + } + } + } } diff --git a/app/package-lock.json b/app/package-lock.json index 2a8c55f..f424448 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -8,6 +8,7 @@ "name": "app", "version": "0.1.0", "dependencies": { + "@babel/plugin-proposal-private-property-in-object": "^7.16.7", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -672,10 +673,17 @@ } }, "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "version": "7.21.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz", + "integrity": "sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-property-in-object instead.", "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, "engines": { "node": ">=6.9.0" }, @@ -1982,6 +1990,18 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/preset-env/node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/preset-env/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", diff --git a/app/src/Pages/Annotate.tsx b/app/src/Pages/Annotate.tsx index d3f1378..5709c83 100644 --- a/app/src/Pages/Annotate.tsx +++ b/app/src/Pages/Annotate.tsx @@ -26,32 +26,76 @@ const Annotate = () => { const handleUpload = async () => { if (!selectedImage) return; - + setIsUploading(true); try { - // TODO: Implement actual backend upload - await new Promise(resolve => setTimeout(resolve, 1500)); // Simulate upload - setUploadedImage(selectedImage); - setSelectedImage(null); + const formData = new FormData(); + const blob = await fetch(selectedImage).then((res) => res.blob()); + formData.append('image', blob); + + const response = await fetch('http://127.0.0.1:5001/api/images/upload', { // Update the URL to match your backend + method: 'POST', + body: formData, + }); + + if (!response.ok) { + throw new Error(`Failed to upload. Status: ${response.status}`); + } + + const uploadData = await response.json(); + console.log('Fetched image metadata:', uploadData); + setUploadedImage(uploadData.image.url); // Save the uploaded image URL + alert(uploadData.message); // Display success message + + // Fetch uploaded image metadata + await fetchImageMetadata(); // Call the function to fetch metadata } catch (error) { console.error('Upload failed:', error); + alert(`Error: ${error}`); // Display error message } finally { setIsUploading(false); } }; + const fetchImageMetadata = async () => { + try { + const response = await fetch('http://127.0.0.1:5001/api/images'); // Update the URL to match your backend + if (!response.ok) { + throw new Error(`Failed to fetch metadata. Status: ${response.status}`); + } + const metadata = await response.json(); + // Assuming metadata is an array of images + console.log('Fetched image metadata:', metadata); + // You can store this metadata in state to display it in your component + } catch (error) { + console.error('Failed to fetch metadata:', error); + alert(`Error: ${error}`); // Display error message + } + }; + const handleDetection = async () => { if (!uploadedImage) return; - + setIsProcessing(true); try { - // TODO: Implement actual object detection - await new Promise(resolve => setTimeout(resolve, 2000)); // Simulate processing - // Sample annotations - setAnnotations([ - { x: 100, y: 100, width: 200, height: 150, label: 'Object 1' }, - { x: 400, y: 300, width: 150, height: 100, label: 'Object 2' }, - ]); + const formData = new FormData(); + const imageBlob = await fetch(uploadedImage).then(res => res.blob()); // Fetch the image as a blob + formData.append('image', imageBlob, 'image.jpg'); // Append the image blob to FormData + + const response = await fetch('http://127.0.0.1:5001/api/detect', { + method: 'POST', + body: formData, // Send the FormData containing the image + }); + + if (!response.ok) { + throw new Error(`Detection failed. Status: ${response.status}`); + } + + const detectionData = await response.json(); + console.log('Detection successful:', detectionData); + + // Assuming detectionData contains annotations + setAnnotations(detectionData.predictions); // Set the annotations from the response } catch (error) { console.error('Detection failed:', error); } finally { diff --git a/object-detector/app.py b/object-detector/app.py index 7cc3890..3685d88 100644 --- a/object-detector/app.py +++ b/object-detector/app.py @@ -1,29 +1,75 @@ -from flask import Flask, request, jsonify +from flask import Flask, request, jsonify, send_from_directory, url_for +import os import torch from PIL import Image from io import BytesIO +from flask_cors import CORS app = Flask(__name__) +CORS(app) + +CORS(app, resources={r"/api/*": {"origins": "http://localhost:3000"}}) + +# Ensure you have a directory to save uploaded images +UPLOAD_FOLDER = 'uploads' +os.makedirs(UPLOAD_FOLDER, exist_ok=True) + +# Set the folder to serve static files (images) +app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER +app.config['STATIC_FOLDER'] = 'static' # Load the YOLOv5 medium model (this will automatically download the weights) model = torch.hub.load('ultralytics/yolov5', 'yolov5m') -@app.route('/predict', methods=['POST']) -def predict(): +image_predictions = {} + +@app.route('/api/images/upload', methods=['POST']) +def upload_image(): # Get the image from the request file = request.files['image'] + if not file: + return jsonify({"message": "No file uploaded"}), 400 - # Open the image using PIL - img = Image.open(BytesIO(file.read())) - + # Save the image + image_path = os.path.join(UPLOAD_FOLDER, file.filename) + file.save(image_path) + # Run inference with the model on the uploaded image + img = Image.open(image_path) results = model(img) - - # Get the results as a pandas DataFrame + + # Get the results as a pandas DataFrame (if you want predictions) predictions = results.pandas().xywh[0].to_dict(orient="records") # Get predictions as a list of dictionaries - - # Return the predictions as a JSON response - return jsonify(predictions) + + # Store predictions in the global dictionary + image_predictions[file.filename] = predictions + + # Return the predictions and image path as a JSON response + return jsonify({ + "message": "Image uploaded successfully", + "image": { + "url": url_for('uploaded_file', filename=file.filename), # Adjust URL using Flask's url_for + "predictions": predictions # Return predictions in response + } + }), 200 + +@app.route('/api/images', methods=['GET']) +def get_images(): + # List all uploaded images + images = [] + for filename in os.listdir(UPLOAD_FOLDER): + if filename.endswith(('.png', '.jpg', '.jpeg')): + predictions = image_predictions.get(filename, []) + images.append({ + "url": url_for('uploaded_file', filename=filename), # Ensure proper URL for image + "filename": filename, + "predictions": predictions + }) + return jsonify(images), 200 + +@app.route('/uploads/') +def uploaded_file(filename): + return send_from_directory(app.config['UPLOAD_FOLDER'], filename) if __name__ == '__main__': app.run(debug=True, port=5001) diff --git a/object-detector/requirements.txt b/object-detector/requirements.txt index 8239196..889ea51 100644 --- a/object-detector/requirements.txt +++ b/object-detector/requirements.txt @@ -1,6 +1,6 @@ Flask==3.1.0 requests==2.25.1 -numpy==2.1.3 +numpy torch==2.5.1 torchvision==0.20.1 pillow==11.0.0 diff --git a/object-detector/uploads/ana.jpg b/object-detector/uploads/ana.jpg new file mode 100644 index 0000000..4c28cc4 Binary files /dev/null and b/object-detector/uploads/ana.jpg differ diff --git a/object-detector/uploads/blob b/object-detector/uploads/blob new file mode 100644 index 0000000..4b14aa9 Binary files /dev/null and b/object-detector/uploads/blob differ diff --git a/object-detector/uploads/image.jpg b/object-detector/uploads/image.jpg new file mode 100644 index 0000000..4c28cc4 Binary files /dev/null and b/object-detector/uploads/image.jpg differ