diff options
| -rw-r--r-- | README.md | 18 | ||||
| -rw-r--r-- | check/app/server/api.py | 67 | ||||
| -rw-r--r-- | client/app.js | 24 |
3 files changed, 75 insertions, 34 deletions
@@ -35,12 +35,22 @@ DB_PASS=some_new_password #### POST /v1/match -Check if an image is in the database. If no images are found within the similarity threshold, add this image to the database. +Check if an image is in the database. If no images are found within the threshold, this image will be added to the database. -Options: +Form parameters: - `threshold` (default: 6) - Minimum similarity threshold. This is the Hamming distance used for phash comparisons. -- `add` (default: true) - Pass `false` if you do not want the image added to the database. - `limit` (default: 1) - Number of results to return. - `url` - Image URL to fetch and test (will be stored if `add` is true) -- `q` (file) - Uploaded file to test (will not be stored)
\ No newline at end of file +- `q` (file) - Uploaded file to test (will not be stored) + +#### POST /v1/similar + +Find similar images to a query image. + +Form parameters: + +- `threshold` (default: 20) - Minimum similarity threshold. +- `limit` (default: 10) - Number of results to return. +- `url` - Image URL to fetch and test (will be stored if `add` is true) +- `q` (file) - Uploaded file to test (will not be stored) diff --git a/check/app/server/api.py b/check/app/server/api.py index 63de5b6..66a0dd1 100644 --- a/check/app/server/api.py +++ b/check/app/server/api.py @@ -15,7 +15,11 @@ from app.utils.file_utils import sha256_stream sanitize_re = re.compile('[\W]+') valid_exts = ['.gif', '.jpg', '.jpeg', '.png'] -LIMIT = 9 +MATCH_THRESHOLD = 20 +MATCH_LIMIT = 10 + +SIMILAR_THRESHOLD = 20 +SIMILAR_LIMIT = 10 api = Blueprint('api', __name__) @@ -26,17 +30,10 @@ def index(): """ return jsonify({ 'status': 'ok' }) -@api.route('/v1/match', methods=['POST']) -def match(): - """ - Search by uploading an image - """ - start = time.time() - +def get_params(default_threshold=MATCH_THRESHOLD, default_limit=MATCH_LIMIT): try: - threshold = int(request.form.get('threshold') or 6) - limit = int(request.form.get('limit') or 1) - add = str(request.form.get('add') or 'true') == 'true' + threshold = int(request.form.get('threshold') or default_threshold) + limit = int(request.form.get('limit') or default_limit) except: return jsonify({ 'success': False, @@ -59,6 +56,7 @@ def match(): 'error': 'not_an_image' }) + raw = None im = Image.open(file.stream).convert('RGB') else: url = request.form.get('url') @@ -81,13 +79,22 @@ def match(): raw = remote_response.read() im = Image.open(io.BytesIO(raw)).convert('RGB') +@api.route('/v1/match', methods=['POST']) +def match(): + """ + Search by uploading an image + """ + start = time.time() + + threshold, limit, raw, im = get_params() + phash = compute_phash_int(im) ext = ext[1:].lower() results = search_by_phash(phash=phash, threshold=threshold, limit=limit) if len(results) == 0: - if add and url: + if url: # hash = sha256_stream(file) hash = sha256_stream(io.BytesIO(raw)) add_phash(sha256=hash, phash=phash, ext=ext, url=url) @@ -97,17 +104,37 @@ def match(): logging.debug('query took {0:.2g} s.'.format(time.time() - start)) - if limit > 1: - return jsonify({ - 'success': True, - 'match': match, - 'results': results, - 'timing': time.time() - start, - }) + return jsonify({ + 'success': True, + 'match': match, + 'results': results, + 'timing': time.time() - start, + }) + +@api.route('/v1/similar', methods=['POST']) +def similar(): + """ + Search by uploading an image + """ + start = time.time() + + threshold, limit, raw, im = get_params(default_threshold=SIMILARITY_THRESHOLD, default_limit=SIMILARITY_LIMIT) + + phash = compute_phash_int(im) + ext = ext[1:].lower() + + results = search_by_phash(phash=phash, threshold=threshold, limit=limit) + + if len(results) == 0: + match = False + else: + match = True + + logging.debug('query took {0:.2g} s.'.format(time.time() - start)) return jsonify({ 'success': True, 'match': match, - 'closest_match': results[0] if len(results) else None, + 'results': results, 'timing': time.time() - start, }) diff --git a/client/app.js b/client/app.js index d18a5aa..b1c5eb4 100644 --- a/client/app.js +++ b/client/app.js @@ -54,7 +54,7 @@ export default class PhashApp extends Component { render() { return ( <div className='app'> - <h1>Perceptual Hash Demo</h1> + <h1>Search by Image</h1> {this.renderQuery()} {this.renderResults()} </div> @@ -72,11 +72,12 @@ export default class PhashApp extends Component { return ( <div className='query'> <label> - <span>Upload image</span> + <span>Query image</span> <UploadImage onUpload={this.upload.bind(this)} /> </label> + <br /> <label> - <span>Enter URL</span> + <span>Add a URL</span> <input type='text' value={this.state.url} @@ -105,11 +106,11 @@ export default class PhashApp extends Component { ) } - const { success, error, match, closest_match } = res + const { success, error, match, matches } = res if (!success) { return ( <div className='results'> - <b>Error: {error}</b> + <b>Error: {error.replace(/_/g, ' ')}</b> </div> ) } @@ -122,13 +123,16 @@ export default class PhashApp extends Component { ) } - const { phash, score, sha256, url} = closest_match return ( <div className='results'> - <img src={url} /><br /> - Closest match: {sha256}<br /> - Score: {score}<br /> - Phash: {phash.toString(16)} + {matches.map(({ phash, score, sha256, url }) => ( + <div className='result'> + <img src={url} /><br /> + Match: {sha256}<br /> + Score: {score}<br /> + Phash: {phash.toString(16)} + </div> + ))} </div> ) } |
