diff --git a/.gitignore b/.gitignore index 12a9eb27814870fb520e00cd9545e3dbf29385f1..5d4be9669829e68e2cfaba33d69614fde9d6703a 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,9 @@ __pycache__/ migrations/ /media/ -media/ \ No newline at end of file +media/ + + +uploads/ + +/uploads/ \ No newline at end of file diff --git a/MisplaceAI/Dockerfile b/MisplaceAI/Dockerfile index 75bc76080b6ed65c800b45f3a8ea52e4cf3681a3..665e631d7a2dd5f37e71a8cb91de417f50fa61c9 100644 --- a/MisplaceAI/Dockerfile +++ b/MisplaceAI/Dockerfile @@ -34,8 +34,8 @@ ENV PYTHONPATH=$PYTHONPATH:/app/models:/app/models/research:/app/models/research # Create the media directory RUN mkdir -p /app/media -# Expose port 8000 for the Django app -EXPOSE 8000 +# Expose port 8080 for the Django app +EXPOSE 8080 # Run the Django server -CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"] +CMD ["python", "manage.py", "runserver", "0.0.0.0:8080"] diff --git a/MisplaceAI/MisplaceAI/__pycache__/settings.cpython-310.pyc b/MisplaceAI/MisplaceAI/__pycache__/settings.cpython-310.pyc index 35c5da5b4fd5d421b404460cb3cdf37b3220e3ae..9bb104919fd41b0c254cad8c4525ffa468b51048 100644 Binary files a/MisplaceAI/MisplaceAI/__pycache__/settings.cpython-310.pyc and b/MisplaceAI/MisplaceAI/__pycache__/settings.cpython-310.pyc differ diff --git a/MisplaceAI/MisplaceAI/settings.py b/MisplaceAI/MisplaceAI/settings.py index b6afaac3a371d2098d1d467f9e1b9055a0a14a52..7696ee18f50cedeb35813f64bdf0b0009d42f59f 100644 --- a/MisplaceAI/MisplaceAI/settings.py +++ b/MisplaceAI/MisplaceAI/settings.py @@ -34,8 +34,9 @@ DEBUG = True -ALLOWED_HOSTS = [] - +# ALLOWED_HOSTS = [] + +ALLOWED_HOSTS = ['*'] # This allows all hosts. Use with caution. # Application definition @@ -153,3 +154,11 @@ STATICFILES_DIRS = [ DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' +if DEBUG: + import requests + try: + ngrok_tunnel = requests.get('http://localhost:4040/api/tunnels').json()['tunnels'][0]['public_url'] + ngrok_hostname = ngrok_tunnel.split("//")[-1].split(":")[0] + ALLOWED_HOSTS.append(ngrok_hostname) + except requests.exceptions.RequestException: + pass \ No newline at end of file diff --git a/MisplaceAI/core/templates/core/navbar.html b/MisplaceAI/core/templates/core/navbar.html index 12bd44095a198ba9c70df720d713364abe5838a4..ab74aceedb7b86bc3d3c7ac108b0aa3a3958c74c 100644 --- a/MisplaceAI/core/templates/core/navbar.html +++ b/MisplaceAI/core/templates/core/navbar.html @@ -11,7 +11,8 @@ </li> <!-- Add link to the image upload page --> <li class="nav-item"> - <a class="nav-link" href="{% url 'process_misplaced_manager:upload_image' %}">Upload Image</a> + <a class="nav-link" href="{% url 'process_misplaced_manager:detection_options' %}">Detect Misplaced + Items</a> </li> {% if user.is_authenticated %} {% if user.is_staff %} diff --git a/MisplaceAI/docker-compose.yml b/MisplaceAI/docker-compose.yml index 14ed08a921be340552864a1188b5310d59f5adc0..ef1b375fc20ad96f2f42270e3bc26352fded32e4 100644 --- a/MisplaceAI/docker-compose.yml +++ b/MisplaceAI/docker-compose.yml @@ -1,17 +1,21 @@ -#docker-compose.yml version: '3.7' services: web: build: . - command: python manage.py runserver 0.0.0.0:8000 + command: python manage.py runserver 0.0.0.0:8080 volumes: - .:/app - ./media:/app/media ports: - - "8000:8000" + - "8080:8080" depends_on: - db + healthcheck: + test: [ "CMD-SHELL", "curl -f http://localhost:8080/ || exit 1" ] + interval: 30s + timeout: 10s + retries: 5 db: image: mysql:5.7 @@ -23,5 +27,17 @@ services: volumes: - mysql_data:/var/lib/mysql + ngrok: + image: wernight/ngrok + volumes: + - ./ngrok.yml:/home/ngrok/.ngrok2/ngrok.yml + command: ngrok start --all + depends_on: + web: + condition: service_healthy + ports: + - "4040:4040" # Expose Ngrok's web interface + - "8080" # Forward the port Django is running on through Ngrok + volumes: mysql_data: diff --git a/MisplaceAI/docs/webAPI.md b/MisplaceAI/docs/webAPI.md new file mode 100644 index 0000000000000000000000000000000000000000..204e4251f756e063c5fab1ba23343fb4f805991b --- /dev/null +++ b/MisplaceAI/docs/webAPI.md @@ -0,0 +1,31 @@ +## Adding ngrok URL to Django ALLOWED_HOSTS + +### Overview +When using ngrok to tunnel your local Django server, you may encounter an Invalid HTTP_HOST header error. This happens because Django’s `ALLOWED_HOSTS` setting does not include the ngrok URL. To resolve this issue, you need to add the ngrok URL to the `ALLOWED_HOSTS` setting in your Django project’s `settings.py` file. + +### Steps to Fix the Issue +1. **Locate Your Django Settings File** + First, locate your Django settings file. This file is typically named `settings.py` and can be found in your project directory. For example, it might be located at: + +<your_project>/<your_project>/settings.py + +### 2. Edit the ALLOWED_HOSTS Setting + Open the `settings.py` file in your preferred text editor or IDE. Find the `ALLOWED_HOSTS` setting and add your ngrok URL to the list. If the `ALLOWED_HOSTS` list is currently empty or not defined, it will look something like this: + + +```bash +ALLOWED_HOSTS = [ + 'localhost', + '127.0.0.1', + 'f382-164-11-203-57.ngrok-free.app', # Add your ngrok URL here +] +``` + +### 3. Alternative Development Setting (Use with Caution) +For development purposes, you can allow all hosts by using a wildcard `*`. However, this is not recommended for production environments due to security risks: + +```bash + +ALLOWED_HOSTS = ['*'] # This allows all hosts. Use with caution. + +``` diff --git a/MisplaceAI/ngrok.yml b/MisplaceAI/ngrok.yml new file mode 100644 index 0000000000000000000000000000000000000000..6ae991e53dbb531ea07d9544e72e4b6cd4773617 --- /dev/null +++ b/MisplaceAI/ngrok.yml @@ -0,0 +1,6 @@ +version: "2" +authtoken: 2gjASz2oymOKoIZiuEWvPHog4MB_4q4if5LATt17Vc1hbsvnC +tunnels: + django: + proto: http + addr: web:8080 # Use the service name defined in docker-compose.yml diff --git a/MisplaceAI/process_misplaced_manager/forms.py b/MisplaceAI/process_misplaced_manager/forms.py index fa7edd5c2e61e3302d2667a786bde1b0f51b0aa0..47b2ce438ef5a2e9971c55e393a9805f941f6514 100644 --- a/MisplaceAI/process_misplaced_manager/forms.py +++ b/MisplaceAI/process_misplaced_manager/forms.py @@ -1,7 +1,12 @@ from django import forms -from .models import UploadedImage +from .models import UploadedImage, UploadedVideo class ImageUploadForm(forms.ModelForm): class Meta: model = UploadedImage fields = ['image'] + +class VideoUploadForm(forms.ModelForm): + class Meta: + model = UploadedVideo + fields = ['video'] diff --git a/MisplaceAI/process_misplaced_manager/models.py b/MisplaceAI/process_misplaced_manager/models.py index 1d73a62583f25455d02930af8b3a7a2470581e90..1a4537810b537322a3a9c1b1a6a5888460813f5a 100644 --- a/MisplaceAI/process_misplaced_manager/models.py +++ b/MisplaceAI/process_misplaced_manager/models.py @@ -1,8 +1,9 @@ from django.db import models class UploadedImage(models.Model): - image = models.ImageField(upload_to='uploads/') + image = models.ImageField(upload_to='images/') uploaded_at = models.DateTimeField(auto_now_add=True) - def __str__(self): - return f"Image {self.id} uploaded at {self.uploaded_at}" +class UploadedVideo(models.Model): + video = models.FileField(upload_to='videos/') + uploaded_at = models.DateTimeField(auto_now_add=True) diff --git a/MisplaceAI/process_misplaced_manager/templates/process_misplaced_manager/detection_options.html b/MisplaceAI/process_misplaced_manager/templates/process_misplaced_manager/detection_options.html new file mode 100644 index 0000000000000000000000000000000000000000..ff86beac9ae6d250458747248b268179283c5d78 --- /dev/null +++ b/MisplaceAI/process_misplaced_manager/templates/process_misplaced_manager/detection_options.html @@ -0,0 +1,20 @@ +{% extends 'core/base.html' %} + +{% block content %} +<h1>Select Detection Method</h1> +<div> + <a href="{% url 'process_misplaced_manager:normal_detection' %}"> + <button>Misplaced Items using Normal Object Detection</button> + </a> +</div> +<div> + <a href="{% url 'process_misplaced_manager:segmentation_detection' %}"> + <button>Misplaced Items using Segmentation</button> + </a> +</div> +<div> + <a href="{% url 'process_misplaced_manager:upload_video' %}"> + <button>Misplaced Items using Video Detection</button> + </a> +</div> +{% endblock %} \ No newline at end of file diff --git a/MisplaceAI/process_misplaced_manager/templates/process_misplaced_manager/display_results.html b/MisplaceAI/process_misplaced_manager/templates/process_misplaced_manager/display_results.html index 0ddd16070dfadb4a17688609ef603bd224667906..8a842a76b773319f10f0594253de5432e85f0089 100644 --- a/MisplaceAI/process_misplaced_manager/templates/process_misplaced_manager/display_results.html +++ b/MisplaceAI/process_misplaced_manager/templates/process_misplaced_manager/display_results.html @@ -6,8 +6,8 @@ <h2>Misplaced Objects</h2> <ul> {% for obj in misplaced_objects %} - <li>{{ obj.class_name }} is misplaced. Allowed locations: {{ obj.allowed_locations }}</li> + <li>{{ obj.class_name }} is misplaced. Allowed locations: {{ obj.allowed_locations|join:", " }}</li> {% endfor %} </ul> -<a href="{% url 'process_misplaced_manager:upload_image' %}">Upload another image</a> +<a href="{% url 'process_misplaced_manager:detection_options' %}">Try another detection</a> {% endblock %} \ No newline at end of file diff --git a/MisplaceAI/process_misplaced_manager/templates/process_misplaced_manager/normal_detection.html b/MisplaceAI/process_misplaced_manager/templates/process_misplaced_manager/normal_detection.html new file mode 100644 index 0000000000000000000000000000000000000000..b388c1c77a40f6f0ada620896ad5d8aed6ece31f --- /dev/null +++ b/MisplaceAI/process_misplaced_manager/templates/process_misplaced_manager/normal_detection.html @@ -0,0 +1,88 @@ +{% extends 'core/base.html' %} + +{% block content %} +<h1>Upload Image for Normal Detection</h1> +<!-- Include WebcamJS library --> +<script src="https://cdnjs.cloudflare.com/ajax/libs/webcamjs/1.0.26/webcam.min.js"></script> + +<form method="post" enctype="multipart/form-data" id="uploadForm"> + {% csrf_token %} + <label for="id_image">Image:</label> + <input type="file" id="id_image" name="image" accept="image/*"> + <button type="submit">Upload</button> +</form> + +<!-- Button to open the camera interface --> +<button id="openCameraBtn">Capture Image from Camera</button> + +<!-- Container for the camera feed --> +<div id="camera" style="display: none;"></div> +<!-- Container for the captured image --> +<div id="capturedImage" style="display: none;"> + <img id="imagePreview" src="" alt="Captured Image"> + <input type="hidden" id="capturedImageData" name="capturedImageData"> +</div> + +<a href="{% url 'process_misplaced_manager:detection_options' %}">Back to Detection Options</a> + +<script> + document.getElementById('openCameraBtn').addEventListener('click', function () { + // Show the camera interface + document.getElementById('camera').style.display = 'block'; + + // Configure and attach the camera + Webcam.set({ + width: 320, + height: 240, + image_format: 'jpeg', + jpeg_quality: 90 + }); + Webcam.attach('#camera'); + + // Capture the image + Webcam.snap(function (data_uri) { + // Show the captured image + document.getElementById('capturedImage').style.display = 'block'; + document.getElementById('imagePreview').src = data_uri; + + // Save the captured image data to the hidden input field + document.getElementById('capturedImageData').value = data_uri; + + // Hide the camera interface + Webcam.reset(); + document.getElementById('camera').style.display = 'none'; + }); + }); + + document.getElementById('uploadForm').addEventListener('submit', function (event) { + // If an image is captured from the camera, prevent the default form submission + const capturedImageData = document.getElementById('capturedImageData').value; + if (capturedImageData) { + event.preventDefault(); + + // Create a FormData object + const formData = new FormData(); + + // Append the CSRF token + formData.append('csrfmiddlewaretoken', '{{ csrf_token }}'); + + // Append the captured image data + formData.append('capturedImageData', capturedImageData); + + // Send the form data via AJAX + fetch('{% url "process_misplaced_manager:normal_detection" %}', { + method: 'POST', + body: formData, + }) + .then(response => response.json()) + .then(data => { + // Handle the response (e.g., redirect to the results page) + window.location.href = data.redirect_url; + }) + .catch(error => { + console.error('Error:', error); + }); + } + }); +</script> +{% endblock %} \ No newline at end of file diff --git a/MisplaceAI/process_misplaced_manager/templates/process_misplaced_manager/segmentation_detection.html b/MisplaceAI/process_misplaced_manager/templates/process_misplaced_manager/segmentation_detection.html new file mode 100644 index 0000000000000000000000000000000000000000..892e93b633cfab9cd627cb583eb5fd2b94dc9a68 --- /dev/null +++ b/MisplaceAI/process_misplaced_manager/templates/process_misplaced_manager/segmentation_detection.html @@ -0,0 +1,12 @@ +{% extends 'core/base.html' %} + +{% block content %} +<h1>Upload Image for Segmentation Detection</h1> +<form method="post" enctype="multipart/form-data"> + {% csrf_token %} + <label for="id_image">Image:</label> + <input type="file" name="image" accept="image/*" capture="camera" required> + <button type="submit">Upload</button> +</form> +<a href="{% url 'process_misplaced_manager:detection_options' %}">Back to Detection Options</a> +{% endblock %} \ No newline at end of file diff --git a/MisplaceAI/process_misplaced_manager/templates/process_misplaced_manager/upload_image.html b/MisplaceAI/process_misplaced_manager/templates/process_misplaced_manager/upload_image.html index 7de40c0ff8da259433f2f03dfbc89a98ff3eb31e..0451cc3677170ef4a2485f59661d25c1e58ffa42 100644 --- a/MisplaceAI/process_misplaced_manager/templates/process_misplaced_manager/upload_image.html +++ b/MisplaceAI/process_misplaced_manager/templates/process_misplaced_manager/upload_image.html @@ -7,4 +7,5 @@ {{ form.as_p }} <button type="submit">Upload</button> </form> +<a href="{% url 'process_misplaced_manager:detection_options' %}">Back to Detection Options</a> {% endblock %} \ No newline at end of file diff --git a/MisplaceAI/process_misplaced_manager/templates/process_misplaced_manager/upload_video.html b/MisplaceAI/process_misplaced_manager/templates/process_misplaced_manager/upload_video.html new file mode 100644 index 0000000000000000000000000000000000000000..f5fe13ce98fee50b4053b806d4112da4fd6a1344 --- /dev/null +++ b/MisplaceAI/process_misplaced_manager/templates/process_misplaced_manager/upload_video.html @@ -0,0 +1,12 @@ +{% extends 'core/base.html' %} + +{% block content %} +<h1>Upload Video for Detection</h1> +<form method="post" enctype="multipart/form-data"> + {% csrf_token %} + <label for="id_video">Video:</label> + <input type="file" name="video" accept="video/*" capture="camcorder" required> + <button type="submit">Upload</button> +</form> +<a href="{% url 'process_misplaced_manager:detection_options' %}">Back to Detection Options</a> +{% endblock %} \ No newline at end of file diff --git a/MisplaceAI/process_misplaced_manager/urls.py b/MisplaceAI/process_misplaced_manager/urls.py index d8e6e973a6319274b60d5d28f171c538fab5d43e..1b521fd654254949776dbe3a97e3e15f9c9ca73c 100644 --- a/MisplaceAI/process_misplaced_manager/urls.py +++ b/MisplaceAI/process_misplaced_manager/urls.py @@ -1,9 +1,13 @@ from django.urls import path -from .views import upload_image, display_results +from . import views app_name = 'process_misplaced_manager' urlpatterns = [ - path('upload/', upload_image, name='upload_image'), - path('results/<int:image_id>/', display_results, name='display_results'), + path('detection-options/', views.detection_options, name='detection_options'), + path('normal-detection/', views.normal_detection, name='normal_detection'), + path('segmentation-detection/', views.segmentation_detection, name='segmentation_detection'), + path('upload-video/', views.upload_video, name='upload_video'), + path('results/<int:image_id>/', views.display_results, name='display_results'), + path('video-results/<int:video_id>/', views.display_video_results, name='display_video_results'), ] diff --git a/MisplaceAI/process_misplaced_manager/views.py b/MisplaceAI/process_misplaced_manager/views.py index 356319757c5f42f31c00e53a6e9514f0a9f6494a..9634b564e23bd5cdb4582c2697d4eb57a95de20b 100644 --- a/MisplaceAI/process_misplaced_manager/views.py +++ b/MisplaceAI/process_misplaced_manager/views.py @@ -1,11 +1,13 @@ from django.shortcuts import render, redirect, get_object_or_404 -from .forms import ImageUploadForm -from .models import UploadedImage +from .forms import ImageUploadForm, VideoUploadForm +from .models import UploadedImage, UploadedVideo from item_detector.utils import run_inference, load_model, create_category_index_from_labelmap from placement_rules.utils import PlacementRules from results_viewer.utils import visualize_misplaced_objects +import base64 import os +from django.core.files.base import ContentFile # Load the model and category index for object detection MODEL_PATH = "models/research/object_detection/faster_rcnn_resnet50_v1_1024x1024_coco17_tpu-8/saved_model" @@ -14,7 +16,38 @@ LABEL_MAP_PATH = "models/research/object_detection/data/mscoco_label_map.pbtxt" detection_model = load_model(MODEL_PATH) category_index = create_category_index_from_labelmap(LABEL_MAP_PATH) -def upload_image(request): +def detection_options(request): + return render(request, 'process_misplaced_manager/detection_options.html') + + +def normal_detection(request): + if request.method == 'POST': + if 'capturedImageData' in request.POST: + captured_image_data = request.POST['capturedImageData'] + format, imgstr = captured_image_data.split(';base64,') + ext = format.split('/')[-1] + image_data = ContentFile(base64.b64decode(imgstr), name='temp.' + ext) + + # Save the image to the UploadedImage model + new_image = UploadedImage.objects.create(image=image_data) + return redirect('process_misplaced_manager:display_results', image_id=new_image.id) + else: + form = ImageUploadForm(request.POST, request.FILES) + if form.is_valid(): + new_image = form.save() + return redirect('process_misplaced_manager:display_results', image_id=new_image.id) + else: + form = ImageUploadForm() + return render(request, 'process_misplaced_manager/normal_detection.html', {'form': form}) + + + + + + + + +def segmentation_detection(request): if request.method == 'POST': form = ImageUploadForm(request.POST, request.FILES) if form.is_valid(): @@ -22,7 +55,17 @@ def upload_image(request): return redirect('process_misplaced_manager:display_results', image_id=new_image.id) else: form = ImageUploadForm() - return render(request, 'process_misplaced_manager/upload_image.html', {'form': form}) + return render(request, 'process_misplaced_manager/segmentation_detection.html', {'form': form}) + +def upload_video(request): + if request.method == 'POST': + form = VideoUploadForm(request.POST, request.FILES) + if form.is_valid(): + new_video = form.save() + return redirect('process_misplaced_manager:display_video_results', video_id=new_video.id) + else: + form = VideoUploadForm() + return render(request, 'process_misplaced_manager/upload_video.html', {'form': form}) def display_results(request, image_id): image = get_object_or_404(UploadedImage, id=image_id) @@ -45,3 +88,18 @@ def display_results(request, image_id): 'output_image_url': "/media/" + os.path.basename(output_image_path), 'misplaced_objects': misplaced_objects }) + +def display_video_results(request, video_id): + # Placeholder for video processing results view + video = get_object_or_404(UploadedVideo, id=video_id) + video_path = video.video.path + + # Here you would run video analysis, currently, it's a placeholder + detected_objects = [] # Replace with actual video detection logic + misplaced_objects = [] # Replace with actual video detection logic + + return render(request, 'process_misplaced_manager/display_video_results.html', { + 'video': video, + 'detected_objects': detected_objects, + 'misplaced_objects': misplaced_objects + }) diff --git a/README.md b/README.md index 00459617fd07615054a22b220f08db79f9cfc44f..3e01936eb5db336d6e6323025cb511b64dc09f1f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,14 @@ # Identification of Misplaced Items +ngrok http 8080 + + + +docker-compose down +docker-compose up -d + +docker-compose up --build ## DATABASE ### Connect though terminal @@ -74,9 +82,9 @@ docker-compose exec web python manage.py migrate create superuser: -``` bash +``` bashhttps://wrb.uwe.ac.uk/Scientia/Portal/Login.aspx?ReturnUrl=%2fScientia%2fPortal%2fMain.aspx - docker-compose exec web python manage.py createsuperuser +docker-compose exec web python manage.py createsuperuser ```