Spaces:
Configuration error
Configuration error
Upload 1519 files
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- app/__pycache__/app.cpython-35.pyc +0 -0
- app/app.py +81 -0
- app/index.csv +0 -0
- app/index.py +40 -0
- app/pyimagesearch/__init__.py +0 -0
- app/pyimagesearch/__init__.pyc +0 -0
- app/pyimagesearch/__pycache__/__init__.cpython-35.pyc +0 -0
- app/pyimagesearch/__pycache__/colordescriptor.cpython-35.pyc +0 -0
- app/pyimagesearch/__pycache__/searcher.cpython-35.pyc +0 -0
- app/pyimagesearch/colordescriptor.py +62 -0
- app/pyimagesearch/colordescriptor.pyc +0 -0
- app/pyimagesearch/searcher.py +46 -0
- app/pyimagesearch/searcher.pyc +0 -0
- app/requirements.txt +25 -0
- app/static/images/.DS_Store +0 -0
- app/static/images/Dol-Guldur-001.png +0 -0
- app/static/images/Dol-Guldur-002.png +0 -0
- app/static/images/Dol-Guldur-003.png +0 -0
- app/static/images/Dol-Guldur-004.png +0 -0
- app/static/images/Dol-Guldur-005.png +0 -0
- app/static/images/Goblin-001.png +0 -0
- app/static/images/Goblin-002.png +0 -0
- app/static/images/Goblin-003.png +0 -0
- app/static/images/Goblin-004.png +0 -0
- app/static/images/Golbin-005.png +0 -0
- app/static/images/Mordor-001.png +0 -0
- app/static/images/Mordor-002.png +0 -0
- app/static/images/Mordor-003.png +0 -0
- app/static/images/Mordor-004.png +0 -0
- app/static/images/Mordor-005.png +0 -0
- app/static/images/Rivendell-001.png +0 -0
- app/static/images/Rivendell-002.png +0 -0
- app/static/images/Rivendell-003.png +0 -0
- app/static/images/Rivendell-004.png +0 -0
- app/static/images/Rivendell-005.png +0 -0
- app/static/images/Shire-001.png +0 -0
- app/static/images/Shire-002.png +0 -0
- app/static/images/Shire-003.png +0 -0
- app/static/images/Shire-004.png +0 -0
- app/static/images/Shire-005.png +0 -0
- app/static/init-preview.png +0 -0
- app/static/main.css +180 -0
- app/static/main.js +113 -0
- app/templates/_base.html +51 -0
- app/templates/index.html +38 -0
- venv/Lib/site-packages/_distutils_hack/__init__.py +222 -0
- venv/Lib/site-packages/_distutils_hack/__pycache__/__init__.cpython-311.pyc +0 -0
- venv/Lib/site-packages/_distutils_hack/__pycache__/override.cpython-311.pyc +0 -0
- venv/Lib/site-packages/_distutils_hack/override.py +1 -0
- venv/Lib/site-packages/distutils-precedence.pth +3 -0
app/__pycache__/app.cpython-35.pyc
ADDED
Binary file (2.47 kB). View file
|
|
app/app.py
ADDED
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import cv2
|
3 |
+
import numpy as np
|
4 |
+
|
5 |
+
|
6 |
+
from flask import Flask, render_template, request, jsonify, redirect, url_for
|
7 |
+
from werkzeug.utils import secure_filename
|
8 |
+
|
9 |
+
|
10 |
+
from pyimagesearch.colordescriptor import ColorDescriptor
|
11 |
+
from pyimagesearch.searcher import Searcher
|
12 |
+
|
13 |
+
|
14 |
+
# create flask instance
|
15 |
+
app = Flask(__name__)
|
16 |
+
|
17 |
+
INDEX = os.path.join(os.path.dirname(__file__), 'index.csv')
|
18 |
+
|
19 |
+
# main route
|
20 |
+
@app.route('/')
|
21 |
+
def index():
|
22 |
+
return render_template('index.html', preview="static/init-preview.png")
|
23 |
+
|
24 |
+
# image database url list route
|
25 |
+
@app.route('/list', methods=['POST'])
|
26 |
+
def image_list():
|
27 |
+
|
28 |
+
if request.method == "POST":
|
29 |
+
|
30 |
+
try:
|
31 |
+
|
32 |
+
imgList = [img for img in list(os.listdir(os.path.join(os.path.dirname(__file__), 'static/images/'))) if img[-4:] in ('.png', '.jpg', '.gif')]
|
33 |
+
|
34 |
+
return jsonify(imgList=imgList)
|
35 |
+
|
36 |
+
except Exception as e:
|
37 |
+
return jsonify({"sorry": "Sorry, no results! Please try again."}), 500
|
38 |
+
|
39 |
+
|
40 |
+
# search route
|
41 |
+
@app.route('/search', methods=['POST'])
|
42 |
+
def search():
|
43 |
+
|
44 |
+
if request.method == "POST":
|
45 |
+
|
46 |
+
RESULTS_ARRAY = []
|
47 |
+
|
48 |
+
# get url
|
49 |
+
image_url = request.form.get('img')
|
50 |
+
|
51 |
+
try:
|
52 |
+
|
53 |
+
# initialize the image descriptor
|
54 |
+
cd = ColorDescriptor((8, 12, 3))
|
55 |
+
|
56 |
+
# load the query image and describe it
|
57 |
+
from skimage import io
|
58 |
+
import cv2
|
59 |
+
|
60 |
+
query = cv2.imread(os.path.join(os.path.dirname(__file__), 'static/images/'+image_url))
|
61 |
+
features = cd.describe(query)
|
62 |
+
|
63 |
+
# perform the search
|
64 |
+
searcher = Searcher(INDEX)
|
65 |
+
results = searcher.search(features)
|
66 |
+
|
67 |
+
# loop over the results, displaying the score and image name
|
68 |
+
for (score, resultID) in results:
|
69 |
+
RESULTS_ARRAY.append(
|
70 |
+
{"image": str(resultID), "score": str(score)})
|
71 |
+
# return success
|
72 |
+
return jsonify(results=(RESULTS_ARRAY[:101]), preview="images/"+image_url)
|
73 |
+
|
74 |
+
except Exception as e:
|
75 |
+
print(str(e))
|
76 |
+
# return error
|
77 |
+
return jsonify({"sorry": "Sorry, no results! Please try again."}), 500
|
78 |
+
|
79 |
+
# run!
|
80 |
+
if __name__ == '__main__':
|
81 |
+
app.run('0.0.0.0', debug=True)
|
app/index.csv
ADDED
The diff for this file is too large to render.
See raw diff
|
|
app/index.py
ADDED
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#import the necessary packages
|
2 |
+
from pyimagesearch.colordescriptor import ColorDescriptor
|
3 |
+
import argparse
|
4 |
+
import glob
|
5 |
+
import cv2
|
6 |
+
|
7 |
+
#construct the argument parser and parse the arguments
|
8 |
+
ap = argparse.ArgumentParser()
|
9 |
+
ap.add_argument("-d", "--dataset", required=True,
|
10 |
+
help = "path to the directory that contains the images to be indexed")
|
11 |
+
ap.add_argument("-i", "--index", required=True,
|
12 |
+
help = "path to where the index should be stored")
|
13 |
+
args = vars(ap.parse_args())
|
14 |
+
|
15 |
+
#initialize the color descriptor
|
16 |
+
cd = ColorDescriptor((8, 12, 3))
|
17 |
+
|
18 |
+
#open the output index file for writing
|
19 |
+
output = open(args["index"], "w")
|
20 |
+
types = ('/*.jpg', '/*.png', '/*.gif') # the tuple of file types
|
21 |
+
files_grabbed = []
|
22 |
+
for files in types:
|
23 |
+
files_grabbed.extend(glob.glob(args["dataset"]+files))
|
24 |
+
|
25 |
+
#use glob to grab the image paths and loop over them
|
26 |
+
for imagePath in files_grabbed:
|
27 |
+
#extract the imageID from the image
|
28 |
+
#path and load the image itself
|
29 |
+
imageID = imagePath[imagePath.rfind("/")+1:]
|
30 |
+
image = cv2.imread(imagePath)
|
31 |
+
|
32 |
+
#describe the image
|
33 |
+
features = cd.describe(image)
|
34 |
+
|
35 |
+
#write the features to file
|
36 |
+
features = [str(f) for f in features]
|
37 |
+
output.write("%s,%s\n" % (imageID, ",".join(features)))
|
38 |
+
|
39 |
+
# close the index file
|
40 |
+
output.close()
|
app/pyimagesearch/__init__.py
ADDED
File without changes
|
app/pyimagesearch/__init__.pyc
ADDED
Binary file (168 Bytes). View file
|
|
app/pyimagesearch/__pycache__/__init__.cpython-35.pyc
ADDED
Binary file (164 Bytes). View file
|
|
app/pyimagesearch/__pycache__/colordescriptor.cpython-35.pyc
ADDED
Binary file (1.71 kB). View file
|
|
app/pyimagesearch/__pycache__/searcher.cpython-35.pyc
ADDED
Binary file (1.62 kB). View file
|
|
app/pyimagesearch/colordescriptor.py
ADDED
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# import the necessary packages
|
2 |
+
import numpy as np
|
3 |
+
import cv2
|
4 |
+
|
5 |
+
class ColorDescriptor:
|
6 |
+
def __init__(self, bins):
|
7 |
+
# store the number of bins for the 3D histogram
|
8 |
+
self.bins = bins
|
9 |
+
|
10 |
+
def describe(self, image):
|
11 |
+
# convert the image to the HSV color space and initialize
|
12 |
+
# the features used to quantify the image
|
13 |
+
image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
|
14 |
+
features = []
|
15 |
+
|
16 |
+
# grab the dimensions and compute the center of the image
|
17 |
+
(h, w) = image.shape[:2]
|
18 |
+
(cX, cY) = (int(w * 0.5), int(h * 0.5))
|
19 |
+
|
20 |
+
# divide the image into four rectangles/segments (top-left,
|
21 |
+
# top-right, bottom-right, bottom-left)
|
22 |
+
segments = [(0, cX, 0, cY), (cX, w, 0, cY), (cX, w, cY, h),
|
23 |
+
(0, cX, cY, h)]
|
24 |
+
|
25 |
+
# construct an elliptical mask representing the center of the
|
26 |
+
# image
|
27 |
+
(axesX, axesY) = (int(w * 0.75) // 2, int(h * 0.75) // 2)
|
28 |
+
ellipMask = np.zeros(image.shape[:2], dtype = "uint8")
|
29 |
+
cv2.ellipse(ellipMask, (cX, cY), (axesX, axesY), 0, 0, 360, 255, -1)
|
30 |
+
|
31 |
+
# loop over the segments
|
32 |
+
for (startX, endX, startY, endY) in segments:
|
33 |
+
# construct a mask for each corner of the image, subtracting
|
34 |
+
# the elliptical center from it
|
35 |
+
cornerMask = np.zeros(image.shape[:2], dtype = "uint8")
|
36 |
+
cv2.rectangle(cornerMask, (startX, startY), (endX, endY), 255, -1)
|
37 |
+
cornerMask = cv2.subtract(cornerMask, ellipMask)
|
38 |
+
|
39 |
+
# extract a color histogram from the image, then update the
|
40 |
+
# feature vector
|
41 |
+
hist = self.histogram(image, cornerMask)
|
42 |
+
features.extend(hist)
|
43 |
+
|
44 |
+
# extract a color histogram from the elliptical region and
|
45 |
+
# update the feature vector
|
46 |
+
hist = self.histogram(image, ellipMask)
|
47 |
+
features.extend(hist)
|
48 |
+
|
49 |
+
# return the feature vector
|
50 |
+
return features
|
51 |
+
|
52 |
+
def histogram(self, image, mask):
|
53 |
+
# extract a 3D color histogram from the masked region of the
|
54 |
+
# image, using the supplied number of bins per channel; then
|
55 |
+
# normalize the histogram
|
56 |
+
hist = cv2.calcHist([image], [0, 1, 2], mask, self.bins,
|
57 |
+
[0, 180, 0, 256, 0, 256])
|
58 |
+
cv2.normalize(hist,hist)
|
59 |
+
hist = hist.flatten()
|
60 |
+
|
61 |
+
# return the histogram
|
62 |
+
return hist
|
app/pyimagesearch/colordescriptor.pyc
ADDED
Binary file (2.07 kB). View file
|
|
app/pyimagesearch/searcher.py
ADDED
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# import the necessary packages
|
2 |
+
import numpy as np
|
3 |
+
import csv
|
4 |
+
|
5 |
+
class Searcher:
|
6 |
+
def __init__(self, indexPath):
|
7 |
+
#store our index path
|
8 |
+
self.indexPath = indexPath
|
9 |
+
|
10 |
+
def search(self, queryFeatures, limit=101):
|
11 |
+
#initialize dictionary of results
|
12 |
+
results = {}
|
13 |
+
|
14 |
+
#open the index file
|
15 |
+
with open(self.indexPath) as f:
|
16 |
+
#initialize the CSV reader
|
17 |
+
reader = csv.reader(f)
|
18 |
+
|
19 |
+
#loop over the rows in the index
|
20 |
+
for row in reader:
|
21 |
+
#parse out the image ID and features, then compute the
|
22 |
+
#chi-squared dist. b/w the features in our index
|
23 |
+
#and our query features
|
24 |
+
features = [float(x) for x in row[1:]]
|
25 |
+
d = self.chi2_distance(features, queryFeatures)
|
26 |
+
|
27 |
+
#Update dictionsary
|
28 |
+
#key is image id, value is similarity
|
29 |
+
results[row[0]] = d
|
30 |
+
|
31 |
+
#close reader
|
32 |
+
f.close()
|
33 |
+
|
34 |
+
#sort in order of relevance
|
35 |
+
results = sorted([(v,k) for (k,v) in results.items()])
|
36 |
+
|
37 |
+
#return our (limited) results
|
38 |
+
return results[:limit]
|
39 |
+
|
40 |
+
def chi2_distance(self, histA, histB, eps=1e-10):
|
41 |
+
#compute chi-squared distance
|
42 |
+
d = 0.5 * np.sum([((a-b)**2)/(a+b+eps)
|
43 |
+
for (a,b) in zip(histA, histB)])
|
44 |
+
|
45 |
+
# return the chi-squared distance
|
46 |
+
return d
|
app/pyimagesearch/searcher.pyc
ADDED
Binary file (1.59 kB). View file
|
|
app/requirements.txt
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
appdirs==1.4.0
|
2 |
+
certifi==2018.8.24
|
3 |
+
click==6.7
|
4 |
+
cycler==0.10.0
|
5 |
+
dask==0.14.0
|
6 |
+
decorator==4.0.11
|
7 |
+
Flask==1.0.2
|
8 |
+
gunicorn==19.7.0
|
9 |
+
itsdangerous==0.24
|
10 |
+
Jinja2==2.10.1
|
11 |
+
MarkupSafe==1.0
|
12 |
+
matplotlib==2.0.0
|
13 |
+
networkx==1.11
|
14 |
+
numpy==1.12.0
|
15 |
+
olefile==0.44
|
16 |
+
opencv-python==4.0.0.21
|
17 |
+
packaging==16.8
|
18 |
+
Pillow==6.2.1
|
19 |
+
pyparsing==2.1.10
|
20 |
+
python-dateutil==2.6.0
|
21 |
+
pytz==2016.10
|
22 |
+
scipy==0.19.0
|
23 |
+
six==1.10.0
|
24 |
+
toolz==0.8.2
|
25 |
+
Werkzeug==0.15.3
|
app/static/images/.DS_Store
ADDED
Binary file (12.3 kB). View file
|
|
app/static/images/Dol-Guldur-001.png
ADDED
![]() |
app/static/images/Dol-Guldur-002.png
ADDED
![]() |
app/static/images/Dol-Guldur-003.png
ADDED
![]() |
app/static/images/Dol-Guldur-004.png
ADDED
![]() |
app/static/images/Dol-Guldur-005.png
ADDED
![]() |
app/static/images/Goblin-001.png
ADDED
![]() |
app/static/images/Goblin-002.png
ADDED
![]() |
app/static/images/Goblin-003.png
ADDED
![]() |
app/static/images/Goblin-004.png
ADDED
![]() |
app/static/images/Golbin-005.png
ADDED
![]() |
app/static/images/Mordor-001.png
ADDED
![]() |
app/static/images/Mordor-002.png
ADDED
![]() |
app/static/images/Mordor-003.png
ADDED
![]() |
app/static/images/Mordor-004.png
ADDED
![]() |
app/static/images/Mordor-005.png
ADDED
![]() |
app/static/images/Rivendell-001.png
ADDED
![]() |
app/static/images/Rivendell-002.png
ADDED
![]() |
app/static/images/Rivendell-003.png
ADDED
![]() |
app/static/images/Rivendell-004.png
ADDED
![]() |
app/static/images/Rivendell-005.png
ADDED
![]() |
app/static/images/Shire-001.png
ADDED
![]() |
app/static/images/Shire-002.png
ADDED
![]() |
app/static/images/Shire-003.png
ADDED
![]() |
app/static/images/Shire-004.png
ADDED
![]() |
app/static/images/Shire-005.png
ADDED
![]() |
app/static/init-preview.png
ADDED
![]() |
app/static/main.css
ADDED
@@ -0,0 +1,180 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
@import url('https://fonts.googleapis.com/css?family=Lato&display=swap');
|
2 |
+
|
3 |
+
* {
|
4 |
+
box-sizing: border-box;
|
5 |
+
padding: 0;
|
6 |
+
margin: 0;
|
7 |
+
font-family: 'Lato', sans-serif;
|
8 |
+
}
|
9 |
+
|
10 |
+
body {
|
11 |
+
background-color: white;
|
12 |
+
}
|
13 |
+
|
14 |
+
.container {
|
15 |
+
width: 100%;
|
16 |
+
}
|
17 |
+
|
18 |
+
.header {
|
19 |
+
text-align: center;
|
20 |
+
font-weight: bold;
|
21 |
+
padding: 10px;
|
22 |
+
color: #0D19A3;
|
23 |
+
border-bottom: 5px solid #15DB95;
|
24 |
+
}
|
25 |
+
|
26 |
+
|
27 |
+
.display {
|
28 |
+
display: grid;
|
29 |
+
grid-template-columns: 1fr 3fr;
|
30 |
+
grid-column-gap: 20px;
|
31 |
+
margin-top: 10px;
|
32 |
+
}
|
33 |
+
|
34 |
+
.left-pane {
|
35 |
+
display: flex;
|
36 |
+
flex-direction: column;
|
37 |
+
align-items: center;
|
38 |
+
justify-content: flex-start;
|
39 |
+
color: #F4E4C1;
|
40 |
+
border-bottom: 3px solid #080F5B;
|
41 |
+
margin-left: 2px;
|
42 |
+
}
|
43 |
+
|
44 |
+
.img-preview {
|
45 |
+
width: 95%;
|
46 |
+
margin: 10px;
|
47 |
+
border: 4px solid #15DB95;
|
48 |
+
}
|
49 |
+
|
50 |
+
#preview-name {
|
51 |
+
color: #080F5B;
|
52 |
+
font-weight: bold;
|
53 |
+
font-size: 1.25em;
|
54 |
+
text-align: center;
|
55 |
+
}
|
56 |
+
|
57 |
+
#select {
|
58 |
+
padding: 10px;
|
59 |
+
background-color: #15DB95;
|
60 |
+
color: white;
|
61 |
+
font-weight: bold;
|
62 |
+
font-size: 1.5em;
|
63 |
+
cursor: pointer;
|
64 |
+
border-radius: 10px;
|
65 |
+
margin: 10px;
|
66 |
+
text-align: center;
|
67 |
+
transition: .3s;
|
68 |
+
}
|
69 |
+
|
70 |
+
#select:hover {
|
71 |
+
transform: scale(.95);
|
72 |
+
-webkit-box-shadow: 0px 0px 4px 3px rgba(8,15,91,1);
|
73 |
+
-moz-box-shadow: 0px 0px 4px 3px rgba(8,15,91,1);
|
74 |
+
box-shadow: 0px 0px 4px 3px rgba(8,15,91,1);
|
75 |
+
}
|
76 |
+
|
77 |
+
#results {
|
78 |
+
display: grid;
|
79 |
+
grid-template-columns: 1fr 1fr 1fr;
|
80 |
+
grid-column-gap: 15px;
|
81 |
+
grid-row-gap: 15px;
|
82 |
+
padding: 10px;
|
83 |
+
}
|
84 |
+
|
85 |
+
#searching {
|
86 |
+
font-weight: bold;
|
87 |
+
font-size: 1.75em;
|
88 |
+
color: #080F5B;
|
89 |
+
}
|
90 |
+
|
91 |
+
.img-result {
|
92 |
+
background-color: #080F5B;
|
93 |
+
color: white;
|
94 |
+
border: 2px solid #F4E4C1;
|
95 |
+
-webkit-box-shadow: 0px 0px 4px 3px rgba(8,15,91,1);
|
96 |
+
-moz-box-shadow: 0px 0px 4px 3px rgba(8,15,91,1);
|
97 |
+
box-shadow: 0px 0px 4px 3px rgba(8,15,91,1);
|
98 |
+
}
|
99 |
+
|
100 |
+
.img-info {
|
101 |
+
display: grid;
|
102 |
+
grid-template-columns: 1fr;
|
103 |
+
align-content: center;
|
104 |
+
justify-items: center;
|
105 |
+
border: 1px solid white;
|
106 |
+
}
|
107 |
+
|
108 |
+
.responsive {
|
109 |
+
width: 100%;
|
110 |
+
max-width: 100%;
|
111 |
+
height: 250px;
|
112 |
+
}
|
113 |
+
|
114 |
+
/* The Modal (background) */
|
115 |
+
.modal-display {
|
116 |
+
display: none; /* Hidden by default */
|
117 |
+
position: fixed; /* Stay in place */
|
118 |
+
z-index: 1; /* Sit on top */
|
119 |
+
padding-top: 100px; /* Location of the box */
|
120 |
+
left: 0;
|
121 |
+
top: 0;
|
122 |
+
width: 100%; /* Full width */
|
123 |
+
height: 100%; /* Full height */
|
124 |
+
overflow: auto; /* Enable scroll if needed */
|
125 |
+
background-color: rgb(0,0,0); /* Fallback color */
|
126 |
+
background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
|
127 |
+
}
|
128 |
+
|
129 |
+
/* Modal Content */
|
130 |
+
.modal-content {
|
131 |
+
background-color: white;
|
132 |
+
margin: auto;
|
133 |
+
padding: 20px;
|
134 |
+
border: 2px solid black;
|
135 |
+
width: 90%;
|
136 |
+
}
|
137 |
+
|
138 |
+
.modal-images-list {
|
139 |
+
display: flex;
|
140 |
+
flex-wrap: wrap;
|
141 |
+
justify-content: space-between;
|
142 |
+
}
|
143 |
+
|
144 |
+
.modal-image {
|
145 |
+
margin-bottom: 10px;
|
146 |
+
width: 30%;
|
147 |
+
cursor: pointer;
|
148 |
+
}
|
149 |
+
|
150 |
+
.modal-image:hover {
|
151 |
+
border: 5px solid #0D19A3;
|
152 |
+
}
|
153 |
+
|
154 |
+
/* The Close Button */
|
155 |
+
.close {
|
156 |
+
color: #aaaaaa;
|
157 |
+
float: right;
|
158 |
+
font-size: 28px;
|
159 |
+
font-weight: bold;
|
160 |
+
transform: translate(12px, -13px)
|
161 |
+
}
|
162 |
+
|
163 |
+
.close:hover,
|
164 |
+
.close:focus {
|
165 |
+
color: #000;
|
166 |
+
text-decoration: none;
|
167 |
+
cursor: pointer;
|
168 |
+
}
|
169 |
+
|
170 |
+
@media (max-width: 500px){
|
171 |
+
.display {
|
172 |
+
grid-template-columns: 1fr;
|
173 |
+
grid-row-gap: 20px;
|
174 |
+
}
|
175 |
+
|
176 |
+
#results {
|
177 |
+
grid-template-columns: 1fr;
|
178 |
+
grid-row-gap: 20px;
|
179 |
+
}
|
180 |
+
}
|
app/static/main.js
ADDED
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// ----- custom js ----- //
|
2 |
+
var imagePath = 'static/images/';
|
3 |
+
|
4 |
+
$(document).ready(function() {
|
5 |
+
|
6 |
+
console.log("ready");
|
7 |
+
modalControl();
|
8 |
+
getAllImages();
|
9 |
+
|
10 |
+
});
|
11 |
+
|
12 |
+
// request for all images in database to display in modal
|
13 |
+
var getAllImages = function(){
|
14 |
+
|
15 |
+
$.ajax({
|
16 |
+
url: "/list",
|
17 |
+
cache: false,
|
18 |
+
contentType: false,
|
19 |
+
processData: false,
|
20 |
+
type: 'POST',
|
21 |
+
success: function(data){
|
22 |
+
displayModalImages(data.imgList);
|
23 |
+
},
|
24 |
+
error: function(error){
|
25 |
+
console.log(error.toString());
|
26 |
+
}
|
27 |
+
});
|
28 |
+
}
|
29 |
+
|
30 |
+
// displays images in modal
|
31 |
+
var displayModalImages = function(imgList){
|
32 |
+
|
33 |
+
for(var i = 0; i < imgList.length; i++){
|
34 |
+
$(".modal-images-list").append("<img src="+imagePath+imgList[i]+" class=modal-image onclick=imageSelectSearch(this) />");
|
35 |
+
|
36 |
+
}
|
37 |
+
|
38 |
+
}
|
39 |
+
|
40 |
+
// handles click of modal image
|
41 |
+
var imageSelectSearch = function(_this) {
|
42 |
+
var src = $(_this).attr("src");
|
43 |
+
|
44 |
+
$("#modal").css("display", "none");
|
45 |
+
$(".img-preview").attr("src", src);
|
46 |
+
$("#results").html("");
|
47 |
+
$("#results").append("<div id=searching>Searching For Results...</div>");
|
48 |
+
|
49 |
+
var image = src.split('/')[2];
|
50 |
+
var imageName = image.split('.')[0];
|
51 |
+
|
52 |
+
$("#preview-name").text('QUERY: '+imageName);
|
53 |
+
|
54 |
+
$.ajax({
|
55 |
+
url: "/search",
|
56 |
+
data: {img: image},
|
57 |
+
cache: false,
|
58 |
+
type: 'POST',
|
59 |
+
success: function(data){
|
60 |
+
displayResults(data.results);
|
61 |
+
},
|
62 |
+
error: function(error){
|
63 |
+
console.log(error.toString());
|
64 |
+
}
|
65 |
+
});
|
66 |
+
|
67 |
+
}
|
68 |
+
|
69 |
+
//display results
|
70 |
+
var displayResults = function(data){
|
71 |
+
|
72 |
+
$("#results").html("");
|
73 |
+
|
74 |
+
for(var i = 0; i < data.length; i++){
|
75 |
+
var image = data[i].image;
|
76 |
+
var score = data[i].score;
|
77 |
+
var element = "<div class=img-result><img class=responsive src="+imagePath+image+"/>\
|
78 |
+
<div class=img-info>"+"<span class=image-name>IMAGE: "+image.split('.')[0]+"</span>\
|
79 |
+
<span class=img-score>SCORE: "+score+"</span></div></div>"
|
80 |
+
$("#results").append(element);
|
81 |
+
}
|
82 |
+
}
|
83 |
+
|
84 |
+
//Controls the opening and closing of the modal
|
85 |
+
var modalControl = function(){
|
86 |
+
|
87 |
+
// Get the modal
|
88 |
+
var modal = document.getElementById("modal");
|
89 |
+
|
90 |
+
// Get the button that opens the modal
|
91 |
+
var btn = document.getElementById("select");
|
92 |
+
console.log(modal);
|
93 |
+
|
94 |
+
// Get the <span> element that closes the modal
|
95 |
+
var span = document.getElementsByClassName("close")[0];
|
96 |
+
|
97 |
+
// When the user clicks the button, open the modal
|
98 |
+
btn.onclick = function() {
|
99 |
+
modal.style.display = "block";
|
100 |
+
}
|
101 |
+
|
102 |
+
// When the user clicks on <span> (x), close the modal
|
103 |
+
span.onclick = function() {
|
104 |
+
modal.style.display = "none";
|
105 |
+
}
|
106 |
+
|
107 |
+
// When the user clicks anywhere outside of the modal, close it
|
108 |
+
window.onclick = function(event) {
|
109 |
+
if (event.target == modal) {
|
110 |
+
modal.style.display = "none";
|
111 |
+
}
|
112 |
+
}
|
113 |
+
}
|
app/templates/_base.html
ADDED
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
3 |
+
|
4 |
+
<head>
|
5 |
+
|
6 |
+
<meta charset="utf-8">
|
7 |
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
8 |
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
9 |
+
<meta name="description" content="content based image retrieval system">
|
10 |
+
<meta name="author" content="Kene Udeh">
|
11 |
+
|
12 |
+
<title>Image Search</title>
|
13 |
+
|
14 |
+
<!-- stylesheets -->
|
15 |
+
<link href="{{ url_for('static', filename='main.css') }}" rel="stylesheet">
|
16 |
+
|
17 |
+
<!-- Scripts -->
|
18 |
+
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.0/jquery.min.js" type="text/javascript"></script>
|
19 |
+
<script src="{{ url_for('static', filename='main.js') }}" type="text/javascript"></script>
|
20 |
+
|
21 |
+
</head>
|
22 |
+
|
23 |
+
<body>
|
24 |
+
|
25 |
+
<!-- Page Content -->
|
26 |
+
<div class="container">
|
27 |
+
|
28 |
+
<!-- messages -->
|
29 |
+
{% for message in get_flashed_messages() %}
|
30 |
+
<div class="alert alert-success alert-dismissible" role="alert">
|
31 |
+
<button type="button" class="close" data-dismiss="alert"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>
|
32 |
+
{{ message }}
|
33 |
+
</div>
|
34 |
+
{% endfor %}
|
35 |
+
|
36 |
+
<!-- child template -->
|
37 |
+
{% block content %}{% endblock %}
|
38 |
+
{% block modal %}{% endblock %}
|
39 |
+
|
40 |
+
<!-- errors -->
|
41 |
+
{% if error %}
|
42 |
+
<p class="error"><strong>Error:</strong> {{ error }}</p>
|
43 |
+
{% endif %}
|
44 |
+
|
45 |
+
|
46 |
+
</div>
|
47 |
+
<!-- /.container -->
|
48 |
+
|
49 |
+
</body>
|
50 |
+
|
51 |
+
</html>
|
app/templates/index.html
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{% extends "_base.html" %}
|
2 |
+
|
3 |
+
{% block content %}
|
4 |
+
|
5 |
+
<div class="header">
|
6 |
+
<h1 class="title">SEARCH BY IMAGE</h1>
|
7 |
+
</div>
|
8 |
+
<hr/>
|
9 |
+
|
10 |
+
<div class="display">
|
11 |
+
<div class="left-pane">
|
12 |
+
<img src="static/init-preview.png" alt="preview image" class="img-preview">
|
13 |
+
<div id="preview-name">Preview</div>
|
14 |
+
<div id="select" class="image-select">
|
15 |
+
Select & Search
|
16 |
+
</div>
|
17 |
+
</div>
|
18 |
+
<div class="right-pane">
|
19 |
+
<div id="results" class="search-results">
|
20 |
+
|
21 |
+
</div>
|
22 |
+
</div>
|
23 |
+
</div>
|
24 |
+
<!-- The Modal -->
|
25 |
+
<div id="modal" class="modal-display">
|
26 |
+
|
27 |
+
<!-- Modal content -->
|
28 |
+
<div class="modal-content">
|
29 |
+
<span class="close">×</span>
|
30 |
+
<div class="modal-images-list">
|
31 |
+
|
32 |
+
</div>
|
33 |
+
</div>
|
34 |
+
|
35 |
+
</div>
|
36 |
+
|
37 |
+
{% endblock %}
|
38 |
+
|
venv/Lib/site-packages/_distutils_hack/__init__.py
ADDED
@@ -0,0 +1,222 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# don't import any costly modules
|
2 |
+
import sys
|
3 |
+
import os
|
4 |
+
|
5 |
+
|
6 |
+
is_pypy = '__pypy__' in sys.builtin_module_names
|
7 |
+
|
8 |
+
|
9 |
+
def warn_distutils_present():
|
10 |
+
if 'distutils' not in sys.modules:
|
11 |
+
return
|
12 |
+
if is_pypy and sys.version_info < (3, 7):
|
13 |
+
# PyPy for 3.6 unconditionally imports distutils, so bypass the warning
|
14 |
+
# https://foss.heptapod.net/pypy/pypy/-/blob/be829135bc0d758997b3566062999ee8b23872b4/lib-python/3/site.py#L250
|
15 |
+
return
|
16 |
+
import warnings
|
17 |
+
|
18 |
+
warnings.warn(
|
19 |
+
"Distutils was imported before Setuptools, but importing Setuptools "
|
20 |
+
"also replaces the `distutils` module in `sys.modules`. This may lead "
|
21 |
+
"to undesirable behaviors or errors. To avoid these issues, avoid "
|
22 |
+
"using distutils directly, ensure that setuptools is installed in the "
|
23 |
+
"traditional way (e.g. not an editable install), and/or make sure "
|
24 |
+
"that setuptools is always imported before distutils."
|
25 |
+
)
|
26 |
+
|
27 |
+
|
28 |
+
def clear_distutils():
|
29 |
+
if 'distutils' not in sys.modules:
|
30 |
+
return
|
31 |
+
import warnings
|
32 |
+
|
33 |
+
warnings.warn("Setuptools is replacing distutils.")
|
34 |
+
mods = [
|
35 |
+
name
|
36 |
+
for name in sys.modules
|
37 |
+
if name == "distutils" or name.startswith("distutils.")
|
38 |
+
]
|
39 |
+
for name in mods:
|
40 |
+
del sys.modules[name]
|
41 |
+
|
42 |
+
|
43 |
+
def enabled():
|
44 |
+
"""
|
45 |
+
Allow selection of distutils by environment variable.
|
46 |
+
"""
|
47 |
+
which = os.environ.get('SETUPTOOLS_USE_DISTUTILS', 'local')
|
48 |
+
return which == 'local'
|
49 |
+
|
50 |
+
|
51 |
+
def ensure_local_distutils():
|
52 |
+
import importlib
|
53 |
+
|
54 |
+
clear_distutils()
|
55 |
+
|
56 |
+
# With the DistutilsMetaFinder in place,
|
57 |
+
# perform an import to cause distutils to be
|
58 |
+
# loaded from setuptools._distutils. Ref #2906.
|
59 |
+
with shim():
|
60 |
+
importlib.import_module('distutils')
|
61 |
+
|
62 |
+
# check that submodules load as expected
|
63 |
+
core = importlib.import_module('distutils.core')
|
64 |
+
assert '_distutils' in core.__file__, core.__file__
|
65 |
+
assert 'setuptools._distutils.log' not in sys.modules
|
66 |
+
|
67 |
+
|
68 |
+
def do_override():
|
69 |
+
"""
|
70 |
+
Ensure that the local copy of distutils is preferred over stdlib.
|
71 |
+
|
72 |
+
See https://github.com/pypa/setuptools/issues/417#issuecomment-392298401
|
73 |
+
for more motivation.
|
74 |
+
"""
|
75 |
+
if enabled():
|
76 |
+
warn_distutils_present()
|
77 |
+
ensure_local_distutils()
|
78 |
+
|
79 |
+
|
80 |
+
class _TrivialRe:
|
81 |
+
def __init__(self, *patterns):
|
82 |
+
self._patterns = patterns
|
83 |
+
|
84 |
+
def match(self, string):
|
85 |
+
return all(pat in string for pat in self._patterns)
|
86 |
+
|
87 |
+
|
88 |
+
class DistutilsMetaFinder:
|
89 |
+
def find_spec(self, fullname, path, target=None):
|
90 |
+
# optimization: only consider top level modules and those
|
91 |
+
# found in the CPython test suite.
|
92 |
+
if path is not None and not fullname.startswith('test.'):
|
93 |
+
return
|
94 |
+
|
95 |
+
method_name = 'spec_for_{fullname}'.format(**locals())
|
96 |
+
method = getattr(self, method_name, lambda: None)
|
97 |
+
return method()
|
98 |
+
|
99 |
+
def spec_for_distutils(self):
|
100 |
+
if self.is_cpython():
|
101 |
+
return
|
102 |
+
|
103 |
+
import importlib
|
104 |
+
import importlib.abc
|
105 |
+
import importlib.util
|
106 |
+
|
107 |
+
try:
|
108 |
+
mod = importlib.import_module('setuptools._distutils')
|
109 |
+
except Exception:
|
110 |
+
# There are a couple of cases where setuptools._distutils
|
111 |
+
# may not be present:
|
112 |
+
# - An older Setuptools without a local distutils is
|
113 |
+
# taking precedence. Ref #2957.
|
114 |
+
# - Path manipulation during sitecustomize removes
|
115 |
+
# setuptools from the path but only after the hook
|
116 |
+
# has been loaded. Ref #2980.
|
117 |
+
# In either case, fall back to stdlib behavior.
|
118 |
+
return
|
119 |
+
|
120 |
+
class DistutilsLoader(importlib.abc.Loader):
|
121 |
+
def create_module(self, spec):
|
122 |
+
mod.__name__ = 'distutils'
|
123 |
+
return mod
|
124 |
+
|
125 |
+
def exec_module(self, module):
|
126 |
+
pass
|
127 |
+
|
128 |
+
return importlib.util.spec_from_loader(
|
129 |
+
'distutils', DistutilsLoader(), origin=mod.__file__
|
130 |
+
)
|
131 |
+
|
132 |
+
@staticmethod
|
133 |
+
def is_cpython():
|
134 |
+
"""
|
135 |
+
Suppress supplying distutils for CPython (build and tests).
|
136 |
+
Ref #2965 and #3007.
|
137 |
+
"""
|
138 |
+
return os.path.isfile('pybuilddir.txt')
|
139 |
+
|
140 |
+
def spec_for_pip(self):
|
141 |
+
"""
|
142 |
+
Ensure stdlib distutils when running under pip.
|
143 |
+
See pypa/pip#8761 for rationale.
|
144 |
+
"""
|
145 |
+
if self.pip_imported_during_build():
|
146 |
+
return
|
147 |
+
clear_distutils()
|
148 |
+
self.spec_for_distutils = lambda: None
|
149 |
+
|
150 |
+
@classmethod
|
151 |
+
def pip_imported_during_build(cls):
|
152 |
+
"""
|
153 |
+
Detect if pip is being imported in a build script. Ref #2355.
|
154 |
+
"""
|
155 |
+
import traceback
|
156 |
+
|
157 |
+
return any(
|
158 |
+
cls.frame_file_is_setup(frame) for frame, line in traceback.walk_stack(None)
|
159 |
+
)
|
160 |
+
|
161 |
+
@staticmethod
|
162 |
+
def frame_file_is_setup(frame):
|
163 |
+
"""
|
164 |
+
Return True if the indicated frame suggests a setup.py file.
|
165 |
+
"""
|
166 |
+
# some frames may not have __file__ (#2940)
|
167 |
+
return frame.f_globals.get('__file__', '').endswith('setup.py')
|
168 |
+
|
169 |
+
def spec_for_sensitive_tests(self):
|
170 |
+
"""
|
171 |
+
Ensure stdlib distutils when running select tests under CPython.
|
172 |
+
|
173 |
+
python/cpython#91169
|
174 |
+
"""
|
175 |
+
clear_distutils()
|
176 |
+
self.spec_for_distutils = lambda: None
|
177 |
+
|
178 |
+
sensitive_tests = (
|
179 |
+
[
|
180 |
+
'test.test_distutils',
|
181 |
+
'test.test_peg_generator',
|
182 |
+
'test.test_importlib',
|
183 |
+
]
|
184 |
+
if sys.version_info < (3, 10)
|
185 |
+
else [
|
186 |
+
'test.test_distutils',
|
187 |
+
]
|
188 |
+
)
|
189 |
+
|
190 |
+
|
191 |
+
for name in DistutilsMetaFinder.sensitive_tests:
|
192 |
+
setattr(
|
193 |
+
DistutilsMetaFinder,
|
194 |
+
f'spec_for_{name}',
|
195 |
+
DistutilsMetaFinder.spec_for_sensitive_tests,
|
196 |
+
)
|
197 |
+
|
198 |
+
|
199 |
+
DISTUTILS_FINDER = DistutilsMetaFinder()
|
200 |
+
|
201 |
+
|
202 |
+
def add_shim():
|
203 |
+
DISTUTILS_FINDER in sys.meta_path or insert_shim()
|
204 |
+
|
205 |
+
|
206 |
+
class shim:
|
207 |
+
def __enter__(self):
|
208 |
+
insert_shim()
|
209 |
+
|
210 |
+
def __exit__(self, exc, value, tb):
|
211 |
+
remove_shim()
|
212 |
+
|
213 |
+
|
214 |
+
def insert_shim():
|
215 |
+
sys.meta_path.insert(0, DISTUTILS_FINDER)
|
216 |
+
|
217 |
+
|
218 |
+
def remove_shim():
|
219 |
+
try:
|
220 |
+
sys.meta_path.remove(DISTUTILS_FINDER)
|
221 |
+
except ValueError:
|
222 |
+
pass
|
venv/Lib/site-packages/_distutils_hack/__pycache__/__init__.cpython-311.pyc
ADDED
Binary file (11.2 kB). View file
|
|
venv/Lib/site-packages/_distutils_hack/__pycache__/override.cpython-311.pyc
ADDED
Binary file (349 Bytes). View file
|
|
venv/Lib/site-packages/_distutils_hack/override.py
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
__import__('_distutils_hack').do_override()
|
venv/Lib/site-packages/distutils-precedence.pth
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:2638ce9e2500e572a5e0de7faed6661eb569d1b696fcba07b0dd223da5f5d224
|
3 |
+
size 151
|