summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/client/common/currentTask.component.js2
-rw-r--r--app/client/common/fileList.component.js2
-rw-r--r--app/client/common/fileViewer.component.js87
-rw-r--r--app/client/common/index.js5
-rw-r--r--app/client/common/taskList.component.js2
-rw-r--r--app/client/common/timeline.component.js166
-rw-r--r--app/client/dataset/dataset.reducer.js5
-rw-r--r--app/client/modules/pix2pixhd/pix2pixhd.actions.js18
-rw-r--r--app/client/modules/pix2pixhd/pix2pixhd.tasks.js20
-rw-r--r--app/client/modules/pix2pixhd/views/pix2pixhd.train.js108
-rw-r--r--app/client/modules/pix2pixhd/views/sequence.editor.js181
-rw-r--r--app/client/util/format.js11
-rw-r--r--app/client/util/math.js16
-rw-r--r--app/relay/modules/pix2pixhd.js10
14 files changed, 430 insertions, 203 deletions
diff --git a/app/client/common/currentTask.component.js b/app/client/common/currentTask.component.js
index ef976bc..c053a13 100644
--- a/app/client/common/currentTask.component.js
+++ b/app/client/common/currentTask.component.js
@@ -31,7 +31,7 @@ function CurrentTask ({ cpu, gpu, processor }) {
? <span>(currently #{epoch})</span>
: ""}
<br/><br/>
- <div className='quiet'>{last_message}</div>
+ <div className='quiet lastMessage'>{last_message}</div>
</div>
)
}
diff --git a/app/client/common/fileList.component.js b/app/client/common/fileList.component.js
index 8f79148..12f6865 100644
--- a/app/client/common/fileList.component.js
+++ b/app/client/common/fileList.component.js
@@ -119,7 +119,7 @@ export const FileRow = props => {
<div className={"epoch " + util.hush_null(epoch)[0]}>{epoch > 0 ? 'ep. ' + epoch : ''}</div>
}
{fields.has('date') &&
- <div className={"date " + util.carbon_date(date)}>{moment(date).format("YYYY-MM-DD")}</div>
+ <div className={"date " + util.carbon_date(date)} title={moment(date).format("YYYY-MM-DD HH:mm")}>{moment(date).format("YYYY-MM-DD")}</div>
}
{fields.has('datetime') &&
<div className={"datetime"}>
diff --git a/app/client/common/fileViewer.component.js b/app/client/common/fileViewer.component.js
index d98073b..4938650 100644
--- a/app/client/common/fileViewer.component.js
+++ b/app/client/common/fileViewer.component.js
@@ -21,11 +21,37 @@ const video_types = {
'mp4': 'video/mp4',
}
+const THROTTLE_FETCH_TIME = 200
+
class FileViewer extends Component {
state = {
loading: false,
stale: false,
- buffer: {}
+ buffer: {},
+ url: null,
+ }
+
+ componentDidMount(){
+ this.fetch()
+ }
+
+ componentDidUpdate(prevProps){
+ if (this.props.file !== prevProps.file) {
+ this.deferFetch()
+ }
+ }
+
+ componentWillUnmount(){
+ if (this.state.url) {
+ window.URL.revokeObjectURL(this.state.url)
+ }
+ }
+
+ deferFetch(){
+ clearTimeout(this.timeout)
+ this.timeout = setTimeout(() => {
+ this.fetch()
+ }, THROTTLE_FETCH_TIME)
}
fetch() {
@@ -40,48 +66,56 @@ class FileViewer extends Component {
this.setState({ buffer: null, loading: true })
if (thumbnail) {
- console.log('fetch thumbnail', fn)
+ // console.log('fetch thumbnail', fn)
const size = parseInt(thumbnail) || 200
actions.socket
.thumbnail({ module, fn, size })
.then(this.loadBuffer.bind(this))
} else {
- console.log('fetch file', fn)
+ // console.log('fetch file', fn)
actions.socket
.read_file({ module, fn })
.then(this.loadBuffer.bind(this))
}
}
+
loadBuffer(buffer) {
- console.log('fetched buffer', buffer)
+ // console.log('fetched buffer', buffer)
+ const { name, buf } = buffer
+ const ext = extension(name)
+ if (this.state.url) {
+ window.URL.revokeObjectURL(this.state.url)
+ }
+ let url
+ if (buf) {
+ if (ext in image_types) {
+ url = getURLFor(buf, image_types[ext])
+ } else if (ext in audio_types) {
+ url = getURLFor(buf, audio_types[ext])
+ } else if (ext in video_types) {
+ url = getURLFor(buf, video_types[ext])
+ } else {
+ url = ab2str(buf)
+ }
+ }
const { stale } = this.state
- this.setState({ buffer, loading: false, stale: false, }, () => {
- console.log('loaded')
+ this.setState({ ext, url, buffer, loading: false, stale: false, }, () => {
+ // console.log('loaded')
if (stale) {
- console.log('stale, fetching...')
+ // console.log('stale, fetching...')
this.fetch()
}
})
}
- componentDidMount(){
- this.fetch()
- }
-
- componentDidUpdate(nextProps){
- if (this.props.file !== nextProps.file) {
- this.fetch()
- }
- }
-
render() {
const { file } = this.props
if (!file) {
return <div className='fileViewer'></div>
}
- const { loading, buffer } = this.state
+ const { loading, buffer, url, ext } = this.state
if (loading) {
- return <div className='fileViewer'>Loading...</div>
+ return <div className='fileViewer'><span>Loading...</span></div>
}
const {
error,
@@ -90,24 +124,23 @@ class FileViewer extends Component {
buf,
} = buffer
if (error) {
- return <div className='fileViewer'>{error}</div>
+ return <div className='fileViewer'><span>{error}</span></div>
}
if (!name) {
return <div className='fileViewer'></div>
}
- if (!buf) {
- return <div className='fileViewer'>File empty</div>
+ if (!buf || !url) {
+ return <div className='fileViewer'><span>File empty</span></div>
}
- const ext = extension(name)
let tag;
if (ext in image_types) {
- tag = <img src={getURLFor(buf, image_types[ext])} />
+ tag = <img src={url} />
} else if (ext in audio_types) {
- tag = <audio src={getURLFor(buf, audio_types[ext])} controls autoplay />
+ tag = <audio src={url} controls autoplay />
} else if (ext in video_types) {
- tag = <video src={getURLFor(buf, video_types[ext])} controls autoplay />
+ tag = <video src={url} controls autoplay />
} else {
- tag = <div className='text'>{ab2str(buf)}</div>
+ tag = <div className='text'>{url}</div>
}
return (
<div className='fileViewer'>{tag}</div>
diff --git a/app/client/common/index.js b/app/client/common/index.js
index e120597..15511fd 100644
--- a/app/client/common/index.js
+++ b/app/client/common/index.js
@@ -21,8 +21,9 @@ import Progress from './progress.component'
import Select from './select.component'
import SelectGroup from './selectGroup.component'
import Slider from './slider.component'
-import TextInput from './textInput.component'
import TaskList from './taskList.component'
+import TextInput from './textInput.component'
+import Timeline from './timeline.component'
import * as Views from './views'
export {
@@ -34,5 +35,5 @@ export {
TextInput, NumberInput,
Slider, Select, SelectGroup, Button, Checkbox,
CurrentTask, TaskList,
- ButtonGrid, AugmentationGrid,
+ ButtonGrid, AugmentationGrid, Timeline,
} \ No newline at end of file
diff --git a/app/client/common/taskList.component.js b/app/client/common/taskList.component.js
index 272ff80..a2f1df0 100644
--- a/app/client/common/taskList.component.js
+++ b/app/client/common/taskList.component.js
@@ -59,7 +59,7 @@ class TaskList extends Component {
<div className='row'>
<div className='activity'>{task.activity} {task.module}</div>
<div className='dataset'>{dataset_link}</div>
- <div className={"age " + util.carbon_date(task.updated_at)}>{util.get_age(task.updated_at)}</div>
+ <div className={"age " + util.carbon_date(task.updated_at)} title={task.updated_at}>{util.get_age(task.updated_at)}</div>
<div className='options'>
<span className='destroy' onClick={() => this.handleDestroy(task)}>x</span>
</div>
diff --git a/app/client/common/timeline.component.js b/app/client/common/timeline.component.js
new file mode 100644
index 0000000..d3739b1
--- /dev/null
+++ b/app/client/common/timeline.component.js
@@ -0,0 +1,166 @@
+import { h, Component } from 'preact'
+import { clamp, norm } from '../util/math'
+
+const initialState = {
+ dir: '/',
+ start: null,
+ end: null,
+ cursor: null,
+ selection: null,
+ width: 0,
+ ratio: 0,
+ loading: true
+}
+
+export default class Timeline extends Component {
+ state = { ...initialState }
+
+ constructor() {
+ super()
+ this.handleMouseDown = this.handleMouseDown.bind(this)
+ this.handleMouseMove = this.handleMouseMove.bind(this)
+ this.handleMouseUp = this.handleMouseUp.bind(this)
+ this.computeOffset = this.computeOffset.bind(this)
+ }
+
+ componentDidMount() {
+ const { sequence } = this.props
+ window.addEventListener('resize', this.computeOffset)
+ window.addEventListener('mousemove', this.handleMouseMove)
+ window.addEventListener('mouseup', this.handleMouseUp)
+ this.computeOffset()
+ if (sequence) {
+ this.reset()
+ }
+ }
+
+ componentDidUpdate(prevProps) {
+ const { sequence } = this.props
+ if (sequence !== prevProps.sequence) {
+ this.reset()
+ }
+ }
+
+ componentWillUnmount(){
+ window.removeEventListener('resize', this.computeOffset)
+ window.removeEventListener('mousemove', this.handleMouseMove)
+ window.removeEventListener('mouseup', this.handleMouseUp)
+ }
+
+ reset(){
+ const { sequence } = this.props
+ if (!sequence || !sequence.length) return
+ const len = sequence.length - 1
+ const start = { frame: sequence[0], i: 0 }
+ const end = { frame: sequence[len], i: len }
+ const width = Math.sqrt(clamp(sequence.length / 15000, 0.3, 1.0)) * Math.min(window.innerWidth - 40, 1000)
+ const ratio = width / sequence.length
+ setTimeout(() => this.computeOffset())
+ this.setState({
+ ...initialState,
+ width,
+ ratio,
+ start,
+ end,
+ })
+ }
+
+ computeOffset(){
+ if (this.ref) {
+ this.offset = this.ref.getBoundingClientRect()
+ }
+ }
+
+ computeFrame(e){
+ const { sequence } = this.props
+ if (!sequence || !sequence.length) return null
+ let x = (e.pageX - this.offset.left) / this.offset.width
+ let y = (e.pageY - this.offset.top) / this.offset.height
+ if (this.state.dragging) {
+ x = clamp(x, 0, 1)
+ y = clamp(y, 0, 1)
+ }
+ if (0 <= x && x <= 1 && 0 <= y && y <= 1) {
+ const index = Math.floor(x * (sequence.length-1))
+ return { frame: sequence[index], i: index }
+ }
+ return null
+ }
+
+ handleMouseDown(e) {
+ this.setState({ dragging: true })
+ const frame = this.computeFrame(e)
+ if (frame) {
+ this.props.onPick && this.props.onPick(frame)
+ this.props.onSelect && this.props.onSelect({ start: frame, end: frame })
+ this.setState({
+ start: frame,
+ end: frame,
+ })
+ }
+ }
+
+ handleMouseMove(e) {
+ const frame = this.computeFrame(e)
+ if (frame) {
+ this.props.onCursor && this.props.onCursor(frame)
+ if (this.state.dragging) {
+ let start = this.state.start
+ let end = frame
+ this.setState({
+ cursor: frame,
+ end: frame,
+ })
+ if (this.props.onSelect) {
+ if (end.i < start.i) {
+ [start, end] = [end, start]
+ }
+ this.props.onSelect({ start, end })
+ }
+ } else {
+ this.setState({
+ cursor: frame
+ })
+ }
+ }
+ }
+
+ handleMouseUp(e) {
+ if (!this.state.dragging) return
+ let { start, end } = this.state
+ if (end.i < start.i) {
+ [start, end] = [end, start]
+ }
+ this.props.onSelect && this.props.onSelect({ start, end })
+ this.setState({ dragging: false })
+ }
+
+ render() {
+ const { sequence } = this.props
+ const { loading, start, end, cursor, width, ratio } = this.state
+ return (
+ <div
+ className='timeline'
+ style={{ width }}
+ ref={ref => this.ref = ref}
+ onMouseDown={this.handleMouseDown}
+ >
+ {ratio && start && end && this.renderSelection(start, end, ratio)}
+ {ratio && cursor && this.renderCursor(cursor, ratio)}
+ </div>
+ )
+ }
+ renderCursor(cursor, ratio){
+ const left = cursor.i * ratio
+ return (
+ <div key='cursor' className='cursor' style={{ left }} />
+ )
+ }
+ renderSelection(start, end, ratio){
+ const left = Math.min(start.i, end.i) * ratio
+ const width = Math.max(Math.abs(start.i - end.i) * ratio, 1)
+ return (
+ <div key='selection' className='selection' style={{ left, width }} />
+ )
+ }
+}
diff --git a/app/client/dataset/dataset.reducer.js b/app/client/dataset/dataset.reducer.js
index f303a7f..9b1c322 100644
--- a/app/client/dataset/dataset.reducer.js
+++ b/app/client/dataset/dataset.reducer.js
@@ -61,10 +61,7 @@ const datasetReducer = (state = datasetInitialState(), action) => {
case types.dataset.list_epochs:
return {
...state,
- data: {
- ...state.data,
- epochs: action.data.epochs,
- }
+ epochs: action.data.epochs,
}
default:
diff --git a/app/client/modules/pix2pixhd/pix2pixhd.actions.js b/app/client/modules/pix2pixhd/pix2pixhd.actions.js
index 2c72f06..d067017 100644
--- a/app/client/modules/pix2pixhd/pix2pixhd.actions.js
+++ b/app/client/modules/pix2pixhd/pix2pixhd.actions.js
@@ -202,18 +202,18 @@ export const list_epochs = (checkpoint_name) => (dispatch) => {
})
}
-export const count_dataset = (checkpoint_name) => (dispatch) => {
+export const count_dataset = (dataset) => (dispatch) => {
const module = pix2pixhdModule.name
util.allProgress([
- actions.socket.list_directory({ module, dir: 'sequences/' + checkpoint_name + '/' }),
- actions.socket.count_directory({ module, dir: 'datasets/' + checkpoint_name + '/train_A/' }),
+ actions.socket.list_directory({ module, dir: 'sequences/' + dataset + '/' }),
+ actions.socket.count_directory({ module, dir: 'datasets/' + dataset + '/train_A/' }),
], (percent, i, n) => {
console.log('pix2pixhd load progress', i, n)
- dispatch({
- type: types.app.load_progress,
- progress: { i, n },
- data: { module: 'pix2pixhd' },
- })
+ // dispatch({
+ // type: types.app.load_progress,
+ // progress: { i, n },
+ // data: { module: 'pix2pixhd' },
+ // })
}).then(res => {
const [sequence, datasetCount] = res //, datasets, results, output, datasetUsage, lossReport] = res
const sequenceCount = sequence.length
@@ -221,7 +221,7 @@ export const count_dataset = (checkpoint_name) => (dispatch) => {
dispatch({
type: types.pix2pixhd.load_dataset_count,
data: {
- name: checkpoint_name,
+ name: dataset,
sequence,
sequenceCount,
datasetCount,
diff --git a/app/client/modules/pix2pixhd/pix2pixhd.tasks.js b/app/client/modules/pix2pixhd/pix2pixhd.tasks.js
index 8fec652..89184d2 100644
--- a/app/client/modules/pix2pixhd/pix2pixhd.tasks.js
+++ b/app/client/modules/pix2pixhd/pix2pixhd.tasks.js
@@ -23,10 +23,12 @@ export const fetch_task = (url, file_id, dataset) => dispatch => {
}
export const train_task = (dataset, folder_id, epochs=1) => dispatch => {
+ dataset = dataset.name || dataset
+ if (dataset === 'PLACEHOLDER') return
const task = {
module: module.name,
activity: 'train',
- dataset: dataset.name || dataset,
+ dataset,
epoch: 0,
epochs: epochs,
opt: {
@@ -58,6 +60,7 @@ export const live_task = (sequence, checkpoint, opt) => dispatch => {
}
export const augment_task = (dataset, opt) => dispatch => {
+ if (dataset === 'PLACEHOLDER') return
const task = {
module: module.name,
activity: 'augment',
@@ -72,14 +75,17 @@ export const augment_task = (dataset, opt) => dispatch => {
return actions.queue.add_task(task)
}
-export const clear_recursive_task = (dataset) => dispatch => {
+export const create_dataset_task = (opt) => dispatch => {
const task = {
module: module.name,
- activity: 'clear_recursive',
- dataset,
+ activity: 'create_dataset',
+ dataset: opt.title,
+ folder_id: opt.folder_id,
+ opt: {
+ ...opt,
+ }
}
console.log(task)
- console.log('add clear recursive task')
+ console.log('add create_dataset task')
return actions.queue.add_task(task)
-}
-
+} \ No newline at end of file
diff --git a/app/client/modules/pix2pixhd/views/pix2pixhd.train.js b/app/client/modules/pix2pixhd/views/pix2pixhd.train.js
index 6aade36..de32fcd 100644
--- a/app/client/modules/pix2pixhd/views/pix2pixhd.train.js
+++ b/app/client/modules/pix2pixhd/views/pix2pixhd.train.js
@@ -24,16 +24,17 @@ import SequenceEditor from './sequence.editor'
import pix2pixhdModule from '../pix2pixhd.module'
class Pix2PixHDTrain extends Component {
+ state = {
+ dataset: 'PLACEHOLDER',
+ epoch: 'latest',
+ augment_name: '',
+ augment_take: 100,
+ augment_make: 20,
+ generated: false,
+ }
constructor(props){
super(props)
this.handleChange = this.handleChange.bind(this)
- this.state = {
- checkpoint_name: 'PLACEHOLDER',
- epoch: 'latest',
- augment_name: '',
- augment_take: 100,
- augment_make: 20,
- }
}
componentDidMount(){
const id = this.props.match.params.id || localStorage.getItem('pix2pixhd.last_id')
@@ -50,15 +51,30 @@ class Pix2PixHDTrain extends Component {
this.props.history.push('/pix2pixhd/new/')
}
if (dataset) {
- this.setState({ checkpoint_name: dataset })
+ this.setState({ dataset })
}
}
componentDidUpdate(prevProps, prevState){
- if (prevState.checkpoint_name !== this.state.checkpoint_name) {
- localStorage.setItem('pix2pixhd.last_dataset', this.state.checkpoint_name)
- this.setState({ epoch: 'latest' })
- this.props.actions.list_epochs(this.state.checkpoint_name)
- this.props.actions.count_dataset(this.state.checkpoint_name)
+ if ((!prevProps.pix2pixhd.data && this.props.pix2pixhd.data)
+ || (prevProps.pix2pixhd.data && prevState.dataset !== this.state.dataset)) {
+ const dataset = this.props.pix2pixhd.data.datasetLookup[this.state.dataset]
+ if (dataset) {
+ const generated = dataset.input
+ .map(f => this.props.pix2pixhd.data.fileLookup[f])
+ .reduce((a,b) => {
+ return b.generated || a
+ }, false)
+ dataset.generated = generated
+ this.setState({ generated })
+ }
+ }
+ if (prevState.dataset !== this.state.dataset) {
+ localStorage.setItem('pix2pixhd.last_dataset', this.state.dataset)
+ this.setState({
+ epoch: 'latest',
+ })
+ this.props.actions.list_epochs(this.state.dataset)
+ this.props.actions.count_dataset(this.state.dataset)
}
}
handleChange(name, value){
@@ -72,17 +88,18 @@ class Pix2PixHDTrain extends Component {
if (this.props.pix2pixhd.loading) {
return <Loading progress={this.props.pix2pixhd.progress} />
}
- const { pix2pixhd, match, history, queue } = this.props
+ const { pix2pixhd, match, history } = this.props
const { folderLookup, datasetLookup } = (pix2pixhd.data || {})
const folder = (folderLookup || {})[pix2pixhd.folder_id] || {}
+ const { checkpoint } = pix2pixhd
// console.log(pix2pixhd)
- const checkpointGroups = Object.keys(folderLookup).map(id => {
+ const sequenceGroups = Object.keys(folderLookup).map(id => {
const folder = this.props.pix2pixhd.data.folderLookup[id]
if (folder.name === 'results') return
const datasets = folder.datasets.map(name => {
const dataset = datasetLookup[name]
- if (dataset.checkpoints.length) {
+ if (dataset.isBuilt && dataset.checkpoints.length) {
return name
}
return null
@@ -94,7 +111,7 @@ class Pix2PixHDTrain extends Component {
}).filter(n => !!n && !!n.options.length).sort((a,b) => a.name.localeCompare(b.name))
// console.log('state', this.props.pix2pixhd.data.epochs)
- // console.log(this.state.checkpoint_name, this.state.epoch)
+ // console.log(this.state.dataset, this.state.epoch)
// console.log(queue)
return (
<div className='app pix2pixhd'>
@@ -103,57 +120,70 @@ class Pix2PixHDTrain extends Component {
</div>
<div className='columns'>
<div className='column'>
- <Group title='Dataset'>
+ <Group title='Sequence'>
<SelectGroup
- name='checkpoint_name'
- title='Dataset'
- options={checkpointGroups}
+ name='dataset'
+ title='Sequence name'
+ options={sequenceGroups}
onChange={this.handleChange}
placeholder='Pick a dataset'
- value={this.state.checkpoint_name}
+ value={this.state.dataset}
/>
<Select
title="Epoch"
name="epoch"
- options={this.props.pix2pixhd.data.epochs}
+ options={this.props.pix2pixhd.epochs}
onChange={this.handleChange}
value={this.state.epoch}
/>
</Group>
</div>
</div>
- <div>
- <Group title='Sequence Editor'>
- <SequenceEditor
- module={pix2pixhdModule}
- checkpoint={this.props.pix2pixhd.checkpoint}
- />
- </Group>
- </div>
+ {checkpoint && checkpoint.sequence && checkpoint.sequence.length
+ ? this.renderEditor()
+ : checkpoint && <div>Sequence empty, augmentation impossible</div>}
+ </div>
+ )
+ }
+
+ renderEditor(){
+ const { pix2pixhd, queue, remote } = this.props
+ const { checkpoint, folder_id } = pix2pixhd
+ const { dataset, generated } = this.state
+ return (
+ <div>
+ <Group title='Sequence Editor'>
+ <SequenceEditor
+ folder_id={folder_id}
+ module={pix2pixhdModule}
+ checkpoint={checkpoint}
+ generated={generated}
+ />
+ </Group>
<div className='columns'>
<div className='column'>
<Group title='Augmentation Grid'>
<AugmentationGrid
- checkpoint={this.props.pix2pixhd.checkpoint}
+ checkpoint={checkpoint}
take={[1,2,3,4,5,10,15,20,25,50,75,100,200,300,400,500,1000]}
make={[1,2,3,4,5,10,15,20,25,50,75,100,200,]}
onAugment={(augment_take, augment_make) => {
- this.props.remote.augment_task(this.state.checkpoint_name, {
+ remote.augment_task(dataset, {
...this.state,
augment_take,
augment_make,
})
}}
onTrain={() => {
- this.props.remote.train_task(this.state.checkpoint_name, pix2pixhd.folder_id, 1)
+ remote.train_task(dataset, folder_id, 1)
setTimeout(() => { // auto-generate epoch demo
- this.props.remote.augment_task(this.state.checkpoint_name, {
+ remote.augment_task(dataset, {
...this.state,
augment_take: 10,
augment_make: 149,
no_symlinks: true,
mov: true,
- folder_id: this.props.pix2pixhd.data.resultsFolder.id
+ folder_id: pix2pixhd.data.resultsFolder.id
})
}, 250)
}}
@@ -181,17 +211,17 @@ class Pix2PixHDTrain extends Component {
<Button
title="Augment dataset"
value="Augment"
- onClick={() => this.props.remote.augment_task(this.state.checkpoint_name, this.state)}
+ onClick={() => this.props.remote.augment_task(dataset, this.state)}
/>
<Button
title="Make a movie without augmenting"
value="Generate"
onClick={() => {
- this.props.remote.augment_task(this.state.checkpoint_name, {
+ this.props.remote.augment_task(dataset, {
...this.state,
no_symlinks: true,
mov: true,
- folder_id: this.props.pix2pixhd.data.resultsFolder.id
+ folder_id: pix2pixhd.data.resultsFolder.id
})
}}
/>
diff --git a/app/client/modules/pix2pixhd/views/sequence.editor.js b/app/client/modules/pix2pixhd/views/sequence.editor.js
index e66aebf..135dfb3 100644
--- a/app/client/modules/pix2pixhd/views/sequence.editor.js
+++ b/app/client/modules/pix2pixhd/views/sequence.editor.js
@@ -2,147 +2,140 @@ import { h, Component } from 'preact'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { Route, Link } from 'react-router-dom'
+import moment from 'moment/min/moment.min'
-import { Loading, FileList, FileViewer } from '../../../common'
+import util from '../../../util'
+import { FileViewer, Timeline, Param, Button, Group, TextInput } from '../../../common'
+
+import * as pix2pixhdTasks from '../pix2pixhd.tasks'
import actions from '../../../actions'
const initialState = {
dir: '/',
- frameA: null,
- frameB: null,
+ cursor: null,
selection: null,
- loading: true
+ title: null,
}
-/*
- when the sequence editor loads,
- reset the selection
- reset the two frames
- set the two frames to the beginning and end of the video
- when mousing over the video
- ideally you would see a tiny thumbnail preview of the frame :)
- - click to start a selection, drag over, mouseup to end the selection
- this should update the start/end frame
- ...
- so there are two things you could do with this
- 1) create an entirely new dataset
- 2) add frames to an existing dataset
- ...
- method 1)
- - requires minimal setup, just a server script which..
- - creates the new sequence folder
- - symlinks the frames
- - runs build_dataset
- - create appropriate file object, using parent dataset's folder_id
- - enqueue initial training
- ---> we can then train on basic subsets, with more control, like we do already...
- - tell parent dataset thing the new sequence name
- ...
- method 2)
- - requires sequence editor to be aware of its own dataset
- - requires sequence editor to know whether a sequence is original or not
-*/
-
class SequenceEditor extends Component {
state = { ...initialState }
constructor() {
super()
- this.handleMouseDown = this.handleMouseDown.bind(this)
- this.handleMouseMove = this.handleMouseMove.bind(this)
- this.handleMouseEnter = this.handleMouseEnter.bind(this)
- this.handleMouseLeave = this.handleMouseLeave.bind(this)
- this.handleMouseUp = this.handleMouseUp.bind(this)
+ this.handleCursor = this.handleCursor.bind(this)
+ this.handleSelect = this.handleSelect.bind(this)
}
componentDidMount() {
const { checkpoint } = this.props
- window.addEventListener('mousemove', this.handleMouseMove)
- window.addEventListener('mouseup', this.handleMouseUp)
- if (checkpoint && checkpoint.sequence) {
- console.log(checkpoint)
- const frameA = checkpoint.sequence[0]
- const frameB = checkpoint.sequence[checkpoint.sequence.length-1]
- this.setState({
- ...initialState,
- frameA,
- frameB,
- })
+ if (checkpoint) {
+ this.reset()
}
}
componentDidUpdate(prevProps) {
const { checkpoint } = this.props
if (checkpoint !== prevProps.checkpoint) {
- console.log(checkpoint)
- const frameA = checkpoint.sequence[0]
- const frameB = checkpoint.sequence[checkpoint.sequence.length-1]
- this.setState({
- ...initialState,
- frameA,
- frameB,
- })
+ this.reset()
}
}
- componentWillUnmount(){
- window.removeEventListener('mouseup', this.handleMouseUp)
- window.removeEventListener('mousemove', this.handleMouseMove)
+ reset(){
+ const { checkpoint } = this.props
+ if (!(checkpoint && checkpoint.sequence)) return
+ console.log(checkpoint)
+ this.setState({
+ ...initialState,
+ title: checkpoint.name + '_' + moment().format("YYYYMMDD")
+ })
}
- handleMouseDown(e) {
- this.setState({ dragging: true })
- }
- handleMouseMove(e) {
- }
- handleMouseEnter(e) {
- }
- handleMouseLeave(e) {
- }
- handleMouseUp(e) {
- this.setState({ dragging: false })
+ handleCursor(cursor) {
+ this.setState({ cursor })
}
- handlePick(file) {
- console.log(file)
- // this.setState({ dir, file: null, loading: true })
+ handleSelect(selection) {
+ this.setState({ selection })
}
render() {
- const { app, checkpoint } = this.props
- const {
- loading,
- selection,
- frameA, frameB,
- } = this.state
- // console.log(this.props, this.state)
- const width = 200
+ const { app, pix2pixhd, remote, checkpoint, folder_id, generated } = this.props
+ const { cursor, selection, title } = this.state
const path = "sequences/" + checkpoint.name
+ console.log(checkpoint, pix2pixhd)
return (
- <div className='sequenceEditor'>
- <div
- className='timeline'
- style={{ width }}
- mouseDown={this.handleSelectionStart}
- mouseEnter={this.handleMouseEnter}
- mouseLeave={this.handleMouseLeave}
- >
- {selection && <div className='selection' style={selection}></div>}
+ <div className='sequenceEditor row'>
+ {selection
+ ? <div className='form'>
+ <Param title='Selection length'>
+ {selection.end.i - selection.start.i}{' frames'}
+ </Param>
+ <Param title='Duration'>
+ {util.frameTimestamp(selection.end.i - selection.start.i)}
+ </Param>
+ <Group title='New dataset'>
+ <TextInput
+ title='Title dataset'
+ value={title}
+ onInput={title => this.setState({ title: title.replace(/ /g, '_').replace(/\/\./g, '') })}
+ />
+ <Button
+ title='Create a new dataset?'
+ onClick={() => remote.create_dataset_task({ title, sequence: checkpoint.name, selection, folder_id })}
+ >
+ Create
+ </Button>
+ </Group>
+ <Group title='Salt dataset'>
+ {generated
+ ? 'Salting is only available on generated datasets.'
+ : 'Salting coming soon!'}
+ </Group>
+ </div>
+ : <div className='form'><Group title='New dataset'>Please select some frames</Group></div>
+ }
+ <div className='rows'>
+ <div className='row'>
+ <Frame label='Cursor' path={path} frame={cursor} />
+ {selection && selection.start &&
+ <Frame label='Selection Start' path={path} frame={selection.start} />
+ }
+ {selection && selection.end &&
+ <Frame label='Selection End' path={path} frame={selection.end} />
+ }
+ </div>
+ <Timeline
+ sequence={checkpoint.sequence}
+ onCursor={this.handleCursor}
+ onSelect={this.handleSelect}
+ />
</div>
- <FileViewer thumbnail path={path} file={this.state.frameA} />
- <FileViewer thumbnail path={path} file={this.state.frameB} />
</div>
)
}
}
+function Frame ({ label, path, frame }) {
+ if (!frame) return <div class='frame'></div>
+ return (
+ <div class='frame'>
+ <FileViewer thumbnail={140} path={path} file={frame.frame} />
+ <div class='spaced'>
+ <span>{label}</span>
+ <span>{'#'}{frame.i} {util.frameTimestamp(frame.i)}</span>
+ </div>
+ </div>
+ )
+}
+
const mapStateToProps = state => ({
app: state.system.app,
+ pix2pixhd: state.module.pix2pixhd,
})
const mapDispatchToProps = (dispatch, ownProps) => ({
- actions: bindActionCreators({}, dispatch),
+ remote: bindActionCreators(pix2pixhdTasks, dispatch),
})
export default connect(mapStateToProps, mapDispatchToProps)(SequenceEditor)
diff --git a/app/client/util/format.js b/app/client/util/format.js
index ee1f47f..2c94de1 100644
--- a/app/client/util/format.js
+++ b/app/client/util/format.js
@@ -1,3 +1,13 @@
+const FRAME_RATE = 25
+export function frameTimestamp(n = 0) {
+ n /= FRAME_RATE
+ let s = pad((n % 60).toFixed(1))
+ n = Math.floor(n / 60)
+ if (n > 60) {
+ return Math.floor(n / 60) + ':' + pad(n % 60) + ':' + s
+ }
+ return (n % 60) + ':' + s
+}
export function timeInSeconds(n){
return (n / 10).toFixed(1) + ' s.'
}
@@ -144,3 +154,4 @@ export function get_age (t) {
return r(age) + "y"
}
export function courtesy_s (n, s) { return n == 1 ? "" : (s || "s") }
+export function pad(n, r){ return n < 10 ? '0' + n : n }
diff --git a/app/client/util/math.js b/app/client/util/math.js
index c301ffd..064d37c 100644
--- a/app/client/util/math.js
+++ b/app/client/util/math.js
@@ -4,14 +4,14 @@ export const norm = (n,a,b) => (n-a) / (b-a)
export const lerp = (n,a,b) => (b-a)*n+a
export const mix = (n,a,b) => a*(1-n)+b*n
export const randint = (n) => Math.floor(Math.random()*n)
-export function randrange(a,b){ return Math.random() * (b-a) + a }
-export function randsign(){ return Math.random() >= 0.5 ? -1 : 1 }
-export function choice (a){ return a[ Math.floor(Math.random() * a.length) ] }
-export function angle(x0,y0,x1,y1){ return Math.atan2(y1-y0,x1-x0) }
-export function dist(x0,y0,x1,y1){ return Math.sqrt(Math.pow(x1-x0,2)+Math.pow(y1-y0,2)) }
-export function xor(a,b){ a=!!a; b=!!b; return (a||b) && !(a&&b) }
-export function quantize(a,b){ return Math.floor(a/b)*b }
-export function shuffle(a){
+export const randrange = (a,b) => Math.random() * (b-a) + a
+export const randsign = () => Math.random() >= 0.5 ? -1 : 1
+export const choice = (a) => a[ Math.floor(Math.random() * a.length) ]
+export const angle = (x0,y0,x1,y1) => Math.atan2(y1-y0, x1-x0)
+export const dist = (x0,y0,x1,y1) => Math.sqrt(Math.pow(x1-x0, 2) + Math.pow(y1-y0, 2))
+export const xor = (a,b) => { a=!!a; b=!!b; return (a||b) && !(a&&b) }
+export const quantize = (a,b) => Math.floor(a/b)*b
+export const shuffle = (a) => {
for (var i = a.length; i > 0; i--){
var r = randint(i)
var swap = a[i-1]
diff --git a/app/relay/modules/pix2pixhd.js b/app/relay/modules/pix2pixhd.js
index c1a2650..7d3c939 100644
--- a/app/relay/modules/pix2pixhd.js
+++ b/app/relay/modules/pix2pixhd.js
@@ -154,16 +154,6 @@ const augment = {
},
after: 'render_recursive',
}
-const clear_recursive = {
- type: 'pytorch',
- script: 'clear_recursive.py',
- params: (task) => {
- const dataset = task.dataset.toLowerCase()
- return [
- '--dataset', dataset,
- ]
- },
-}
const live = {
type: 'pytorch',
script: 'live.py',