plant recognition
This commit is contained in:
17
app.py
17
app.py
@@ -1,4 +1,4 @@
|
||||
from flask import Flask, render_template, request, redirect, url_for, flash, session
|
||||
from flask import Flask, render_template, request, redirect, url_for, flash, session, jsonify
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_migrate import Migrate
|
||||
from datetime import datetime
|
||||
@@ -6,6 +6,7 @@ from werkzeug.security import generate_password_hash, check_password_hash
|
||||
from werkzeug.utils import secure_filename
|
||||
import os
|
||||
from config import Config
|
||||
import random
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config.from_object(Config)
|
||||
@@ -1438,6 +1439,20 @@ def seed_db():
|
||||
))
|
||||
db.session.commit()
|
||||
|
||||
@app.route('/recognize-plant', methods=['GET', 'POST'])
|
||||
def recognize_plant():
|
||||
if request.method == 'GET':
|
||||
return render_template('recognize_plant.html')
|
||||
|
||||
# For proof of concept, just return a random plant
|
||||
plants = Plant.query.all()
|
||||
if not plants:
|
||||
flash('No plants found in the database.', 'warning')
|
||||
return redirect(url_for('home'))
|
||||
|
||||
random_plant = random.choice(plants)
|
||||
return jsonify({'redirect': url_for('plant', plant_id=random_plant.id)})
|
||||
|
||||
if __name__ == "__main__":
|
||||
with app.app_context():
|
||||
db.create_all() # Create all tables
|
||||
|
||||
@@ -10,25 +10,21 @@
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/tooltip.css') }}">
|
||||
</head>
|
||||
<body class="min-h-screen bg-gradient-to-br from-[#e6ebe0] via-[#b7c7a3] to-[#6b8f71] bg-fixed">
|
||||
<nav class="bg-[#f5f7f2]/95 shadow-lg backdrop-blur-md" id="main-navbar">
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="flex justify-between">
|
||||
<div class="flex space-x-7">
|
||||
<div>
|
||||
<a href="{{ url_for('home') }}" class="flex items-center py-4">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-7 h-7 text-[#6b8f71] mr-2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 2C7 2 2 7 2 12c0 5 5 10 10 10s10-5 10-10c0-5-5-10-10-10zm0 0c0 4 4 8 8 8" />
|
||||
</svg>
|
||||
<span class="font-bold text-[#3e5637] text-xl tracking-tight">Verpot Je Lot</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center space-x-3">
|
||||
<!-- Admin links moved to footer -->
|
||||
<header class="bg-white shadow-md">
|
||||
<div class="container mx-auto px-4 py-4">
|
||||
<div class="flex justify-between items-center">
|
||||
<a href="{{ url_for('home') }}" class="text-2xl font-bold text-[#4e6b50]">
|
||||
<i class="fas fa-leaf mr-2"></i>Planty
|
||||
</a>
|
||||
<div class="flex items-center">
|
||||
<a href="{{ url_for('recognize_plant') }}" class="inline-flex items-center px-4 py-2 bg-[#4e6b50] text-white rounded-lg hover:bg-[#3e5637] transition-colors duration-200 shadow-sm">
|
||||
<i class="fas fa-camera-retro mr-2"></i>
|
||||
<span class="font-medium">Recognize Plant</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="container mx-auto px-4 py-8">
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
|
||||
129
templates/recognize_plant.html
Normal file
129
templates/recognize_plant.html
Normal file
@@ -0,0 +1,129 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Recognize Plant{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="max-w-2xl mx-auto bg-white p-8 rounded-2xl shadow-xl mt-8">
|
||||
<h1 class="text-3xl font-bold text-[#4e6b50] mb-6 text-center">Plant Recognition</h1>
|
||||
|
||||
<div class="space-y-6">
|
||||
<!-- Camera Capture -->
|
||||
<div class="bg-[#f8f9fa] p-6 rounded-xl border border-[#e6ebe0]">
|
||||
<h2 class="text-xl font-semibold text-[#4e6b50] mb-4">Take a Photo</h2>
|
||||
<div class="relative">
|
||||
<video id="camera" class="w-full rounded-lg mb-4 hidden"></video>
|
||||
<canvas id="canvas" class="w-full rounded-lg mb-4 hidden"></canvas>
|
||||
<div id="camera-placeholder" class="w-full aspect-video bg-gray-200 rounded-lg mb-4 flex items-center justify-center">
|
||||
<span class="text-gray-500">Camera preview will appear here</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-4">
|
||||
<button id="start-camera" class="btn-main flex-1">
|
||||
<i class="fas fa-camera mr-2"></i>Start Camera
|
||||
</button>
|
||||
<button id="capture" class="btn-main flex-1 hidden">
|
||||
<i class="fas fa-camera-retro mr-2"></i>Capture
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- File Upload -->
|
||||
<div class="bg-[#f8f9fa] p-6 rounded-xl border border-[#e6ebe0]">
|
||||
<h2 class="text-xl font-semibold text-[#4e6b50] mb-4">Upload Image</h2>
|
||||
<form id="upload-form" method="POST" enctype="multipart/form-data" class="space-y-4">
|
||||
<div class="flex items-center justify-center w-full">
|
||||
<label for="plant-image" class="flex flex-col items-center justify-center w-full h-32 border-2 border-[#6b8f71] border-dashed rounded-lg cursor-pointer bg-white hover:bg-[#f8f9fa]">
|
||||
<div class="flex flex-col items-center justify-center pt-5 pb-6">
|
||||
<i class="fas fa-cloud-upload-alt text-3xl text-[#6b8f71] mb-2"></i>
|
||||
<p class="mb-2 text-sm text-[#4e6b50]"><span class="font-semibold">Click to upload</span> or drag and drop</p>
|
||||
<p class="text-xs text-[#6b8f71]">PNG, JPG or JPEG</p>
|
||||
</div>
|
||||
<input id="plant-image" name="plant-image" type="file" class="hidden" accept="image/*" />
|
||||
</label>
|
||||
</div>
|
||||
<button type="submit" class="btn-main w-full">
|
||||
<i class="fas fa-search mr-2"></i>Recognize Plant
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 text-center">
|
||||
<a href="{{ url_for('home') }}" class="inline-block bg-[#e6ebe0] text-[#3e5637] hover:bg-[#b7c7a3] hover:text-[#4e6b50] font-semibold px-6 py-2 rounded-lg shadow transition-colors duration-200">
|
||||
← Back to Home
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const startCameraBtn = document.getElementById('start-camera');
|
||||
const captureBtn = document.getElementById('capture');
|
||||
const camera = document.getElementById('camera');
|
||||
const canvas = document.getElementById('canvas');
|
||||
const cameraPlaceholder = document.getElementById('camera-placeholder');
|
||||
const uploadForm = document.getElementById('upload-form');
|
||||
let stream = null;
|
||||
|
||||
// Camera functionality
|
||||
startCameraBtn.addEventListener('click', async () => {
|
||||
try {
|
||||
stream = await navigator.mediaDevices.getUserMedia({ video: true });
|
||||
camera.srcObject = stream;
|
||||
camera.classList.remove('hidden');
|
||||
cameraPlaceholder.classList.add('hidden');
|
||||
startCameraBtn.classList.add('hidden');
|
||||
captureBtn.classList.remove('hidden');
|
||||
} catch (err) {
|
||||
console.error('Error accessing camera:', err);
|
||||
alert('Could not access camera. Please make sure you have granted camera permissions.');
|
||||
}
|
||||
});
|
||||
|
||||
captureBtn.addEventListener('click', () => {
|
||||
canvas.width = camera.videoWidth;
|
||||
canvas.height = camera.videoHeight;
|
||||
canvas.getContext('2d').drawImage(camera, 0, 0);
|
||||
|
||||
// Convert canvas to blob and submit
|
||||
canvas.toBlob((blob) => {
|
||||
const formData = new FormData();
|
||||
formData.append('plant-image', blob, 'capture.jpg');
|
||||
|
||||
fetch('/recognize-plant', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
}).then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.redirect) {
|
||||
window.location.href = data.redirect;
|
||||
}
|
||||
});
|
||||
}, 'image/jpeg');
|
||||
});
|
||||
|
||||
// File upload functionality
|
||||
uploadForm.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(uploadForm);
|
||||
|
||||
fetch('/recognize-plant', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
}).then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.redirect) {
|
||||
window.location.href = data.redirect;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Cleanup camera when leaving page
|
||||
window.addEventListener('beforeunload', () => {
|
||||
if (stream) {
|
||||
stream.getTracks().forEach(track => track.stop());
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user