diff --git a/main.py b/main.py new file mode 100644 index 0000000000000000000000000000000000000000..9c44ed9dbb41a4c6b3d95b41f8013703c4af6e48 --- /dev/null +++ b/main.py @@ -0,0 +1,76 @@ +import plantvision +import requests +from io import BytesIO +import pickle as pkl +from flask import Flask, render_template, request, session, jsonify, url_for +from PIL import Image +import os +import time +import random +from pathlib import Path +THIS_FOLDER = Path(__file__).parent.resolve() + +app = Flask(__name__) +app.secret_key = 'pi-33pp-co-sk-33' +app.template_folder = os.path.abspath(f'{THIS_FOLDER}/web/templates') +app.static_folder = os.path.abspath(f'{THIS_FOLDER}/web/static') +print(app.static_folder) + +flowerLayers = None +leafLayers = None +fruitLayers = None + +@app.route('/') +def home(): + return render_template('index.html') + +@app.route('/guess', methods=['POST']) +def guess(): + global flowerLayers, leafLayers, fruitLayers + + if request.method == 'POST': + print('Thinking...') + + img = request.files.get('uploaded-image') + feature = request.form.get('feature') + + tensor = plantvision.processImage(img, feature) + predictions = plantvision.see(tensor, feature, 6) + + with open(f'{THIS_FOLDER}/resources/speciesNameToKey.pkl','rb') as f: + speciesNameToKey = pkl.load(f) + with open(f'{THIS_FOLDER}/resources/speciesNameToVernacular.pkl','rb') as f: + speciesNameToVernacular = pkl.load(f) + with open(f'{THIS_FOLDER}/resources/{feature}speciesIndexDict.pkl','rb') as f: + speciesNameToIndex = pkl.load(f) + + urls = [] + predicted_image_urls = [] + for p in predictions: + key = speciesNameToKey[p] + img = speciesNameToIndex[p] + query = '' + for i in p.split(' '): + query += i + query += '+' + urls.append(f'https://www.google.com/search?q={query[:-1]}') + predicted_image_urls.append(f"https://storage.googleapis.com/bmllc-images-bucket/images/img{img}.jpeg") + + names = [] + for p in predictions: + try: + names.append(speciesNameToVernacular[p]) + except: + names.append(p) + + response = { + 'names': names, + 'species': predictions, + 'predictions': urls, + 'images': predicted_image_urls + } + + return jsonify(response) + +if __name__ == '__main__': + app.run(port=int(os.environ.get("PORT", 8080)),host='0.0.0.0',debug=True) diff --git a/plantvision.py b/plantvision.py new file mode 100644 index 0000000000000000000000000000000000000000..6fdb5cf27a6793a9d4177f6c8c20bce1bec2b8a2 --- /dev/null +++ b/plantvision.py @@ -0,0 +1,115 @@ +import requests +from io import BytesIO +from PIL import Image, ImageOps +import torchvision.transforms as T +import torch +import gc +import pickle as pkl +from pathlib import Path +THIS_FOLDER = Path(__file__).parent.resolve() +import datetime as dt +from transformers import AutoModel +import torch.nn as nn +import torch.nn.functional as F +import logging +logging.disable(logging.INFO) +logging.disable(logging.WARNING) + +visionTransformer = AutoModel.from_pretrained(r"google/vit-base-patch16-224-in21k") + +class PlantVision(nn.Module): + def __init__(self, num_classes): + super(PlantVision, self).__init__() + self.vit = visionTransformer + count = 0 + for child in self.vit.children(): + count += 1 + if count < 4: + for param in child.parameters(): + param.requires_grad = False + self.vitLayers = list(self.vit.children()) + self.vitTop = nn.Sequential(*self.vitLayers[:-2]) + self.vitNorm = list(self.vit.children())[2] + self.vit = None + gc.collect() + self.vitFlatten = nn.Flatten() + self.vitLinear = nn.Linear(151296,num_classes) + self.fc = nn.Linear(num_classes, num_classes) + + def forward(self, input): + output = self.vitTop(input).last_hidden_state + output = self.vitNorm(output) + output = self.vitFlatten(output) + output = F.relu(self.vitLinear(output)) + output = self.fc(output) + return output + +device = 'cpu' # ('cuda' if torch.cuda.is_available else 'cpu') + +with open(fr'{THIS_FOLDER}/resources/flowerLabelSet.pkl', 'rb') as f: + flowerLabelSet = pkl.load(f) + +with open(fr'{THIS_FOLDER}/resources/leafLabelSet.pkl', 'rb') as f: + leafLabelSet = pkl.load(f) + +with open(fr'{THIS_FOLDER}/resources/fruitLabelSet.pkl', 'rb') as f: + fruitLabelSet = pkl.load(f) + +def loadModel(feature, labelSet): + model = PlantVision(num_classes=len(labelSet)) + model.vitFlatten.load_state_dict(torch.load(BytesIO(requests.get(f"https://storage.googleapis.com/bmllc-plant-model-bucket/{feature}-vitFlatten-weights.pt").content), map_location=torch.device(device)), strict=False) + model.vitLinear.load_state_dict(torch.load(BytesIO(requests.get(f"https://storage.googleapis.com/bmllc-plant-model-bucket/{feature}-vitLinear-weights.pt").content), map_location=torch.device(device)), strict=False) + model.fc.load_state_dict(torch.load(BytesIO(requests.get(f"https://storage.googleapis.com/bmllc-plant-model-bucket/{feature}-fc-weights.pt").content), map_location=torch.device(device)), strict=False) + model = model.half() + return model + +start = dt.datetime.now() +flower = loadModel('flower',flowerLabelSet) +leaf = loadModel('leaf',leafLabelSet) +fruit = loadModel('fruit',fruitLabelSet) +print(dt.datetime.now() - start) + +def processImage(imagePath, feature): + with open(fr'{THIS_FOLDER}/resources/{feature}MeansAndStds.pkl', 'rb') as f: + meansAndStds = pkl.load(f) + + img = Image.open(imagePath).convert('RGB') + cropped = ImageOps.fit(img, (224,224), Image.Resampling.LANCZOS) + + process = T.Compose([ + T.CenterCrop(224), + T.ToTensor(), + T.ConvertImageDtype(torch.float32), + T.Normalize( + mean=meansAndStds['mean'], + std=meansAndStds['std']) + ]) + + return process(cropped) + +def see(tensor,feature,k): + + if feature=='flower': + model = flower.float() + labelSet = flowerLabelSet + + elif feature=='leaf': + model = leaf.float() + labelSet = leafLabelSet + + elif feature=='fruit': + model = fruit.float() + labelSet = fruitLabelSet + + with torch.no_grad(): + output = model(tensor.unsqueeze(0)) + top = torch.topk(output,k,dim=1) + predictions = top.indices[0] + + predictedSpecies = [] + for i in predictions: + predictedSpecies.append(labelSet[i]) + + model = None + gc.collect() + return predictedSpecies diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..a76ca1fdc08b828d13bce174e53add6fb312e6d8 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,11 @@ +Flask==2.1.0 +gunicorn==20.1.0 +torch +torchvision +pillow +tqdm +argparse +pathlib +transformers +fastapi +uvicorn \ No newline at end of file diff --git a/resources/flowerImageIndices.pkl b/resources/flowerImageIndices.pkl new file mode 100644 index 0000000000000000000000000000000000000000..5f9d7edbfc6f0748d8fd106d56d4ba0bdab67ff8 --- /dev/null +++ b/resources/flowerImageIndices.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9189a92359774fb40bb605424ae3b3a2e6ae2b9a92b1c0bb61fb79981549ddfb +size 113164 diff --git a/resources/flowerImageLabels.pkl b/resources/flowerImageLabels.pkl new file mode 100644 index 0000000000000000000000000000000000000000..620ca0f8ead9623da9f11d42ed1ca5cf8505af57 --- /dev/null +++ b/resources/flowerImageLabels.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9fd985d5e407b226c439e966aadc18f6332195f8cd118ccdd18bd4cf1de54b63 +size 203485 diff --git a/resources/flowerLabelSet.pkl b/resources/flowerLabelSet.pkl new file mode 100644 index 0000000000000000000000000000000000000000..eece434b98a2695b29d36c18eb2ff3ad0761656d --- /dev/null +++ b/resources/flowerLabelSet.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:89ba3712eb686badc02a768e6b0f09974c4161466ea92cee1c9b86f70e33af15 +size 23964 diff --git a/resources/flowerMeansAndStds.pkl b/resources/flowerMeansAndStds.pkl new file mode 100644 index 0000000000000000000000000000000000000000..a9d88a7815d2591594d5afaf78d4b6df26d716a7 --- /dev/null +++ b/resources/flowerMeansAndStds.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:76a3349b54abf757e5ef18d4801320c8194561eedab4ce3ea9739ff2bca25d8f +size 236 diff --git a/resources/flowerspeciesIndexDict.pkl b/resources/flowerspeciesIndexDict.pkl new file mode 100644 index 0000000000000000000000000000000000000000..a116d7bd6e7f6e9f5810991838a9dc18eeb449fc --- /dev/null +++ b/resources/flowerspeciesIndexDict.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:571c2b76bc6f92e97bd51bd37224b62f7c1ecc6c02dd7768c2c1cc0212f9d26b +size 70059 diff --git a/resources/fruitImageIndices.pkl b/resources/fruitImageIndices.pkl new file mode 100644 index 0000000000000000000000000000000000000000..fef1b1661d01f0bc6b8280206c63a897b3858dc2 --- /dev/null +++ b/resources/fruitImageIndices.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a5a902edb6214f6c4eedba3213b7bdd24c5f87cc62db33c66f645a4bd0444f0f +size 17734 diff --git a/resources/fruitImageLabels.pkl b/resources/fruitImageLabels.pkl new file mode 100644 index 0000000000000000000000000000000000000000..81bbf27463d86ec6a3403e0450cceb93aea8cda5 --- /dev/null +++ b/resources/fruitImageLabels.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c260c9bce0614a1197a92ffa29600d3890e97f733e702c4a5870a21eb8f665a8 +size 27866 diff --git a/resources/fruitLabelSet.pkl b/resources/fruitLabelSet.pkl new file mode 100644 index 0000000000000000000000000000000000000000..cd295a764928d70012b82f21b81de48f48e7c53f --- /dev/null +++ b/resources/fruitLabelSet.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:875210d10918a499c84815b3393afc8f3e4fc07979c872714053c40059857da5 +size 5164 diff --git a/resources/fruitMeansAndStds.pkl b/resources/fruitMeansAndStds.pkl new file mode 100644 index 0000000000000000000000000000000000000000..5842d85829a239918de716a9f3ec5df67f810956 --- /dev/null +++ b/resources/fruitMeansAndStds.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1479a0b5e531db1b783fa3fe8f47bb6278fc43c0dc9448decb53f855df83fe72 +size 236 diff --git a/resources/fruitspeciesIndexDict.pkl b/resources/fruitspeciesIndexDict.pkl new file mode 100644 index 0000000000000000000000000000000000000000..c27cf9e79072b2ed9ce9e072dabf6e881b4f2c4c --- /dev/null +++ b/resources/fruitspeciesIndexDict.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b045988de9a625ce8ebb81adf64f00bf2709ce76868c388c95aeaeb1a918c930 +size 22526 diff --git a/resources/fruitspeciesNameToID.pkl b/resources/fruitspeciesNameToID.pkl new file mode 100644 index 0000000000000000000000000000000000000000..5053f45ff6d5f1f004cef71892941617dfc420d2 --- /dev/null +++ b/resources/fruitspeciesNameToID.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8ffb83cf9e6053dbc3888d5e6b3e2d784bfd09333fb5058b51f3f1da0d005fc1 +size 584518 diff --git a/resources/imageIndices.pkl b/resources/imageIndices.pkl new file mode 100644 index 0000000000000000000000000000000000000000..7dcd610659ca239336b8e358c8322875ceaf56b1 --- /dev/null +++ b/resources/imageIndices.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:99ebbdcb3b5124aab5cb156b8be0672041fa16b70acb5e1efbb0a796dd54cf5b +size 650154 diff --git a/resources/imageLabels.pkl b/resources/imageLabels.pkl new file mode 100644 index 0000000000000000000000000000000000000000..1f548297a34898efbe59652f4f00eb6c3b365a0f --- /dev/null +++ b/resources/imageLabels.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:76c77a8a0bf3e1316e382b13db60c9b6dc90143ba9712f07f24ea6684b5b7da9 +size 1771457 diff --git a/resources/imageLocations.pkl b/resources/imageLocations.pkl new file mode 100644 index 0000000000000000000000000000000000000000..9b5ffae34ee23fb4cb47122740dee4f770c3b66a --- /dev/null +++ b/resources/imageLocations.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3af966e13a46d4fddda1abe0764db5a0fa3b1996b113756b7fe2db1160a4ebbb +size 2234643 diff --git a/resources/imageSelections.pkl b/resources/imageSelections.pkl new file mode 100644 index 0000000000000000000000000000000000000000..50e9e990664617776b4ae8583f4d78bc3a6f0eab --- /dev/null +++ b/resources/imageSelections.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c306952141e794e935a1bdcc4a35aa45bc2858756533683a159ac1f44ec4fed5 +size 2407338 diff --git a/resources/infoDict.pkl b/resources/infoDict.pkl new file mode 100644 index 0000000000000000000000000000000000000000..228836808ac6f52346d94d9ce87ae7619bde8e71 --- /dev/null +++ b/resources/infoDict.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c5602ffd87f6bb46b53ac6c4cece0703a3595721a1b8cc6cc4e650154bdbfd6e +size 10577505 diff --git a/resources/labelSet.pkl b/resources/labelSet.pkl new file mode 100644 index 0000000000000000000000000000000000000000..7e895abe0b0bdaf7b0343026e4b3fc19a6bf8ca3 --- /dev/null +++ b/resources/labelSet.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3f46f078cdab57f85ac70a3da7e11cc9f001c493a40e1ea32c7da50acfb8af2b +size 36457 diff --git a/resources/leafImageIndices.pkl b/resources/leafImageIndices.pkl new file mode 100644 index 0000000000000000000000000000000000000000..f6051ca7a34ec8dfb6d4d1b7ff1d462aed9333ae --- /dev/null +++ b/resources/leafImageIndices.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:26463c81aa1fdde63490deafb70016d6efe5d5241d25f4e4dd63aeac14b4452b +size 144737 diff --git a/resources/leafImageLabels.pkl b/resources/leafImageLabels.pkl new file mode 100644 index 0000000000000000000000000000000000000000..b3b7f725dcb7e74f0ba9c0c843f90bcbe7967df5 --- /dev/null +++ b/resources/leafImageLabels.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2f8ae09eb663be008a260a2f30d668ff28b042a72578178783017add5a5034f7 +size 259577 diff --git a/resources/leafLabelSet.pkl b/resources/leafLabelSet.pkl new file mode 100644 index 0000000000000000000000000000000000000000..d8e221cd6caf7d583669fe6c4520abb09f809d77 --- /dev/null +++ b/resources/leafLabelSet.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c25e4e98ea2bd5a313ca032c212aeb688e84afca6458757889be84a282c4580e +size 25482 diff --git a/resources/leafMeansAndStds.pkl b/resources/leafMeansAndStds.pkl new file mode 100644 index 0000000000000000000000000000000000000000..379326c97e36c3a93c5a15f65d17dd832ce2266e --- /dev/null +++ b/resources/leafMeansAndStds.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ab072ba014fe39029378b2c088d8a8ab85f1291163e25e060e621f00cedb194c +size 236 diff --git a/resources/leafspeciesIndexDict.pkl b/resources/leafspeciesIndexDict.pkl new file mode 100644 index 0000000000000000000000000000000000000000..b4f18202048cf1fd7c0900f781bfc68b66b0ab92 --- /dev/null +++ b/resources/leafspeciesIndexDict.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:92fbc241dc2825a4f83beb61e3be3a4ae59e0d04cb3a68a80e58fbb3aff0d4ec +size 69399 diff --git a/resources/meansAndStds.pkl b/resources/meansAndStds.pkl new file mode 100644 index 0000000000000000000000000000000000000000..d2980b58bf68cccdebb91badcfee88b7d52cb2de --- /dev/null +++ b/resources/meansAndStds.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5f9c146afe67291a4143714b466a3cb7eed425bfed1218103930ebafeefc0747 +size 236 diff --git a/resources/speciesNameToID.pkl b/resources/speciesNameToID.pkl new file mode 100644 index 0000000000000000000000000000000000000000..5053f45ff6d5f1f004cef71892941617dfc420d2 --- /dev/null +++ b/resources/speciesNameToID.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8ffb83cf9e6053dbc3888d5e6b3e2d784bfd09333fb5058b51f3f1da0d005fc1 +size 584518 diff --git a/resources/speciesNameToKey.pkl b/resources/speciesNameToKey.pkl new file mode 100644 index 0000000000000000000000000000000000000000..c3f05ee1e14c08c2973a5a7023d82b546299c267 --- /dev/null +++ b/resources/speciesNameToKey.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0edd05755d1b9ca01291426ff35a9964589d4cd52f1cbea3f032b9dbfc7f2b17 +size 94428 diff --git a/resources/speciesNameToVernacular.pkl b/resources/speciesNameToVernacular.pkl new file mode 100644 index 0000000000000000000000000000000000000000..7b7b2f2aff359765ebad3c35ae81b6df6b01a4d7 --- /dev/null +++ b/resources/speciesNameToVernacular.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2207518c0f274b0b579fe8bf70c6605b3abaeb7a725adebab5b2bd3ec6201e41 +size 140185 diff --git a/web/static/loading.gif b/web/static/loading.gif new file mode 100644 index 0000000000000000000000000000000000000000..64142ace027c0eab6595a87c7a4f70f5c84b3010 Binary files /dev/null and b/web/static/loading.gif differ diff --git a/web/static/predicted-images/img0.jpeg b/web/static/predicted-images/img0.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..5e098913138d35de2506394433b0777c179314d6 Binary files /dev/null and b/web/static/predicted-images/img0.jpeg differ diff --git a/web/static/predicted-images/img1.jpeg b/web/static/predicted-images/img1.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..5d33d0a4ba38574069ff56d276d712fa06496d18 Binary files /dev/null and b/web/static/predicted-images/img1.jpeg differ diff --git a/web/static/predicted-images/img10.jpeg b/web/static/predicted-images/img10.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..c5e00433edd06e13136f8f23939146fdd3e446f3 Binary files /dev/null and b/web/static/predicted-images/img10.jpeg differ diff --git a/web/static/predicted-images/img11.jpeg b/web/static/predicted-images/img11.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..4339e59646be57c2505969d45c2a1892cf5beae3 Binary files /dev/null and b/web/static/predicted-images/img11.jpeg differ diff --git a/web/static/predicted-images/img12.jpeg b/web/static/predicted-images/img12.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..2dfb21b4cdb32ddf09508b6260600a675b57e751 Binary files /dev/null and b/web/static/predicted-images/img12.jpeg differ diff --git a/web/static/predicted-images/img13.jpeg b/web/static/predicted-images/img13.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..308f846bdb46b9f3cd07c451ef4223107cd85198 Binary files /dev/null and b/web/static/predicted-images/img13.jpeg differ diff --git a/web/static/predicted-images/img14.jpeg b/web/static/predicted-images/img14.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..0f55e918c474ea2161ecc18d5b57f23d1224c27d Binary files /dev/null and b/web/static/predicted-images/img14.jpeg differ diff --git a/web/static/predicted-images/img15.jpeg b/web/static/predicted-images/img15.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..178f336ac691cc50e5b09623b84cd8608f3035f9 Binary files /dev/null and b/web/static/predicted-images/img15.jpeg differ diff --git a/web/static/predicted-images/img16.jpeg b/web/static/predicted-images/img16.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..2425eabc3255f8fc9100be83afba55751e0aaf22 Binary files /dev/null and b/web/static/predicted-images/img16.jpeg differ diff --git a/web/static/predicted-images/img17.jpeg b/web/static/predicted-images/img17.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..691d82232b0ea25c3dd1fa1037a72d562f62a953 Binary files /dev/null and b/web/static/predicted-images/img17.jpeg differ diff --git a/web/static/predicted-images/img18.jpeg b/web/static/predicted-images/img18.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..ffbf02af8c8bb011db6644daf900f3fffcad5e09 Binary files /dev/null and b/web/static/predicted-images/img18.jpeg differ diff --git a/web/static/predicted-images/img19.jpeg b/web/static/predicted-images/img19.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..cf99ccf28146af1dc3aa95490b821ba34cb67fcb Binary files /dev/null and b/web/static/predicted-images/img19.jpeg differ diff --git a/web/static/predicted-images/img2.jpeg b/web/static/predicted-images/img2.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..93247c316b4134a5dd16f2bbbde55e5a21c187dc Binary files /dev/null and b/web/static/predicted-images/img2.jpeg differ diff --git a/web/static/predicted-images/img3.jpeg b/web/static/predicted-images/img3.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..9fcabba96b45873dd14843f3b721e063534607b4 Binary files /dev/null and b/web/static/predicted-images/img3.jpeg differ diff --git a/web/static/predicted-images/img4.jpeg b/web/static/predicted-images/img4.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..d74c28ef9e365a1a9e8c6f426810af544792faf5 Binary files /dev/null and b/web/static/predicted-images/img4.jpeg differ diff --git a/web/static/predicted-images/img5.jpeg b/web/static/predicted-images/img5.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..b37e2bd7310b0e68fdd9e8dd2560efab31ea1603 Binary files /dev/null and b/web/static/predicted-images/img5.jpeg differ diff --git a/web/static/predicted-images/img6.jpeg b/web/static/predicted-images/img6.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..fec3973f6b2cb351a69c08b93827518cc52b0ef8 Binary files /dev/null and b/web/static/predicted-images/img6.jpeg differ diff --git a/web/static/predicted-images/img7.jpeg b/web/static/predicted-images/img7.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..8a84b2ce6ad730aac45de097468c257a8c67418a Binary files /dev/null and b/web/static/predicted-images/img7.jpeg differ diff --git a/web/static/predicted-images/img8.jpeg b/web/static/predicted-images/img8.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..7b30db630adfd847df9abed873e631d6b6d74773 Binary files /dev/null and b/web/static/predicted-images/img8.jpeg differ diff --git a/web/static/predicted-images/img9.jpeg b/web/static/predicted-images/img9.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..87f0b7cd1de2567c20e8e04208c2978d4eba39bb Binary files /dev/null and b/web/static/predicted-images/img9.jpeg differ diff --git a/web/static/script.js b/web/static/script.js new file mode 100644 index 0000000000000000000000000000000000000000..114d5c29c694cafb4eb4ad07e7be89e64e553d94 --- /dev/null +++ b/web/static/script.js @@ -0,0 +1,174 @@ +document.addEventListener("DOMContentLoaded", () => { + let lastUploadedImage = null; + const dropzone = document.getElementById("image-dropzone"); + const featureSelect = document.getElementById("feature-select"); + const thinkingText = document.getElementById("thinking-text"); + + function handleImageUpload(file) { + const predictedImagesContainer = document.getElementById("predicted-images"); + predictedImagesContainer.innerHTML = ""; + + const imageElement = document.createElement("img"); + imageElement.src = "static/loading.gif"; + predictedImagesContainer.appendChild(imageElement); + + const selectedFeature = featureSelect.value; + const formData = new FormData(); + thinkingText.innerText = "Loading the model, it might take a second..."; + formData.append("uploaded-image", file); + formData.append("feature", selectedFeature); + lastUploadedImage = file; + fetch("/guess", { + method: "POST", + body: formData, + }) + .then((response) => response.json()) + .then((data) => { + console.log(data); + displayResults(data); + }) + .catch((error) => { + console.error("Error uploading image:", error); + }); + + const reader = new FileReader(); + reader.onloadend = () => { + const uploadedImage = new Image(); + + uploadedImage.src = reader.result; + uploadedImage.classList.add("uploaded-image"); + uploadedImage.onload = async () => { + + const dropzoneWidth = dropzone.offsetWidth; + const dropzoneHeight = dropzone.offsetHeight; + const imageWidth = uploadedImage.width; + const imageHeight = uploadedImage.height; + const widthScale = dropzoneWidth / imageWidth; + const heightScale = dropzoneHeight / imageHeight; + const scaleFactor = Math.min(widthScale, heightScale); + uploadedImage.width = imageWidth * (scaleFactor-0.1); + uploadedImage.height = imageHeight * (scaleFactor-0.1); + dropzone.innerHTML = ""; + dropzone.appendChild(uploadedImage); + }; + }; + + if (file) { + reader.readAsDataURL(file); + } + } + + +dropzone.addEventListener("click", () => { + const fileInput = document.createElement("input"); + fileInput.type = "file"; + fileInput.accept = "image/*"; + fileInput.onchange = (e) => { + const file = e.target.files[0]; + handleImageUpload(file); + }; + fileInput.click(); +}); + + +dropzone.addEventListener("dragover", (e) => { + e.preventDefault(); + dropzone.classList.add("highlight"); +}); + + +dropzone.addEventListener("dragleave", () => { + dropzone.classList.remove("highlight"); +}); + + +dropzone.addEventListener("drop", (e) => { + e.preventDefault(); + dropzone.classList.remove("highlight"); + + const file = e.dataTransfer.files[0]; + handleImageUpload(file); +}); + +featureSelect.addEventListener("change", () => { + if (lastUploadedImage) { + handleImageUpload(lastUploadedImage); + } +}); + +function displayResults(data) { + thinkingText.innerText = "Looks like..."; + const predictedImagesContainer = document.getElementById("predicted-images"); + predictedImagesContainer.innerHTML = ""; // Clear previous images + + for (let i = 0; i < data.images.length; i++) { + const imageUrl = data.images[i]; + const predictionUrl = data.predictions[i]; + const name = data.names[i]; + const species = data.species[i]; + + // Create a container div for each image and its anchor + const imageContainer = document.createElement("div"); + + // Create the anchor element + const anchorElement = document.createElement("a"); + anchorElement.href = predictionUrl; + anchorElement.target = "_blank"; // Open the link in a new tab + + // Create the caption element + const captionElement = document.createElement("div"); + captionElement.classList.add("image-caption"); + captionElement.textContent = name; // Use the name from the 'names' list as the caption text + + // Create the caption element + const speciesElement = document.createElement("div"); + speciesElement.classList.add("image-species"); + speciesElement.textContent = species; // Use the name from the 'names' list as the caption text + + // Create the image element + const imageElement = document.createElement("img"); + // Add cache-busting parameter to the image URL + const cacheBustUrl = imageUrl + `?cache=${Date.now()}`; + // Set the lazy loading attribute + imageElement.loading = "lazy"; + // Set the data-src attribute with the cache-busted URL + imageElement.src = cacheBustUrl; + + const tooltip = document.createElement("div"); + tooltip.classList.add("image-tooltip"); + tooltip.textContent = species; // Use the name from the 'names' list as the tooltip text + + // Append the image to the anchor and the anchor to the container + anchorElement.appendChild(imageElement); + anchorElement.appendChild(captionElement); + imageContainer.appendChild(tooltip); + imageContainer.appendChild(anchorElement); + captionElement.style.opacity = "0"; + captionElement.style.opacity = "1"; + + + // Append the container to the predictedImagesContainer + predictedImagesContainer.appendChild(imageContainer); + + // Add the tooltip element to the document body + document.body.appendChild(tooltip); + + // Add event listeners to handle tooltip visibility + imageContainer.addEventListener("mouseover", () => { + tooltip.style.opacity = "1"; + }); + + imageContainer.addEventListener("mouseout", () => { + tooltip.style.opacity = "0"; + }); + + // Position the tooltip dynamically based on cursor movement + imageContainer.addEventListener("mousemove", (event) => { + tooltip.style.left = event.pageX + "px"; + tooltip.style.top = event.pageY + "px"; + }); + } +} + + +}); diff --git a/web/static/styles.css b/web/static/styles.css new file mode 100644 index 0000000000000000000000000000000000000000..8f0dd4a270c404b1a8f07b5f62a6a8076283a13c --- /dev/null +++ b/web/static/styles.css @@ -0,0 +1,167 @@ +body { + font-family: 'Helvetica'; + margin-top: 100px; + margin-left: 100px; + margin-right: 100px; + padding: 0; +} + +.header { + margin-top: 40px; + margin-bottom: 20px; + background-color: #ffffff; + color: #000000; + position: absolute; + text-align: center; + top: 40px; + left: 50%; + transform: translateX(-50%); +} + +.container { + margin-top: 120px; + height: auto; + top: 100px; + display: flex; + justify-content: space-around; + padding: 20px; + position: relative; + align-items: stretch; +} + +.left-container { + width: 52%; + padding: 20px; + text-align: center; + align-items: center; + align-content: center; +} + +.right-container { + margin-left:-2px; + width: 48%; + padding: 20px; + border: 2px solid #000000; + text-align: left; +} + +.dropzone { + width: 52%; + min-height: 500px; + border: 2px solid #000000; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; +} + +#uploaded-image { + max-width: 100%; + max-height: 200px; + display: none; + align-items: center; +} + +.github-container { + display: flex; + justify-content: center; + margin-top: 100px; +} + +.github-container a { + display: flex; + color:#000000; + align-items: center; +} + +.feature-select { + display: flex; + justify-content: center; + margin-top: 10px; +} + +/* The container for the predicted images */ +#predicted-images { + display: grid; + row-gap: 1%; + column-gap: 1%; + width: 100%; + grid-template-columns: repeat(3, 1fr); + grid-template-rows: repeat(3, fr); + position: relative; /* Set the container as a relative positioning context */ +} + +/* The predicted image */ +#predicted-images img { + width: 100%; + height: auto; + display: block; + align-items: center; +} + +/* The tooltip */ +.image-tooltip { + position: absolute; + bottom: 100%; /* Position the tooltip above the image */ + left: 50%; /* Center the tooltip horizontally */ + transform: translate(0%, -60%); /* Center both horizontally and vertically */ + background-color: rgba(0, 0, 0, 0.6); + padding-left: 10px; + padding-right: 10px; + padding-top: 10px; + padding-bottom: 20px; + color: white; + font-size: 12px; + pointer-events: none; + opacity: 0; /* Initially set the tooltip to be transparent */ +} + +/* Apply the hover effect */ +#predicted-images img { + /* Set initial transition properties */ + transition: all 0.3s ease-in-out; + transform: scale(1); /* Set the initial scale to 1 (normal size) */ +} + +#predicted-images img:hover { + filter: brightness(70%); + transform: scale(1.07); /* Scale the image up to 1.1 times its original size on hover */ +} + +/* Show the tooltip on image hover */ +#predicted-images .image-container:hover .image-tooltip { +opacity: 1; /* Make the tooltip visible on image hover */ +visibility: visible; /* Show the tooltip on image hover */ +} + +/* The caption */ +.image-caption { + bottom: 0; + left: 0; + width: auto; + gap: 0%; + padding: 10px; + align-items: center; + text-decoration: none; + font-size: 14px; + text-align: center; + font-weight: normal; + pointer-events: none; /* Prevent caption from blocking clicks */ +} + +.image-species { + opacity: 0; +} + +#predicted-images a { + color: #4e4e4e; + text-decoration: none; + font-weight: normal; +} + +#predicted-images > div > a:hover .image-caption{ + color: #000000; + font-size: 14px; + transition: 0.5s; +} + diff --git a/web/templates/index.html b/web/templates/index.html new file mode 100644 index 0000000000000000000000000000000000000000..9edcbb7ac740e5d0d142dddf4e2a495c0429436a --- /dev/null +++ b/web/templates/index.html @@ -0,0 +1,52 @@ + + + + What Plant Is This? + + + + +
+

What Plant is This?

+

Have a neural net try to identify the plant you found.

+ +
+ + +
+ +
+ +
+
+

Drop an image

+
+ Uploaded Image +
+

Suggestions will appear here...

+
+
+
+
+
+
+ + +
+ + +

See the code

+
+
+ +
+




+
+ + + +