Add project files
1
.~lock.test_results.csv#
Normal file
@@ -0,0 +1 @@
|
||||
,suman,sumanhc,31.07.2026 13:03,file:///home/suman/.config/libreoffice/4;
|
||||
27
Rules.txt
Normal file
@@ -0,0 +1,27 @@
|
||||
The folder to edit: /media/suman/Backup_of_extra_/Sasi/Flowers_images/Templates
|
||||
(this is config.TEMPLATE_IMAGES_DIR — currently template1.png, template2.png, template4.png, template5.png, template6.png)
|
||||
|
||||
Add a new template
|
||||
Copy the new photo into that folder (.png, .jpg, .jpeg, .bmp, or .webp all work). Give it a clean name — that filename (without extension) becomes its display name in the gallery and in match results, e.g. template7.png → shown as "Template7".
|
||||
Restart the server (see below).
|
||||
Remove a template
|
||||
Delete its file from that folder.
|
||||
Also delete its matching cached file at /media/suman/Backup_of_extra_/Sasi/featureTransform/cache/templates_nobg/<name>.png (harmless if you skip it — it's just an orphaned cache file wasting a little disk space — but tidy to remove).
|
||||
Restart the server.
|
||||
Replace a template's photo but keep the same filename
|
||||
This is the one case that needs an extra step: the background-removal cache is keyed by filename only, not the image content. If you overwrite template4.png with a different photo but don't touch the cache, the app will keep using the old cutout and old matching features.
|
||||
|
||||
Overwrite the file in the Templates folder.
|
||||
Delete the stale cache file: rm /media/suman/Backup_of_extra_/Sasi/featureTransform/cache/templates_nobg/template4.png
|
||||
Restart the server.
|
||||
(Simplest blanket rule if you're changing several templates at once: just wipe the whole cache folder — rm -f /media/suman/Backup_of_extra_/Sasi/featureTransform/cache/templates_nobg/* — and let it rebuild on next startup.)
|
||||
|
||||
Why a restart is required
|
||||
The homepage gallery re-scans the folder on every page load, so it'd show new files immediately — but the actual matching pipeline (SIFT/ORB keypoints, SuperPoint features, LoFTR tensors, per-template masks) is precomputed once at server startup and held in memory for speed. Adding/removing files on disk doesn't touch that in-memory set until the process restarts and reruns bootstrap().
|
||||
|
||||
To restart:
|
||||
|
||||
|
||||
pkill -f "python3 app.py" # or: kill <pid> from `ps aux | grep app.py`
|
||||
cd /media/suman/Backup_of_extra_/Sasi/featureTransform
|
||||
python3 app.py
|
||||
BIN
__pycache__/app.cpython-310.pyc
Normal file
BIN
__pycache__/config.cpython-310.pyc
Normal file
BIN
__pycache__/config.cpython-311.pyc
Normal file
BIN
__pycache__/config.cpython-38.pyc
Normal file
BIN
__pycache__/testVaseMatcher.cpython-310.pyc
Normal file
227
app.py
Normal file
@@ -0,0 +1,227 @@
|
||||
"""
|
||||
Vase Matcher -- Flask backend.
|
||||
|
||||
Upload a photo of a vase; it's matched against the fixed template set using
|
||||
four methods (SIFT, ORB, SuperPoint+LightGlue, LoFTR), each scored
|
||||
independently and timed. Template-side data is precomputed once at startup
|
||||
so a request only ever processes the single uploaded image.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
from logging.handlers import RotatingFileHandler
|
||||
|
||||
import config # noqa: F401 (must be imported first -- caps thread envs)
|
||||
|
||||
|
||||
def _configure_logging():
|
||||
"""All app + pipeline.* + werkzeug loggers propagate to the root logger,
|
||||
so attaching handlers here is enough to capture everything in one file --
|
||||
useful for correlating the last thing that happened before a crash (e.g.
|
||||
an OOM kill, which the process itself never gets to log) against
|
||||
`dmesg`/`journalctl -k` timestamps."""
|
||||
os.makedirs(config.LOG_DIR, exist_ok=True)
|
||||
fmt = logging.Formatter("%(asctime)s %(levelname)-7s [%(name)s] %(message)s")
|
||||
|
||||
root = logging.getLogger()
|
||||
if root.handlers:
|
||||
return # already configured (e.g. module imported twice)
|
||||
root.setLevel(logging.INFO)
|
||||
|
||||
file_handler = RotatingFileHandler(config.LOG_FILE, maxBytes=5 * 1024 * 1024,
|
||||
backupCount=5)
|
||||
file_handler.setFormatter(fmt)
|
||||
root.addHandler(file_handler)
|
||||
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setFormatter(fmt)
|
||||
root.addHandler(console_handler)
|
||||
|
||||
|
||||
_configure_logging()
|
||||
|
||||
import requests
|
||||
from flask import Flask, jsonify, render_template, request, send_from_directory
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
from pipeline import engine, utils, verify
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config["MAX_CONTENT_LENGTH"] = config.MAX_CONTENT_LENGTH_BYTES
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
templates = []
|
||||
for fname in utils.list_template_files():
|
||||
name = utils.template_name(fname)
|
||||
templates.append({"name": name, "filename": fname})
|
||||
return render_template("index.html", templates=templates,
|
||||
methods=list(config.METHOD_LABELS.items()))
|
||||
|
||||
|
||||
@app.route("/template_image/<path:filename>")
|
||||
def template_image(filename):
|
||||
safe = secure_filename(filename)
|
||||
return send_from_directory(config.TEMPLATE_IMAGES_DIR, safe)
|
||||
|
||||
|
||||
@app.route("/uploads/<request_id>/<path:filename>")
|
||||
def uploaded_file(request_id, filename):
|
||||
safe_id = secure_filename(request_id)
|
||||
safe_name = secure_filename(filename)
|
||||
request_dir = os.path.join(config.UPLOADS_DIR, safe_id)
|
||||
return send_from_directory(request_dir, safe_name)
|
||||
|
||||
|
||||
@app.errorhandler(413)
|
||||
def too_large(_e):
|
||||
logger.warning("Upload rejected: exceeds %d byte limit", config.MAX_CONTENT_LENGTH_BYTES)
|
||||
return jsonify({"error": "Image too large (15 MB limit)."}), 413
|
||||
|
||||
|
||||
@app.route("/api/match", methods=["POST"])
|
||||
def api_match():
|
||||
file = request.files.get("image")
|
||||
if file is None or file.filename == "":
|
||||
logger.warning("Match request rejected: no image uploaded")
|
||||
return jsonify({"error": "No image uploaded."}), 400
|
||||
|
||||
ext = os.path.splitext(file.filename)[1].lower()
|
||||
if ext not in config.VALID_EXTS:
|
||||
logger.warning("Match request rejected: unsupported file type %r", ext)
|
||||
return jsonify({"error": f"Unsupported file type: {ext or 'unknown'}"}), 400
|
||||
|
||||
image_bytes = file.read()
|
||||
if not image_bytes:
|
||||
logger.warning("Match request rejected: empty file")
|
||||
return jsonify({"error": "Uploaded file is empty."}), 400
|
||||
|
||||
filename = file.filename
|
||||
try:
|
||||
image_bytes, was_compressed = utils.compress_image_bytes(
|
||||
image_bytes, config.COMPRESS_ABOVE_BYTES, config.MAX_IMAGE_DIM
|
||||
)
|
||||
except Exception:
|
||||
logger.exception("Compression failed for %r, using original bytes", filename)
|
||||
was_compressed = False
|
||||
|
||||
if was_compressed:
|
||||
# Re-encoded as JPEG regardless of the original format, so the
|
||||
# filename extension needs to match -- otherwise the saved
|
||||
# "original.*" file gets served with the wrong Content-Type later.
|
||||
filename = os.path.splitext(filename)[0] + ".jpg"
|
||||
|
||||
try:
|
||||
result = engine.process_upload(image_bytes, filename)
|
||||
except Exception as e:
|
||||
logger.exception("Match pipeline failed")
|
||||
return jsonify({"error": f"Processing failed: {e}"}), 500
|
||||
|
||||
return jsonify(result)
|
||||
|
||||
|
||||
@app.route("/api/count_flowers", methods=["POST"])
|
||||
def api_count_flowers():
|
||||
"""Opt-in, heavyweight: runs SAM instance segmentation over an
|
||||
already-processed upload to count individual flowers and cluster them
|
||||
by color as a rough proxy for distinct "kinds". Kept as its own request
|
||||
(rather than folded into /api/match) since it unloads the matching
|
||||
pipeline's GPU-resident models first -- it's meant to be triggered
|
||||
explicitly, not on every upload."""
|
||||
data = request.get_json(silent=True) or {}
|
||||
request_id = data.get("request_id")
|
||||
template = data.get("template")
|
||||
if not request_id:
|
||||
return jsonify({"error": "Missing request_id."}), 400
|
||||
|
||||
safe_id = secure_filename(request_id)
|
||||
try:
|
||||
result = engine.count_flowers(safe_id, template=template)
|
||||
except FileNotFoundError:
|
||||
return jsonify({"error": "Upload not found (it may have expired)."}), 404
|
||||
except Exception as e:
|
||||
logger.exception("[%s] flower count request failed", safe_id)
|
||||
return jsonify({"error": f"Flower counting failed: {e}"}), 500
|
||||
|
||||
result["request_id"] = safe_id
|
||||
return jsonify(result)
|
||||
|
||||
|
||||
@app.route("/api/flower_summary", methods=["POST"])
|
||||
def api_flower_summary():
|
||||
"""Lightweight, auto-triggered (not opt-in) companion to the weighted
|
||||
verdict: flower count comparison + CLIP-only flower similarity. Fired
|
||||
automatically right after a confident match, shown beside the weighted
|
||||
card while /api/count_flowers stays behind its own button for the
|
||||
heavier SAM+YOLO-World+DINOv2/vase comparison."""
|
||||
data = request.get_json(silent=True) or {}
|
||||
request_id = data.get("request_id")
|
||||
template = data.get("template")
|
||||
if not request_id or not template:
|
||||
return jsonify({"error": "Missing request_id or template."}), 400
|
||||
|
||||
safe_id = secure_filename(request_id)
|
||||
try:
|
||||
result = engine.flower_summary(safe_id, template)
|
||||
except FileNotFoundError:
|
||||
return jsonify({"error": "Upload not found (it may have expired)."}), 404
|
||||
except ValueError as e:
|
||||
return jsonify({"error": str(e)}), 400
|
||||
except Exception as e:
|
||||
logger.exception("[%s] flower summary request failed", safe_id)
|
||||
return jsonify({"error": f"Flower summary failed: {e}"}), 500
|
||||
|
||||
return jsonify(result)
|
||||
|
||||
|
||||
@app.route("/api/verify", methods=["POST"])
|
||||
def api_verify():
|
||||
"""Optional follow-up step: cross-checks the winning template against
|
||||
the user's original upload via an external vision-LLM endpoint. Kept
|
||||
separate from /api/match so a slow or unreachable verification service
|
||||
never affects the core (fast, local) matching result."""
|
||||
data = request.get_json(silent=True) or {}
|
||||
request_id = data.get("request_id")
|
||||
template = data.get("template")
|
||||
|
||||
if not request_id or not template:
|
||||
return jsonify({"error": "Missing request_id or template."}), 400
|
||||
|
||||
safe_id = secure_filename(request_id)
|
||||
request_dir = os.path.join(config.UPLOADS_DIR, safe_id)
|
||||
if not os.path.isdir(request_dir):
|
||||
return jsonify({"error": "Upload not found (it may have expired)."}), 404
|
||||
|
||||
originals = [f for f in os.listdir(request_dir) if f.startswith("original.")]
|
||||
if not originals:
|
||||
return jsonify({"error": "Original upload not found."}), 404
|
||||
upload_path = os.path.join(request_dir, originals[0])
|
||||
|
||||
templates = engine.templates_meta()
|
||||
if template not in templates:
|
||||
return jsonify({"error": f"Unknown template: {template}"}), 400
|
||||
template_path = os.path.join(config.TEMPLATE_IMAGES_DIR,
|
||||
templates[template]["original_filename"])
|
||||
|
||||
logger.info("[%s] verifying against %s via external endpoint", safe_id, template)
|
||||
try:
|
||||
result = verify.verify_images(template_path, upload_path)
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.warning("[%s] verification endpoint unreachable: %s", safe_id, e)
|
||||
return jsonify({"error": f"Verification service unavailable: {e}"}), 502
|
||||
except Exception as e:
|
||||
logger.exception("[%s] verification failed", safe_id)
|
||||
return jsonify({"error": f"Verification failed: {e}"}), 500
|
||||
|
||||
return jsonify(result)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
logger.info("Starting Vase Matcher on %s:%d (logs: %s)",
|
||||
config.HOST, config.PORT, config.LOG_FILE)
|
||||
engine.bootstrap()
|
||||
app.run(host=config.HOST, port=config.PORT, debug=False, threaded=True,
|
||||
use_reloader=False)
|
||||
BIN
cache/templates_nobg/SKU_1.png
vendored
Normal file
|
After Width: | Height: | Size: 158 KiB |
BIN
cache/templates_nobg/SKU_1_COLORED.png
vendored
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
cache/templates_nobg/SKU_2.png
vendored
Normal file
|
After Width: | Height: | Size: 320 KiB |
BIN
cache/templates_nobg/SKU_3.png
vendored
Normal file
|
After Width: | Height: | Size: 162 KiB |
BIN
cache/templates_nobg/SKU_4.png
vendored
Normal file
|
After Width: | Height: | Size: 179 KiB |
BIN
cache/templates_nobg/SKU_5.png
vendored
Normal file
|
After Width: | Height: | Size: 319 KiB |
BIN
cache/templates_nobg/SKU_ULTRA_6.png
vendored
Normal file
|
After Width: | Height: | Size: 115 KiB |
BIN
cache/uploads_nobg/004d589769942b548689.png
vendored
Normal file
|
After Width: | Height: | Size: 982 KiB |
BIN
cache/uploads_nobg/01f615081418be7de3f7.png
vendored
Normal file
|
After Width: | Height: | Size: 517 KiB |
BIN
cache/uploads_nobg/042bfeaadf8c323dbacf.png
vendored
Normal file
|
After Width: | Height: | Size: 516 KiB |
BIN
cache/uploads_nobg/042c226f30d3d700f7f5.png
vendored
Normal file
|
After Width: | Height: | Size: 712 KiB |
BIN
cache/uploads_nobg/0575d8159a35e2f65588.png
vendored
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
BIN
cache/uploads_nobg/057f6857b92759b08165.png
vendored
Normal file
|
After Width: | Height: | Size: 702 KiB |
BIN
cache/uploads_nobg/060e3bfb6b97813821e7.png
vendored
Normal file
|
After Width: | Height: | Size: 686 KiB |
BIN
cache/uploads_nobg/067af25a344f3868ccb3.png
vendored
Normal file
|
After Width: | Height: | Size: 507 KiB |
BIN
cache/uploads_nobg/06c1e18d4ce8065988ab.png
vendored
Normal file
|
After Width: | Height: | Size: 612 KiB |
BIN
cache/uploads_nobg/08e202994840b58241c7.png
vendored
Normal file
|
After Width: | Height: | Size: 842 KiB |
BIN
cache/uploads_nobg/0a74f1ad1f0f03b75f2b.png
vendored
Normal file
|
After Width: | Height: | Size: 784 KiB |
BIN
cache/uploads_nobg/0bdd04230588bfdf6666.png
vendored
Normal file
|
After Width: | Height: | Size: 320 KiB |
BIN
cache/uploads_nobg/0dd51a2101d3cae26ebb.png
vendored
Normal file
|
After Width: | Height: | Size: 564 KiB |
BIN
cache/uploads_nobg/0febb74515604c6b8672.png
vendored
Normal file
|
After Width: | Height: | Size: 354 KiB |
BIN
cache/uploads_nobg/102d7801cfb45dfb4b2a.png
vendored
Normal file
|
After Width: | Height: | Size: 644 KiB |
BIN
cache/uploads_nobg/11b3ebd78f6ff600db69.png
vendored
Normal file
|
After Width: | Height: | Size: 855 KiB |
BIN
cache/uploads_nobg/11de87b41cccba587fb4.png
vendored
Normal file
|
After Width: | Height: | Size: 473 KiB |
BIN
cache/uploads_nobg/1255b61f5d3b77446baf.png
vendored
Normal file
|
After Width: | Height: | Size: 446 KiB |
BIN
cache/uploads_nobg/1292d2fcfc02180f2c97.png
vendored
Normal file
|
After Width: | Height: | Size: 736 KiB |
BIN
cache/uploads_nobg/1316216c7f970f89b191.png
vendored
Normal file
|
After Width: | Height: | Size: 624 KiB |
BIN
cache/uploads_nobg/13af9e3bf59129893e74.png
vendored
Normal file
|
After Width: | Height: | Size: 598 KiB |
BIN
cache/uploads_nobg/15cb6672a1cbb6a46a16.png
vendored
Normal file
|
After Width: | Height: | Size: 582 KiB |
BIN
cache/uploads_nobg/15e520d0ff352501f0e6.png
vendored
Normal file
|
After Width: | Height: | Size: 666 KiB |
BIN
cache/uploads_nobg/16ee3d841987bb6bfa7c.png
vendored
Normal file
|
After Width: | Height: | Size: 603 KiB |
BIN
cache/uploads_nobg/17b53e219bb6dd9d9e3d.png
vendored
Normal file
|
After Width: | Height: | Size: 415 KiB |
BIN
cache/uploads_nobg/182b780cd358ac96f355.png
vendored
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
cache/uploads_nobg/183bb82185732e35513f.png
vendored
Normal file
|
After Width: | Height: | Size: 639 KiB |
BIN
cache/uploads_nobg/196dbe1d4491a776c86b.png
vendored
Normal file
|
After Width: | Height: | Size: 613 KiB |
BIN
cache/uploads_nobg/19fbb4e63afc5319966f.png
vendored
Normal file
|
After Width: | Height: | Size: 734 KiB |
BIN
cache/uploads_nobg/1c21fe406ba5beda6933.png
vendored
Normal file
|
After Width: | Height: | Size: 686 KiB |
BIN
cache/uploads_nobg/1ef15b7853b272b47636.png
vendored
Normal file
|
After Width: | Height: | Size: 885 KiB |
BIN
cache/uploads_nobg/202d77695c53ef6644dd.png
vendored
Normal file
|
After Width: | Height: | Size: 926 KiB |
BIN
cache/uploads_nobg/222b3d4a4f56360470ef.png
vendored
Normal file
|
After Width: | Height: | Size: 715 KiB |
BIN
cache/uploads_nobg/236176143df82340ff08.png
vendored
Normal file
|
After Width: | Height: | Size: 771 KiB |
BIN
cache/uploads_nobg/26d6a03cf4075ff59b0d.png
vendored
Normal file
|
After Width: | Height: | Size: 617 KiB |
BIN
cache/uploads_nobg/28d6fc0164d715c6f3f8.png
vendored
Normal file
|
After Width: | Height: | Size: 522 KiB |
BIN
cache/uploads_nobg/2b1d6a924ec7c17cdb94.png
vendored
Normal file
|
After Width: | Height: | Size: 602 KiB |
BIN
cache/uploads_nobg/2ce6253683e48f0a6667.png
vendored
Normal file
|
After Width: | Height: | Size: 280 KiB |
BIN
cache/uploads_nobg/2d13e77c87468c76df81.png
vendored
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
cache/uploads_nobg/2e2ac9eea3e7937fcfc3.png
vendored
Normal file
|
After Width: | Height: | Size: 635 KiB |
BIN
cache/uploads_nobg/2e37e8c11ba48b1e7e74.png
vendored
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
cache/uploads_nobg/2e4f6037bf031eaa79ee.png
vendored
Normal file
|
After Width: | Height: | Size: 807 KiB |
BIN
cache/uploads_nobg/2ff05f7daeba65e3b27a.png
vendored
Normal file
|
After Width: | Height: | Size: 626 KiB |
BIN
cache/uploads_nobg/3016295b946a8c8e6bdd.png
vendored
Normal file
|
After Width: | Height: | Size: 645 KiB |
BIN
cache/uploads_nobg/317a417631fd6634b6fc.png
vendored
Normal file
|
After Width: | Height: | Size: 447 KiB |
BIN
cache/uploads_nobg/31ceb46fd7ed29fbba3f.png
vendored
Normal file
|
After Width: | Height: | Size: 818 KiB |
BIN
cache/uploads_nobg/33011577cd17a05b3ae3.png
vendored
Normal file
|
After Width: | Height: | Size: 438 KiB |
BIN
cache/uploads_nobg/33f3dbeb46194a62b636.png
vendored
Normal file
|
After Width: | Height: | Size: 596 KiB |
BIN
cache/uploads_nobg/346a83b613316d9658aa.png
vendored
Normal file
|
After Width: | Height: | Size: 652 KiB |
BIN
cache/uploads_nobg/368adc00a60a82926597.png
vendored
Normal file
|
After Width: | Height: | Size: 808 KiB |
BIN
cache/uploads_nobg/385ba3ca327c7e7a370a.png
vendored
Normal file
|
After Width: | Height: | Size: 100 KiB |
BIN
cache/uploads_nobg/38d78c5205b9117ce0bb.png
vendored
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
cache/uploads_nobg/39fab6b578abba699555.png
vendored
Normal file
|
After Width: | Height: | Size: 412 KiB |
BIN
cache/uploads_nobg/3a712643ea1467254b4a.png
vendored
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
cache/uploads_nobg/3ad8e508741e4c1365a9.png
vendored
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
cache/uploads_nobg/3aed7f462e86f3e22052.png
vendored
Normal file
|
After Width: | Height: | Size: 401 KiB |
BIN
cache/uploads_nobg/3bae8d1aef008939b165.png
vendored
Normal file
|
After Width: | Height: | Size: 1.9 MiB |
BIN
cache/uploads_nobg/3d048d2b04762f00ab1d.png
vendored
Normal file
|
After Width: | Height: | Size: 404 KiB |
BIN
cache/uploads_nobg/3da7bf16addd6e00a85d.png
vendored
Normal file
|
After Width: | Height: | Size: 750 KiB |
BIN
cache/uploads_nobg/3ddee75eb52ccb150b0c.png
vendored
Normal file
|
After Width: | Height: | Size: 324 KiB |
BIN
cache/uploads_nobg/3e4da253dcb6efaacbc8.png
vendored
Normal file
|
After Width: | Height: | Size: 748 KiB |
BIN
cache/uploads_nobg/40aadda3a6ffa0ed45a1.png
vendored
Normal file
|
After Width: | Height: | Size: 660 KiB |
BIN
cache/uploads_nobg/4305fd4599c0bdf66f91.png
vendored
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
cache/uploads_nobg/4337b186ece0d1cfa0a0.png
vendored
Normal file
|
After Width: | Height: | Size: 808 KiB |
BIN
cache/uploads_nobg/4396d45abb94176d4074.png
vendored
Normal file
|
After Width: | Height: | Size: 546 KiB |
BIN
cache/uploads_nobg/45632c3282f7641e0a78.png
vendored
Normal file
|
After Width: | Height: | Size: 370 KiB |
BIN
cache/uploads_nobg/461bab408fc9f4b60eec.png
vendored
Normal file
|
After Width: | Height: | Size: 456 KiB |
BIN
cache/uploads_nobg/46e17fab52c0e32f46ce.png
vendored
Normal file
|
After Width: | Height: | Size: 147 KiB |
BIN
cache/uploads_nobg/4710266a3e14ee993bc4.png
vendored
Normal file
|
After Width: | Height: | Size: 282 KiB |
BIN
cache/uploads_nobg/477715d22b81401b3d15.png
vendored
Normal file
|
After Width: | Height: | Size: 684 KiB |
BIN
cache/uploads_nobg/47c80cc3b60d668d6692.png
vendored
Normal file
|
After Width: | Height: | Size: 639 KiB |
BIN
cache/uploads_nobg/47ee0ae28a16b0ad5f05.png
vendored
Normal file
|
After Width: | Height: | Size: 792 KiB |
BIN
cache/uploads_nobg/483a339e7fd21894b1d4.png
vendored
Normal file
|
After Width: | Height: | Size: 692 KiB |
BIN
cache/uploads_nobg/489f3238c89082851d8f.png
vendored
Normal file
|
After Width: | Height: | Size: 736 KiB |
BIN
cache/uploads_nobg/4907487d0e1f6426ecd3.png
vendored
Normal file
|
After Width: | Height: | Size: 618 KiB |
BIN
cache/uploads_nobg/4935b94032ea4daf77a9.png
vendored
Normal file
|
After Width: | Height: | Size: 525 KiB |
BIN
cache/uploads_nobg/4b697b41ced4ad2164f9.png
vendored
Normal file
|
After Width: | Height: | Size: 564 KiB |
BIN
cache/uploads_nobg/4c51513cce6f63172343.png
vendored
Normal file
|
After Width: | Height: | Size: 533 KiB |
BIN
cache/uploads_nobg/4e3c560da5af3ae236a7.png
vendored
Normal file
|
After Width: | Height: | Size: 596 KiB |
BIN
cache/uploads_nobg/4fb5943663d5fa6a1a65.png
vendored
Normal file
|
After Width: | Height: | Size: 751 KiB |
BIN
cache/uploads_nobg/5195654cd102cb1d8f1a.png
vendored
Normal file
|
After Width: | Height: | Size: 412 KiB |
BIN
cache/uploads_nobg/566652ef1b840e84c8b1.png
vendored
Normal file
|
After Width: | Height: | Size: 497 KiB |
BIN
cache/uploads_nobg/5861425de7fffd39eaac.png
vendored
Normal file
|
After Width: | Height: | Size: 396 KiB |
BIN
cache/uploads_nobg/5a635eb7e0837b490ece.png
vendored
Normal file
|
After Width: | Height: | Size: 558 KiB |