diff options
25 files changed, 404 insertions, 1191 deletions
diff --git a/package-lock.json b/package-lock.json index ecd3699e..6d36e3ff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8722,6 +8722,11 @@ "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", "dev": true }, + "svgtodatauri": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/svgtodatauri/-/svgtodatauri-0.0.0.tgz", + "integrity": "sha512-fpCtt6yWqnK7WQEi+6d/18vfy0Job7wHFSvfVX/9Qlq/njluKttwNwx4nHDaX1dBX/RZX0q6ZApgJWKf0q05qw==" + }, "symbol-observable": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", diff --git a/package.json b/package.json index 25c9984e..3dbb8353 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "redux-thunk": "^2.3.0", "snapsvg": "^0.5.1", "store2": "^2.7.0", + "svgtodatauri": "0.0.0", "tabulator-tables": "^4.1.3", "three": "^0.100.0", "three-orbitcontrols": "^2.99.1", diff --git a/scraper/client/actions.js b/scraper/client/actions.js index 4e1fd620..8a1f13a6 100644 --- a/scraper/client/actions.js +++ b/scraper/client/actions.js @@ -30,3 +30,13 @@ export const getAddress = sha256 => dispatch => { export const postAddress = data => dispatch => { api(dispatch, post, 'address', '/api/address/add', data) } + +export const getVerification = sha256 => dispatch => { + api(dispatch, get, 'address', '/api/verify/' + sha256, {}) +} + +export const postVerification = data => dispatch => { + api(dispatch, post, 'address', '/api/verify/add', data) +} + + diff --git a/scraper/client/app.js b/scraper/client/app.js index 7600fe52..ab7e2ca7 100644 --- a/scraper/client/app.js +++ b/scraper/client/app.js @@ -21,6 +21,7 @@ export default class App extends Component { <Route exact path="/paper/:key/info/" component={Paper.Info} /> <Route exact path="/paper/:key/random/" component={Paper.Random} /> <Route exact path="/paper/:key/address/:sha256" component={Paper.Address} /> + <Route exact path="/paper/:key/verify/:sha256" component={Paper.Verify} /> </Switch> </div> </div> diff --git a/scraper/client/common/header.component.js b/scraper/client/common/header.component.js index 4da5af8a..2f084979 100644 --- a/scraper/client/common/header.component.js +++ b/scraper/client/common/header.component.js @@ -10,7 +10,6 @@ import * as actions from '../actions' class Header extends Component { componentDidMount() { this.props.actions.getPapers() - this.props.actions.getInstitutions() } pickPaper(e) { diff --git a/scraper/client/paper/paper.address.js b/scraper/client/paper/paper.address.js index 267fe16b..90e28139 100644 --- a/scraper/client/paper/paper.address.js +++ b/scraper/client/paper/paper.address.js @@ -28,6 +28,7 @@ class PaperAddress extends Component { componentDidMount() { const { sha256 } = this.props.match.params + this.props.actions.getInstitutions() this.props.actions.getAddress(sha256) const citationState = this.getCitationState(sha256) console.log('DID MOUNT') diff --git a/scraper/client/paper/paper.verify.js b/scraper/client/paper/paper.verify.js new file mode 100644 index 00000000..cfce8f28 --- /dev/null +++ b/scraper/client/paper/paper.verify.js @@ -0,0 +1,205 @@ +import React, { Component } from 'react' +import { bindActionCreators } from 'redux' +import { connect } from 'react-redux' + +import * as actions from '../actions' + +import { history } from '../store' + +import { Loader, Autocomplete } from '../common' + +const initialState = { + citation: null, + verify: '', + verified_by: '', + notes: '', + pdf_index: 0, +} +class PaperVerify extends Component { + state = { + ...initialState + } + + componentDidMount() { + const { sha256 } = this.props.match.params + this.props.actions.getInstitutions() + this.props.actions.getVerification(sha256) + const citationState = this.getVerificationState(sha256) + console.log('DID MOUNT') + this.setState(citationState) + } + + componentDidUpdate(oldProps) { + const { sha256 } = this.props.match.params + const { address } = this.props.api + const { sha256: oldSha256 } = oldProps.match.params + const { address: oldAddress } = oldProps.api + const oldPaper = oldAddress ? oldAddress.paper : null + const paper = address ? address.paper : null + + if (oldSha256 && sha256 !== oldSha256) { + console.log('update address') + this.props.actions.getVerification(sha256) + const citationState = this.getCitationState(sha256) + this.setState({ + ...initialState, + ...citationState, + }) + } else if (address && !address.loading && address.paper && (!oldPaper || oldPaper !== address.paper)) { + if (paper.error) { + console.log('USING PAPER ADDRESS') + const citationState = this.getCitationState(sha256) + this.setState({ + ...initialState, + ...citationState, + }) + } else { + // console.log(paper) + console.log('GOT CUSTOM ADDRESS') + const citationState = this.getCitationState(sha256) + this.setState({ + ...citationState, + verified: paper.verified, + verified_by: paper.verified_by, + }) + } + } else if (oldProps.api.unknownCitations !== this.props.api.unknownCitations) { + const citationState = this.getCitationState(sha256) + this.setState(citationState) + } + } + + getCitationState(sha256) { + const { paperInfo, unknownCitations } = this.props.api + let citation = (unknownCitations.citations || []).find(f => f.id === sha256) + if (!citation) { + citation = (paperInfo.citations || []).find(f => f.id === sha256) + } + // console.log(sha256, citation) + let state = { citation } + let addresses = citation ? citation.addresses : [] + if (addresses) { + addresses.forEach((address, i) => { + state['institution_' + (i + 1)] = address.address + }) + } + return state + } + + save() { + console.log(this.state) + this.props.actions.postVerification({ + paper_id: this.state.citation.id, + title: this.state.citation.title, + verified: this.state.verified, + verified_by: this.state.verified_by, + notes: this.state.notes, + }) + this.next(false) + } + + next() { + const { key } = this.props.api.paperInfo.dataset + const { unknownCitations } = this.props.api + let citationIndex = (unknownCitations.citations || []) + .findIndex(f => f.id === this.state.citation.id) + if (citationIndex === -1) { + history.push('/paper/' + key + '/info/') + } else { + citationIndex += 1 + if (citationIndex >= unknownCitations.length) { + history.push('/paper/' + key + '/info/') + } else { + let nextId = unknownCitations.citations[citationIndex].id + history.push('/paper/' + key + '/address/' + nextId) + } + } + } + + render() { + let { paperInfo, unknownCitations } = this.props.api + if (paperInfo.loading || unknownCitations.loading) return <Loader /> + console.log(this.state) + const { citation } = this.state + if (!citation) { + return <div>Citation not found in this paper</div> + } + console.log(citation) + return ( + <div className='form'> + <h3>{citation.title}</h3> + <div className='gray'> + {citation.id} + {' | PDFs: '} + {citation.pdf.map((pdf,i) => { + const domain = pdf.replace('www.','').split('/').slice(2,3)[0] || 'unknown' + return ( + <a + key={i} + onClick={() => this.setState({ pdf_index: i })} + className={i === this.state.pdf_index ? 'selected pdfLink' : 'pdfLink'} + > + {'[' + domain + '] '} + </a> + ) + })} + </div> + + <div className='param'> + <input + className='vetting' + type='checkbox' + value={this.state.verified} + onChange={e => this.setState({ + verified: e.target.value, + })} + /> + </div> + + <div className='param'> + <input + type='text' + className='verified_by' + value={this.state.verified_by} + placeholder='Verified by' + onChange={e => this.setState({ notes: e.target.value })} + /> + </div> + + <div className='param'> + <input + type='text' + className='notes' + value={this.state.notes} + placeholder='Notes' + onChange={e => this.setState({ notes: e.target.value })} + /> + </div> + + <div className='param'> + <button + className='btn btn-primary' + onClick={this.save.bind(this)} + ref={ref => this.button = ref} + >Save Verification</button> + + <button + className='btn' + onClick={this.next.bind(this)} + >{'Next >'}</button> + </div> + + <iframe className='pdfViewer' src={citation.pdf[this.state.pdf_index]} /> + </div> + ) + } +} + +const mapStateToProps = state => ({ + api: state.api, +}) +const mapDispatchToProps = dispatch => ({ + actions: bindActionCreators({ ...actions }, dispatch), +}) + +export default connect(mapStateToProps, mapDispatchToProps)(PaperVerify) diff --git a/scraper/client/search/browse.component.js b/scraper/client/search/browse.component.js deleted file mode 100644 index e9ddb04e..00000000 --- a/scraper/client/search/browse.component.js +++ /dev/null @@ -1,77 +0,0 @@ -import React, { Component } from 'react' -import { Link, withRouter } from 'react-router-dom' -import { bindActionCreators } from 'redux' -import { connect } from 'react-redux' - -import { Loader, Keyframes, Video } from '../common' -// import { Coco } from '../metadata' -import * as searchActions from './search.actions' -import * as metadataActions from '../metadata/metadata.actions' -import SearchMeta from './search.meta' - -class Browse extends Component { - componentDidMount() { - this.browse() - } - - componentDidUpdate(prevProps) { - if (prevProps.match.params !== this.props.match.params) { - this.browse() - } - } - - browse() { - const { hash } = this.props.match.params - if (hash) { - this.props.searchActions.browse(hash) - } - if (hash) { - this.props.metadataActions.fetchMetadata(hash) - } - } - - render() { - const { browse, options } = this.props - console.log('browse', browse) - - if (!browse || browse.reset || browse.loading) { - return <div className="browseComponent column"><h3>Loading keyframes...</h3><Loader /></div> - } - return ( - <div className="browseComponent column"> - <h3>Video Preview</h3> - <Video size={'md'} /> - <SearchMeta query={browse} sugarcube /> - <div className='row buttons'> - <Link - to={'/metadata/' + browse.hash} - className='btn' - > - View Full Metadata - </Link> - </div> - <h3>Keyframes</h3> - <Keyframes - frames={browse.frames} - showHash - showTimestamp - showSearchButton - showSaveButton - /> - </div> - ) - } -} - -const mapStateToProps = state => ({ - browse: state.search.browse, - options: state.search.options, - metadata: state.metadata, -}) - -const mapDispatchToProps = dispatch => ({ - searchActions: bindActionCreators({ ...searchActions }, dispatch), - metadataActions: bindActionCreators({ ...metadataActions }, dispatch), -}) - -export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Browse)) diff --git a/scraper/client/search/index.js b/scraper/client/search/index.js deleted file mode 100644 index 99c2b74b..00000000 --- a/scraper/client/search/index.js +++ /dev/null @@ -1,17 +0,0 @@ -import Menu from './search.menu' -import Container from './search.container' -import Meta from './search.meta' -import Query from './search.query' -import Results from './search.results' -import Browse from './browse.component' - -import './search.css' - -export { - Menu, - Container, - Meta, - Query, - Results, - Browse, -} diff --git a/scraper/client/search/panicButton.component.js b/scraper/client/search/panicButton.component.js deleted file mode 100644 index a12c817b..00000000 --- a/scraper/client/search/panicButton.component.js +++ /dev/null @@ -1,49 +0,0 @@ -import React, { Component } from 'react' -import { withRouter } from 'react-router-dom' -import { bindActionCreators } from 'redux' -import { connect } from 'react-redux' - -import * as actions from './search.actions' - -class PanicButton extends Component { - constructor() { - super() - this.keydown = this.keydown.bind(this) - } - - componentDidMount() { - document.addEventListener('keydown', this.keydown) - } - - componentWillUnmount() { - document.removeEventListener('keydown', this.keydown) - } - - keydown(e) { - if (e.keyCode === 27) { - this.panic() - } - } - - panic() { - this.props.actions.panic() - this.props.history.push('/search/') - } - - render() { - return ( - <button className='btn panic' onClick={() => this.panic()}> - <span>⚠</span> Panic - </button> - ) - } -} - -const mapStateToProps = state => ({ -}) - -const mapDispatchToProps = dispatch => ({ - actions: bindActionCreators({ panic: actions.panic }, dispatch) -}) - -export default withRouter(connect(mapStateToProps, mapDispatchToProps)(PanicButton)) diff --git a/scraper/client/search/search.actions.js b/scraper/client/search/search.actions.js deleted file mode 100644 index 95cea433..00000000 --- a/scraper/client/search/search.actions.js +++ /dev/null @@ -1,162 +0,0 @@ -// import fetchJsonp from 'fetch-jsonp' -import * as types from '../types' -// import { hashPath } from '../util' -import { store, history } from '../store' -import { post, pad, verify, preloadImage } from '../util' -import querystring from 'query-string' - -// urls - -const url = { - upload: () => process.env.API_HOST + '/search/api/upload', - search: () => process.env.API_HOST + '/search/api/fetch', - searchByVerifiedFrame: (verified, hash, frame) => process.env.API_HOST + '/search/api/search/' + verified + '/' + hash + '/' + pad(frame, 6), - searchByFrame: (hash, frame) => process.env.API_HOST + '/search/api/search/' + hash + '/' + pad(frame, 6), - browse: hash => process.env.API_HOST + '/search/api/list/' + hash, - random: () => process.env.API_HOST + '/search/api/random', - check: () => process.env.API_HOST + '/api/images/import/search', -} -export const publicUrl = { - browse: hash => '/search/browse/' + hash, - searchByVerifiedFrame: (verified, hash, frame) => '/search/keyframe/' + verify(verified) + '/' + hash + '/' + pad(frame, 6), - searchByFrame: (hash, frame) => '/search/keyframe/' + hash + '/' + pad(frame, 6), - review: () => '/search/review/' -} - -// standard loading events - -const loading = (tag, offset) => ({ - type: types.search.loading, - tag, - offset -}) -const loaded = (tag, data, offset = 0) => ({ - type: types.search.loaded, - tag, - data, - offset -}) -const error = (tag, err) => ({ - type: types.search.error, - tag, - err -}) - -// search UI functions - -export const panic = () => dispatch => { - history.push('/search/') - dispatch({ type: types.search.panic }) -} -export const updateOptions = opt => dispatch => { - dispatch({ type: types.search.update_options, opt }) -} - -// API functions - -export const upload = (file, query) => dispatch => { - const { options } = store.getState().search - const tag = 'query' - const fd = new FormData() - fd.append('query_img', file) - fd.append('limit', options.perPage) - if (!query) { - dispatch(loading(tag)) - } - post(url.upload(), fd) - .then(data => { - if (query) { - const { timing } = data.query - data.query = { - ...query, - timing, - } - let qs = {} - if (data.query.crop) { - let { x, y, w, h } = data.query.crop - qs.crop = [x, y, w, h].map(n => parseInt(n, 10)).join(',') - } - if (query.url && !query.hash) { - qs.url = query.url - } - // history.push(window.location.pathname + '#' + querystring.stringify(qs)) - // window.location.hash = querystring.stringify(qs) - } else if (data.query.url && !window.location.search.match(data.query.url)) { - history.push('/search/?url=' + data.query.url) - } - dispatch(loaded(tag, data)) - }) - .catch(err => dispatch(error(tag, err))) -} -export const searchByVerifiedFrame = (verified, hash, frame, offset = 0) => dispatch => { - const { options } = store.getState().search - const tag = 'query' - dispatch(loading(tag, offset)) - const qs = querystring.stringify({ limit: options.perPage, offset }) - preloadImage({ verified, hash, frame }) - fetch(url.searchByVerifiedFrame(verified, hash, frame) + '?' + qs, { - method: 'GET', - mode: 'cors', - }) - .then(data => data.json()) - .then(data => dispatch(loaded(tag, data, offset))) - .catch(err => dispatch(error(tag, err))) -} -export const searchByFrame = (hash, frame, offset = 0) => dispatch => { - const { options } = store.getState().search - const tag = 'query' - dispatch(loading(tag, offset)) - const qs = querystring.stringify({ limit: options.perPage, offset }) - preloadImage({ verified: false, hash, frame }) - fetch(url.searchByFrame(hash, frame) + '?' + qs, { - method: 'GET', - mode: 'cors', - }) - .then(data => data.json()) - .then(data => dispatch(loaded(tag, data, offset))) - .catch(err => dispatch(error(tag, err))) -} -export const search = (uri, offset = 0) => dispatch => { - const { options } = store.getState().search - const tag = 'query' - dispatch(loading(tag, offset)) - const qs = querystring.stringify({ url: uri, limit: options.perPage, offset }) - if (uri.indexOf('static') === 0) { - preloadImage({ uri }) - } - fetch(url.search(uri) + '?' + qs, { - method: 'GET', - mode: 'cors', - }) - .then(data => data.json()) - .then(data => dispatch(loaded(tag, data, offset))) - .catch(err => dispatch(error(tag, err))) -} -export const browse = hash => dispatch => { - const tag = 'browse' - dispatch(loading(tag)) - fetch(url[tag](hash), { - method: 'GET', - mode: 'cors', - }) - .then(data => data.json()) - .then(data => dispatch(loaded(tag, data))) - .catch(err => dispatch(error(tag, err))) -} -export const random = () => dispatch => { - const { options } = store.getState().search - const qs = querystring.stringify({ limit: options.perPage }) - const tag = 'query' - dispatch(loading(tag)) - fetch(url.random() + '?' + qs, { - method: 'GET', - mode: 'cors', - }) - .then(data => data.json()) - .then(data => { - dispatch(loaded(tag, data)) - history.push(publicUrl.searchByVerifiedFrame(data.query.verified, data.query.hash, data.query.frame)) - // window.history.pushState(null, 'VSearch: Results', publicUrl.searchByVerifiedFrame(data.query.verified, data.query.hash, data.query.frame)) - }) - .catch(err => dispatch(error(tag, err))) -} diff --git a/scraper/client/search/search.container.js b/scraper/client/search/search.container.js deleted file mode 100644 index 965d7c8e..00000000 --- a/scraper/client/search/search.container.js +++ /dev/null @@ -1,101 +0,0 @@ -import React, { Component } from 'react' -import { withRouter } from 'react-router-dom' -import { bindActionCreators } from 'redux' -import { connect } from 'react-redux' -import * as querystring from 'querystring' - -import * as searchActions from './search.actions' -import * as metadataActions from '../metadata/metadata.actions' - -import SearchQuery from './search.query' -import SearchResults from './search.results' -import SearchSafety from './search.safety' - -class SearchContainer extends Component { - componentDidMount() { - const qs = querystring.parse(this.props.location.search.substr(1)) - if (qs && qs.url) { - this.props.searchActions.search(qs.url) - } else { - this.searchByHash() - } - } - - componentDidUpdate(prevProps) { - if (prevProps.match.params !== this.props.match.params && JSON.stringify(this.props.match.params) !== JSON.stringify(prevProps.match.params)) { - this.searchByHash() - } - // const qsOld = querystring.parse(prevProps.location.search.substr(1)) - // const qsNew = querystring.parse(this.props.location.search.substr(1)) - // if (qsOld && qsNew && qsNew.url && qsNew.url !== qsOld.url) { - // this.props.actions.search(qsNew.url) - // } - } - - searchByHash(offset = 0) { - const { verified, hash, frame } = this.props.match.params - if (verified && hash && frame) { - this.props.searchActions.searchByVerifiedFrame(verified, hash, frame, offset) - } else if (hash && frame) { - this.props.searchActions.searchByFrame(hash, frame, offset) - } - if (hash && !offset) { - this.props.metadataActions.fetchMetadata(hash) - } - } - - searchByOffset() { - const offset = this.props.query.results.length - const qs = querystring.parse(this.props.location.search.substr(1)) - if (qs && qs.url) { - this.props.searchActions.search(qs.url, offset) - } - else { - this.searchByHash(offset) - } - } - - render() { - const { query, results, loadingMore } = this.props.query - const options = this.props.options - // console.log('search container', query, results, loadingMore) - let showLoadMore = true - if (!query || query.reset || query.loading || !results || !results.length) { - showLoadMore = false - } - let isWide = (results && results.length > Math.min(options.perPage, 30)) - let isMoreLoaded = (results && results.length > options.perPage) - return ( - <div className='searchContainer'> - <SearchQuery /> - <SearchResults /> - {showLoadMore - ? !loadingMore - ? <button - onClick={() => this.searchByOffset()} - className={isWide ? 'btn loadMore wide' : 'btn loadMore'} - > - Load more - </button> - : <div className='loadingMore'>{'Loading more results...'}</div> - : <div> - </div> - } - {!isMoreLoaded && <SearchSafety />} - </div> - ) - } -} - -const mapStateToProps = state => ({ - query: state.search.query, - options: state.search.options, - metadata: state.metadata, -}) - -const mapDispatchToProps = dispatch => ({ - searchActions: bindActionCreators({ ...searchActions }, dispatch), - metadataActions: bindActionCreators({ ...metadataActions }, dispatch), -}) - -export default withRouter(connect(mapStateToProps, mapDispatchToProps)(SearchContainer)) diff --git a/scraper/client/search/search.css b/scraper/client/search/search.css deleted file mode 100644 index 280e53fe..00000000 --- a/scraper/client/search/search.css +++ /dev/null @@ -1,230 +0,0 @@ -.btn span { - font-size: large; -} -.row { - display: flex; - flex-direction: row; -} -.column { - display: flex; - flex-direction: column; -} - -.searchContainer h3 { - padding: 0; - margin-top: 0; - margin-bottom: 5px; - margin-left: 3px; -} -.searchContainer h4 { - margin-left: 0; - width: 100%; -} -.searchContainer .subtitle { - display: block; - margin-left: 3px; - margin-bottom: 10px; -} -.searchForm { - display: flex; - justify-content: space-between; - align-items: center; - width: 100%; - padding: 20px; - background: #eee; -} -.searchForm .row { - align-items: center; -} - -.searchMeta { - display: flex; - flex-direction: column; - font-size: 14px; - line-height: 18px; - padding: 0; -} -.searchMeta span { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - max-width: calc(100vw - 23px - 640px - 30px); -} - -.keyframe .thumbnail { - position: relative; - cursor: pointer; -} -.keyframe .searchButtons { - position: absolute; - bottom: 0; left: 0; - padding: 0 5px 15px 5px; - width: 100%; - text-align: center; - opacity: 0; - transition: all 0.2s; -} -.desktop .keyframe .thumbnail:hover .searchButtons, -.mobile .keyframe .searchButtons { - opacity: 1; -} -.keyframe .searchButtons .btn { - margin-right: 0; - height: auto; - padding: 0.15rem 0.3rem; -} -.keyframe a { - text-decoration: none; -} - -.body > div.searchForm { - padding-bottom: 20px; -} -.upload { - position: relative; - cursor: pointer; -} -.upload .btn { - pointer-events: none; - cursor: pointer; -} -.upload input { - position: absolute; - top: 0; left: 0; - width: 100%; height: 100%; - opacity: 0; - cursor: pointer; -} - -.reviewSaved, -.browseComponent, -.searchQuery { - margin: 0px 10px; - padding: 13px; -} -.searchQuery img { - cursor: crosshair; - user-select: none; - max-width: 640px; - max-height: 480px; -} -.searchContainer .searchQuery h3 { - margin-left: 0; - margin-bottom: 10px; -} - -.searchBox { - min-width: 640px; - margin: 0 10px 0 0; - background-color: #eee; - position: relative; -} -.searchBox img { - display: block; -} -.searchBox .box { - position: absolute; - cursor: crosshair; - border: 1px solid #11f; - background-color: rgba(16,16,255,0.1); -} - -.searchResults { - margin: 0 20px 20px 20px; - display: flex; - flex-direction: row; - flex-wrap: wrap; -} -.searchResultsHeading { - width: 100%; -} -.searchOptions .row { - font-size: 12px; - margin-left: 10px; -} -.searchOptions input { - font-size: 12px; - margin-right: 5px; - font-family: Helvetica, sans-serif; -} -.searchOptions input[type=text], -.searchOptions input[type=number] { - width: 30px; - text-align: right; -} -.keyframeGroup { - max-width: 650px; - display: flex; - flex-direction: row; - flex-wrap: wrap; - align-items: flex-start; - align-content: flex-start; - justify-content: flex-start; -} -.keyframeGroup h4 a { - color: #888; - text-decoration: none -} -.keyframeGroup h4 a:hover { - text-decoration: underline -} - -/* load more button that gets bigger */ - -.loadMore { - width: 400px; - margin: 20px; - height: 40px; - transition: all; -} -.loadMore.wide { - width: calc(100% - 40px); - margin: 20px; - height: 100px; -} -.loadingMore { - margin: 20px 20px 200px 20px; -} - -/* health and safety warning */ - -.safety div { - display: inline-block; - margin: 20px 20px; - padding: 10px; - background: #fff8e8; - color: #111; - box-shadow: 0 1px 2px rgba(0,0,0,0.2); - font-size: 13px; - line-height: 1.4; -} -.safety ul { - margin: 0; - padding: 0 21px; -} -.safety li { - padding: 1px 0 0 0; -} -.safety h4 { - margin-top: 5px; -} - -/* browser section */ - -.browseComponent h3 { - margin-bottom: 10px; -} -.browseComponent .buttons { - margin-top: 10px; -} - -/* disable twiddle button on input[type=number] */ - -input::-webkit-outer-spin-button, -input::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; -} -input[type='number'] { - -moz-appearance:textfield; -} diff --git a/scraper/client/search/search.menu.js b/scraper/client/search/search.menu.js deleted file mode 100644 index f5f9423e..00000000 --- a/scraper/client/search/search.menu.js +++ /dev/null @@ -1,96 +0,0 @@ -import React, { Component } from 'react' -import { Link } from 'react-router-dom' -import { bindActionCreators } from 'redux' -import { connect } from 'react-redux' - -import * as actions from './search.actions' -import PanicButton from './panicButton.component' - -class SearchMenu extends Component { - upload(e) { - const files = e.dataTransfer ? e.dataTransfer.files : e.target.files - let i - let file - for (i = 0; i < files.length; i++) { - file = files[i] - if (file && file.type.match('image.*')) break - } - if (!file) return - this.props.actions.upload(file) - } - - random() { - this.props.actions.random() - } - - render() { - const { savedCount, options } = this.props - return ( - <div className="searchForm row"> - <div className='row'> - <div className='upload'> - <button className='btn'><span>⤴</span> Search by Upload</button> - <input - type="file" - name="img" - accept="image/*" - onChange={this.upload.bind(this)} - required - /> - </div> - <button className='btn random' onClick={this.random.bind(this)}><span>♘</span> Random</button> - <PanicButton /> - <Link to={actions.publicUrl.review()}> - <button className='btn btn-primary'><span>⇪</span>{ - ' ' + savedCount + ' Saved Image' + (savedCount === 1 ? '' : 's') - }</button> - </Link> - </div> - - <div className='row searchOptions'> - <select - className='form-select' - onChange={e => this.props.actions.updateOptions({ thumbnailSize: e.target.value })} - value={options.thumbnailSize} - > - <option value='th'>Thumbnail</option> - <option value='sm'>Small</option> - <option value='md'>Medium</option> - <option value='lg'>Large</option> - </select> - <label className='row'> - <input - type='checkbox' - checked={options.groupByHash} - onChange={e => this.props.actions.updateOptions({ groupByHash: e.target.checked })} - /> - {' Group by hash'} - </label> - <label className='row'> - <input - type='number' - value={options.perPage} - className='perPage' - min={1} - max={100} - onChange={e => this.props.actions.updateOptions({ perPage: e.target.value })} - onBlur={() => window.location.reload()} - /> - {' per page'} - </label> - </div> - </div> - ) - } -} - -const mapStateToProps = state => ({ - options: state.search.options, - savedCount: state.review.count, -}) - -const mapDispatchToProps = dispatch => ({ - actions: bindActionCreators({ ...actions }, dispatch) -}) - -export default connect(mapStateToProps, mapDispatchToProps)(SearchMenu) diff --git a/scraper/client/search/search.meta.js b/scraper/client/search/search.meta.js deleted file mode 100644 index b4eaeaad..00000000 --- a/scraper/client/search/search.meta.js +++ /dev/null @@ -1,68 +0,0 @@ -import React, { Component } from 'react' -import { Link } from 'react-router-dom' -import { bindActionCreators } from 'redux' -import { connect } from 'react-redux' -import { format } from 'date-fns' - -import { timestamp } from '../util' -import * as searchActions from './search.actions' - -class SearchMeta extends Component { - render() { - const { query, metadata, sugarcube } = this.props - if (!query || !metadata || !metadata.mediainfo || metadata.metadata === 'loading') { - return <div className='searchMeta'></div> - } - const sugarcubeId = metadata.mediainfo.sugarcube_id - const { video } = metadata.mediainfo.metadata.mediainfo - const { x, y, w, h } = query.crop || {} - return ( - <div className='searchMeta'> - {'verified' in query && - <span className={query.verified ? 'verified' : 'unverified'}> - {query.verified ? 'verified' : 'unverified'} - </span> - } - {query.hash && - <span> - {'sha256: '} - <Link className="sha256" to={searchActions.publicUrl.browse(query.hash)}>{query.hash}</Link> - </span> - } - {query.frame && - <span> - {'Frame: '} - {timestamp(query.frame, video.frame_rate)} - {' / '} - {timestamp(video.duration / 1000, 1)} - </span> - } - {query.crop && - <span> - {'Crop: '}{parseInt(w, 10) + 'x' + parseInt(h, 10) + ' @ (' + parseInt(x, 10) + ', ' + parseInt(y, 10) + ')'} - </span> - } - {!!(video && video.encoded_date) && - <span> - {'Date: '}{format(new Date(video.encoded_date), "DD-MMM-YYYY")} - </span> - } - {!!(sugarcube && sugarcubeId) && - <span> - sugarcube: {sugarcubeId} - </span> - } - </div> - ) - } -} - -const mapStateToProps = state => ({ - metadata: state.metadata, -}) - -const mapDispatchToProps = dispatch => ({ - actions: bindActionCreators({ ...searchActions }, dispatch), -}) - -export default connect(mapStateToProps, mapDispatchToProps)(SearchMeta) diff --git a/scraper/client/search/search.query.js b/scraper/client/search/search.query.js deleted file mode 100644 index 276f1943..00000000 --- a/scraper/client/search/search.query.js +++ /dev/null @@ -1,227 +0,0 @@ -import React, { Component } from 'react' -import { Link } from 'react-router-dom' -import { bindActionCreators } from 'redux' -import { connect } from 'react-redux' -import toBlob from 'data-uri-to-blob' - -import { clamp, px } from '../util' -import { Loader } from '../common' - -import * as searchActions from './search.actions' -import SearchMeta from './search.meta' - -const defaultState = { - dragging: false, - draggingBox: false, - bounds: null, - mouseX: 0, - mouseY: 0, - box: { - x: 0, - y: 0, - w: 0, - h: 0, - } -} - -class SearchQuery extends Component { - state = { - ...defaultState - } - - constructor() { - super() - this.handleMouseDown = this.handleMouseDown.bind(this) - this.handleMouseDownOnBox = this.handleMouseDownOnBox.bind(this) - this.handleMouseMove = this.handleMouseMove.bind(this) - this.handleMouseUp = this.handleMouseUp.bind(this) - } - - componentDidMount() { - document.body.addEventListener('mousemove', this.handleMouseMove) - document.body.addEventListener('mouseup', this.handleMouseUp) - } - - componentDidUpdate(prevProps) { - // console.log(this.props.query.query, !prevProps.query.query) - if (this.state.bounds && (!this.props.query.query || !prevProps.query.query || this.props.query.query.url !== prevProps.query.query.url)) { - this.setState({ ...defaultState }) - } - } - - componentWillUnmount() { - document.body.removeEventListener('mousemove', this.handleMouseMove) - document.body.removeEventListener('mouseup', this.handleMouseUp) - } - - handleMouseDown(e) { - e.preventDefault() - const bounds = this.imgRef.getBoundingClientRect() - const mouseX = e.pageX - const mouseY = e.pageY - const x = mouseX - bounds.left - const y = mouseY - bounds.top - const w = 1 - const h = 1 - this.setState({ - dragging: true, - bounds, - mouseX, - mouseY, - box: { - x, y, w, h, - } - }) - } - - handleMouseDownOnBox(e) { - const bounds = this.imgRef.getBoundingClientRect() - const mouseX = e.pageX - const mouseY = e.pageY - this.setState({ - draggingBox: true, - bounds, - mouseX, - mouseY, - initialBox: { - ...this.state.box - }, - box: { - ...this.state.box - } - }) - } - - handleMouseMove(e) { - const { - dragging, draggingBox, - bounds, mouseX, mouseY, initialBox, box - } = this.state - if (dragging) { - e.preventDefault() - let { x, y } = box - let w = clamp(e.pageX - mouseX, 0, bounds.width - x) - let h = clamp(e.pageY - mouseY, 0, bounds.height - y) - this.setState({ - box: { - x, y, w, h, - } - }) - } else if (draggingBox) { - e.preventDefault() - let { x, y, w, h } = initialBox - let dx = (e.pageX - mouseX) - let dy = (e.pageY - mouseY) - this.setState({ - box: { - x: clamp(x + dx, 0, bounds.width - w), - y: clamp(y + dy, 0, bounds.height - h), - w, - h, - } - }) - } - } - - handleMouseUp(e) { - const { actions } = this.props - const { dragging, draggingBox, bounds, box } = this.state - if (!dragging && !draggingBox) return - e.preventDefault() - const { x, y, w, h } = box - // console.log(x, y, w, h) - const img = this.imgRef - const canvas = document.createElement('canvas') - const ctx = canvas.getContext('2d') - const ratio = img.naturalWidth / bounds.width - canvas.width = w * ratio - canvas.height = h * ratio - if (w < 10 || h < 10) { - this.setState({ dragging: false, draggingBox: false, box: { x: 0, y: 0, w: 0, h: 0 } }) - return - } - this.setState({ dragging: false, draggingBox: false }) - // query_div.appendChild(canvas) - const newImage = new Image() - let loaded = false - newImage.onload = () => { - if (loaded) return - loaded = true - newImage.onload = null - ctx.drawImage( - newImage, - Math.round(x * ratio), - Math.round(y * ratio), - Math.round(w * ratio), - Math.round(h * ratio), - 0, 0, canvas.width, canvas.height - ) - const blob = toBlob(canvas.toDataURL('image/jpeg', 0.9)) - actions.upload(blob, { - ...this.props.query.query, - crop: { - x, y, w, h, - } - }) - } - // console.log(img.src) - newImage.crossOrigin = 'anonymous' - newImage.src = img.src - if (newImage.complete) { - newImage.onload() - } - } - - render() { - const { query } = this.props.query - const { box } = this.state - const { x, y, w, h } = box - if (!query) return null - if (query.loading) { - return <div className="searchQuery column"><h2>Loading results...</h2><Loader /></div> - } - let { url } = query - if (url && url.indexOf('static') === 0) { - url = '/search/' + url - } - return ( - <div className="searchQuery row"> - <div className="searchBox"> - <img - src={url} - ref={ref => this.imgRef = ref} - onMouseDown={this.handleMouseDown} - crossOrigin='anonymous' - /> - {!!w && - <div - className="box" - style={{ - left: x, - top: y, - width: w, - height: h, - }} - onMouseDown={this.handleMouseDownOnBox} - /> - } - </div> - <div> - <h3>Your Query</h3> - <SearchMeta query={query} /> - </div> - </div> - ) - } -} - -const mapStateToProps = state => ({ - query: state.search.query, - options: state.search.options, -}) - -const mapDispatchToProps = dispatch => ({ - actions: bindActionCreators({ ...searchActions }, dispatch), -}) - -export default connect(mapStateToProps, mapDispatchToProps)(SearchQuery) diff --git a/scraper/client/search/search.reducer.js b/scraper/client/search/search.reducer.js deleted file mode 100644 index b9de60bd..00000000 --- a/scraper/client/search/search.reducer.js +++ /dev/null @@ -1,84 +0,0 @@ -import * as types from '../types' -import session from '../session' - -const initialState = () => ({ - query: { reset: true }, - browse: { reset: true }, - options: { - thumbnailSize: session('thumbnailSize') || 'th', - perPage: parseInt(session('perPage'), 10) || 50, - groupByHash: session('groupByHash'), - } -}) - -const loadingState = { - query: { - query: { loading: true }, - results: [] - }, - loading: { - loading: true - } -} - -export default function searchReducer(state = initialState(), action) { - // console.log(action.type, action) - switch (action.type) { - case types.search.loading: - if (action.tag === 'query' && action.offset) { - return { - ...state, - query: { - ...state.query, - loadingMore: true, - } - } - } - return { - ...state, - [action.tag]: loadingState[action.tag] || loadingState.loading, - } - - case types.search.loaded: - if (action.tag === 'query' && action.offset) { - return { - ...state, - query: { - query: action.data.query, - results: [ - ...state.query.results, - ...action.data.results, - ], - loadingMore: false, - } - } - } - return { - ...state, - [action.tag]: action.data, - } - - case types.search.error: - return { - ...state, - [action.tag]: { error: action.err }, - } - - case types.search.panic: - return { - ...initialState(), - } - - case types.search.update_options: - session.setAll(action.opt) - return { - ...state, - options: { - ...action.opt, - } - } - - default: - return state - } -} diff --git a/scraper/client/search/search.results.js b/scraper/client/search/search.results.js deleted file mode 100644 index 8b9e0c5e..00000000 --- a/scraper/client/search/search.results.js +++ /dev/null @@ -1,49 +0,0 @@ -import React, { Component } from 'react' -import { Link, withRouter } from 'react-router-dom' -import { bindActionCreators } from 'redux' -import { connect } from 'react-redux' -import * as querystring from 'querystring' - -import { Keyframes } from '../common' -import * as searchActions from './search.actions' - -function SearchResults({ query, results, options }) { - if (!query || query.reset || query.loading || !results) { - return <div></div> - } - if (!query.loading && !results.length) { - return <div className='searchResults'><h3>No results</h3></div> - } - return ( - <div className="searchResults"> - <div className='searchResultsHeading row'> - <div className='column'> - <h3>Search Results</h3> - <small className="subtitle"> - {'Searched 10,523,176 frames from 576,234 videos (took '}{query.timing.toFixed(2)}{' ms)'} - </small> - </div> - </div> - <Keyframes - frames={results} - showHash - showTimestamp={options.groupByHash} - showSearchButton - showSaveButton - groupByHash={options.groupByHash} - /> - </div> - ) -} - -const mapStateToProps = state => ({ - query: state.search.query.query, - results: state.search.query.results, - options: state.search.options, -}) - -const mapDispatchToProps = dispatch => ({ - searchActions: bindActionCreators({ ...searchActions }, dispatch), -}) - -export default withRouter(connect(mapStateToProps, mapDispatchToProps)(SearchResults)) diff --git a/scraper/client/search/search.safety.js b/scraper/client/search/search.safety.js deleted file mode 100644 index b4f92664..00000000 --- a/scraper/client/search/search.safety.js +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react' - -export default function SearchWarning() { - return ( - <div className='safety'> - <div> - <h4>Safety Tips</h4> - <ul> - <li> Look away if you see something traumatic </li> - <li> Hit <tt>ESC</tt> to activate panic mode (hides all images) </li> - <li> Use thumbnails to reduce details </li> - <li> Take breaks and refresh yourself with positive imagery </li> - </ul> - </div> - </div> - ) -} diff --git a/scraper/s2-geocode-server.py b/scraper/s2-geocode-server.py index edf768aa..62e01b0d 100644 --- a/scraper/s2-geocode-server.py +++ b/scraper/s2-geocode-server.py @@ -14,6 +14,7 @@ load_dotenv() from util import * locations_worksheet = fetch_worksheet('paper_locations') +verifications_worksheet = fetch_worksheet('verifications') paper_lookup = fetch_google_lookup('citation_lookup') addresses = AddressBook() @@ -89,6 +90,45 @@ def add_address(): 'status': 'ok' }) +@app.route('/api/verify/<sha256>', methods=['GET']) +def find_verification(sha256): + worksheet = fetch_worksheet('verifications') + try: + cell = worksheet.find(sha256) + except: + return jsonify({ + 'error': 'no_match' + }) + if cell and cell.row: + keys = worksheet.row_values(1) + values_list = worksheet.row_values(cell.row) + lookup = {} + for key, value in zip(keys, values_list): + lookup[key] = value + return jsonify({ + 'paper': lookup, + }) + else: + return jsonify({ + 'error': 'no_match' + }) + +@app.route('/api/verify/add', methods=['POST']) +def add_verifications(): + form = request.get_json() + print(form) + # id, title, verified, verified_by, notes + locations_worksheet.append_row([ + form['paper_id'], + form['title'], + form['verified'], + form['verified_by'], + form['notes'], + ]) + return jsonify({ + 'status': 'ok' + }) + if __name__=="__main__": - app.run("0.0.0.0", debug=False) + app.run("0.0.0.0", port=7727, debug=False) diff --git a/site/public/datasets/brainwash/index.html b/site/public/datasets/brainwash/index.html index e5baca7a..55c1b977 100644 --- a/site/public/datasets/brainwash/index.html +++ b/site/public/datasets/brainwash/index.html @@ -43,12 +43,10 @@ </section> <section class="applet_container"> -<!-- <div style="position: absolute;top: 0px;right: -55px;width: 180px;font-size: 14px;">Labeled Faces in the Wild Dataset<br><span class="numc" style="font-size: 11px;">20 citations</span> -</div> --> <div class="applet" data-payload="{"command": "chart"}"></div> </section><section> - <h3>Information Supply Chain</h3> + <h3>Biometric Trade Routes (beta)</h3> <!-- <div class="map-sidebar right-sidebar"> <h3>Legend</h3> @@ -61,7 +59,7 @@ --> <p> To understand how Brainwash Dataset has been used around the world... - affected global research on computer vision, surveillance, defense, and consumer technology, the and where this dataset has been used the locations of each organization that used or referenced the datast + affected global research on computer vision, surveillance, defense, and consumer technology, the and where this dataset has been used the locations of each organization that used or referenced the datast </p> </section> @@ -79,7 +77,7 @@ <section> <p class='subp'> - Standardized paragraph of text about the map. Sed ut perspiciatis, unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam eaque ipsa, quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt, explicabo. + The data is generated by collecting all citations for all original research papers associated with the dataset. Then the PDFs are then converted to text and the organization names are extracted and geocoded. Because of the automated approach to extracting data, actual use of the dataset can not yet be confirmed. This visualization is provided to help locate and confirm usage and will be updated as data noise is reduced. </p> </section><section><p>Add more analysis here</p> </section><section> @@ -95,11 +93,11 @@ <h3>Citations</h3> <p> - The citations used for the geographic visualizations were collected from <a href="https://www.semanticscholar.org">Semantic Scholar</a>, a website which aggregates + Citations were collected from <a href="https://www.semanticscholar.org">Semantic Scholar</a>, a website which aggregates and indexes research papers. Metadata was extracted from these papers, including extracting names of institutions automatically from PDFs, and then the addresses were geocoded. Data is not yet manually verified, and reflects anytime the paper was cited. Some papers may only mention the dataset in passing, while others use it as part of their research methodology. </p> <p> - Add [button/link] to download CSV. Add search input field to filter. Expand number of rows to 10. Reduce URL text to show only the domain (ie https://arxiv.org/pdf/123456 --> arxiv.org) + Add button/link to download CSV </p> <div class="applet" data-payload="{"command": "citations"}"></div> diff --git a/site/public/datasets/cofw/index.html b/site/public/datasets/cofw/index.html index 20138c3c..605a325a 100644 --- a/site/public/datasets/cofw/index.html +++ b/site/public/datasets/cofw/index.html @@ -108,8 +108,6 @@ To increase the number of training images, and since COFW has the exact same la </section> <section class="applet_container"> - <div style="position: absolute;top: 0px;right: -55px;width: 180px;font-size: 14px;">Labeled Faces in the Wild Dataset<br><span class="numc" style="font-size: 11px;">20 citations</span> -</div> <div class="applet" data-payload="{"command": "chart"}"></div> </section><section><p>TODO</p> <h2>- replace graphic</h2> diff --git a/site/public/datasets/index.html b/site/public/datasets/index.html index d9452b11..2a412322 100644 --- a/site/public/datasets/index.html +++ b/site/public/datasets/index.html @@ -66,13 +66,25 @@ <span class='title'>MARS</span> <div class='fields'> <div class='year visible'><span>2016</span></div> - <div class='purpose'><span>Motion Analysis and Re-identification Set</span></div> + <div class='purpose'><span>Motion analysis and person re-identification</span></div> <div class='images'><span>1,191,003 images</span></div> <div class='identities'><span>1,261 </span></div> </div> </div> </a> + <a href="/datasets/viper/" style="background-image: url(https://nyc3.digitaloceanspaces.com/megapixels/v1/datasets/viper/assets/index.jpg)"> + <div class="dataset"> + <span class='title'>VIPeR</span> + <div class='fields'> + <div class='year visible'><span>2007</span></div> + <div class='purpose'><span>pedestrian re-identification</span></div> + <div class='images'><span>1,264 images</span></div> + <div class='identities'><span>632 </span></div> + </div> + </div> + </a> + </div> </section> diff --git a/site/public/datasets/lfw/index.html b/site/public/datasets/lfw/index.html index 8670f909..477673e2 100644 --- a/site/public/datasets/lfw/index.html +++ b/site/public/datasets/lfw/index.html @@ -90,8 +90,6 @@ </section> <section class="applet_container"> - <div style="position: absolute;top: 0px;right: -55px;width: 180px;font-size: 14px;">Labeled Faces in the Wild Dataset<br><span class="numc" style="font-size: 11px;">20 citations</span> -</div> <div class="applet" data-payload="{"command": "chart"}"></div> </section><section> diff --git a/site/public/datasets/viper/index.html b/site/public/datasets/viper/index.html new file mode 100644 index 00000000..cbd866f4 --- /dev/null +++ b/site/public/datasets/viper/index.html @@ -0,0 +1,122 @@ +<!doctype html> +<html> +<head> + <title>MegaPixels</title> + <meta charset="utf-8" /> + <meta name="author" content="Adam Harvey" /> + <meta name="description" content="VIPeR is a person re-identification dataset of images captured at UC Santa Cruz in 2007" /> + <meta name="referrer" content="no-referrer" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" /> + <link rel='stylesheet' href='/assets/css/fonts.css' /> + <link rel='stylesheet' href='/assets/css/tabulator.css' /> + <link rel='stylesheet' href='/assets/css/css.css' /> + <link rel='stylesheet' href='/assets/css/leaflet.css' /> + <link rel='stylesheet' href='/assets/css/applets.css' /> +</head> +<body> + <header> + <a class='slogan' href="/"> + <div class='logo'></div> + <div class='site_name'>MegaPixels</div> + </a> + <div class='links'> + <a href="/datasets/">Datasets</a> + <a href="/about/">About</a> + </div> + </header> + <div class="content content-dataset"> + + <section class='intro_section' style='background-image: url(https://nyc3.digitaloceanspaces.com/megapixels/v1/datasets/viper/assets/background.jpg)'><div class='inner'><div class='hero_desc'><span class='bgpad'><span style='color: #ffaa00'>VIPeR</span> is a person re-identification dataset of images captured at UC Santa Cruz in 2007</span></div><div class='hero_subdesc'><span class='bgpad'>VIPeR contains 1,264 images and 632 persons on the UC Santa Cruz campus and is used to train person re-identification algorithms for surveillance +</span></div></div></section><section><div class='left-sidebar'><div class='meta'><div><div class='gray'>Published</div><div>2007</div></div><div><div class='gray'>Images</div><div>1,264</div></div><div><div class='gray'>Persons</div><div>632</div></div><div><div class='gray'>Created by</div><div>UC Santa Cruz</div></div></div></div><h2>VIPeR Dataset</h2> +<p>(PAGE UNDER DEVELOPMENT)</p> +<p><em>VIPeR (Viewpoint Invariant Pedestrian Recognition)</em> is a dataset of pedestrian images captured at University of California Santa Cruz in 2007. Accoriding to the reserachers 2 "cameras were placed in different locations in an academic setting and subjects were notified of the presence of cameras, but were not coached or instructed in any way."</p> +<p>VIPeR is amongst the most widely used publicly available person re-identification datasets. In 2017 the VIPeR dataset was combined into a larger person re-identification created by the Chinese University of Hong Kong called PETA (PEdesTrian Attribute).</p> +</section><section> + <h3>Who used VIPeR?</h3> + + <p> + This bar chart presents a ranking of the top countries where citations originated. Mouse over individual columns + to see yearly totals. Colors are only assigned to the top 10 overall countries. + </p> + + </section> + +<section class="applet_container"> + <div class="applet" data-payload="{"command": "chart"}"></div> +</section><section> + + <h3>Biometric Trade Routes (beta)</h3> +<!-- + <div class="map-sidebar right-sidebar"> + <h3>Legend</h3> + <ul> + <li><span style="color: #f2f293">■</span> Industry</li> + <li><span style="color: #f30000">■</span> Academic</li> + <li><span style="color: #3264f6">■</span> Government</li> + </ul> + </div> + --> + <p> + To understand how VIPeR has been used around the world... + affected global research on computer vision, surveillance, defense, and consumer technology, the and where this dataset has been used the locations of each organization that used or referenced the datast + </p> + + </section> + +<section class="applet_container"> + <div class="applet" data-payload="{"command": "map"}"></div> +</section> + +<div class="caption"> + <div class="map-legend-item edu">Academic</div> + <div class="map-legend-item com">Industry</div> + <div class="map-legend-item gov">Government</div> + Data is compiled from <a href="https://www.semanticscholar.org">Semantic Scholar</a> and not yet manually verified. +</div> + +<section> + <p class='subp'> + The data is generated by collecting all citations for all original research papers associated with the dataset. Then the PDFs are then converted to text and the organization names are extracted and geocoded. Because of the automated approach to extracting data, actual use of the dataset can not yet be confirmed. This visualization is provided to help locate and confirm usage and will be updated as data noise is reduced. + </p> +</section><section> + + + <div class="hr-wave-holder"> + <div class="hr-wave-line hr-wave-line1"></div> + <div class="hr-wave-line hr-wave-line2"></div> + </div> + + <h2>Supplementary Information</h2> +</section><section class="applet_container"> + + <h3>Citations</h3> + <p> + Citations were collected from <a href="https://www.semanticscholar.org">Semantic Scholar</a>, a website which aggregates + and indexes research papers. Metadata was extracted from these papers, including extracting names of institutions automatically from PDFs, and then the addresses were geocoded. Data is not yet manually verified, and reflects anytime the paper was cited. Some papers may only mention the dataset in passing, while others use it as part of their research methodology. + </p> + <p> + Add button/link to download CSV + </p> + + <div class="applet" data-payload="{"command": "citations"}"></div> +</section> + + </div> + <footer> + <div> + <a href="/">MegaPixels.cc</a> + <a href="/about/disclaimer/">Disclaimer</a> + <a href="/about/terms/">Terms of Use</a> + <a href="/about/privacy/">Privacy</a> + <a href="/about/">About</a> + <a href="/about/team/">Team</a> + </div> + <div> + MegaPixels ©2017-19 Adam R. Harvey / + <a href="https://ahprojects.com">ahprojects.com</a> + </div> + </footer> +</body> + +<script src="/assets/js/dist/index.js"></script> +</html>
\ No newline at end of file |
