summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md18
-rw-r--r--check/app/server/api.py67
-rw-r--r--client/app.js24
3 files changed, 75 insertions, 34 deletions
diff --git a/README.md b/README.md
index e95635f..f4a9103 100644
--- a/README.md
+++ b/README.md
@@ -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>
)
}