adityaaarora commited on
Commit
ef62b3e
Β·
1 Parent(s): 0c16a3a

Add matcher

Browse files
Files changed (42) hide show
  1. matcher/__init__.py +0 -0
  2. matcher/__pycache__/__init__.cpython-313.pyc +0 -0
  3. matcher/__pycache__/admin.cpython-313.pyc +0 -0
  4. matcher/__pycache__/apps.cpython-313.pyc +0 -0
  5. matcher/__pycache__/models.cpython-313.pyc +0 -0
  6. matcher/__pycache__/similarity.cpython-313.pyc +0 -0
  7. matcher/__pycache__/urls.cpython-313.pyc +0 -0
  8. matcher/__pycache__/views.cpython-313.pyc +0 -0
  9. matcher/admin.py +0 -0
  10. matcher/apps.py +0 -0
  11. matcher/management/commands/__pycache__/populate_products.cpython-313.pyc +0 -0
  12. matcher/management/commands/populate_products.py +55 -0
  13. matcher/matcher/__init__.py +0 -0
  14. matcher/matcher/__pycache__/__init__.cpython-313.pyc +0 -0
  15. matcher/matcher/__pycache__/admin.cpython-313.pyc +0 -0
  16. matcher/matcher/__pycache__/apps.cpython-313.pyc +0 -0
  17. matcher/matcher/__pycache__/models.cpython-313.pyc +0 -0
  18. matcher/matcher/__pycache__/similarity.cpython-313.pyc +0 -0
  19. matcher/matcher/__pycache__/urls.cpython-313.pyc +0 -0
  20. matcher/matcher/__pycache__/views.cpython-313.pyc +0 -0
  21. matcher/matcher/admin.py +0 -0
  22. matcher/matcher/apps.py +0 -0
  23. matcher/matcher/management/commands/__pycache__/populate_products.cpython-313.pyc +0 -0
  24. matcher/matcher/management/commands/populate_products.py +55 -0
  25. matcher/matcher/migrations/0001_initial.py +32 -0
  26. matcher/matcher/migrations/__init__.py +0 -0
  27. matcher/matcher/migrations/__pycache__/0001_initial.cpython-313.pyc +0 -0
  28. matcher/matcher/migrations/__pycache__/__init__.cpython-313.pyc +0 -0
  29. matcher/matcher/models.py +14 -0
  30. matcher/matcher/similarity.py +66 -0
  31. matcher/matcher/tests.py +0 -0
  32. matcher/matcher/urls.py +6 -0
  33. matcher/matcher/views.py +76 -0
  34. matcher/migrations/0001_initial.py +32 -0
  35. matcher/migrations/__init__.py +0 -0
  36. matcher/migrations/__pycache__/0001_initial.cpython-313.pyc +0 -0
  37. matcher/migrations/__pycache__/__init__.cpython-313.pyc +0 -0
  38. matcher/models.py +14 -0
  39. matcher/similarity.py +66 -0
  40. matcher/tests.py +0 -0
  41. matcher/urls.py +6 -0
  42. matcher/views.py +76 -0
matcher/__init__.py ADDED
File without changes
matcher/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (169 Bytes). View file
 
matcher/__pycache__/admin.cpython-313.pyc ADDED
Binary file (166 Bytes). View file
 
matcher/__pycache__/apps.cpython-313.pyc ADDED
Binary file (165 Bytes). View file
 
matcher/__pycache__/models.cpython-313.pyc ADDED
Binary file (1.34 kB). View file
 
matcher/__pycache__/similarity.cpython-313.pyc ADDED
Binary file (3.33 kB). View file
 
matcher/__pycache__/urls.cpython-313.pyc ADDED
Binary file (345 Bytes). View file
 
matcher/__pycache__/views.cpython-313.pyc ADDED
Binary file (3.64 kB). View file
 
matcher/admin.py ADDED
File without changes
matcher/apps.py ADDED
File without changes
matcher/management/commands/__pycache__/populate_products.cpython-313.pyc ADDED
Binary file (3.43 kB). View file
 
matcher/management/commands/populate_products.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ #
3
+
4
+
5
+ import os
6
+ from django.core.management.base import BaseCommand
7
+ from django.conf import settings
8
+ from django.core.files import File
9
+ from matcher.models import Product
10
+ from matcher.similarity import extract_features
11
+ from PIL import Image as PILImage
12
+
13
+ class Command(BaseCommand):
14
+ help = "Populate the database with products from images in media/uploads/."
15
+
16
+ def handle(self, *args, **kwargs):
17
+ uploads_dir = os.path.join(settings.MEDIA_ROOT, "uploads")
18
+
19
+ if not os.path.exists(uploads_dir):
20
+ self.stdout.write(self.style.ERROR(f"Folder not found: {uploads_dir}"))
21
+ return
22
+
23
+ # Clear old products
24
+ Product.objects.all().delete()
25
+ self.stdout.write("Deleted old products.")
26
+
27
+ for filename in os.listdir(uploads_dir):
28
+ file_path = os.path.join(uploads_dir, filename)
29
+
30
+ if not filename.lower().endswith((".jpg", ".jpeg", ".png")):
31
+ continue
32
+
33
+ try:
34
+ pil_image = PILImage.open(file_path).convert("RGB")
35
+ features = extract_features(pil_image)
36
+
37
+ product = Product(
38
+ name=os.path.splitext(filename)[0],
39
+ category="General",
40
+ )
41
+
42
+ with open(file_path, "rb") as f:
43
+ product.image.save(filename, File(f), save=False)
44
+
45
+ if features is not None:
46
+ product.feature_vector = features.tobytes()
47
+
48
+ product.save()
49
+ self.stdout.write(self.style.SUCCESS(f"Added {filename}"))
50
+
51
+ except Exception as e:
52
+ self.stdout.write(self.style.ERROR(f"Error processing {filename}: {e}"))
53
+
54
+
55
+ self.stdout.write(self.style.SUCCESS("Finished populating products."))
matcher/matcher/__init__.py ADDED
File without changes
matcher/matcher/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (169 Bytes). View file
 
matcher/matcher/__pycache__/admin.cpython-313.pyc ADDED
Binary file (166 Bytes). View file
 
matcher/matcher/__pycache__/apps.cpython-313.pyc ADDED
Binary file (165 Bytes). View file
 
matcher/matcher/__pycache__/models.cpython-313.pyc ADDED
Binary file (1.34 kB). View file
 
matcher/matcher/__pycache__/similarity.cpython-313.pyc ADDED
Binary file (3.33 kB). View file
 
matcher/matcher/__pycache__/urls.cpython-313.pyc ADDED
Binary file (345 Bytes). View file
 
matcher/matcher/__pycache__/views.cpython-313.pyc ADDED
Binary file (3.64 kB). View file
 
matcher/matcher/admin.py ADDED
File without changes
matcher/matcher/apps.py ADDED
File without changes
matcher/matcher/management/commands/__pycache__/populate_products.cpython-313.pyc ADDED
Binary file (3.43 kB). View file
 
matcher/matcher/management/commands/populate_products.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ #
3
+
4
+
5
+ import os
6
+ from django.core.management.base import BaseCommand
7
+ from django.conf import settings
8
+ from django.core.files import File
9
+ from matcher.models import Product
10
+ from matcher.similarity import extract_features
11
+ from PIL import Image as PILImage
12
+
13
+ class Command(BaseCommand):
14
+ help = "Populate the database with products from images in media/uploads/."
15
+
16
+ def handle(self, *args, **kwargs):
17
+ uploads_dir = os.path.join(settings.MEDIA_ROOT, "uploads")
18
+
19
+ if not os.path.exists(uploads_dir):
20
+ self.stdout.write(self.style.ERROR(f"Folder not found: {uploads_dir}"))
21
+ return
22
+
23
+ # Clear old products
24
+ Product.objects.all().delete()
25
+ self.stdout.write("Deleted old products.")
26
+
27
+ for filename in os.listdir(uploads_dir):
28
+ file_path = os.path.join(uploads_dir, filename)
29
+
30
+ if not filename.lower().endswith((".jpg", ".jpeg", ".png")):
31
+ continue
32
+
33
+ try:
34
+ pil_image = PILImage.open(file_path).convert("RGB")
35
+ features = extract_features(pil_image)
36
+
37
+ product = Product(
38
+ name=os.path.splitext(filename)[0],
39
+ category="General",
40
+ )
41
+
42
+ with open(file_path, "rb") as f:
43
+ product.image.save(filename, File(f), save=False)
44
+
45
+ if features is not None:
46
+ product.feature_vector = features.tobytes()
47
+
48
+ product.save()
49
+ self.stdout.write(self.style.SUCCESS(f"Added {filename}"))
50
+
51
+ except Exception as e:
52
+ self.stdout.write(self.style.ERROR(f"Error processing {filename}: {e}"))
53
+
54
+
55
+ self.stdout.write(self.style.SUCCESS("Finished populating products."))
matcher/matcher/migrations/0001_initial.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Generated by Django 5.2.5 on 2025-08-26 08:59
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ initial = True
9
+
10
+ dependencies = [
11
+ ]
12
+
13
+ operations = [
14
+ migrations.CreateModel(
15
+ name='Product',
16
+ fields=[
17
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18
+ ('name', models.CharField(max_length=255)),
19
+ ('category', models.CharField(max_length=255)),
20
+ ('image', models.ImageField(upload_to='products/')),
21
+ ('feature_vector', models.BinaryField(blank=True, null=True)),
22
+ ],
23
+ ),
24
+ migrations.CreateModel(
25
+ name='UploadedImage',
26
+ fields=[
27
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
28
+ ('image', models.ImageField(upload_to='uploads/')),
29
+ ('uploaded_at', models.DateTimeField(auto_now_add=True)),
30
+ ],
31
+ ),
32
+ ]
matcher/matcher/migrations/__init__.py ADDED
File without changes
matcher/matcher/migrations/__pycache__/0001_initial.cpython-313.pyc ADDED
Binary file (1.47 kB). View file
 
matcher/matcher/migrations/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (180 Bytes). View file
 
matcher/matcher/models.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from django.db import models
2
+
3
+ class Product(models.Model):
4
+ name = models.CharField(max_length=255)
5
+ category = models.CharField(max_length=255)
6
+ image = models.ImageField(upload_to='products/')
7
+ feature_vector = models.BinaryField(null=True, blank=True)
8
+
9
+ def __str__(self):
10
+ return self.name
11
+
12
+ class UploadedImage(models.Model):
13
+ image = models.ImageField(upload_to='uploads/')
14
+ uploaded_at = models.DateTimeField(auto_now_add=True)
matcher/matcher/similarity.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from transformers import ViTImageProcessor, ViTModel
3
+ from PIL import Image
4
+ import numpy as np
5
+ from sklearn.metrics.pairwise import cosine_similarity
6
+ import requests
7
+ from io import BytesIO
8
+
9
+ # We are now using a much smaller "tiny" version of the Vision Transformer because render was not able to handle and demanding to pay for larger models.
10
+ MODEL_NAME = 'WinKawaks/vit-tiny-patch16-224'
11
+
12
+ try:
13
+ image_processor = ViTImageProcessor.from_pretrained(MODEL_NAME)
14
+ model = ViTModel.from_pretrained(MODEL_NAME)
15
+ except Exception as e:
16
+ print(f"Error loading model: {e}")
17
+ image_processor = None
18
+ model = None
19
+
20
+ def extract_features(image: Image.Image) -> np.ndarray | None:
21
+ """
22
+ Extracts a feature vector from an image using the ViT model.
23
+ """
24
+ if not model or not image_processor:
25
+ print("Model or image processor not loaded.")
26
+ return None
27
+
28
+ try:
29
+ inputs = image_processor(images=image, return_tensors="pt")
30
+
31
+ with torch.no_grad():
32
+ outputs = model(**inputs)
33
+
34
+ last_hidden_states = outputs.last_hidden_state
35
+ features = last_hidden_states.mean(dim=1).cpu().numpy()
36
+ return features
37
+ except Exception as e:
38
+ print(f"An error occurred during feature extraction: {e}")
39
+ return None
40
+
41
+ def find_similar_products(uploaded_features, all_products, top_n=20):
42
+ """
43
+ Finds similar products by comparing feature vectors using cosine similarity.
44
+ """
45
+ if uploaded_features is None or not all_products:
46
+ return []
47
+
48
+ product_features = np.array([np.frombuffer(p.feature_vector, dtype=np.float32) for p in all_products])
49
+
50
+ if product_features.ndim == 1:
51
+ product_features = product_features.reshape(1, -1)
52
+ if uploaded_features.ndim == 1:
53
+ uploaded_features = uploaded_features.reshape(1, -1)
54
+
55
+ similarities = cosine_similarity(uploaded_features, product_features)[0]
56
+
57
+ similar_products_with_scores = []
58
+ for i, product in enumerate(all_products):
59
+ similar_products_with_scores.append({
60
+ 'product': product,
61
+ 'similarity': similarities[i]
62
+ })
63
+
64
+ similar_products_with_scores.sort(key=lambda x: x['similarity'], reverse=True)
65
+
66
+ return similar_products_with_scores[:top_n]
matcher/matcher/tests.py ADDED
File without changes
matcher/matcher/urls.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ from django.urls import path
2
+ from . import views
3
+
4
+ urlpatterns = [
5
+ path('', views.index, name='index'),
6
+ ]
matcher/matcher/views.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ from io import BytesIO
3
+ from django.shortcuts import render
4
+ from django.core.files.base import ContentFile
5
+ from .models import Product, UploadedImage
6
+ from .similarity import extract_features, find_similar_products
7
+ from PIL import Image as PILImage
8
+ import numpy as np
9
+
10
+ def index(request):
11
+ similar_products = []
12
+ uploaded_image_url = None
13
+ error_message = None
14
+ loading = False
15
+
16
+ if request.method == 'POST':
17
+ loading = True
18
+ image_file = request.FILES.get('image_file')
19
+ image_url_from_post = request.POST.get('image_url')
20
+
21
+ image_to_process = None
22
+ image_bytes = None
23
+ image_filename = 'uploaded_image.jpg'
24
+
25
+ try:
26
+ if image_file:
27
+ image_filename = image_file.name
28
+ image_bytes = image_file.read()
29
+ image_to_process = PILImage.open(BytesIO(image_bytes)).convert("RGB")
30
+
31
+ elif image_url_from_post:
32
+ headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'}
33
+ response = requests.get(image_url_from_post, headers=headers, timeout=15)
34
+ response.raise_for_status()
35
+ image_bytes = response.content
36
+ image_to_process = PILImage.open(BytesIO(image_bytes)).convert("RGB")
37
+ image_filename = image_url_from_post.split('/')[-1]
38
+
39
+ else:
40
+ error_message = "Please upload an image or provide a URL."
41
+
42
+ if image_to_process and image_bytes:
43
+ uploaded_image_instance = UploadedImage()
44
+ uploaded_image_instance.image.save(image_filename, ContentFile(image_bytes))
45
+
46
+ uploaded_image_url = uploaded_image_instance.image.url
47
+
48
+ uploaded_features = extract_features(image_to_process)
49
+
50
+ if uploaded_features is not None:
51
+ all_products = list(Product.objects.exclude(feature_vector__isnull=True))
52
+ similar_products = find_similar_products(uploaded_features, all_products)
53
+
54
+ min_similarity = request.POST.get('similarity_score')
55
+ if min_similarity:
56
+ min_similarity = float(min_similarity)
57
+ similar_products = [p for p in similar_products if p['similarity'] >= min_similarity]
58
+ else:
59
+ error_message = "Could not extract features from the image."
60
+
61
+ except PILImage.UnidentifiedImageError:
62
+ error_message = "Could not identify the file as an image. Please check the file or URL."
63
+ except requests.exceptions.RequestException as e:
64
+ error_message = f"Failed to retrieve image from URL: {e}"
65
+ except Exception as e:
66
+ error_message = f"An unexpected error occurred: {e}"
67
+
68
+ loading = False
69
+
70
+ return render(request, 'matcher/index.html', {
71
+ 'similar_products': similar_products,
72
+ 'uploaded_image_url': uploaded_image_url,
73
+ 'error_message': error_message,
74
+ 'loading': loading,
75
+ })
76
+
matcher/migrations/0001_initial.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Generated by Django 5.2.5 on 2025-08-26 08:59
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ initial = True
9
+
10
+ dependencies = [
11
+ ]
12
+
13
+ operations = [
14
+ migrations.CreateModel(
15
+ name='Product',
16
+ fields=[
17
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18
+ ('name', models.CharField(max_length=255)),
19
+ ('category', models.CharField(max_length=255)),
20
+ ('image', models.ImageField(upload_to='products/')),
21
+ ('feature_vector', models.BinaryField(blank=True, null=True)),
22
+ ],
23
+ ),
24
+ migrations.CreateModel(
25
+ name='UploadedImage',
26
+ fields=[
27
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
28
+ ('image', models.ImageField(upload_to='uploads/')),
29
+ ('uploaded_at', models.DateTimeField(auto_now_add=True)),
30
+ ],
31
+ ),
32
+ ]
matcher/migrations/__init__.py ADDED
File without changes
matcher/migrations/__pycache__/0001_initial.cpython-313.pyc ADDED
Binary file (1.47 kB). View file
 
matcher/migrations/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (180 Bytes). View file
 
matcher/models.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from django.db import models
2
+
3
+ class Product(models.Model):
4
+ name = models.CharField(max_length=255)
5
+ category = models.CharField(max_length=255)
6
+ image = models.ImageField(upload_to='products/')
7
+ feature_vector = models.BinaryField(null=True, blank=True)
8
+
9
+ def __str__(self):
10
+ return self.name
11
+
12
+ class UploadedImage(models.Model):
13
+ image = models.ImageField(upload_to='uploads/')
14
+ uploaded_at = models.DateTimeField(auto_now_add=True)
matcher/similarity.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from transformers import ViTImageProcessor, ViTModel
3
+ from PIL import Image
4
+ import numpy as np
5
+ from sklearn.metrics.pairwise import cosine_similarity
6
+ import requests
7
+ from io import BytesIO
8
+
9
+ # We are now using a much smaller "tiny" version of the Vision Transformer because render was not able to handle and demanding to pay for larger models.
10
+ MODEL_NAME = 'WinKawaks/vit-tiny-patch16-224'
11
+
12
+ try:
13
+ image_processor = ViTImageProcessor.from_pretrained(MODEL_NAME)
14
+ model = ViTModel.from_pretrained(MODEL_NAME)
15
+ except Exception as e:
16
+ print(f"Error loading model: {e}")
17
+ image_processor = None
18
+ model = None
19
+
20
+ def extract_features(image: Image.Image) -> np.ndarray | None:
21
+ """
22
+ Extracts a feature vector from an image using the ViT model.
23
+ """
24
+ if not model or not image_processor:
25
+ print("Model or image processor not loaded.")
26
+ return None
27
+
28
+ try:
29
+ inputs = image_processor(images=image, return_tensors="pt")
30
+
31
+ with torch.no_grad():
32
+ outputs = model(**inputs)
33
+
34
+ last_hidden_states = outputs.last_hidden_state
35
+ features = last_hidden_states.mean(dim=1).cpu().numpy()
36
+ return features
37
+ except Exception as e:
38
+ print(f"An error occurred during feature extraction: {e}")
39
+ return None
40
+
41
+ def find_similar_products(uploaded_features, all_products, top_n=20):
42
+ """
43
+ Finds similar products by comparing feature vectors using cosine similarity.
44
+ """
45
+ if uploaded_features is None or not all_products:
46
+ return []
47
+
48
+ product_features = np.array([np.frombuffer(p.feature_vector, dtype=np.float32) for p in all_products])
49
+
50
+ if product_features.ndim == 1:
51
+ product_features = product_features.reshape(1, -1)
52
+ if uploaded_features.ndim == 1:
53
+ uploaded_features = uploaded_features.reshape(1, -1)
54
+
55
+ similarities = cosine_similarity(uploaded_features, product_features)[0]
56
+
57
+ similar_products_with_scores = []
58
+ for i, product in enumerate(all_products):
59
+ similar_products_with_scores.append({
60
+ 'product': product,
61
+ 'similarity': similarities[i]
62
+ })
63
+
64
+ similar_products_with_scores.sort(key=lambda x: x['similarity'], reverse=True)
65
+
66
+ return similar_products_with_scores[:top_n]
matcher/tests.py ADDED
File without changes
matcher/urls.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ from django.urls import path
2
+ from . import views
3
+
4
+ urlpatterns = [
5
+ path('', views.index, name='index'),
6
+ ]
matcher/views.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ from io import BytesIO
3
+ from django.shortcuts import render
4
+ from django.core.files.base import ContentFile
5
+ from .models import Product, UploadedImage
6
+ from .similarity import extract_features, find_similar_products
7
+ from PIL import Image as PILImage
8
+ import numpy as np
9
+
10
+ def index(request):
11
+ similar_products = []
12
+ uploaded_image_url = None
13
+ error_message = None
14
+ loading = False
15
+
16
+ if request.method == 'POST':
17
+ loading = True
18
+ image_file = request.FILES.get('image_file')
19
+ image_url_from_post = request.POST.get('image_url')
20
+
21
+ image_to_process = None
22
+ image_bytes = None
23
+ image_filename = 'uploaded_image.jpg'
24
+
25
+ try:
26
+ if image_file:
27
+ image_filename = image_file.name
28
+ image_bytes = image_file.read()
29
+ image_to_process = PILImage.open(BytesIO(image_bytes)).convert("RGB")
30
+
31
+ elif image_url_from_post:
32
+ headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'}
33
+ response = requests.get(image_url_from_post, headers=headers, timeout=15)
34
+ response.raise_for_status()
35
+ image_bytes = response.content
36
+ image_to_process = PILImage.open(BytesIO(image_bytes)).convert("RGB")
37
+ image_filename = image_url_from_post.split('/')[-1]
38
+
39
+ else:
40
+ error_message = "Please upload an image or provide a URL."
41
+
42
+ if image_to_process and image_bytes:
43
+ uploaded_image_instance = UploadedImage()
44
+ uploaded_image_instance.image.save(image_filename, ContentFile(image_bytes))
45
+
46
+ uploaded_image_url = uploaded_image_instance.image.url
47
+
48
+ uploaded_features = extract_features(image_to_process)
49
+
50
+ if uploaded_features is not None:
51
+ all_products = list(Product.objects.exclude(feature_vector__isnull=True))
52
+ similar_products = find_similar_products(uploaded_features, all_products)
53
+
54
+ min_similarity = request.POST.get('similarity_score')
55
+ if min_similarity:
56
+ min_similarity = float(min_similarity)
57
+ similar_products = [p for p in similar_products if p['similarity'] >= min_similarity]
58
+ else:
59
+ error_message = "Could not extract features from the image."
60
+
61
+ except PILImage.UnidentifiedImageError:
62
+ error_message = "Could not identify the file as an image. Please check the file or URL."
63
+ except requests.exceptions.RequestException as e:
64
+ error_message = f"Failed to retrieve image from URL: {e}"
65
+ except Exception as e:
66
+ error_message = f"An unexpected error occurred: {e}"
67
+
68
+ loading = False
69
+
70
+ return render(request, 'matcher/index.html', {
71
+ 'similar_products': similar_products,
72
+ 'uploaded_image_url': uploaded_image_url,
73
+ 'error_message': error_message,
74
+ 'loading': loading,
75
+ })
76
+