diff --git a/MisplaceAI/process_misplaced_manager/tests/__init__.py b/MisplaceAI/process_misplaced_manager/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/MisplaceAI/process_misplaced_manager/tests/misplacedTestAlgorithm/test_Misplaced_Items_Image.py b/MisplaceAI/process_misplaced_manager/tests/misplacedTestAlgorithm/test_Misplaced_Items_Image.py new file mode 100644 index 0000000000000000000000000000000000000000..2ad26913981aab903a4fd893224e4465577e9e86 --- /dev/null +++ b/MisplaceAI/process_misplaced_manager/tests/misplacedTestAlgorithm/test_Misplaced_Items_Image.py @@ -0,0 +1,209 @@ +# MisplaceAI/process_misplaced_manager/tests/misplacedTestAlgorithm/test_Misplaced_Items_Image.py + +""" +This file tests the accuracy of the misplaced item detection algorithm by uploading images +containing various items and verifying if the system correctly identifies items that are +misplaced according to predefined rules. It includes three tests: +1. Verifying that a remote is detected as misplaced in the image 'test1.jpg'. +2. Verifying that a handbag is detected as misplaced in the image 'test2.jpg'. +3. Verifying that no items are detected as misplaced in the image 'test3.jpg'. +Additionally, it ensures that the uploaded images are deleted after the tests. +""" + +import os +import base64 +from django.urls import reverse +from rest_framework import status +from rest_framework.test import APITestCase +from django.contrib.auth.models import User +from process_misplaced_manager.models import DetectionLimitSetting, DailyDetectionLimit + +class TestAlgorithmAccuracy(APITestCase): + def setUp(self): + # Create and login an admin user + self.admin_user = User.objects.create_superuser(username='admin2', password='adminpassword') + self.client.login(username='admin2', password='adminpassword') + + # Create items + # Create an item named 'remote' + self.item_remote = self.client.post(reverse('rules:admin_manage_item'), {'name': 'remote'}).data['id'] + # Create an item named 'handbag' + self.item_handbag = self.client.post(reverse('rules:admin_manage_item'), {'name': 'handbag'}).data['id'] + # Create an item named 'wine glass' + self.item_wine_glass = self.client.post(reverse('rules:admin_manage_item'), {'name': 'wine glass'}).data['id'] + + # Create locations + # Create a location named 'dining table' + self.location_dining_table = self.client.post(reverse('rules:admin_manage_location'), {'name': 'dining table'}).data['id'] + # Create a location named 'coffee table' + self.location_coffee_table = self.client.post(reverse('rules:admin_manage_location'), {'name': 'coffee table'}).data['id'] + + # Create rules + # Create a rule that the 'remote' is allowed on the 'dining table' + self.client.post(reverse('rules:admin_manage_rule'), { + 'item': self.item_remote, + 'locations': [self.location_dining_table] + }) + # Create a rule that the 'handbag' is allowed on the 'dining table' + self.client.post(reverse('rules:admin_manage_rule'), { + 'item': self.item_handbag, + 'locations': [self.location_dining_table] + }) + # Create a rule that the 'wine glass' is allowed on the 'dining table' + self.client.post(reverse('rules:admin_manage_rule'), { + 'item': self.item_wine_glass, + 'locations': [self.location_dining_table] + }) + + # Create and login the test user + self.user = User.objects.create_user(username='testuser', password='testpassword') + self.client.login(username='testuser', password='testpassword') + + # Set up the test image paths + self.test_image_paths = { + 'test1.jpg': '/app/process_misplaced_manager/tests/test_files/pictures/test1.jpg', + 'test2.jpg': '/app/process_misplaced_manager/tests/test_files/pictures/test2.jpg', + 'test3.jpg': '/app/process_misplaced_manager/tests/test_files/pictures/test3.jpg' + } + + # Set up the URL for the normal detection endpoint + self.detection_url = reverse('process_misplaced_manager:normal_detection') + + # Set up the URL for the delete image endpoint + self.delete_url = lambda image_name: reverse('process_misplaced_manager:delete_image_by_name', args=[image_name]) + + # Set up a detection limit setting + DetectionLimitSetting.objects.create(daily_image_limit=10, daily_video_limit=5) + DailyDetectionLimit.objects.create(user=self.user) + + def test_remote_misplaced_detection(self): + """ + Test that the remote is detected as misplaced in the uploaded image 'test1.jpg'. + """ + test_image_path = self.test_image_paths['test1.jpg'] + + # Verify the test image path exists + assert os.path.exists(test_image_path), f"Test image not found at {test_image_path}" + + # Open the image file and encode it to base64 + with open(test_image_path, 'rb') as image_file: + image_data = image_file.read() + base64_image = base64.b64encode(image_data).decode('utf-8') + # Prepare the data to be sent in the request + data = { + 'capturedImageData': f'data:image/jpeg;base64,{base64_image}' + } + # Send the POST request to the normal detection endpoint + response = self.client.post(self.detection_url, data, format='json') + + # Check that the response status is 200 OK + self.assertEqual(response.status_code, status.HTTP_200_OK) + + # Retrieve the misplaced objects from the response + misplaced_objects = response.data['misplaced_objects'] + + # Check the structure of misplaced_objects + print(f"Misplaced objects FROM test_remote_misplaced_detection : {misplaced_objects}") + misplaced_item_names = [obj['class_name'] for obj in misplaced_objects if 'class_name' in obj] + + # Check that the remote is detected as misplaced + self.assertIn('remote', misplaced_item_names) + + # Delete the uploaded image + uploaded_image_url = response.data['output_image_url'] + uploaded_image_name = uploaded_image_url.split('/')[-1] + delete_url = self.delete_url(uploaded_image_name) + delete_response = self.client.delete(delete_url) + + # Check that the delete response status is 204 No Content + self.assertEqual(delete_response.status_code, status.HTTP_204_NO_CONTENT) + # Verify that the image file is deleted + self.assertFalse(os.path.exists(os.path.join('/app/media/images', uploaded_image_name)), f"Image still exists at {uploaded_image_name}") + + def test_handbag_misplaced_detection(self): + """ + Test that the handbag is detected as misplaced in the uploaded image 'test2.jpg'. + """ + test_image_path = self.test_image_paths['test2.jpg'] + + # Verify the test image path exists + assert os.path.exists(test_image_path), f"Test image not found at {test_image_path}" + + # Open the image file and encode it to base64 + with open(test_image_path, 'rb') as image_file: + image_data = image_file.read() + base64_image = base64.b64encode(image_data).decode('utf-8') + # Prepare the data to be sent in the request + data = { + 'capturedImageData': f'data:image/jpeg;base64,{base64_image}' + } + # Send the POST request to the normal detection endpoint + response = self.client.post(self.detection_url, data, format='json') + + # Check that the response status is 200 OK + self.assertEqual(response.status_code, status.HTTP_200_OK) + + # Retrieve the misplaced objects from the response + misplaced_objects = response.data['misplaced_objects'] + + # Check the structure of misplaced_objects + print(f"Misplaced objects FROM test_handbag_misplaced_detection : {misplaced_objects}") + misplaced_item_names = [obj['class_name'] for obj in misplaced_objects if 'class_name' in obj] + + # Check that the handbag is detected as misplaced + self.assertIn('handbag', misplaced_item_names) + + # Delete the uploaded image + uploaded_image_url = response.data['output_image_url'] + uploaded_image_name = uploaded_image_url.split('/')[-1] + delete_url = self.delete_url(uploaded_image_name) + delete_response = self.client.delete(delete_url) + + # Check that the delete response status is 204 No Content + self.assertEqual(delete_response.status_code, status.HTTP_204_NO_CONTENT) + # Verify that the image file is deleted + self.assertFalse(os.path.exists(os.path.join('/app/media/images', uploaded_image_name)), f"Image still exists at {uploaded_image_name}") + + def test_no_misplaced_items_detection(self): + """ + Test that no items are detected as misplaced in the uploaded image 'test3.jpg'. + """ + test_image_path = self.test_image_paths['test3.jpg'] + + # Verify the test image path exists + assert os.path.exists(test_image_path), f"Test image not found at {test_image_path}" + + # Open the image file and encode it to base64 + with open(test_image_path, 'rb') as image_file: + image_data = image_file.read() + base64_image = base64.b64encode(image_data).decode('utf-8') + # Prepare the data to be sent in the request + data = { + 'capturedImageData': f'data:image/jpeg;base64,{base64_image}' + } + # Send the POST request to the normal detection endpoint + response = self.client.post(self.detection_url, data, format='json') + + # Check that the response status is 200 OK + self.assertEqual(response.status_code, status.HTTP_200_OK) + + # Retrieve the misplaced objects from the response + misplaced_objects = response.data['misplaced_objects'] + + # Check the structure of misplaced_objects + print(f"Misplaced objects FROM test_no_misplaced_items_detection : {misplaced_objects}") + misplaced_item_names = [obj['class_name'] for obj in misplaced_objects if 'class_name' in obj] + + # Check that no items are detected as misplaced + self.assertEqual(len(misplaced_item_names), 0) + + # Delete the uploaded image + uploaded_image_url = response.data['output_image_url'] + uploaded_image_name = uploaded_image_url.split('/')[-1] + delete_url = self.delete_url(uploaded_image_name) + delete_response = self.client.delete(delete_url) + + # Check that the delete response status is 204 No Content + self.assertEqual(delete_response.status_code, status.HTTP_204_NO_CONTENT) + # Verify that the image file is deleted + self.assertFalse(os.path.exists(os.path.join('/app/media/images', uploaded_image_name)), f"Image still exists at {uploaded_image_name}") diff --git a/MisplaceAI/process_misplaced_manager/tests/misplacedTestAlgorithm/test_Misplaced_Items_video.py b/MisplaceAI/process_misplaced_manager/tests/misplacedTestAlgorithm/test_Misplaced_Items_video.py new file mode 100644 index 0000000000000000000000000000000000000000..7fa3432761b63397fc6ec2c9a6ec86c3d0fde842 --- /dev/null +++ b/MisplaceAI/process_misplaced_manager/tests/misplacedTestAlgorithm/test_Misplaced_Items_video.py @@ -0,0 +1,191 @@ +#MisplaceAI\process_misplaced_manager\tests\misplacedTestAlgorithm\test_Misplaced_Items_video.py + +""" +This file contains tests for video detection functionality in the MisplaceAI application. +It includes tests to: +1. Verify that a specific item ('potted plant') is detected as misplaced in an uploaded video. +2. Ensure that the uploaded video can be deleted successfully. +3. Check that the length of the processed video is as expected (30 seconds). +""" + +import os # Importing the os module for interacting with the operating system +from django.urls import reverse # Importing reverse to generate URL for a given view +from rest_framework import status # Importing status to use HTTP status codes +from rest_framework.test import APITestCase # Importing APITestCase to create API test cases +from django.contrib.auth.models import User # Importing User model to create test users +from process_misplaced_manager.models import DetectionLimitSetting, DailyDetectionLimit # Importing models for setting detection limits +from moviepy.editor import VideoFileClip # Importing VideoFileClip to handle video files + +class TestVideoAlgorithmAccuracy(APITestCase): + def setUp(self): + """ + Set up the initial conditions for the tests, including creating an admin user, a test user, + and setting up necessary items, locations, rules, and detection limits. + """ + # Create and login an admin user + self.admin_user = User.objects.create_superuser(username='admin', password='adminpassword') + self.client.login(username='admin', password='adminpassword') + + # Create item 'potted plant' for the test and get its ID + self.item_potted_plant = self.client.post(reverse('rules:admin_manage_item'), {'name': 'potted plant'}).data['id'] + + # Create location 'chair' for the test and get its ID + self.location_chair = self.client.post(reverse('rules:admin_manage_location'), {'name': 'chair'}).data['id'] + + # Create rule associating the 'potted plant' item with the 'chair' location + self.client.post(reverse('rules:admin_manage_rule'), { + 'item': self.item_potted_plant, + 'locations': [self.location_chair] + }) + + # Create and login the test user + self.user = User.objects.create_user(username='testuser', password='testpassword') + self.client.login(username='testuser', password='testpassword') + + # Set up the path to the test video file + self.test_video_path = '/app/process_misplaced_manager/tests/test_files/videos/videoTest1.mp4' + + # Set up the URL for the video detection endpoint + self.detection_url = reverse('process_misplaced_manager:upload_video') + + # Set up the URL for the delete video endpoint using a lambda function to insert the video name + self.delete_url = lambda video_name: reverse('process_misplaced_manager:delete_video', args=[video_name]) + + # Create detection limit settings with limits for image and video detections + DetectionLimitSetting.objects.create(daily_image_limit=10, daily_video_limit=5) + + # Create daily detection limit record for the test user + DailyDetectionLimit.objects.create(user=self.user) + + def test_potted_plant_misplaced_detection(self): + """ + Test that the potted plant is detected as misplaced in the uploaded video. + """ + # Get the path to the test video + test_video_path = self.test_video_path + + # Verify the test video path exists + assert os.path.exists(test_video_path), f"Test video not found at {test_video_path}" + + # Open the test video file in binary read mode + with open(test_video_path, 'rb') as video_file: + # Prepare form data for the POST request, including the video file, frames_jump, and frame_delay + form_data = { + 'video': video_file, + 'frames_jump': 30, + 'frame_delay': 15 + } + # Send the POST request to the video detection endpoint + response = self.client.post(self.detection_url, form_data, format='multipart') + + # Assert that the response status code is HTTP 201 Created + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + # Fetch the video results by getting the video ID from the response data + video_id = response.data['id'] + results_url = reverse('process_misplaced_manager:display_video_results', args=[video_id]) + results_response = self.client.get(results_url) + + # Assert that the response status code is HTTP 200 OK + self.assertEqual(results_response.status_code, status.HTTP_200_OK) + + # Get the misplaced objects from the response data + misplaced_objects = results_response.data['misplaced_objects'] + + # Print the misplaced objects for debugging purposes + print(f"Misplaced objects FROM test_potted_plant_misplaced_detection: {misplaced_objects}") + + # Extract the names of misplaced items from the response data + misplaced_item_names = [obj['class_name'] for frame in misplaced_objects for obj in frame if 'class_name' in obj] + + # Check that 'potted plant' is detected as misplaced + self.assertIn('potted plant', misplaced_item_names) + + def test_video_deletion(self): + """ + Test that the uploaded video is deleted successfully. + """ + # Get the path to the test video + test_video_path = self.test_video_path + + # Verify the test video path exists + assert os.path.exists(test_video_path), f"Test video not found at {test_video_path}" + + # Open the test video file in binary read mode + with open(test_video_path, 'rb') as video_file: + # Prepare form data for the POST request, including the video file, frames_jump, and frame_delay + form_data = { + 'video': video_file, + 'frames_jump': 30, + 'frame_delay': 15 + } + # Send the POST request to the video detection endpoint + response = self.client.post(self.detection_url, form_data, format='multipart') + + # Assert that the response status code is HTTP 201 Created + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + # Get the video name from the response data + video_name = response.data['video'].split('/')[-1] + + # Generate the URL for the delete video endpoint using the video name + delete_url = self.delete_url(video_name) + + # Send the DELETE request to the delete video endpoint + delete_response = self.client.delete(delete_url) + + # Assert that the response status code is HTTP 204 No Content + self.assertEqual(delete_response.status_code, status.HTTP_204_NO_CONTENT) + + # Assert that the video file no longer exists in the specified path + self.assertFalse(os.path.exists(os.path.join('/app/media/videos', video_name)), f"Video still exists at {video_name}") + + def test_processed_video_length(self): + """ + Test that the length of the processed video is as expected. + """ + # Get the path to the test video + test_video_path = self.test_video_path + + # Verify the test video path exists + assert os.path.exists(test_video_path), f"Test video not found at {test_video_path}" + + # Open the test video file in binary read mode + with open(test_video_path, 'rb') as video_file: + # Prepare form data for the POST request, including the video file, frames_jump, and frame_delay + form_data = { + 'video': video_file, + 'frames_jump': 30, + 'frame_delay': 15 + } + # Send the POST request to the video detection endpoint + response = self.client.post(self.detection_url, form_data, format='multipart') + + # Assert that the response status code is HTTP 201 Created + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + # Fetch the video results by getting the video ID from the response data + video_id = response.data['id'] + results_url = reverse('process_misplaced_manager:display_video_results', args=[video_id]) + results_response = self.client.get(results_url) + + # Assert that the response status code is HTTP 200 OK + self.assertEqual(results_response.status_code, status.HTTP_200_OK) + + # Get the output video URL from the response data + output_video_url = results_response.data['output_video_url'] + + # Construct the output video path from the URL + output_video_path = os.path.join('/app/media/videos', output_video_url.split('/')[-1]) + + # Load the output video file + video_clip = VideoFileClip(output_video_path) + + # Get the duration of the video in seconds + video_length = video_clip.duration + + # Print the processed video length for debugging purposes + print(f"Processed video length: {video_length} seconds") + + # Assert that the video length is approximately 30 seconds (with a tolerance of 1 second) + self.assertTrue(abs(video_length - 30) < 1, f"Video length is not as expected: {video_length} seconds") diff --git a/MisplaceAI/process_misplaced_manager/tests/test_files/pictures/test1.jpg b/MisplaceAI/process_misplaced_manager/tests/test_files/pictures/test1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ee29af65c7abec938082edda4d306163ee9c89bf Binary files /dev/null and b/MisplaceAI/process_misplaced_manager/tests/test_files/pictures/test1.jpg differ diff --git a/MisplaceAI/process_misplaced_manager/tests/test_files/pictures/test2.jpg b/MisplaceAI/process_misplaced_manager/tests/test_files/pictures/test2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..28cc9904de574ddfdbb316ef042eeac032bb5144 Binary files /dev/null and b/MisplaceAI/process_misplaced_manager/tests/test_files/pictures/test2.jpg differ diff --git a/MisplaceAI/process_misplaced_manager/tests/test_files/pictures/test3.jpg b/MisplaceAI/process_misplaced_manager/tests/test_files/pictures/test3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..16b7b124200ea564e1b3c9c5ddfbb8b850329cee Binary files /dev/null and b/MisplaceAI/process_misplaced_manager/tests/test_files/pictures/test3.jpg differ diff --git a/MisplaceAI/process_misplaced_manager/tests/test_files/videos/videoTest1.mp4 b/MisplaceAI/process_misplaced_manager/tests/test_files/videos/videoTest1.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..44198fd8a025537e8fdf0630835a906f08fb7127 Binary files /dev/null and b/MisplaceAI/process_misplaced_manager/tests/test_files/videos/videoTest1.mp4 differ diff --git a/MisplaceAI/pytest.ini b/MisplaceAI/pytest.ini index dd5b8c75d03b00e3cecd8fe7a80972d4c491f3a2..f3e17b00ca9957feafddae4e7fd1178b20df6b06 100644 --- a/MisplaceAI/pytest.ini +++ b/MisplaceAI/pytest.ini @@ -2,7 +2,15 @@ DJANGO_SETTINGS_MODULE = MisplaceAI.settings addopts = -v --ignore=MisplaceAI/models testpaths = + process_misplaced_manager/tests/misplacedTestAlgorithm/ rules/tests user_dashboard/tests + process_misplaced_manager/tests/ + filterwarnings = ignore::django.utils.deprecation.RemovedInDjango41Warning + ignore::DeprecationWarning:scipy.ndimage.filters + ignore::DeprecationWarning:numpy.* + ignore::DeprecationWarning:tensorflow.* + ignore:.*textsize is deprecated and will be removed in Pillow 10.*:DeprecationWarning +