From 1c82f7ec6a603978322e16470547731e92e947c6 Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Mon, 17 Dec 2018 20:15:41 +0100 Subject: adding verbiage and timing --- megapixels/app/models/sql_factory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'megapixels/app/models') diff --git a/megapixels/app/models/sql_factory.py b/megapixels/app/models/sql_factory.py index 9a44941b..02b722df 100644 --- a/megapixels/app/models/sql_factory.py +++ b/megapixels/app/models/sql_factory.py @@ -98,7 +98,7 @@ class SqlDataset: return None session = Session() # for obj in session.query(table).filter_by(id=id): - print(table) + # print(table) obj = session.query(table).filter(table.id == id).first() session.close() return obj.toJSON() -- cgit v1.2.3-70-g09d2 From 2194c77729202dfe58acb0de67bad7b65e3f7f5d Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Mon, 17 Dec 2018 23:04:54 +0100 Subject: unicode --- megapixels/app/models/sql_factory.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'megapixels/app/models') diff --git a/megapixels/app/models/sql_factory.py b/megapixels/app/models/sql_factory.py index 02b722df..c955b885 100644 --- a/megapixels/app/models/sql_factory.py +++ b/megapixels/app/models/sql_factory.py @@ -125,7 +125,7 @@ class SqlDataset: class UUID(self.base_model): __tablename__ = self.name + "_uuid" id = Column(Integer, primary_key=True) - uuid = Column(String(36), nullable=False) + uuid = Column(String(36, convert_unicode=True), nullable=False) def toJSON(self): return { 'id': self.id, @@ -167,9 +167,9 @@ class SqlDataset: class Identity(self.base_model): __tablename__ = self.name + "_identity" id = Column(Integer, primary_key=True) - fullname = Column(String(36), nullable=False) - description = Column(String(36), nullable=False) - gender = Column(String(1), nullable=False) + fullname = Column(String(36, convert_unicode=True), nullable=False) + description = Column(String(36, convert_unicode=True), nullable=False) + gender = Column(String(1, convert_unicode=True), nullable=False) images = Column(Integer, nullable=False) image_id = Column(Integer, nullable=False) def toJSON(self): -- cgit v1.2.3-70-g09d2 From ed7541f7e18ad8622ebecae588eace89608880c2 Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Mon, 17 Dec 2018 23:14:58 +0100 Subject: unicode --- megapixels/app/models/sql_factory.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'megapixels/app/models') diff --git a/megapixels/app/models/sql_factory.py b/megapixels/app/models/sql_factory.py index c955b885..cf652c6d 100644 --- a/megapixels/app/models/sql_factory.py +++ b/megapixels/app/models/sql_factory.py @@ -10,7 +10,7 @@ from sqlalchemy.ext.declarative import declarative_base from app.utils.file_utils import load_recipe, load_csv_safe from app.settings import app_cfg as cfg -connection_url = "mysql+mysqldb://{}:{}@{}/{}".format( +connection_url = "mysql+mysqldb://{}:{}@{}/{}?charset=utf8".format( os.getenv("DB_USER"), os.getenv("DB_PASS"), os.getenv("DB_HOST"), @@ -35,7 +35,7 @@ def load_sql_datasets(replace=False, base_model=None): global datasets, loaded, Session if loaded: return datasets - engine = create_engine(connection_url) + engine = create_engine(connection_url, encoding="utf-8") Session = sessionmaker(bind=engine) for path in glob.iglob(os.path.join(cfg.DIR_FAISS_METADATA, "*")): dataset = load_sql_dataset(path, replace, engine, base_model) -- cgit v1.2.3-70-g09d2 From f2c7e5a9cabb5524fcd6fd9fb786a4223bbc7b1a Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Mon, 17 Dec 2018 23:23:06 +0100 Subject: unicode --- megapixels/app/models/sql_factory.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'megapixels/app/models') diff --git a/megapixels/app/models/sql_factory.py b/megapixels/app/models/sql_factory.py index cf652c6d..414ef3a6 100644 --- a/megapixels/app/models/sql_factory.py +++ b/megapixels/app/models/sql_factory.py @@ -10,7 +10,7 @@ from sqlalchemy.ext.declarative import declarative_base from app.utils.file_utils import load_recipe, load_csv_safe from app.settings import app_cfg as cfg -connection_url = "mysql+mysqldb://{}:{}@{}/{}?charset=utf8".format( +connection_url = "mysql+mysqlconnector://{}:{}@{}/{}?charset=utf8mb4".format( os.getenv("DB_USER"), os.getenv("DB_PASS"), os.getenv("DB_HOST"), @@ -36,6 +36,11 @@ def load_sql_datasets(replace=False, base_model=None): if loaded: return datasets engine = create_engine(connection_url, encoding="utf-8") + # db.set_character_set('utf8') + # dbc = db.cursor() + # dbc.execute('SET NAMES utf8;') + # dbc.execute('SET CHARACTER SET utf8;') + # dbc.execute('SET character_set_connection=utf8;') Session = sessionmaker(bind=engine) for path in glob.iglob(os.path.join(cfg.DIR_FAISS_METADATA, "*")): dataset = load_sql_dataset(path, replace, engine, base_model) -- cgit v1.2.3-70-g09d2 From 7b8e6f9a7d3eb36b72b53d5e754b9c7916b98ed7 Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Tue, 18 Dec 2018 01:03:10 +0100 Subject: namesearchg --- client/actions.js | 4 +- client/applet.js | 5 +- client/faceSearch/faceSearch.container.js | 2 +- client/faceSearch/faceSearch.result.js | 16 +++++- client/index.js | 1 - client/nameSearch/index.js | 5 ++ client/nameSearch/nameSearch.actions.js | 52 ++++++++++++++++++ client/nameSearch/nameSearch.container.js | 24 +++++++++ client/nameSearch/nameSearch.query.js | 48 +++++++++++++++++ client/nameSearch/nameSearch.reducer.js | 32 +++++++++++ client/nameSearch/nameSearch.result.js | 88 +++++++++++++++++++++++++++++++ client/store.js | 8 +-- client/tables.js | 4 ++ client/types.js | 3 ++ megapixels/app/models/sql_factory.py | 14 +++++ megapixels/app/server/api.py | 41 +++++++------- megapixels/app/settings/app_cfg.py | 2 +- site/assets/css/applets.css | 24 +++++++-- 18 files changed, 339 insertions(+), 34 deletions(-) create mode 100644 client/nameSearch/index.js create mode 100644 client/nameSearch/nameSearch.actions.js create mode 100644 client/nameSearch/nameSearch.container.js create mode 100644 client/nameSearch/nameSearch.query.js create mode 100644 client/nameSearch/nameSearch.reducer.js create mode 100644 client/nameSearch/nameSearch.result.js (limited to 'megapixels/app/models') diff --git a/client/actions.js b/client/actions.js index bb011838..2be8229d 100644 --- a/client/actions.js +++ b/client/actions.js @@ -1,5 +1,7 @@ import * as faceSearch from './faceSearch/faceSearch.actions' +import * as nameSearch from './nameSearch/nameSearch.actions' export { - faceSearch + faceSearch, + nameSearch, } diff --git a/client/applet.js b/client/applet.js index 4d2a8e6c..80d40657 100644 --- a/client/applet.js +++ b/client/applet.js @@ -1,13 +1,16 @@ import React, { Component } from 'react' import { Container as FaceSearchContainer } from './faceSearch' +import { Container as NameSearchContainer } from './nameSearch' export default class Applet extends Component { render() { - console.log(this.props) + // console.log(this.props) switch (this.props.payload.cmd) { case 'face_search': return + case 'name_search': + return default: return
{'Megapixels'}
} diff --git a/client/faceSearch/faceSearch.container.js b/client/faceSearch/faceSearch.container.js index f96961db..94c6eb9f 100644 --- a/client/faceSearch/faceSearch.container.js +++ b/client/faceSearch/faceSearch.container.js @@ -10,7 +10,7 @@ import FaceSearchResult from './faceSearch.result' class FaceSearchContainer extends Component { render() { const { payload } = this.props - console.log(payload) + // console.log(payload) return (
diff --git a/client/faceSearch/faceSearch.result.js b/client/faceSearch/faceSearch.result.js index d63f3265..936bc8d2 100644 --- a/client/faceSearch/faceSearch.result.js +++ b/client/faceSearch/faceSearch.result.js @@ -32,6 +32,7 @@ class FaceSearchResult extends Component { render() { const { dataset } = this.props.payload const { query, distances, results, loading, error } = this.props.result + console.log(this.props.result) if (loading) { return (
@@ -43,7 +44,7 @@ class FaceSearchResult extends Component { ) } if (error) { - console.log(error) + // console.log(error) let errorMessage = errors[error] || errors.error return (
{errorMessage}
@@ -60,10 +61,21 @@ class FaceSearchResult extends Component { const els = results.map((result, i) => { const distance = distances[i] const { uuid } = result.uuid + const { x, y, w, h } = result.roi const { fullname, gender, description, images } = result.identity + const bbox = { + left: (100 * x) + '%', + top: (100 * y) + '%', + width: (100 * w) + '%', + height: (100 * h) + '%', + } + // console.log(bbox) return (
- +
+ +
+
{fullname} {'('}{gender}{')'}
{description}
{courtesyS(images, 'image')}{' in dataset'}
diff --git a/client/index.js b/client/index.js index 2beb5526..93341a77 100644 --- a/client/index.js +++ b/client/index.js @@ -38,7 +38,6 @@ function appendApplets(applets) { el.classList.add('loaded') break default: - console.log('react', el, payload) appendReactApplet(el, payload) el.classList.add('loaded') break diff --git a/client/nameSearch/index.js b/client/nameSearch/index.js new file mode 100644 index 00000000..8c6475e4 --- /dev/null +++ b/client/nameSearch/index.js @@ -0,0 +1,5 @@ +import Container from './nameSearch.container' + +export { + Container, +} diff --git a/client/nameSearch/nameSearch.actions.js b/client/nameSearch/nameSearch.actions.js new file mode 100644 index 00000000..290ee38d --- /dev/null +++ b/client/nameSearch/nameSearch.actions.js @@ -0,0 +1,52 @@ +// import fetchJsonp from 'fetch-jsonp' +import * as types from '../types' +// import { hashPath } from '../util' +import { post } from '../util' +// import querystring from 'query-string' + +// urls + +const url = { + search: (dataset, q) => process.env.API_HOST + '/api/dataset/' + dataset + '/name?q=' + encodeURIComponent(q), +} +export const publicUrl = { +} + +// standard loading events + +const loading = (tag, offset) => ({ + type: types.nameSearch.loading, + tag, + offset +}) +const loaded = (tag, data, offset = 0) => ({ + type: types.nameSearch.loaded, + tag, + data, + offset +}) +const error = (tag, err) => ({ + type: types.nameSearch.error, + tag, + err +}) + +// search UI functions + +export const updateOptions = opt => dispatch => { + dispatch({ type: types.nameSearch.update_options, opt }) +} + +// API functions + +export const search = (payload, q) => dispatch => { + const tag = 'result' + const fd = new FormData() + fd.append('q', q) + dispatch(loading(tag)) + post(url.search(payload.dataset, q), fd) + .then(data => { + dispatch(loaded(tag, data)) + }) + .catch(err => dispatch(error(tag, err))) +} diff --git a/client/nameSearch/nameSearch.container.js b/client/nameSearch/nameSearch.container.js new file mode 100644 index 00000000..b0de0c3a --- /dev/null +++ b/client/nameSearch/nameSearch.container.js @@ -0,0 +1,24 @@ +import React, { Component } from 'react' +import { bindActionCreators } from 'redux' +import { connect } from 'react-redux' + +import * as actions from './nameSearch.actions' + +import NameSearchQuery from './nameSearch.query' +import NameSearchResult from './nameSearch.result' + +class NameSearchContainer extends Component { + render() { + const { payload } = this.props + // console.log(payload) + return ( +
+ + +
+ ) + } +} + + +export default NameSearchContainer diff --git a/client/nameSearch/nameSearch.query.js b/client/nameSearch/nameSearch.query.js new file mode 100644 index 00000000..b82e324b --- /dev/null +++ b/client/nameSearch/nameSearch.query.js @@ -0,0 +1,48 @@ +import React, { Component } from 'react' +import { bindActionCreators } from 'redux' +import { connect } from 'react-redux' + +import * as actions from './nameSearch.actions' + +class NameSearchQuery extends Component { + state = { + value: null + } + + handleInput(value) { + this.setState({ q: value }) + if (value.length > 2) { + this.props.actions.search(this.props.payload, value) + } + } + + render() { + return ( +
+

Find Your Name

+

Searching {13456} identities

+

+ {'Enter your name to see if you were included in this dataset..'} +

+ this.handleInput(e.target.value)} + /> +
+ ) + } +} + +const mapStateToProps = state => ({ + result: state.nameSearch.result, + options: state.nameSearch.options, +}) + +const mapDispatchToProps = dispatch => ({ + actions: bindActionCreators({ ...actions }, dispatch), +}) + +export default connect(mapStateToProps, mapDispatchToProps)(NameSearchQuery) diff --git a/client/nameSearch/nameSearch.reducer.js b/client/nameSearch/nameSearch.reducer.js new file mode 100644 index 00000000..101c93ea --- /dev/null +++ b/client/nameSearch/nameSearch.reducer.js @@ -0,0 +1,32 @@ +import * as types from '../types' + +const initialState = () => ({ + query: {}, + result: {}, + loading: false, +}) + +export default function nameSearchReducer(state = initialState(), action) { + switch (action.type) { + case types.nameSearch.loading: + return { + ...state, + [action.tag]: { loading: true }, + } + + case types.nameSearch.loaded: + return { + ...state, + [action.tag]: action.data, + } + + case types.nameSearch.error: + return { + ...state, + [action.tag]: { error: action.err }, + } + + default: + return state + } +} diff --git a/client/nameSearch/nameSearch.result.js b/client/nameSearch/nameSearch.result.js new file mode 100644 index 00000000..9e20228c --- /dev/null +++ b/client/nameSearch/nameSearch.result.js @@ -0,0 +1,88 @@ +import React, { Component } from 'react' +import { bindActionCreators } from 'redux' +import { connect } from 'react-redux' +import { courtesyS } from '../util' + +import * as actions from './nameSearch.actions' +import { Loader } from '../common' + +const errors = { + nomatch: ( +
+

Name not found

+ {"No names matched your query."} +
+ ), + error: ( +
+

{"No matches found"}

+
+ ), +} + +class NameSearchResult extends Component { + render() { + const { dataset } = this.props.payload + const { query, results, loading, error } = this.props.result + console.log(this.props.result) + if (loading) { + return ( +
+
+ +
+
+ ) + } + if (error) { + console.log(error) + let errorMessage = errors[error] || errors.error + return ( +
{errorMessage}
+ ) + } + if (!results) { + return
+ } + if (!results.length) { + return ( +
{errors.nomatch}
+ ) + } + const els = results.map((result, i) => { + const { uuid } = result.uuid + const { fullname, gender, description, images } = result.identity + return ( +
+ + {fullname} {'('}{gender}{')'}
+ {description}
+ {courtesyS(images, 'image')}{' in dataset'}
+
+ ) + }) + + return ( +
+
+ {'Search took '}{Math.round(query.timing * 1000) + ' ms'} +
+
+ {els} +
+
+ ) + } +} + +const mapStateToProps = state => ({ + query: state.nameSearch.query, + result: state.nameSearch.result, + options: state.nameSearch.options, +}) + +const mapDispatchToProps = dispatch => ({ + actions: bindActionCreators({ ...actions }, dispatch), +}) + +export default connect(mapStateToProps, mapDispatchToProps)(NameSearchResult) diff --git a/client/store.js b/client/store.js index 03f983a5..13612f2d 100644 --- a/client/store.js +++ b/client/store.js @@ -1,16 +1,12 @@ import { applyMiddleware, compose, combineReducers, createStore } from 'redux' import thunk from 'redux-thunk' -// import metadataReducer from './metadata/metadata.reducer' import faceSearchReducer from './faceSearch/faceSearch.reducer' -// import reviewReducer from './review/review.reducer' +import nameSearchReducer from './nameSearch/nameSearch.reducer' const rootReducer = combineReducers({ - auth: (state = {}) => state, - // auth: (state = login()) => state, - // metadata: metadataReducer, faceSearch: faceSearchReducer, - // review: reviewReducer, + nameSearch: nameSearchReducer, }) function configureStore(initialState = {}) { diff --git a/client/tables.js b/client/tables.js index 2a2699f9..b4c13887 100644 --- a/client/tables.js +++ b/client/tables.js @@ -74,4 +74,8 @@ export default function append(el, payload) { } }) } + + if (fields.length > 1 && fields[1].indexOf('filter')) { + const filter = fields[1].split(' ') + } } diff --git a/client/types.js b/client/types.js index d295d0d1..fb1fbe30 100644 --- a/client/types.js +++ b/client/types.js @@ -10,5 +10,8 @@ export const faceSearch = tagAsType('faceSearch', [ 'loading', 'loaded', 'error', 'update_options', ]) +export const nameSearch = tagAsType('nameSearch', [ + 'loading', 'loaded', 'error', 'update_options', +]) export const init = '@@INIT' diff --git a/megapixels/app/models/sql_factory.py b/megapixels/app/models/sql_factory.py index 414ef3a6..da95b539 100644 --- a/megapixels/app/models/sql_factory.py +++ b/megapixels/app/models/sql_factory.py @@ -97,6 +97,20 @@ class SqlDataset: 'pose': self.select('pose', id), } + def search_name(self, q): + table = self.get_table('identity_meta') + uuid_table = self.get_table('uuids') + + identity = table.query.filter(table.fullname.like(q)).order_by(table.fullname.desc()).limit(30) + identities = [] + for row in identity: + uuid = uuid_table.query.filter(uuid_table.id == row.image_id).first() + identities.append({ + 'uuid': uuid.toJSON(), + 'identity': row.toJSON(), + }) + return identities + def select(self, table, id): table = self.get_table(table) if not table: diff --git a/megapixels/app/server/api.py b/megapixels/app/server/api.py index 8ff06611..33cf45df 100644 --- a/megapixels/app/server/api.py +++ b/megapixels/app/server/api.py @@ -28,26 +28,26 @@ def index(): return jsonify({ 'datasets': list_datasets() }) -@api.route('/dataset/') -def show(name): +@api.route('/dataset/') +def show(dataset_name): """Show the data that a dataset will return""" - dataset = get_dataset(name) + dataset = get_dataset(dataset_name) if dataset: return jsonify(dataset.describe()) else: return jsonify({ 'status': 404 }) -@api.route('/dataset//face/', methods=['POST']) -def upload(name): +@api.route('/dataset//face', methods=['POST']) +def upload(dataset_name): """Query an image against FAISS and return the matching identities""" start = time.time() - dataset = get_dataset(name) - if name not in faiss_datasets: + dataset = get_dataset(dataset_name) + if dataset_name not in faiss_datasets: return jsonify({ 'error': 'invalid dataset' }) - faiss_dataset = faiss_datasets[name] + faiss_dataset = faiss_datasets[dataset_name] file = request.files['query_img'] fn = file.filename if fn.endswith('blob'): @@ -106,17 +106,21 @@ def upload(name): for _d, _i in zip(distances, indexes): if _d <= THRESHOLD: dists.append(round(float(_d), 2)) - ids.append(_i) + ids.append(_i+1) results = [ dataset.get_identity(int(_i)) for _i in ids ] # print(distances) # print(ids) + # 'bbox': str(bboxes[0]), + # 'bbox_dim': str(bbox), + print(bboxes[0]) + print(bbox) + query = { - 'bbox': bboxes[0], - 'bbox_dim': bbox, 'timing': round(time.time() - start, 3), + 'bbox': str(bbox), } # print(results) return jsonify({ @@ -126,20 +130,21 @@ def upload(name): }) -@api.route('/dataset//name', methods=['GET']) -def name_lookup(dataset): +@api.route('/dataset//name', methods=['GET','POST']) +def name_lookup(dataset_name): """Find a name in the dataset""" start = time.time() - dataset = get_dataset(name) + dataset = get_dataset(dataset_name) - # we have a query from the request query string... - # use this to do a like* query on the identities_meta table + q = request.args.get('q') + print(q) query = { + 'q': q, 'timing': time.time() - start, } - results = [] - + results = dataset.search_name(q + '%') if q else None + # print(results) return jsonify({ 'query': query, diff --git a/megapixels/app/settings/app_cfg.py b/megapixels/app/settings/app_cfg.py index d7752739..55fed166 100644 --- a/megapixels/app/settings/app_cfg.py +++ b/megapixels/app/settings/app_cfg.py @@ -89,7 +89,7 @@ CKPT_ZERO_PADDING = 9 HASH_TREE_DEPTH = 3 HASH_BRANCH_SIZE = 3 -DLIB_FACEREC_JITTERS = 25 # number of face recognition jitters +DLIB_FACEREC_JITTERS = 5 # number of face recognition jitters DLIB_FACEREC_PADDING = 0.25 # default dlib POSE_MINMAX_YAW = (-25,25) diff --git a/site/assets/css/applets.css b/site/assets/css/applets.css index edd5b709..315d72e0 100644 --- a/site/assets/css/applets.css +++ b/site/assets/css/applets.css @@ -16,7 +16,15 @@ flex-direction: row; justify-content: flex-start; } - +.q { + width: 100%; + padding: 5px; + font-size: 14pt; +} +.timing { + font-size: 9pt; + padding-top: 10px; +} .results { margin-top: 10px; padding-bottom: 10px; @@ -34,9 +42,10 @@ font-weight: 500; } .results > div img { + display: block; margin-bottom: 4px; - width: 200px; - height: 200px; + width: 190px; + height: 190px; background: rgba(255,255,255,0.05); } .results > div:nth-child(3n+1) { @@ -45,6 +54,15 @@ .query h2 { margin-top: 0; padding-top: 0; } +.img { + position: relative; +} +.img .bbox { + position: absolute; + color: rgba(255,0,0,1); + background: rgba(255,0,0,0.05); + border: 1px solid; +} .cta { padding-left: 20px; font-size: 11pt; -- cgit v1.2.3-70-g09d2