diff options
Diffstat (limited to 'client/faceAnalysis')
| -rw-r--r-- | client/faceAnalysis/faceAnalysis.actions.js | 91 | ||||
| -rw-r--r-- | client/faceAnalysis/faceAnalysis.container.js | 24 | ||||
| -rw-r--r-- | client/faceAnalysis/faceAnalysis.query.js | 86 | ||||
| -rw-r--r-- | client/faceAnalysis/faceAnalysis.reducer.js | 48 | ||||
| -rw-r--r-- | client/faceAnalysis/faceAnalysis.result.js | 131 | ||||
| -rw-r--r-- | client/faceAnalysis/index.js | 5 |
6 files changed, 385 insertions, 0 deletions
diff --git a/client/faceAnalysis/faceAnalysis.actions.js b/client/faceAnalysis/faceAnalysis.actions.js new file mode 100644 index 00000000..4a6fe6ed --- /dev/null +++ b/client/faceAnalysis/faceAnalysis.actions.js @@ -0,0 +1,91 @@ +// import fetchJsonp from 'fetch-jsonp' +import * as types from '../types' +// import { hashPath } from '../util' +import { store } from '../store' +import { get, post } from '../util' +// import querystring from 'query-string' + +// urls + +const url = { + upload: () => process.env.API_HOST + '/task/upload/demo', +} +export const publicUrl = { +} + +// standard loading events + +const loading = (tag, offset) => ({ + ts: Date.now(), + type: types.faceAnalysis.loading, + tag, + offset +}) +const loaded = (tag, data, offset = 0) => ({ + ts: Date.now(), + type: types.faceAnalysis.loaded, + tag, + data, + offset +}) +const polled = (data, offset = 0) => ({ + ts: Date.now(), + type: types.faceAnalysis.poll, + data, + offset +}) +const error = (tag, err) => ({ + type: types.faceAnalysis.error, + tag, + err +}) + +// search UI functions + +export const updateOptions = opt => dispatch => { + dispatch({ type: types.faceAnalysis.update_options, opt }) +} + +// API functions + +// task polling + +const POLL_DELAY = 500 +let pollTimeout = null + +export const poll = (payload, taskURL) => dispatch => { + clearTimeout(pollTimeout) + // console.log('polling...') + get(taskURL) + .then(data => { + // console.log('poll', data) + dispatch(polled(data)) + // console.log(data.state) + if (data.state === 'COMPLETE' || data.state === 'SUCCESS') { + console.log('complete!') + } else if (data.state === 'ERROR' || data.state === 'FAILURE') { + console.log('errorr!') + dispatch(error(data)) + } else { + pollTimeout = setTimeout(() => poll(payload, taskURL)(dispatch), POLL_DELAY) + } + }) + .catch(err => dispatch(error('result', err))) +} + +export const upload = (payload, file) => dispatch => { + const tag = 'task' + const fd = new FormData() + fd.append('query_img', file) + dispatch(loading(tag)) + post(url.upload(), fd) + .then(data => { + // console.log('loaded!', tag, data) + dispatch(loaded(tag, data)) + const { result, taskURL } = data + if (result && taskURL) { + poll(payload, taskURL)(dispatch) + } + }) + .catch(err => dispatch(error(tag, err))) +} diff --git a/client/faceAnalysis/faceAnalysis.container.js b/client/faceAnalysis/faceAnalysis.container.js new file mode 100644 index 00000000..24848455 --- /dev/null +++ b/client/faceAnalysis/faceAnalysis.container.js @@ -0,0 +1,24 @@ +import React, { Component } from 'react' +import { bindActionCreators } from 'redux' +import { connect } from 'react-redux' + +import * as actions from './faceAnalysis.actions' + +import FaceAnalysisQuery from './faceAnalysis.query' +import FaceAnalysisResult from './faceAnalysis.result' + +class FaceAnalysisContainer extends Component { + render() { + const { payload } = this.props + // console.log(payload) + return ( + <div className='analysisContainer'> + <FaceAnalysisQuery payload={payload} /> + <FaceAnalysisResult payload={payload} /> + </div> + ) + } +} + + +export default FaceAnalysisContainer diff --git a/client/faceAnalysis/faceAnalysis.query.js b/client/faceAnalysis/faceAnalysis.query.js new file mode 100644 index 00000000..33dd641f --- /dev/null +++ b/client/faceAnalysis/faceAnalysis.query.js @@ -0,0 +1,86 @@ +import React, { Component } from 'react' +import { bindActionCreators } from 'redux' +import { connect } from 'react-redux' + +import { Loader, UploadImage } from '../common' +import * as actions from './faceAnalysis.actions' + +// function parse_bbox(s) { +// // "BBox: (77,86), (166, 177), width:89, height:91" +// try { +// const [x, y, w, h, width, height] = s.replace(/\D/g, ' ').replace(/\s+/g, ' ').trim().split(' ') +// return { x, y, w, h } +// } +// } + +class FaceAnalysisQuery extends Component { + state = { + image: null + } + + upload(blob) { + if (this.state.image) { + URL.revokeObjectURL(this.state.image) + } + const url = URL.createObjectURL(blob) + this.setState({ image: url }) + this.props.actions.upload(this.props.payload, blob) + } + + componentWillUnmount() { + if (this.state.image) { + URL.revokeObjectURL(this.state.image) + } + } + + render() { + const { result } = this.props + const { image } = this.state + const style = {} + if (image) { + style.backgroundImage = 'url(' + image + ')' + style.backgroundSize = 'cover' + style.opacity = 1 + } + return ( + <div className='query row'> + <div className='uploadContainer'> + <div style={style}> + {image ? null : <img src="/assets/img/icon_camera.svg" />} + <UploadImage onUpload={this.upload.bind(this)} /> + </div> + {result.loading && ( + <div className='loading' style={style}> + <Loader /> + </div> + )} + </div> + <div className='cta'> + <h2>Face Analysis</h2> + <p> + {'Put yourself under the microscope of various facial recognition algorithms. See what can be determined from a photo.'} + </p> + <ol> + <li>Upload a photo of yourself and be judged by the algorithm</li> + <li>{'Your search data is only stored for the duration of this analysis and is immediately cleared '} + {'once you leave this page.'}</li> + </ol> + <p> + Read more about <a href='/about/privacy/'>privacy</a>. + </p> + </div> + </div> + ) + } +} + +const mapStateToProps = state => ({ + result: state.faceAnalysis.result, + options: state.faceAnalysis.options, +}) + +const mapDispatchToProps = dispatch => ({ + actions: bindActionCreators({ ...actions }, dispatch), +}) + +export default connect(mapStateToProps, mapDispatchToProps)(FaceAnalysisQuery) diff --git a/client/faceAnalysis/faceAnalysis.reducer.js b/client/faceAnalysis/faceAnalysis.reducer.js new file mode 100644 index 00000000..d9be7447 --- /dev/null +++ b/client/faceAnalysis/faceAnalysis.reducer.js @@ -0,0 +1,48 @@ +import * as types from '../types' + +const initialState = () => ({ + query: {}, + task: {}, + result: {}, + loading: false, + startTime: 0, + timing: 0, +}) + +export default function faceAnalysisReducer(state = initialState(), action) { + const { startTime } = state + switch (action.type) { + case types.faceAnalysis.loading: + return { + ...state, + startTime: action.ts, + timing: 0, + [action.tag]: { loading: true }, + } + + case types.faceAnalysis.loaded: + return { + ...state, + timing: action.ts - startTime, + [action.tag]: action.data, + } + + case types.faceAnalysis.poll: + return { + ...state, + timing: action.ts - startTime, + result: action.data, + } + + case types.faceAnalysis.error: + console.log('error', action) + return { + ...state, + timing: action.ts - startTime, + [action.tag]: { error: action.err }, + } + + default: + return state + } +} diff --git a/client/faceAnalysis/faceAnalysis.result.js b/client/faceAnalysis/faceAnalysis.result.js new file mode 100644 index 00000000..9d77f258 --- /dev/null +++ b/client/faceAnalysis/faceAnalysis.result.js @@ -0,0 +1,131 @@ +import React, { Component } from 'react' +import { bindActionCreators } from 'redux' +import { connect } from 'react-redux' + +import * as actions from './faceAnalysis.actions' +import { Loader } from '../common' + +const errors = { + bbox: ( + <div> + <h2>No face found</h2> + {"Sorry, we didn't detect a face in that image. "} + {"Please choose an image where the face is large and clear."} + </div> + ), + nomatch: ( + <div> + <h2>{"You're clear"}</h2> + {"No images in this dataset match your face. We show only matches above 70% probability."} + </div> + ), + error: ( + <div> + <h2>{"No matches found"}</h2> + {"Sorry, an error occured."} + </div> + ), + not_an_image: ( + <div> + <h2>{"Not an image"}</h2> + {"Sorry, the file you uploaded was not recognized as an image. Please upload a JPG or PNG image and try again."} + </div> + ), +} + +class FaceAnalysisResult extends Component { + render() { + const { result, timing } = this.props + const { data, error, loading, message } = result + let { step, total } = data || {} + // console.log(step, total) + if (loading) { + return ( + <div className='result'> + <div> + <Loader /><br /> + <h2>Uploading...</h2> + </div> + </div> + ) + } + if (error) { + // console.log(error) + let errorMessage = errors[error] || errors.error + return ( + <div className='result'>{errorMessage}</div> + ) + } + // console.log(result) + if (!total) { + return ( + <div className='result'></div> + ) + } + + console.log(data.data) + const results = [ + 'blur_fn', 'points_3d_68', 'landmarks_3d_68', 'landmarks_2d_68', 'pose', + ].map(tag => { + if (tag in data.data) { + const { title, url } = data.data[tag] + return ( + <div key={tag}> + <img src={url} /> + <span>{title}</span> + </div> + ) + } + return null + }).filter(a => a) + + const statisticsLabels = ['Age (Real)', 'Age (Apparent)', 'Gender', 'Beauty score', 'Emotion'] + const statistics = [ + 'age_real', 'age_apparent', 'gender', 'beauty', 'emotion' + ].map((tag, i) => { + if (tag in data.data.statistics) { + return ( + <tr key={tag}> + <td> + {statisticsLabels[i]} + </td> + <td> + {data.data.statistics[tag]} + </td> + </tr> + ) + } + return null + }).filter(a => a) + + return ( + <div> + <div className='results'> + {results} + </div> + {!!statistics.length && ( + <table> + {statistics} + </table> + )} + <div className="about"> + <small>Step {step} / {total} {message}</small><br /> + <small>Query {step === total ? 'took' : 'timer:'} {(timing / 1000).toFixed(2)} s.</small> + </div> + </div> + ) + } +} + +const mapStateToProps = state => ({ + query: state.faceAnalysis.query, + result: state.faceAnalysis.result, + timing: state.faceAnalysis.timing, + options: state.faceAnalysis.options, +}) + +const mapDispatchToProps = dispatch => ({ + actions: bindActionCreators({ ...actions }, dispatch), +}) + +export default connect(mapStateToProps, mapDispatchToProps)(FaceAnalysisResult) diff --git a/client/faceAnalysis/index.js b/client/faceAnalysis/index.js new file mode 100644 index 00000000..efa39ded --- /dev/null +++ b/client/faceAnalysis/index.js @@ -0,0 +1,5 @@ +import Container from './faceAnalysis.container' + +export { + Container, +} |
