From 36b6082dfa768cbf35d40dc2c82706dfae0b687b Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Fri, 14 Dec 2018 17:24:23 +0100 Subject: flask server click script --- faiss/__init__.py | 0 faiss/requirements.txt | 11 + faiss/run.sh | 1 + faiss/server.py | 68 +++++ faiss/static/css/app.css | 289 ++++++++++++++++++++ faiss/static/favicon.ico | Bin 0 -> 15086 bytes faiss/static/img/play.png | Bin 0 -> 1231 bytes faiss/static/index.html | 83 ++++++ faiss/static/js/app.js | 491 ++++++++++++++++++++++++++++++++++ faiss/static/js/dataUriToBlob.js | 58 ++++ faiss/static/js/metadata-app.js | 50 ++++ faiss/static/js/store2.min.js | 5 + faiss/static/metadata.html | 11 + faiss/static/search.html | 1 + faiss/util.py | 29 ++ faiss/wsgi.py | 5 + megapixels/app/models/sql_factory.py | 1 - megapixels/app/server/api/image.py | 40 +++ megapixels/app/server/create.py | 27 ++ megapixels/app/server/static | 1 + megapixels/cli_flask.py | 19 ++ megapixels/commands/faiss/build_db.py | 21 +- 22 files changed, 1203 insertions(+), 8 deletions(-) create mode 100644 faiss/__init__.py create mode 100644 faiss/requirements.txt create mode 100644 faiss/run.sh create mode 100644 faiss/server.py create mode 100644 faiss/static/css/app.css create mode 100644 faiss/static/favicon.ico create mode 100644 faiss/static/img/play.png create mode 100644 faiss/static/index.html create mode 100644 faiss/static/js/app.js create mode 100644 faiss/static/js/dataUriToBlob.js create mode 100644 faiss/static/js/metadata-app.js create mode 100644 faiss/static/js/store2.min.js create mode 100644 faiss/static/metadata.html create mode 100644 faiss/static/search.html create mode 100644 faiss/util.py create mode 100644 faiss/wsgi.py create mode 100644 megapixels/app/server/api/image.py create mode 100644 megapixels/app/server/create.py create mode 120000 megapixels/app/server/static create mode 100644 megapixels/cli_flask.py diff --git a/faiss/__init__.py b/faiss/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/faiss/requirements.txt b/faiss/requirements.txt new file mode 100644 index 00000000..1d60aabc --- /dev/null +++ b/faiss/requirements.txt @@ -0,0 +1,11 @@ +Pillow +h5py +tensorflow +Keras +Flask +opencv-python +imagehash +scikit-image +scikit-learn +imutils + diff --git a/faiss/run.sh b/faiss/run.sh new file mode 100644 index 00000000..8f9e77e2 --- /dev/null +++ b/faiss/run.sh @@ -0,0 +1 @@ +uwsgi --http 127.0.0.1:5000 --file wsgi.py --callable app --processes 1 diff --git a/faiss/server.py b/faiss/server.py new file mode 100644 index 00000000..a8c660fa --- /dev/null +++ b/faiss/server.py @@ -0,0 +1,68 @@ +#!python + +import os +import sys +import json +import time +import argparse +import cv2 as cv +import numpy as np +from datetime import datetime +from flask import Flask, request, render_template, jsonify +from PIL import Image # todo: try to remove PIL dependency +import re + +sanitize_re = re.compile('[\W]+') +valid_exts = ['.gif', '.jpg', '.jpeg', '.png'] + +from dotenv import load_dotenv +load_dotenv() + +from feature_extractor import FeatureExtractor + +DEFAULT_LIMIT = 50 + +app = Flask(__name__, static_url_path="/search/static", static_folder="static") + +# static api routes - this routing is actually handled in the JS +@app.route('/', methods=['GET']) +def index(): + return app.send_static_file('metadata.html') + +# search using an uploaded file +@app.route('/search/api/upload', methods=['POST']) +def upload(): + file = request.files['query_img'] + fn = file.filename + if fn.endswith('blob'): + fn = 'filename.jpg' + + basename, ext = os.path.splitext(fn) + print("got {}, type {}".format(basename, ext)) + if ext.lower() not in valid_exts: + return jsonify({ 'error': 'not an image' }) + + uploaded_fn = datetime.now().isoformat() + "_" + basename + uploaded_fn = sanitize_re.sub('', uploaded_fn) + uploaded_img_path = "static/uploaded/" + uploaded_fn + ext + uploaded_img_path = uploaded_img_path.lower() + print('query: {}'.format(uploaded_img_path)) + + img = Image.open(file.stream).convert('RGB') + # img.save(uploaded_img_path) + # vec = db.load_feature_vector_from_file(uploaded_img_path) + vec = fe.extract(img) + # print(vec.shape) + + results = db.search(vec, limit=limit) + query = { + 'timing': time.time() - start, + } + print(results) + return jsonify({ + 'results': results, + }) + +if __name__=="__main__": + app.run("0.0.0.0", debug=False) + diff --git a/faiss/static/css/app.css b/faiss/static/css/app.css new file mode 100644 index 00000000..a3b24736 --- /dev/null +++ b/faiss/static/css/app.css @@ -0,0 +1,289 @@ +/* css boilerplate */ + +* { box-sizing: border-box; } +html,body { + margin: 0; padding: 0; + width: 100%; height: 100%; +} +body { + font-family: Helvetica, sans-serif; + font-weight: 300; + padding-top: 60px; +} + +/* header */ + +header { + position: fixed; + top: 0; + left: 0; + height: 60px; + width: 100%; + background: #11f; + color: white; + align-items: stretch; + display: flex; + flex-wrap: wrap; + justify-content: space-between; + z-index: 3; +} +header > section { + justify-content: flex-start; + align-items: center; + display: flex; + flex: 1 0; + font-weight: bold; +} +header > section:last-of-type { + justify-content: flex-end; +} +header a { + color: hsla(0,0%,100%,.89); + text-decoration: none; + line-height: 18px; + font-size: 14px; + font-weight: 700; + padding: .35rem .4rem; + white-space: nowrap; +} +header .logged-in { + font-size: 12px; + font-weight: normal; + padding: 0 0.5rem; +} +header .logout { + padding: 0 6px; + border-left: 1px solid #99f; +} +header .logout a { + font-size: 12px; +} +.menuToggle { + width: 30px; + height: 30px; + margin: 5px; + cursor: pointer; + line-height: 1; +} + +/* form at the top */ + +#form { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + margin: 20px; + padding: 20px; + border: 1px solid #ddd; +} +input[type=text] { + border: 1px solid #888; + padding: 4px; + font-size: 15px; +} +input[type=file] { + max-width: 200px; + border-radius: 2px; +} +input[type=file]:invalid + button { visibility: hidden!important; } +input[type=file]:valid + button { visibility: visible!important; } +#form > div { + display: flex; + flex-direction: row; + align-items: center; +} +#form > div * { + margin: 0 3px; +} + +/* saving UI form */ + +label { + display: block; + white-space: nowrap; + padding-bottom: 10px; +} +label:last-child { + padding-bottom: 0; +} +label span { + display: inline-block; + min-width: 80px; +} +.saving_ui { + display: none; +} +.saving .saving_ui { + display: flex; + border: 1px solid #ddd; + margin: 20px; + padding: 20px; + flex-direction: row; + justify-content: space-between; +} + +/* query box, shows either searched image, directory name, etc */ + +.loading .results, +.prefetch .query, .prefetch .results, +.browsing .score, .browsing .browse, +.photo .browse, +.saving .score { + display: none; +} +.browsing .query div { display: inline; margin-left: 5px; font-weight: bold; } +.saving .query div { display: inline; margin-left: 5px; font-weight: bold; } +.load_message { + opacity: 0; +} +.loading .load_message { + display: block; + margin: 20px; + font-weight: bold; +} + +.query { + margin: 20px; +} +.query > div { + margin-top: 10px; + position: relative; + display: flex; + flex-direction: row; + align-items: flex-start; +} +.query img { + cursor: crosshair; + max-width: 400px; + display: block; +} +.query > div > .box { + position: absolute; + border: 1px solid #11f; + background: rgba(17,17,255,0.1); + pointer-events: none; +} +.query canvas { + margin-left: 20px; + max-width: 200px; +} + +/* search results */ + +.results { + display: flex; + flex-direction: row; + flex-wrap: wrap; +} +.results > div { + display: flex; + flex-direction: column; + justify-content: flex-end; + width: 210px; + margin: 15px; + padding: 5px; + border: 1px solid transparent; +} +.results > div.saved { + border-radius: 2px; + background: #fafaaa; +} +.results > div img { + cursor: pointer; + max-width: 210px; + margin-bottom: 10px; +} +.results > div > div { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; +} +.results a:visited .btn { + color: #99d; +} +.score { + font-size: 12px; + color: #444; +} + + +/* spinner */ + +.loader { + display: flex; + align-items: center; + justify-content: center; + position: absolute; + top: 0; left: 0; + width: 100%; height: 100%; + background: rgba(255,255,255,0.9); +} +.loader > div { + background: white; + padding: 20px; + box-shadow: 0 1px 2px #bbb; + border-radius: 2px; +} +.spinner { + position: relative; + width: 32px; + height: 32px; + color: #11f; + margin: 0 auto; +} +.spinner:after { + position: absolute; + margin: auto; + width: 100%; + height: 100%; + top: 0; + left: 0; + right: 0; + bottom: 0; + content: " "; + display: inline-block; + border-radius: 50%; + border-style: solid; + border-width: 0.15em; + -webkit-background-clip: padding-box; + border-color: currentColor currentColor currentColor transparent; + box-sizing: border-box; + -webkit-animation: ld-cycle 0.7s infinite linear; + animation: ld-cycle 0.7s infinite linear; +} +@-webkit-keyframes ld-cycle { + 0%, 50%, 100% { + animation-timing-function: cubic-bezier(0.5, 0.5, 0.5, 0.5); + } + 0% { + -webkit-transform: rotate(0); + transform: rotate(0); + } + 50% { + -webkit-transform: rotate(180deg); + transform: rotate(180deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} +@keyframes ld-cycle { + 0%, 50%, 100% { + animation-timing-function: cubic-bezier(0.5, 0.5, 0.5, 0.5); + } + 0% { + -webkit-transform: rotate(0); + transform: rotate(0); + } + 50% { + -webkit-transform: rotate(180deg); + transform: rotate(180deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} diff --git a/faiss/static/favicon.ico b/faiss/static/favicon.ico new file mode 100644 index 00000000..d97f2f59 Binary files /dev/null and b/faiss/static/favicon.ico differ diff --git a/faiss/static/img/play.png b/faiss/static/img/play.png new file mode 100644 index 00000000..40f76045 Binary files /dev/null and b/faiss/static/img/play.png differ diff --git a/faiss/static/index.html b/faiss/static/index.html new file mode 100644 index 00000000..cf59c628 --- /dev/null +++ b/faiss/static/index.html @@ -0,0 +1,83 @@ + + + + + + + +VFrame Image Import + + + +
+ + + +
+ +
+ +
+
+ + +
+
+ + + +
+
+ +
+
+ + + + +
+
+ +
+ +
+
+ +
+
+ + +
+ + + + + + + + diff --git a/faiss/static/js/app.js b/faiss/static/js/app.js new file mode 100644 index 00000000..77164c76 --- /dev/null +++ b/faiss/static/js/app.js @@ -0,0 +1,491 @@ +/* eslint no-use-before-define: 0, camelcase: 0, one-var-declaration-per-line: 0, one-var: 0, quotes: 0, prefer-destructuring: 0, no-alert: 0, no-console: 0, no-multi-assign: 0 */ + +function loadApp() { + const result_template = document.querySelector('#result-template').innerHTML + const results_el = document.querySelector('.results') + const query_div = document.body.querySelector('.query > div') + let bounds + let token, username + let x, y, mouse_x, mouse_y, dx, dy, box + let dragging = false + let cropping = false + let creating = false + let did_check = false + + function init() { + login() + bind() + route() + } + function bind() { + window.onpopstate = route + document.querySelector('[name=img]').addEventListener('change', upload) + on('click', '.results a', preventDefault) + on('click', '.search', search) + on('click', '.panic', panic) + on('click', '.upload_again', upload_again) + on('click', '.browse', browse) + on('click', '.results img', save) + on('click', '.view_saved', loadSaved) + on('click', '.create_new_group', createNewGroup) + on('click', '.reset', reset) + on('click', '.random', random) + on('click', '.check', check) + on('mousedown', '.query img', down) + window.addEventListener('mousemove', move) + window.addEventListener('mouseup', up) + window.addEventListener('keydown', keydown) + } + function route() { + const path = window.location.pathname.split('/') + // remove initial slash + path.shift() + // remove dummy route + if (path[0] === 'search') path.shift() + switch (path[0]) { + case 'fetch': + search({ target: { url: window.location.search.substr(1).split('=')[1] } }) + break + case 'view': + search(path.slice(1)) + break + case 'q': + if (path.length === 3) { + search({ target: { dir: path[1], fn: path[2] } }) + } else { + browse({ target: { dir: path[1], fn: null } }) + } + break + case 'saved': + loadSaved() + break + default: + break + } + } + function keydown(e) { + switch (e.keyCode) { + case 27: // escape + panic() + break + default: + break + } + } + + // load search results + function loadResults(data) { + console.log(data) + if (!data.query.url) return + // console.log(data) + document.body.className = 'searching' + const path = getPathFromImage(data.query.url) + pushState('searching', "/search/fetch/?url=" + path.url) + if (path.dir === 'uploaded' && path.fn.match('_filename')) { + loadMessage( + "< Back | " + + "Searching subregion, " + + "found " + data.results.length + " images" + ) + } else { + loadMessage( + "Found " + data.results.length + " images" + ) + } + loadQuery(data.query.url) + if (!data.results.length) { + results_el.innerHTML = "No results" + return + } + const saved = window.store.get('saved', []) + + results_el.innerHTML = data.results.map(res => { + const { distance, file, hash, frame, url } = res + const isSaved = saved.indexOf(url) !== -1 + const { type } = getPathFromImage(url) + let className = isSaved ? 'saved' : '' + className += ' ' + type + let t = result_template + .replace('{score}', Math.floor(clamp(1 - distance, 0, 1) * 100) + "%") + .replace('{browse}', '/search/q/' + hash) + .replace('{search}', '/search/view/' + [file, hash, frame].join('/')) + .replace('{metadata}', '/metadata/' + hash) + .replace('{className}', className) + .replace('{saved_msg}', isSaved ? 'Saved' : 'Save') + .replace('{img}', url) + return t + }).join('') + } + + function loadDirectory(data) { + console.log(data) + document.body.className = 'browsing' + pushState('searching', "/search/q/" + data.path) + loadMessage("Video: " + data.path + "") + loadQuery("") + if (!data.results.length) { + results_el.innerHTML = "No frames found" + return + } + const saved = window.store.get('saved', []) + results_el.innerHTML = data.results + .map(result => [parseInt(result.frame, 10), result]) + .sort((a, b) => a[0] - b[0]) + .map(pair => { + let { file, hash, frame, url } = pair[1] + const isSaved = saved.indexOf(url) !== -1 + let className = isSaved ? 'saved' : '' + let t = result_template + .replace('{img}', url) + .replace('{browse}', '/search/q/' + hash) + .replace('{search}', '/search/view/' + [file, hash, frame].join('/')) + .replace('{metadata}', '/metadata/' + hash) + .replace('{className}', className) + .replace('{saved_msg}', isSaved ? 'Saved' : 'Save') + return t + }).join('') + } + function loadSaved() { + document.body.className = 'saving' + pushState('View saved', "/search/saved") + const saved = window.store.get('saved', []) + cropping = false + loadMessage(saved.length + " saved image" + (saved.length === 1 ? "" : "s")) + loadQuery('') + const box_el = document.querySelector('.box') + if (box_el) box_el.parentNode.removeChild(box_el) + results_el.innerHTML = saved.map(href => { + const { url, dir } = getPathFromImage({ src: href }) + let className = 'saved' + let t = result_template + .replace('{img}', href) + .replace('{browse}', '/search/q/' + dir) + .replace('{search}', '/search/fetch/?url=' + url) + .replace('{metadata}', '/metadata/' + dir) + .replace('{className}', className) + .replace('{saved_msg}', 'Saved') + return t + }).join('') + } + function loadQuery(path) { + if (cropping) return + const qd = document.querySelector('.query div') + qd.innerHTML = '' + if (path.match(/(gif|jpe?g|png)$/)) { + const img = new Image() + img.setAttribute('crossorigin', 'anonymous') + img.src = path.replace('sm', 'md') + qd.appendChild(img) + } else { + qd.innerHTML = path || "" + } + } + function loadMessage(msg) { + document.querySelector('.query .msg').innerHTML = msg + } + + // panic button + function panic() { + loadMessage('Query cleared') + loadQuery('') + results_el.innerHTML = '' + } + + // adding stuff to localstorage + function save(e) { + const { url } = getPathFromImage(e.target) + const saved = window.store.get('saved', []) + let newList = saved || [] + if (saved.indexOf(url) !== -1) { + newList = saved.filter(f => f !== url) + e.target.parentNode.classList.remove('saved') + } else { + newList.push(url) + e.target.parentNode.classList.add('saved') + } + window.store.set('saved', newList) + } + function reset() { + const shouldReset = window.confirm("This will reset the saved images. Are you sure?") + if (!shouldReset) return + window.store.set('saved', []) + loadSaved() + document.querySelector('[name=title]').value = '' + window.alert("Reset saved images") + } + + // submit the new group + function createNewGroup() { + const title = document.querySelector('[name=title]').value.trim().replace(/[^-_a-zA-Z0-9 ]/g, "") + const saved = window.store.get('saved', []) + const graphic = document.querySelector('[name=graphic]').checked + if (!title.length) return alert("Please enter a title for this group") + if (!saved.length) return alert("Please pick some images to save") + if (!did_check) { + alert('Automatically checking for duplicates. Please doublecheck your selection.') + return check() + } + if (creating) return null + creating = true + return http_post("/api/images/import/new/", { + title, + graphic, + saved + }).then(res => { + console.log(res) + window.store.set('saved', []) + window.location.href = '/groups/show/' + res.image_group.id + }).catch(res => { + alert('Error creating group. The server response is logged to the console.') + console.log(res) + creating = false + }) + } + + // api queries + function login() { + const isLocal = (window.location.hostname === '0.0.0.0') + try { + // csrftoken = "test" // getCookie('csrftoken') + const auth = JSON.parse(window.store.get('persist:root').auth) + token = auth.token + username = auth.user.username + if (!token && !isLocal) { + window.location.href = '/' + } + } catch (e) { + if (!isLocal) { + window.location.href = '/' + } + } + document.querySelector('.logged-in .capitalize').innerHTML = username || 'user' + } + + function upload(e) { + cropping = false + const files = e.dataTransfer ? e.dataTransfer.files : e.target.files + let i, f + for (i = 0, f; i < files.length; i++) { + f = files[i] + if (f && f.type.match('image.*')) break + } + if (!f) return + do_upload(f) + } + + function do_upload(f) { + const fd = new FormData() + fd.append('query_img', f) + document.body.className = 'loading' + http_post('/search/api/upload', fd).then(loadResults) + } + + function upload_again() { + const { files } = document.querySelector('input[type=file]') + if (!files.length) { + window.alert('Please upload a file.') + return + } + upload({ + dataTransfer: { files } + }) + } + + function search(e) { + if (e.length) return search_by_vector(e) + const { url } = getPath(e.target) + cropping = false + document.body.className = 'loading' + loadQuery(url) + loadMessage('Loading results...') + http_get('/search/api/fetch/?url=' + url).then(loadResults) + } + + function search_by_vector(e) { + cropping = false + document.body.className = 'loading' + loadQuery('') + loadMessage('Loading results...') + http_get('/search/api/search/' + e.join('/')).then(loadResults) + } + + function browse(e) { + document.body.className = 'loading' + cropping = false + let dir; + if (e.target.dir) { + dir = e.target.dir + } + else { + const href = e.target.parentNode.href + dir = href.split('/')[5] + console.log(href, dir) + } + loadMessage('Listing video...') + http_get('/search/api/list/' + dir).then(loadDirectory) + } + + function check() { + http_post('/api/images/import/search/', { + saved: window.store.get('saved') || [], + }).then(res => { + console.log(res) + const { good, bad } = res + did_check = true + window.store.set('saved', good) + if (!bad.length) { + return alert("No duplicates found.") + } + bad.forEach(path => { + const el = document.querySelector('img[src="' + path + '"]') + if (el) el.parentNode.classList.remove('saved') + }) + return alert("Untagged " + bad.length + " duplicate" + (bad.length === 1 ? "" : "s") + ".") + }) + } + + function random() { + http_get('/search/api/random').then(loadResults) + } + + // drawing a box + function down(e) { + e.preventDefault() + dragging = true + bounds = query_div.querySelector('img').getBoundingClientRect() + mouse_x = e.pageX + mouse_y = e.pageY + x = mouse_x - bounds.left + y = mouse_y - bounds.top + dx = dy = 0 + box = document.querySelector('.box') || document.createElement('div') + box.className = 'box' + box.style.left = x + 'px' + box.style.top = y + 'px' + box.style.width = 0 + 'px' + box.style.height = 0 + 'px' + query_div.appendChild(box) + } + function move(e) { + if (!dragging) return + e.preventDefault() + dx = clamp(e.pageX - mouse_x, 0, bounds.width - x) + dy = clamp(e.pageY - mouse_y, 0, bounds.height - y) + box.style.width = dx + 'px' + box.style.height = dy + 'px' + } + function up(e) { + if (!dragging) return + dragging = false + e.preventDefault() + const img = query_div.querySelector('img') + const canvas = query_div.querySelector('canvas') || document.createElement('canvas') + const ctx = canvas.getContext('2d') + const ratio = img.naturalWidth / bounds.width + canvas.width = dx * ratio + canvas.height = dy * ratio + if (dx < 10 || dy < 10) { + if (canvas.parentNode) canvas.parentNode.removeChild(canvas) + const box_el = document.querySelector('.box') + if (box_el) box_el.parentNode.removeChild(box_el) + return + } + query_div.appendChild(canvas) + ctx.drawImage( + img, + x * ratio, + y * ratio, + dx * ratio, + dy * ratio, + 0, 0, canvas.width, canvas.height + ) + cropping = true + const blob = window.dataUriToBlob(canvas.toDataURL('image/jpeg', 0.9)) + do_upload(blob) + } + + // utility functions + function http_get(url) { + return fetch(url).then(res => res.json()) + } + function http_post(url, data) { + let headers + if (data instanceof FormData) { + headers = { + Accept: 'application/json, application/xml, text/play, text/html, *.*', + Authorization: 'Token ' + token, + } + } else { + headers = { + Accept: 'application/json, application/xml, text/play, text/html, *.*', + 'Content-Type': 'application/json; charset=utf-8', + Authorization: 'Token ' + token, + } + data = JSON.stringify(data) + } + + // headers['X-CSRFToken'] = csrftoken + return fetch(url, { + method: 'POST', + body: data, + credentials: 'include', + headers, + }).then(res => res.json()) + } + function on(evt, sel, handler) { + document.addEventListener(evt, function (event) { + let t = event.target + while (t && t !== this) { + if (t.matches(sel)) { + handler.call(t, event) + } + t = t.parentNode + } + }) + } + function getPathFromImage(el) { + const url = el.src ? el.src : el + const partz = url.split('/') + let type, dir, fn + if (partz.length === 3) { + type = 'photo' + dir = '' + fn = '' + } + if (partz.length === 9) { + type = 'photo' + dir = partz[6] + fn = '' + } else if (partz.length === 10) { + type = 'video' + dir = partz[6] + fn = partz[7] + } + return { type, dir, fn, url } + } + function getPath(el) { + if (el.url) { + return getPathFromImage(el.url) + } if (el.dir) { + return el + } + el = el.parentNode.parentNode.parentNode.querySelector('img') + return getPathFromImage(el) + } + function pushState(txt, path) { + if (window.location.pathname === path) return + console.log('pushstate', path) + window.history.pushState({}, txt, path) + } + function preventDefault(e) { + if (e && !e.target.classList.contains('metadata')) { + e.preventDefault() + } + } + function clamp(n, a, b) { return n < a ? a : n < b ? n : b } + + // initialize the app when the DOM is ready + document.addEventListener('DOMContentLoaded', init) +} + +loadApp() diff --git a/faiss/static/js/dataUriToBlob.js b/faiss/static/js/dataUriToBlob.js new file mode 100644 index 00000000..80189b8d --- /dev/null +++ b/faiss/static/js/dataUriToBlob.js @@ -0,0 +1,58 @@ +var dataUriToUint8Array = function(uri){ + var data = uri.split(',')[1]; + var bytes = atob(data); + var buf = new ArrayBuffer(bytes.length); + var u8 = new Uint8Array(buf); + for (var i = 0; i < bytes.length; i++) { + u8[i] = bytes.charCodeAt(i); + } + return u8 +} + +window.dataUriToBlob = (function(){ +/** + * Blob constructor. + */ + +var Blob = window.Blob; + +/** + * ArrayBufferView support. + */ + +var hasArrayBufferView = new Blob([new Uint8Array(100)]).size == 100; + +/** + * Return a `Blob` for the given data `uri`. + * + * @param {String} uri + * @return {Blob} + * @api public + */ + +var dataUriToBlob = function(uri){ + var data = uri.split(',')[1]; + var bytes = atob(data); + var buf = new ArrayBuffer(bytes.length); + var arr = new Uint8Array(buf); + for (var i = 0; i < bytes.length; i++) { + arr[i] = bytes.charCodeAt(i); + } + + if (!hasArrayBufferView) arr = buf; + var blob = new Blob([arr], { type: mime(uri) }); + blob.slice = blob.slice || blob.webkitSlice; + return blob; +}; + +/** + * Return data uri mime type. + */ + +function mime(uri) { + return uri.split(';')[0].slice(5); +} + +return dataUriToBlob; + +})() diff --git a/faiss/static/js/metadata-app.js b/faiss/static/js/metadata-app.js new file mode 100644 index 00000000..fa2265fa --- /dev/null +++ b/faiss/static/js/metadata-app.js @@ -0,0 +1,50 @@ +!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:r})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=212)}([function(e,t,n){var r=n(99),o=36e5,i=6e4,a=2,u=/[T ]/,s=/:/,c=/^(\d{2})$/,l=[/^([+-]\d{2})$/,/^([+-]\d{3})$/,/^([+-]\d{4})$/],f=/^(\d{4})/,d=[/^([+-]\d{4})/,/^([+-]\d{5})/,/^([+-]\d{6})/],p=/^-(\d{2})$/,h=/^-?(\d{3})$/,m=/^-?(\d{2})-?(\d{2})$/,v=/^-?W(\d{2})$/,y=/^-?W(\d{2})-?(\d{1})$/,g=/^(\d{2}([.,]\d*)?)$/,_=/^(\d{2}):?(\d{2}([.,]\d*)?)$/,b=/^(\d{2}):?(\d{2}):?(\d{2}([.,]\d*)?)$/,w=/([Z+-].*)$/,x=/^(Z)$/,E=/^([+-])(\d{2})$/,O=/^([+-])(\d{2}):?(\d{2})$/;function S(e,t,n){t=t||0,n=n||0;var r=new Date(0);r.setUTCFullYear(e,0,4);var o=7*t+n+1-(r.getUTCDay()||7);return r.setUTCDate(r.getUTCDate()+o),r}e.exports=function(e,t){if(r(e))return new Date(e.getTime());if("string"!=typeof e)return new Date(e);var n=(t||{}).additionalDigits;n=null==n?a:Number(n);var T=function(e){var t,n={},r=e.split(u);if(s.test(r[0])?(n.date=null,t=r[0]):(n.date=r[0],t=r[1]),t){var o=w.exec(t);o?(n.time=t.replace(o[1],""),n.timezone=o[1]):n.time=t}return n}(e),k=function(e,t){var n,r=l[t],o=d[t];if(n=f.exec(e)||o.exec(e)){var i=n[1];return{year:parseInt(i,10),restDateString:e.slice(i.length)}}if(n=c.exec(e)||r.exec(e)){var a=n[1];return{year:100*parseInt(a,10),restDateString:e.slice(a.length)}}return{year:null}}(T.date,n),R=k.year,j=function(e,t){if(null===t)return null;var n,r,o,i;if(0===e.length)return(r=new Date(0)).setUTCFullYear(t),r;if(n=p.exec(e))return r=new Date(0),o=parseInt(n[1],10)-1,r.setUTCFullYear(t,o),r;if(n=h.exec(e)){r=new Date(0);var a=parseInt(n[1],10);return r.setUTCFullYear(t,0,a),r}if(n=m.exec(e)){r=new Date(0),o=parseInt(n[1],10)-1;var u=parseInt(n[2],10);return r.setUTCFullYear(t,o,u),r}if(n=v.exec(e))return i=parseInt(n[1],10)-1,S(t,i);if(n=y.exec(e)){i=parseInt(n[1],10)-1;var s=parseInt(n[2],10)-1;return S(t,i,s)}return null}(k.restDateString,R);if(j){var P,C=j.getTime(),M=0;return T.time&&(M=function(e){var t,n,r;if(t=g.exec(e))return(n=parseFloat(t[1].replace(",",".")))%24*o;if(t=_.exec(e))return n=parseInt(t[1],10),r=parseFloat(t[2].replace(",",".")),n%24*o+r*i;if(t=b.exec(e)){n=parseInt(t[1],10),r=parseInt(t[2],10);var a=parseFloat(t[3].replace(",","."));return n%24*o+r*i+1e3*a}return null}(T.time)),T.timezone?P=function(e){var t,n;return(t=x.exec(e))?0:(t=E.exec(e))?(n=60*parseInt(t[2],10),"+"===t[1]?-n:n):(t=O.exec(e))?(n=60*parseInt(t[2],10)+parseInt(t[3],10),"+"===t[1]?-n:n):0}(T.timezone):(P=new Date(C+M).getTimezoneOffset(),P=new Date(C+M+P*i).getTimezoneOffset()),new Date(C+M+P*i)}return new Date(e)}},function(e,t,n){"use strict";e.exports=n(213)},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(220),o=n(117),i=n(224);n.d(t,"Provider",function(){return r.b}),n.d(t,"createProvider",function(){return r.a}),n.d(t,"connectAdvanced",function(){return o.a}),n.d(t,"connect",function(){return i.a})},function(e,t){var n;n=function(){return this}();try{n=n||Function("return this")()||(0,eval)("this")}catch(e){"object"==typeof window&&(n=window)}e.exports=n},function(e,t,n){"use strict";t.__esModule=!0;var r=function(e){return e&&e.__esModule?e:{default:e}}(n(340));t.default=r.default||function(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:{},t=arguments[1];if(u)throw u;for(var r=!1,o={},i=0;i0&&void 0!==arguments[0]?arguments[0]:0;e/=arguments.length>1&&void 0!==arguments[1]?arguments[1]:25;var t=p(Math.round(e)%60);return(e=Math.floor(e/60))>60?Math.floor(e/60)+":"+p(e%60)+":"+t:e%60+":"+t},t.percent=function(e){return(100*e).toFixed(1)+"%"},t.px=function(e,t){return Math.round(e*t)+"px"},t.clamp=function(e,t,n){return e3&&void 0!==arguments[3]?arguments[3]:"th";return["https://sa-vframe.ams3.digitaloceanspaces.com/v1/media/keyframes",d(e)?null:"unverified",h(t),f(n,6),r,"index.jpg"].filter(function(e){return!!e}).join("/")},v=(t.metadataUri=function(e,t){return"/metadata/"+e+"/"+t+"/"},t.keyframeUri=function(e,t){return"/metadata/"+e+"/keyframe/"+f(t,6)+"/"},t.preloadImage=function(e){var t=e.verified,n=e.hash,r=e.frame,o=e.url;n&&r&&(o=m(t,n,r,"md"));var i=new Image,a=!1;i.onload=function(){a||(a=!0,i.onload=null)},i.crossOrigin="anonymous",i.src=o,i.complete&&i.onload()},null),y="",g="",_=(t.post=function(e,t,n){_();var o=void 0;t instanceof FormData?o={Accept:"application/json, application/xml, text/play, text/html, *.*"}:(o={Accept:"application/json, application/xml, text/play, text/html, *.*","Content-Type":"application/json; charset=utf-8"},t=(0,r.default)(t));var i={method:"POST",body:t,headers:o,credentials:"include"};return n&&(o.Authorization="Token "+y),fetch(e,i).then(function(e){return e.json()})},t.login=function(){if(v)return v;var e="0.0.0.0"===window.location.hostname||"127.0.0.1"===window.location.hostname;try{var t=JSON.parse(JSON.parse(localStorage.getItem("persist:root")).auth);return y=t.token,g=t.user.username,y&&console.log("logged in",g),v=t,y||e||(window.location.href="/"),t}catch(t){return e||(window.location.href="/"),{}}})},function(e,t,n){var r=n(13),o=n(10),i=n(34),a=n(26),u=n(25),s=function(e,t,n){var c,l,f,d=e&s.F,p=e&s.G,h=e&s.S,m=e&s.P,v=e&s.B,y=e&s.W,g=p?o:o[t]||(o[t]={}),_=g.prototype,b=p?r:h?r[t]:(r[t]||{}).prototype;for(c in p&&(n=t),n)(l=!d&&b&&void 0!==b[c])&&u(g,c)||(f=l?b[c]:n[c],g[c]=p&&"function"!=typeof b[c]?n[c]:v&&l?i(f,r):y&&b[c]==f?function(e){var t=function(t,n,r){if(this instanceof e){switch(arguments.length){case 0:return new e;case 1:return new e(t);case 2:return new e(t,n)}return new e(t,n,r)}return e.apply(this,arguments)};return t.prototype=e.prototype,t}(f):m&&"function"==typeof f?i(Function.call,f):f,m&&((g.virtual||(g.virtual={}))[c]=f,e&s.R&&_&&!_[c]&&a(_,c,f)))};s.F=1,s.G=2,s.S=4,s.P=8,s.B=16,s.W=32,s.U=64,s.R=128,e.exports=s},function(e,t,n){"use strict";e.exports=function(e,t,n,r,o,i,a,u){if(!e){var s;if(void 0===t)s=new Error("Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.");else{var c=[n,r,o,i,a,u],l=0;(s=new Error(t.replace(/%s/g,function(){return c[l++]}))).name="Invariant Violation"}throw s.framesToPop=1,s}}},function(e,t,n){var r=n(23);e.exports=function(e){if(!r(e))throw TypeError(e+" is not an object!");return e}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.random=t.browse=t.search=t.searchByFrame=t.searchByVerifiedFrame=t.upload=t.updateOptions=t.panic=t.publicUrl=void 0;var r=s(n(4)),o=function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t.default=e,t}(n(39)),i=n(94),a=n(17),u=s(n(354));function s(e){return e&&e.__esModule?e:{default:e}}var c={upload:function(){return"https://syrianarchive.vframe.io/search/api/upload"},search:function(){return"https://syrianarchive.vframe.io/search/api/fetch"},searchByVerifiedFrame:function(e,t,n){return"https://syrianarchive.vframe.io/search/api/search/"+e+"/"+t+"/"+(0,a.pad)(n,6)},searchByFrame:function(e,t){return"https://syrianarchive.vframe.io/search/api/search/"+e+"/"+(0,a.pad)(t,6)},browse:function(e){return"https://syrianarchive.vframe.io/search/api/list/"+e},random:function(){return"https://syrianarchive.vframe.io/search/api/random"},check:function(){return"https://syrianarchive.vframe.io/api/images/import/search"}},l=t.publicUrl={browse:function(e){return"/search/browse/"+e},searchByVerifiedFrame:function(e,t,n){return"/search/keyframe/"+(0,a.verify)(e)+"/"+t+"/"+(0,a.pad)(n,6)},searchByFrame:function(e,t){return"/search/keyframe/"+e+"/"+(0,a.pad)(t,6)},review:function(){return"/search/review/"}},f=function(e,t){return{type:o.search.loading,tag:e,offset:t}},d=function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0;return{type:o.search.loaded,tag:e,data:t,offset:n}},p=function(e,t){return{type:o.search.error,tag:e,err:t}};t.panic=function(){return function(e){i.history.push("/search/"),e({type:o.search.panic})}},t.updateOptions=function(e){return function(t){t({type:o.search.update_options,opt:e})}},t.upload=function(e,t){return function(n){var o=i.store.getState().search.options,u=new FormData;u.append("query_img",e),u.append("limit",o.perPage),t||n(f("query")),(0,a.post)(c.upload(),u).then(function(e){if(t){var o=e.query.timing;e.query=(0,r.default)({},t,{timing:o});var a={};if(e.query.crop){var u=e.query.crop,s=u.x,c=u.y,l=u.w,f=u.h;a.crop=[s,c,l,f].map(function(e){return parseInt(e,10)}).join(",")}t.url&&!t.hash&&(a.url=t.url)}else e.query.url&&!window.location.search.match(e.query.url)&&i.history.push("/search/?url="+e.query.url);n(d("query",e))}).catch(function(e){return n(p("query",e))})}},t.searchByVerifiedFrame=function(e,t,n){var r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:0;return function(o){var s=i.store.getState().search.options;o(f("query",r));var l=u.default.stringify({limit:s.perPage,offset:r});(0,a.preloadImage)({verified:e,hash:t,frame:n}),fetch(c.searchByVerifiedFrame(e,t,n)+"?"+l,{method:"GET",mode:"cors"}).then(function(e){return e.json()}).then(function(e){return o(d("query",e,r))}).catch(function(e){return o(p("query",e))})}},t.searchByFrame=function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0;return function(r){var o=i.store.getState().search.options;r(f("query",n));var s=u.default.stringify({limit:o.perPage,offset:n});(0,a.preloadImage)({verified:!1,hash:e,frame:t}),fetch(c.searchByFrame(e,t)+"?"+s,{method:"GET",mode:"cors"}).then(function(e){return e.json()}).then(function(e){return r(d("query",e,n))}).catch(function(e){return r(p("query",e))})}},t.search=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0;return function(n){var r=i.store.getState().search.options;n(f("query",t));var o=u.default.stringify({url:e,limit:r.perPage,offset:t});0===e.indexOf("static")&&(0,a.preloadImage)({uri:e}),fetch(c.search(e)+"?"+o,{method:"GET",mode:"cors"}).then(function(e){return e.json()}).then(function(e){return n(d("query",e,t))}).catch(function(e){return n(p("query",e))})}},t.browse=function(e){return function(t){var n="browse";t(f(n)),fetch(c[n](e),{method:"GET",mode:"cors"}).then(function(e){return e.json()}).then(function(e){return t(d(n,e))}).catch(function(e){return t(p(n,e))})}},t.random=function(){return function(e){var t=i.store.getState().search.options,n=u.default.stringify({limit:t.perPage});e(f("query")),fetch(c.random()+"?"+n,{method:"GET",mode:"cors"}).then(function(e){return e.json()}).then(function(t){e(d("query",t)),i.history.push(l.searchByVerifiedFrame(t.query.verified,t.query.hash,t.query.frame))}).catch(function(t){return e(p("query",t))})}}},function(e,t,n){var r=n(20),o=n(125),i=n(77),a=Object.defineProperty;t.f=n(24)?Object.defineProperty:function(e,t,n){if(r(e),t=i(t,!0),r(n),o)try{return a(e,t,n)}catch(e){}if("get"in n||"set"in n)throw TypeError("Accessors not supported!");return"value"in n&&(e[t]=n.value),e}},function(e,t){e.exports=function(e){return"object"==typeof e?null!==e:"function"==typeof e}},function(e,t,n){e.exports=!n(35)(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(e,t){var n={}.hasOwnProperty;e.exports=function(e,t){return n.call(e,t)}},function(e,t,n){var r=n(22),o=n(43);e.exports=n(24)?function(e,t,n){return r.f(e,t,o(1,n))}:function(e,t,n){return e[t]=n,e}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(275);n.d(t,"createBrowserHistory",function(){return r.a});var o=n(278);n.d(t,"createHashHistory",function(){return o.a});var i=n(279);n.d(t,"createMemoryHistory",function(){return i.a});var a=n(62);n.d(t,"createLocation",function(){return a.a}),n.d(t,"locationsAreEqual",function(){return a.b});var u=n(47);n.d(t,"parsePath",function(){return u.d}),n.d(t,"createPath",function(){return u.b})},function(e,t,n){e.exports={default:n(331),__esModule:!0}},function(e,t,n){var r=n(0),o=n(30);e.exports=function(e){var t=r(e),n=t.getFullYear(),i=new Date(0);i.setFullYear(n+1,0,4),i.setHours(0,0,0,0);var a=o(i),u=new Date(0);u.setFullYear(n,0,4),u.setHours(0,0,0,0);var s=o(u);return t.getTime()>=a.getTime()?n+1:t.getTime()>=s.getTime()?n:n-1}},function(e,t,n){var r=n(66);e.exports=function(e){return r(e,{weekStartsOn:1})}},function(e,t,n){var r=n(0);e.exports=function(e){var t=r(e);return t.setHours(0,0,0,0),t}},function(e,t){"function"==typeof Object.create?e.exports=function(e,t){e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}})}:e.exports=function(e,t){e.super_=t;var n=function(){};n.prototype=t.prototype,e.prototype=new n,e.prototype.constructor=e}},function(e,t,n){"use strict";var r=n(70),o=Object.keys||function(e){var t=[];for(var n in e)t.push(n);return t};e.exports=f;var i=n(54);i.inherits=n(32);var a=n(201),u=n(111);i.inherits(f,a);for(var s=o(u.prototype),c=0;c1)for(var n=1;n=t.length?{value:void 0,done:!0}:(e=r(t,n),this._i+=e.length,{value:e,done:!1})})},function(e,t,n){var r=n(129),o=n(82);e.exports=Object.keys||function(e){return r(e,o)}},function(e,t){var n={}.toString;e.exports=function(e){return n.call(e).slice(8,-1)}},function(e,t,n){"use strict";n.d(t,"a",function(){return r}),n.d(t,"f",function(){return o}),n.d(t,"c",function(){return i}),n.d(t,"e",function(){return a}),n.d(t,"g",function(){return u}),n.d(t,"d",function(){return s}),n.d(t,"b",function(){return c});var r=function(e){return"/"===e.charAt(0)?e:"/"+e},o=function(e){return"/"===e.charAt(0)?e.substr(1):e},i=function(e,t){return new RegExp("^"+t+"(\\/|\\?|#|$)","i").test(e)},a=function(e,t){return i(e,t)?e.substr(t.length):e},u=function(e){return"/"===e.charAt(e.length-1)?e.slice(0,-1):e},s=function(e){var t=e||"/",n="",r="",o=t.indexOf("#");-1!==o&&(r=t.substr(o),t=t.substr(0,o));var i=t.indexOf("?");return-1!==i&&(n=t.substr(i),t=t.substr(0,i)),{pathname:t,search:"?"===n?"":n,hash:"#"===r?"":r}},c=function(e){var t=e.pathname,n=e.search,r=e.hash,o=t||"/";return n&&"?"!==n&&(o+="?"===n.charAt(0)?n:"?"+n),r&&"#"!==r&&(o+="#"===r.charAt(0)?r:"#"+r),o}},function(e,t){e.exports=function(e){var t=[];return t.toString=function(){return this.map(function(t){var n=function(e,t){var n=e[1]||"",r=e[3];if(!r)return n;if(t&&"function"==typeof btoa){var o=function(e){return"/*# sourceMappingURL=data:application/json;charset=utf-8;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(e))))+" */"}(r),i=r.sources.map(function(e){return"/*# sourceURL="+r.sourceRoot+e+" */"});return[n].concat(i).concat([o]).join("\n")}return[n].join("\n")}(t,e);return t[2]?"@media "+t[2]+"{"+n+"}":n}).join("")},t.i=function(e,n){"string"==typeof e&&(e=[[null,e,""]]);for(var r={},o=0;o=0&&s.splice(t,1)}function h(e){var t=document.createElement("style");return void 0===e.attrs.type&&(e.attrs.type="text/css"),m(t,e.attrs),d(e,t),t}function m(e,t){Object.keys(t).forEach(function(n){e.setAttribute(n,t[n])})}function v(e,t){var n,r,o,i;if(t.transform&&e.css){if(!(i=t.transform(e.css)))return function(){};e.css=i}if(t.singleton){var s=u++;n=a||(a=h(t)),r=g.bind(null,n,s,!1),o=g.bind(null,n,s,!0)}else e.sourceMap&&"function"==typeof URL&&"function"==typeof URL.createObjectURL&&"function"==typeof URL.revokeObjectURL&&"function"==typeof Blob&&"function"==typeof btoa?(n=function(e){var t=document.createElement("link");return void 0===e.attrs.type&&(e.attrs.type="text/css"),e.attrs.rel="stylesheet",m(t,e.attrs),d(e,t),t}(t),r=function(e,t,n){var r=n.css,o=n.sourceMap,i=void 0===t.convertToAbsoluteUrls&&o;(t.convertToAbsoluteUrls||i)&&(r=c(r));o&&(r+="\n/*# sourceMappingURL=data:application/json;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(o))))+" */");var a=new Blob([r],{type:"text/css"}),u=e.href;e.href=URL.createObjectURL(a),u&&URL.revokeObjectURL(u)}.bind(null,n,t),o=function(){p(n),n.href&&URL.revokeObjectURL(n.href)}):(n=h(t),r=function(e,t){var n=t.css,r=t.media;r&&e.setAttribute("media",r);if(e.styleSheet)e.styleSheet.cssText=n;else{for(;e.firstChild;)e.removeChild(e.firstChild);e.appendChild(document.createTextNode(n))}}.bind(null,n),o=function(){p(n)});return r(e),function(t){if(t){if(t.css===e.css&&t.media===e.media&&t.sourceMap===e.sourceMap)return;r(e=t)}else o()}}e.exports=function(e,t){if("undefined"!=typeof DEBUG&&DEBUG&&"object"!=typeof document)throw new Error("The style-loader cannot be used in a non-browser environment");(t=t||{}).attrs="object"==typeof t.attrs?t.attrs:{},t.singleton||"boolean"==typeof t.singleton||(t.singleton=o()),t.insertInto||(t.insertInto="head"),t.insertAt||(t.insertAt="bottom");var n=f(e,t);return l(n,t),function(e){for(var o=[],i=0;io?1:0}},function(e,t,n){(function(e){function n(e){return Object.prototype.toString.call(e)}t.isArray=function(e){return Array.isArray?Array.isArray(e):"[object Array]"===n(e)},t.isBoolean=function(e){return"boolean"==typeof e},t.isNull=function(e){return null===e},t.isNullOrUndefined=function(e){return null==e},t.isNumber=function(e){return"number"==typeof e},t.isString=function(e){return"string"==typeof e},t.isSymbol=function(e){return"symbol"==typeof e},t.isUndefined=function(e){return void 0===e},t.isRegExp=function(e){return"[object RegExp]"===n(e)},t.isObject=function(e){return"object"==typeof e&&null!==e},t.isDate=function(e){return"[object Date]"===n(e)},t.isError=function(e){return"[object Error]"===n(e)||e instanceof Error},t.isFunction=function(e){return"function"==typeof e},t.isPrimitive=function(e){return null===e||"boolean"==typeof e||"number"==typeof e||"string"==typeof e||"symbol"==typeof e||void 0===e},t.isBuffer=e.isBuffer}).call(t,n(204).Buffer)},function(e,t){var n=0,r=Math.random();e.exports=function(e){return"Symbol(".concat(void 0===e?"":e,")_",(++n+r).toString(36))}},function(e,t){e.exports=function(e){if("function"!=typeof e)throw TypeError(e+" is not a function!");return e}},function(e,t,n){var r=n(22).f,o=n(25),i=n(14)("toStringTag");e.exports=function(e,t,n){e&&!o(e=n?e:e.prototype,i)&&r(e,i,{configurable:!0,value:t})}},function(e,t,n){n(254);for(var r=n(13),o=n(26),i=n(36),a=n(14)("toStringTag"),u="CSSRuleList,CSSStyleDeclaration,CSSValueList,ClientRectList,DOMRectList,DOMStringList,DOMTokenList,DataTransferItemList,FileList,HTMLAllCollection,HTMLCollection,HTMLFormElement,HTMLSelectElement,MediaList,MimeTypeArray,NamedNodeMap,NodeList,PaintRequestList,Plugin,PluginArray,SVGLengthList,SVGNumberList,SVGPathSegList,SVGPointList,SVGStringList,SVGTransformList,SourceBufferList,StyleSheetList,TextTrackCueList,TextTrackList,TouchList".split(","),s=0;s may have only one child element"),this.unlisten=r.listen(function(){e.setState({match:e.computeMatch(r.location.pathname)})})},t.prototype.componentWillReceiveProps=function(e){o()(this.props.history===e.history,"You cannot change ")},t.prototype.componentWillUnmount=function(){this.unlisten()},t.prototype.render=function(){var e=this.props.children;return e?s.a.Children.only(e):null},t}(s.a.Component);p.propTypes={history:l.a.object.isRequired,children:l.a.node},p.contextTypes={router:l.a.object},p.childContextTypes={router:l.a.object.isRequired},t.a=p},function(e,t,n){"use strict";var r=n(140),o=n.n(r),i={},a=0;t.a=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments[2];"string"==typeof t&&(t={path:t});var r=t,u=r.path,s=r.exact,c=void 0!==s&&s,l=r.strict,f=void 0!==l&&l,d=r.sensitive,p=void 0!==d&&d;if(null==u)return n;var h=function(e,t){var n=""+t.end+t.strict+t.sensitive,r=i[n]||(i[n]={});if(r[e])return r[e];var u=[],s={re:o()(e,u,t),keys:u};return a<1e4&&(r[e]=s,a++),s}(u,{end:c,strict:f,sensitive:p}),m=h.re,v=h.keys,y=m.exec(e);if(!y)return null;var g=y[0],_=y.slice(1),b=e===g;return c&&!b?null:{path:u,url:"/"===u&&""===g?"/":g,isExact:b,params:v.reduce(function(e,t,n){return e[t.name]=_[n],e},{})}}},function(e,t,n){"use strict";t.__esModule=!0;var r=function(e){return e&&e.__esModule?e:{default:e}}(n(126));t.default=function(e,t,n){return t in e?(0,r.default)(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}},function(e,t,n){var r=n(0);e.exports=function(e,t){var n=t&&Number(t.weekStartsOn)||0,o=r(e),i=o.getDay(),a=(i0?r:n)(e)}},function(e,t,n){var r=n(20),o=n(251),i=n(82),a=n(74)("IE_PROTO"),u=function(){},s=function(){var e,t=n(76)("iframe"),r=i.length;for(t.style.display="none",n(131).appendChild(t),t.src="javascript:",(e=t.contentWindow.document).open(),e.write(" + diff --git a/faiss/static/search.html b/faiss/static/search.html new file mode 100644 index 00000000..056d06c1 --- /dev/null +++ b/faiss/static/search.html @@ -0,0 +1 @@ +search.html \ No newline at end of file diff --git a/faiss/util.py b/faiss/util.py new file mode 100644 index 00000000..97afbc22 --- /dev/null +++ b/faiss/util.py @@ -0,0 +1,29 @@ +import time +import simplejson as json +import pickle +from os import path +from collections import namedtuple + +# Converts JSON el['key'] to Pythonic object-style el.key +def _json_object_hook(d): + return namedtuple('X', d.keys())(*d.values()) + +# Load a JSON recipe +def load_recipe(path): + with open(path) as fh: + return json.load(fh, object_hook=_json_object_hook) + +# Load a pickle file +def load_pickle(data_dir, pkl_fn): + load_start = time.time() + with open(path.join(str(data_dir), str(pkl_fn)), 'rb') as fh: + raw = fh.read() + data = pickle.loads(raw) + load_end = time.time() + load_time = load_end - load_start + print("Pickle load time: {:.1f}s".format(load_time)) + return data + +def read_json(fn): + with open(fn, 'r') as json_file: + return json.load(json_file) diff --git a/faiss/wsgi.py b/faiss/wsgi.py new file mode 100644 index 00000000..371862fb --- /dev/null +++ b/faiss/wsgi.py @@ -0,0 +1,5 @@ +from server import app + +if __name__ == "__main__": + app.run() + diff --git a/megapixels/app/models/sql_factory.py b/megapixels/app/models/sql_factory.py index ecca0c7f..525492f1 100644 --- a/megapixels/app/models/sql_factory.py +++ b/megapixels/app/models/sql_factory.py @@ -14,7 +14,6 @@ connection_url = "mysql+mysqldb://{}:{}@{}/{}".format( # Session = sessionmaker(bind=engine) # session = Session() - class SqlDataset: """ Bridge between the facial information CSVs connected to the datasets, and MySQL diff --git a/megapixels/app/server/api/image.py b/megapixels/app/server/api/image.py new file mode 100644 index 00000000..f2f4a4f9 --- /dev/null +++ b/megapixels/app/server/api/image.py @@ -0,0 +1,40 @@ +from flask import Blueprint, render_template, abort +# from jinja2 import TemplateNotFound + +router = Blueprint('image', __name__) + +@router.route('//test', methods=['POST']) +def test(name): + # dataset = +@router.route('//face', methods=['POST']) +def upload(name): + file = request.files['query_img'] + fn = file.filename + if fn.endswith('blob'): + fn = 'filename.jpg' + + basename, ext = os.path.splitext(fn) + print("got {}, type {}".format(basename, ext)) + if ext.lower() not in valid_exts: + return jsonify({ 'error': 'not an image' }) + + uploaded_fn = datetime.now().isoformat() + "_" + basename + uploaded_fn = sanitize_re.sub('', uploaded_fn) + uploaded_img_path = "static/uploaded/" + uploaded_fn + ext + uploaded_img_path = uploaded_img_path.lower() + print('query: {}'.format(uploaded_img_path)) + + img = Image.open(file.stream).convert('RGB') + # img.save(uploaded_img_path) + # vec = db.load_feature_vector_from_file(uploaded_img_path) + vec = fe.extract(img) + # print(vec.shape) + + results = db.search(vec, limit=limit) + query = { + 'timing': time.time() - start, + } + print(results) + return jsonify({ + 'results': results, + }) diff --git a/megapixels/app/server/create.py b/megapixels/app/server/create.py new file mode 100644 index 00000000..1119ee8f --- /dev/null +++ b/megapixels/app/server/create.py @@ -0,0 +1,27 @@ +from flask import Flask, Blueprint +from flask_sqlalchemy import SQLAlchemy +from app.models.sql_factory import connection_url + +from app.server.api import router as api_router + +# from app.server.views.assets import assets + +db = SQLAlchemy() + +def create_app(script_info=None): + app = Flask(__name__, static_url_path='') + app.config['SQLALCHEMY_DATABASE_URI'] = connection_url + app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False + + db.init_app(app) + app.register_blueprint(api) + + @app.route('/', methods=['GET']) + def index(): + return app.send_static_file('index.html') + + @app.shell_context_processor + def shell_context(): + return { 'app': app, 'db': db } + + return app diff --git a/megapixels/app/server/static b/megapixels/app/server/static new file mode 120000 index 00000000..1dc7a639 --- /dev/null +++ b/megapixels/app/server/static @@ -0,0 +1 @@ +../../../site/public \ No newline at end of file diff --git a/megapixels/cli_flask.py b/megapixels/cli_flask.py new file mode 100644 index 00000000..369bec01 --- /dev/null +++ b/megapixels/cli_flask.py @@ -0,0 +1,19 @@ +# -------------------------------------------------------- +# wrapper for flask CLI API +# -------------------------------------------------------- + +import click + +from flask.cli import FlaskGroup +from app.server.create import create_app + +# from app.settings import app_cfg as cfg +# from app.utils import logger_utils + +cli = FlaskGroup(create_app=create_app) + +# -------------------------------------------------------- +# Entrypoint +# -------------------------------------------------------- +if __name__ == '__main__': + cli() diff --git a/megapixels/commands/faiss/build_db.py b/megapixels/commands/faiss/build_db.py index c90d178b..52c4980f 100644 --- a/megapixels/commands/faiss/build_db.py +++ b/megapixels/commands/faiss/build_db.py @@ -17,11 +17,15 @@ from app.settings import app_cfg as cfg def cli(ctx): """import the various CSVs into MySQL """ - datasets = [] + load_sql_datasets(clobber=True) + +def load_sql_datasets(path, clobber=False): + datasets = {} for path in glob.iglob(os.path.join(cfg.DIR_FAISS_METADATA, "*")): - build_dataset(path) + dataset = load_sql_dataset(path, clobber) + datasets[dataset.name] = dataset -def build_dataset(path): +def load_sql_dataset(path, clobber=False): name = os.path.basename(path) dataset = SqlDataset(name) @@ -30,9 +34,12 @@ def build_dataset(path): table = dataset.get_table(key) if table is None: continue - df = pd.read_csv(fn) + if clobber: + df = pd.read_csv(fn) + + # fix columns that are named "index", a sql reserved word + df.columns = table.__table__.columns.keys() - # fix columns that are named "index", a sql reserved word - df.columns = table.__table__.columns.keys() + df.to_sql(name=table.__tablename__, con=engine, if_exists='replace', index=False) - df.to_sql(name=table.__tablename__, con=engine, if_exists='replace', index=False) + return dataset \ No newline at end of file -- cgit v1.2.3-70-g09d2