diff options
| author | Adam Harvey <adam@ahprojects.com> | 2018-12-23 01:37:03 +0100 |
|---|---|---|
| committer | Adam Harvey <adam@ahprojects.com> | 2018-12-23 01:37:03 +0100 |
| commit | 4452e02e8b04f3476273574a875bb60cfbb4568b (patch) | |
| tree | 3ffa44f9621b736250a8b94da14a187dc785c2fe /client/common | |
| parent | 2a65f7a157bd4bace970cef73529867b0e0a374d (diff) | |
| parent | 5340bee951c18910fd764241945f1f136b5a22b4 (diff) | |
.
Diffstat (limited to 'client/common')
| -rw-r--r-- | client/common/classifier.component.js | 99 | ||||
| -rw-r--r-- | client/common/common.css | 1 | ||||
| -rw-r--r-- | client/common/detectionBoxes.component.js | 15 | ||||
| -rw-r--r-- | client/common/detectionList.component.js | 16 | ||||
| -rw-r--r-- | client/common/gate.component.js | 21 | ||||
| -rw-r--r-- | client/common/header.component.js | 1 | ||||
| -rw-r--r-- | client/common/index.js | 25 | ||||
| -rw-r--r-- | client/common/loader.component.js | 11 | ||||
| -rw-r--r-- | client/common/sidebar.component.js | 18 | ||||
| -rw-r--r-- | client/common/table.component.js | 121 | ||||
| -rw-r--r-- | client/common/video.component.js | 47 |
11 files changed, 375 insertions, 0 deletions
diff --git a/client/common/classifier.component.js b/client/common/classifier.component.js new file mode 100644 index 00000000..af6a4934 --- /dev/null +++ b/client/common/classifier.component.js @@ -0,0 +1,99 @@ +import React, { Component } from 'react' +import { courtesyS } from '../util' + +import { TableTuples, DetectionList, Keyframe } from '.' + +export default class Classifier extends Component { + render() { + const { + tag, + sha256, + verified, + keyframes = {}, + labels, + summary, + aspectRatio = 1.777, + showAll, + } = this.props + let totalDetections = 0 + const keys = Object + .keys(keyframes) + .map(s => parseInt(s, 10)) + const validKeyframes = keys + .sort((a, b) => a - b) + .map(frame => { + const detections = keyframes[frame] + if (detections.length || showAll) { + totalDetections += detections.length + return { frame, detections } + } + return null + }) + .filter(f => !!f) + const detectionLookup = validKeyframes + .reduce((a, b) => { + b.detections.reduce((aa, { idx }) => { + if (!(idx in aa)) aa[idx] = [labels[idx], 0] + aa[idx][1] += 1 + return aa + }, a) + return a + }, {}) + const detectionCounts = Object.keys(detectionLookup) + .map(idx => detectionLookup[idx]) + .sort((a, b) => b[1] - a[1]) + + if (summary) { + return ( + <div> + <h3>{tag}{' Detections'}</h3> + <TableTuples + list={detectionCounts} + /> + </div> + ) + } + return ( + <div> + <h2>{tag}</h2> + <h3>Detections</h3> + <TableTuples + list={detectionCounts} + /> + <h3>Frames</h3> + <ul className='meta'> + <li> + {'Displaying '}{validKeyframes.length}{' / '}{courtesyS(keys.length, 'frame')} + </li> + <li> + {courtesyS(totalDetections, 'detection')}{' found'} + </li> + </ul> + <div className='thumbnails'> + {validKeyframes.map(({ frame, detections }) => ( + <Keyframe + key={frame} + sha256={sha256} + frame={frame} + verified={verified} + size='th' + showFrame + showTimestamp + aspectRatio={aspectRatio} + detectionList={[ + { labels, detections } + ]} + > + <DetectionList + labels={labels} + detections={detections} + width={160} + height={90} + /> + </Keyframe> + ))} + </div> + </div> + ) + } +} diff --git a/client/common/common.css b/client/common/common.css new file mode 100644 index 00000000..56cf2fe9 --- /dev/null +++ b/client/common/common.css @@ -0,0 +1 @@ +* {}
\ No newline at end of file diff --git a/client/common/detectionBoxes.component.js b/client/common/detectionBoxes.component.js new file mode 100644 index 00000000..c4872ea8 --- /dev/null +++ b/client/common/detectionBoxes.component.js @@ -0,0 +1,15 @@ +import React from 'react' + +import { px } from '../util' + +export default function DetectionBoxes({ detections, width, height }) { + return detections.map(({ rect }, i) => ( + rect && + <div className='rect' key={i} style={{ + left: px(rect[0], width), + top: px(rect[1], height), + width: px(rect[2] - rect[0], width), + height: px(rect[3] - rect[1], height), + }} /> + )) +} diff --git a/client/common/detectionList.component.js b/client/common/detectionList.component.js new file mode 100644 index 00000000..416e66d8 --- /dev/null +++ b/client/common/detectionList.component.js @@ -0,0 +1,16 @@ +import React from 'react' + +export default function DetectionList({ detections, labels, tag, showEmpty }) { + return ( + <span className='detectionList'> + {tag && <h3>{tag}</h3>} + {!detections.length && showEmpty && <label><small>No detections</small></label>} + {detections.map(({ idx, score, rect }, i) => ( + <label key={i}> + <small className='title'>{(labels[idx] || 'Unknown').replace(/_/, ' ')}</small> + <small>{score.toFixed(2)}</small> + </label> + ))} + </span> + ) +} diff --git a/client/common/gate.component.js b/client/common/gate.component.js new file mode 100644 index 00000000..9bf9287b --- /dev/null +++ b/client/common/gate.component.js @@ -0,0 +1,21 @@ +import React from 'react' +import { connect } from 'react-redux' + +function Gate(props) { + const { app, tag, View } = props + const data = app[tag] + if (!data) return null + if (data === 'loading') { + return <div className='tableObject loading'>{tag}{': Loading'}</div> + } + if (data.err) { + return <div className='tableObject error'>{tag}{' Error: '}{data.err}</div> + } + return <View data={data} {...props} /> +} + +const mapStateToProps = state => ({ + app: state.metadata +}) + +export default connect(mapStateToProps)(Gate) diff --git a/client/common/header.component.js b/client/common/header.component.js new file mode 100644 index 00000000..84fe306f --- /dev/null +++ b/client/common/header.component.js @@ -0,0 +1 @@ +/* imported from main vcat application */ diff --git a/client/common/index.js b/client/common/index.js new file mode 100644 index 00000000..cfb34b32 --- /dev/null +++ b/client/common/index.js @@ -0,0 +1,25 @@ +import Classifier from './classifier.component' +import DetectionBoxes from './detectionBoxes.component' +import DetectionList from './detectionList.component' +// import Header from './header.component' +import Loader from './loader.component' +import Sidebar from './sidebar.component' +import Gate from './gate.component' +import Video from './video.component' +import { TableObject, TableArray, TableTuples, TableRow, TableCell } from './table.component' +import './common.css' + +export { + Sidebar, + Loader, + Gate, + TableObject, + TableArray, + TableTuples, + TableRow, + TableCell, + Classifier, + DetectionList, + DetectionBoxes, + Video, +} diff --git a/client/common/loader.component.js b/client/common/loader.component.js new file mode 100644 index 00000000..5930c63e --- /dev/null +++ b/client/common/loader.component.js @@ -0,0 +1,11 @@ +import React from 'react' + +export default function Loader() { + return ( + <div className='loaderWrapper'> + <div className='loader'> + <img src="/assets/img/loader.gif" /> + </div> + </div> + ) +} diff --git a/client/common/sidebar.component.js b/client/common/sidebar.component.js new file mode 100644 index 00000000..afbf8c8c --- /dev/null +++ b/client/common/sidebar.component.js @@ -0,0 +1,18 @@ +import React, { Component } from 'react' +import { connect } from 'react-redux' + +class Sidebar extends Component { + render() { + return ( + <div className="sidebar"> + </div> + ) + // <NavLink to={'/metadata/' + hash + '/summary/'}>Summary</NavLink> + } +} + +const mapStateToProps = state => ({ + hash: state.metadata.hash, +}) + +export default connect(mapStateToProps)(Sidebar) diff --git a/client/common/table.component.js b/client/common/table.component.js new file mode 100644 index 00000000..76a1d57c --- /dev/null +++ b/client/common/table.component.js @@ -0,0 +1,121 @@ +import React from 'react' + +import { formatName } from '../util' + +const __HR__ = '__HR__' + +export function TableObject({ tag, object, order, summary }) { + if (!object) return null + if (object === 'loading') { + return <div className='tableObject loading'>{tag}{': Loading'}</div> + } + if (object.err) { + return <div className='tableObject error'>{tag}{' Error: '}{object.err}</div> + } + let objects = Object.keys(object) + if (order) { + const grouped = objects.reduce((a, b) => { + const index = order.indexOf(b) + if (index !== -1) { + a.order.push([index, b]) + } else { + a.alpha.push(b) + } + return a + }, { order: [], alpha: [] }) + objects = grouped.order + .sort((a, b) => a[0] - b[0]) + .map(([i, s]) => s) + if (!summary) { + objects = objects + // .concat([__HR__]) + .concat(grouped.alpha.sort()) + } + } else { + objects = objects.sort() + } + return ( + <div> + {tag && <h3>{tag}</h3>} + <table className={'tableObject ' + tag}> + <tbody> + {objects.map((key, i) => ( + <TableRow key={key + '_' + i} name={key} value={object[key]} /> + ))} + </tbody> + </table> + </div> + ) +} + +export function TableArray({ tag, list }) { + if (!list) return null + return ( + <div> + {tag && <h3>{tag}</h3>} + <table className={'tableArray ' + tag}> + <tbody> + {list.map((value, i) => ( + <tr key={tag + '_' + i}> + <TableCell value={value} /> + </tr> + ))} + </tbody> + </table> + </div> + ) +} + +export function TableTuples({ tag, list }) { + if (!list) return null + return ( + <div> + {tag && <h3>{tag}</h3>} + <table className={'tableTuples ' + tag}> + <tbody> + {list.map(([key, ...values], i) => ( + <tr key={tag + '_' + i}> + <th>{formatName(key)}</th> + {values.map((value, j) => ( + <TableCell key={i + '_' + j} value={value} /> + ))} + </tr> + ))} + </tbody> + </table> + </div> + ) +} + +export function TableRow({ name, value }) { + if (name === __HR__) { + return ( + <tr> + <th className='tr'> + <hr /> + </th> + </tr> + ) + } + return ( + <tr> + <th>{formatName(name)}</th> + <TableCell name={name} value={value} /> + </tr> + ) +} + +export function TableCell({ value }) { + if (value && typeof value === 'object') { + if (value._raw) { + value = value.value + } else if (value.length) { + value = <TableArray nested tag={''} list={value} /> + } else { + value = <TableObject nested tag={''} object={value} /> + } + } + return ( + <td>{value}</td> + ) +} diff --git a/client/common/video.component.js b/client/common/video.component.js new file mode 100644 index 00000000..e5525bf6 --- /dev/null +++ b/client/common/video.component.js @@ -0,0 +1,47 @@ +import React, { Component } from 'react' +import { connect } from 'react-redux' +import { imageUrl, widths } from '../util' + +import { Gate } from '.' + +class Video extends Component { + state = { + playing: false, + } + + render() { + const { app, data, size } = this.props + const { playing } = this.state + const { sugarcube } = data.metadata + const url = sugarcube.fp.replace('/var/www/files/', 'https://cube.syrianarchive.org/') + const { sha256, verified } = app.mediainfo + const { video } = app.mediainfo.metadata.mediainfo + const keyframe = app.keyframe.metadata.keyframe.basic[0] + return ( + <div className='video'> + {playing + ? <video src={url} autoPlay controls muted /> + : <div + className='bg' + style={{ + width: widths[size || 'sm'], + height: widths[size || 'sm'] / video.aspect_ratio, + backgroundImage: 'url(' + imageUrl(verified, sha256, keyframe, size) + ')', + }} + onClick={() => this.setState({ playing: true })} + > + <div className='play'></div> + </div> + } + </div> + ) + } +} + +const mapStateToProps = () => ({ + tag: 'sugarcube', +}) + +export default connect(mapStateToProps)(props => ( + <Gate View={Video} {...props} /> +)) |
