Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Volume Tasks #3712

Merged
merged 40 commits into from
Feb 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
e1b25a5
[WIP] volume tasks
fm3 Jan 30, 2019
4df850f
fix volume task creation + requesting. TODO: download
fm3 Jan 31, 2019
6823756
allow to choose volume tasks via UI for simple task creation
philippotto Jan 31, 2019
7117734
enforce page reload when switching from/to/between volume tasks; show…
philippotto Feb 1, 2019
5f8ea8a
add tracing type to task type
fm3 Feb 1, 2019
4153a14
multi-task download: fetch volume data
fm3 Feb 1, 2019
b6ad9c0
add enums to evolutions
fm3 Feb 1, 2019
4365fa4
scalafmt
fm3 Feb 1, 2019
288da2d
add volume data to multi-task zip downloads
fm3 Feb 1, 2019
06958ff
scalafmt
fm3 Feb 1, 2019
05d41c3
add tasktype tracing type to test db
fm3 Feb 1, 2019
7b4e312
validate tracing type when reading task type from db
fm3 Feb 1, 2019
2a58ae9
adapt front-end to volume task types
philippotto Feb 1, 2019
af022b8
Merge branch 'volume-tasks' of github.com:scalableminds/webknossos in…
philippotto Feb 1, 2019
7cd5419
update some flow types
philippotto Feb 1, 2019
17f85f4
update snapshots
philippotto Feb 1, 2019
ed81cf5
Merge branch 'master' into volume-tasks
fm3 Feb 4, 2019
9019a21
Merge branch 'master' into volume-tasks
fm3 Feb 4, 2019
02872c7
update changelog + migrations
fm3 Feb 4, 2019
b010f42
add error handling for the as-yet unsupported volume task features
fm3 Feb 4, 2019
3b6d713
disable nml upload if volume task is selected
philippotto Feb 4, 2019
0e1c9eb
Merge branch 'volume-tasks' of github.com:scalableminds/webknossos in…
philippotto Feb 4, 2019
9d1648e
remove superfluous return type
fm3 Feb 5, 2019
79b9362
Merge branch 'master' into volume-tasks
fm3 Feb 5, 2019
b206bca
update evolution numbers
fm3 Feb 5, 2019
a461620
persist volume task bounding box in nml
fm3 Feb 5, 2019
2077f3f
read volume task bounding box from nml after re-upload
fm3 Feb 5, 2019
7240c90
scalafmt
fm3 Feb 5, 2019
86b4730
don't clip rendering of color layer in volume tasks
philippotto Feb 5, 2019
0ffd269
Merge branch 'volume-tasks' of github.com:scalableminds/webknossos in…
philippotto Feb 5, 2019
7b67f28
Merge branch 'master' into volume-tasks
fm3 Feb 5, 2019
89e474e
undo change to saving skeleton bounding box to nml
fm3 Feb 5, 2019
f592178
don't clip segmentation outside task bounding box
philippotto Feb 5, 2019
90e3532
Merge branch 'volume-tasks' of github.com:scalableminds/webknossos in…
philippotto Feb 5, 2019
c60be75
fix typo
philippotto Feb 5, 2019
e711b3b
Update CHANGELOG.md
fm3 Feb 5, 2019
5aae131
apply PR feedback
philippotto Feb 5, 2019
362fea5
Merge branch 'volume-tasks' of github.com:scalableminds/webknossos in…
philippotto Feb 5, 2019
13b4582
remove typ attribute from task
philippotto Feb 5, 2019
f29e38f
update snapshots
philippotto Feb 5, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.md).
## Unreleased
[Commits](https://github.com/scalableminds/webknossos/compare/19.02.0...HEAD)

### Added

- Added the possibility to create volume annotation tasks. When creating a task type, select whether to create `volume` or `skeleton` tasks. Note that compound viewing for volume tasks is not supported yet. Same for creating volume tasks from uploaded nml/data files. [#3712](https://github.com/scalableminds/webknossos/pull/3712)

### Changed


### Fixed

- The modals for a new task description and recommended task settings are no longer shown in read-only tracings. [#3724](https://github.com/scalableminds/webknossos/pull/3724)
Expand Down
2 changes: 1 addition & 1 deletion MIGRATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ User-facing changes are documented in the [changelog](CHANGELOG.md).
-

### Postgres Evolutions:
-
- [038-add-tasktype-tracingtype.sql](conf/evolutions/038-add-tasktype-tracingtype.sql)


## [19.02.0](https://github.com/scalableminds/webknossos/releases/tag/19.02.0) - 2019-02-04
Expand Down
1 change: 1 addition & 0 deletions app/assets/javascripts/admin/api_flow_types.js
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ export type APITaskType = {
+teamName: string,
+settings: APISettings,
+recommendedConfiguration: ?string,
+tracingType: "skeleton" | "volume",
};

export type TaskStatus = { +open: number, +active: number, +finished: number };
Expand Down
73 changes: 53 additions & 20 deletions app/assets/javascripts/admin/task/task_create_form_view.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
// @flow
import { Form, Select, Button, Card, Radio, Upload, Modal, Icon, InputNumber, Spin } from "antd";
import {
Row,
Col,
Form,
Select,
Button,
Card,
Radio,
Upload,
Modal,
Icon,
InputNumber,
Spin,
} from "antd";
import { type RouterHistory, withRouter } from "react-router-dom";
import React from "react";
import _ from "lodash";
Expand Down Expand Up @@ -160,7 +173,6 @@ class TaskCreateFormView extends React.PureComponent<Props, State> {
formValues.nmlFiles = formValues.nmlFiles.map(
wrapperFile => wrapperFile.originFileObj,
);

response = await createTaskFromNML(formValues);
} else {
response = await createTasks([formValues]);
Expand All @@ -183,6 +195,20 @@ class TaskCreateFormView extends React.PureComponent<Props, State> {
return e && e.fileList;
};

isVolumeTaskType = (taskTypeId?: string): boolean => {
const selectedTaskTypeId = taskTypeId || this.props.form.getFieldValue("taskTypeId");
const selectedTaskType = this.state.taskTypes.find(
taskType => taskType.id === selectedTaskTypeId,
);
return selectedTaskType != null ? selectedTaskType.tracingType === "volume" : false;
};

onChangeTaskType = (taskTypeId: string) => {
if (this.isVolumeTaskType(taskTypeId)) {
this.setState({ isNMLSpecification: false });
}
};

render() {
const { getFieldDecorator } = this.props.form;
const isEditingMode = this.props.taskId != null;
Expand All @@ -207,6 +233,7 @@ class TaskCreateFormView extends React.PureComponent<Props, State> {
style={fullWidth}
autoFocus
disabled={isEditingMode}
onChange={this.onChangeTaskType}
>
{this.state.taskTypes.map((taskType: APITaskType) => (
<Option key={taskType.id} value={taskType.id}>
Expand All @@ -217,24 +244,29 @@ class TaskCreateFormView extends React.PureComponent<Props, State> {
)}
</FormItem>

<FormItem label="Experience Domain" hasFeedback>
{getFieldDecorator("neededExperience.domain", {
rules: [{ required: true }],
})(
<SelectExperienceDomain
disabled={isEditingMode}
placeholder="Select an Experience Domain"
notFoundContent={messages["task.domain_does_not_exist"]}
width={100}
/>,
)}
</FormItem>

<FormItem label="Experience Value" hasFeedback>
{getFieldDecorator("neededExperience.value", {
rules: [{ required: true }, { type: "number" }],
})(<InputNumber style={fullWidth} disabled={isEditingMode} />)}
</FormItem>
<Row gutter={8}>
<Col span={12}>
<FormItem label="Experience Domain" hasFeedback>
{getFieldDecorator("neededExperience.domain", {
rules: [{ required: true }],
})(
<SelectExperienceDomain
disabled={isEditingMode}
placeholder="Select an Experience Domain"
notFoundContent={messages["task.domain_does_not_exist"]}
width={100}
/>,
)}
</FormItem>
</Col>
<Col span={12}>
<FormItem label="Experience Value" hasFeedback>
{getFieldDecorator("neededExperience.value", {
rules: [{ required: true }, { type: "number" }],
})(<InputNumber style={fullWidth} disabled={isEditingMode} />)}
</FormItem>
</Col>
</Row>

<FormItem label={instancesLabel} hasFeedback>
{getFieldDecorator("openInstances", {
Expand Down Expand Up @@ -296,6 +328,7 @@ class TaskCreateFormView extends React.PureComponent<Props, State> {
onChange={(evt: SyntheticInputEvent<*>) =>
this.setState({ isNMLSpecification: evt.target.value === "nml" })
}
disabled={this.isVolumeTaskType()}
>
<Radio value="manual" disabled={isEditingMode}>
Manually Specify Starting Postion
Expand Down
55 changes: 40 additions & 15 deletions app/assets/javascripts/admin/tasktype/task_type_create_view.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// @flow
import { Form, Checkbox, Input, Select, Card, Button } from "antd";
import { Button, Card, Checkbox, Form, Input, Radio, Select } from "antd";
import { type RouterHistory, withRouter } from "react-router-dom";
import React from "react";
import _ from "lodash";
Expand All @@ -16,6 +16,8 @@ import RecommendedConfigurationView, {
DEFAULT_RECOMMENDED_CONFIGURATION,
} from "admin/tasktype/recommended_configuration_view";

const RadioGroup = Radio.Group;

const FormItem = Form.Item;
const { Option } = Select;
const { TextArea } = Input;
Expand Down Expand Up @@ -94,10 +96,11 @@ class TaskTypeCreateView extends React.PureComponent<Props, State> {

render() {
const { getFieldDecorator } = this.props.form;
const titlePrefix = this.props.taskTypeId ? "Update " : "Create";
const isEditingMode = this.props.taskTypeId != null;
const titlePrefix = isEditingMode ? "Update " : "Create";

return (
<div className="container">
<div className="container" style={{ maxWidth: 1600, margin: "0 auto" }}>
<Card title={<h3>{titlePrefix} Task Type</h3>}>
<Form onSubmit={this.handleSubmit} layout="vertical">
<FormItem label="Summary" hasFeedback>
Expand Down Expand Up @@ -152,6 +155,21 @@ class TaskTypeCreateView extends React.PureComponent<Props, State> {
})(<TextArea rows={10} />)}
</FormItem>

<FormItem label="Tracing Type">
{getFieldDecorator("tracingType", {
initialValue: "skeleton",
})(
<RadioGroup>
<Radio value="skeleton" disabled={isEditingMode}>
Skeleton
</Radio>
<Radio value="volume" disabled={isEditingMode}>
Volume
</Radio>
</RadioGroup>,
)}
</FormItem>

<FormItem label="Allowed Modes" hasFeedback>
{getFieldDecorator("settings.allowedModes", {
rules: [{ required: true }],
Expand All @@ -170,18 +188,6 @@ class TaskTypeCreateView extends React.PureComponent<Props, State> {
)}
</FormItem>

<FormItem label="Settings">
{getFieldDecorator("settings.somaClickingAllowed", {
valuePropName: "checked",
})(<Checkbox>Allow Single-node-tree mode (&quot;Soma clicking&quot;)</Checkbox>)}
</FormItem>

<FormItem>
{getFieldDecorator("settings.branchPointsAllowed", {
valuePropName: "checked",
})(<Checkbox>Allow Branchpoints</Checkbox>)}
</FormItem>

<FormItem label="Preferred Mode" hasFeedback>
{getFieldDecorator("settings.preferredMode")(
<Select allowClear optionFilterProp="children" style={{ width: "100%" }}>
Expand All @@ -193,6 +199,25 @@ class TaskTypeCreateView extends React.PureComponent<Props, State> {
)}
</FormItem>

<div
style={{
display:
this.props.form.getFieldValue("tracingType") === "skeleton" ? "block" : "none",
}}
>
<FormItem label="Settings">
{getFieldDecorator("settings.somaClickingAllowed", {
valuePropName: "checked",
})(<Checkbox>Allow Single-node-tree mode (&quot;Soma clicking&quot;)</Checkbox>)}
</FormItem>

<FormItem>
{getFieldDecorator("settings.branchPointsAllowed", {
valuePropName: "checked",
})(<Checkbox>Allow Branchpoints</Checkbox>)}
</FormItem>
</div>

<FormItem>
<RecommendedConfigurationView
form={this.props.form}
Expand Down
24 changes: 18 additions & 6 deletions app/assets/javascripts/admin/tasktype/task_type_list_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,12 +182,24 @@ class TaskTypeListView extends React.PureComponent<Props, State> {
dataIndex="settings"
key="allowedModes"
width={100}
render={settings =>
settings.allowedModes.map(mode => (
<Tag key={mode} color={mode === settings.preferredMode ? "blue" : null}>
{mode}
</Tag>
))
render={(settings, taskType) =>
[
taskType.tracingType === "skeleton" ? (
<Tag color="green" key="tracingType">
skeleton
</Tag>
) : (
<Tag color="orange" key="tracingType">
volume
</Tag>
),
].concat(
settings.allowedModes.map(mode => (
<Tag key={mode} color={mode === settings.preferredMode ? "blue" : null}>
{mode}
</Tag>
)),
)
}
/>
<Column
Expand Down
2 changes: 2 additions & 0 deletions app/assets/javascripts/dashboard/dashboard_task_list_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,8 @@ class DashboardTaskListView extends React.PureComponent<PropsWithRouter, State>
<span style={{ marginRight: 8 }}>
{task.type.summary} (<FormattedDate timestamp={task.created} />)
</span>
{task.annotation.tracing.skeleton == null ? null : <Tag color="green">skeleton</Tag>}
{task.annotation.tracing.volume == null ? null : <Tag color="orange">volume</Tag>}
{task.type.settings.allowedModes.map(mode => (
<Tag key={mode}>{mode}</Tag>
))}
Expand Down
11 changes: 8 additions & 3 deletions app/assets/javascripts/oxalis/api/api_latest.js
Original file line number Diff line number Diff line change
Expand Up @@ -371,23 +371,28 @@ class TracingApi {
const state = Store.getState();
const { annotationType, annotationId } = state.tracing;
const { task } = state;
if (task == null) {
// Satisfy flow
throw new Error("Cannot find task to finish.");
}

await Model.save();
await finishAnnotation(annotationId, annotationType);
try {
const annotation = await requestTask();

const isDifferentDataset = state.dataset.name !== annotation.dataSetName;
const isDifferentTaskType = annotation.task.type.id !== Utils.__guard__(task, x => x.type.id);
const isDifferentTaskType = annotation.task.type.id !== task.type.id;
const involvesVolumeTask = state.tracing.volume != null || annotation.tracing.volume != null;

const currentScript = task != null && task.script != null ? task.script.gist : null;
const currentScript = task.script != null ? task.script.gist : null;
const nextScript = annotation.task.script != null ? annotation.task.script.gist : null;
const isDifferentScript = currentScript !== nextScript;

const newTaskUrl = `/annotations/${annotation.typ}/${annotation.id}`;

// In some cases the page needs to be reloaded, in others the tracing can be hot-swapped
if (isDifferentDataset || isDifferentTaskType || isDifferentScript) {
if (isDifferentDataset || isDifferentTaskType || isDifferentScript || involvesVolumeTask) {
location.href = newTaskUrl;
} else {
await this.restart(annotation.typ, annotation.id, ControlModeEnum.TRACE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export function getByteCount(dataset: APIDataset, layerName: string): number {
return getByteCountFromLayer(getLayerByName(dataset, layerName));
}

type Boundary = { lowerBoundary: Vector3, upperBoundary: Vector3 };
export type Boundary = { lowerBoundary: Vector3, upperBoundary: Vector3 };

export function getLayerBoundaries(dataset: APIDataset, layerName: string): Boundary {
const { topLeft, width, height, depth } = getLayerByName(dataset, layerName).boundingBox;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,13 +120,25 @@ class DataCube {
this.cubes[i] = new CubeEntry(zoomedCubeBoundary);
}

this.boundingBox = new BoundingBox(getSomeTracing(Store.getState().tracing).boundingBox, this);
const shouldBeRestrictedByTracingBoundingBox = () => {
const { task } = Store.getState();
const isVolumeTask = task != null && task.type.tracingType === "volume";
return !isVolumeTask;
};
this.boundingBox = new BoundingBox(
shouldBeRestrictedByTracingBoundingBox()
? getSomeTracing(Store.getState().tracing).boundingBox
: null,
this,
);

listenToStoreProperty(
state => getSomeTracing(state.tracing).boundingBox,
boundingBox => {
this.boundingBox = new BoundingBox(boundingBox, this);
this.forgetOutOfBoundaryBuckets();
if (shouldBeRestrictedByTracingBoundingBox()) {
this.boundingBox = new BoundingBox(boundingBox, this);
this.forgetOutOfBoundaryBuckets();
}
},
);
}
Expand Down
13 changes: 13 additions & 0 deletions app/assets/javascripts/oxalis/model/reducers/reducer_helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import Maybe from "data.maybe";

import type { APIAnnotation, ServerBoundingBox } from "admin/api_flow_types";
import type { Annotation, BoundingBoxObject } from "oxalis/store";
import type { Boundary } from "oxalis/model/accessors/dataset_accessor";
import type { BoundingBoxType } from "oxalis/constants";
import { V3 } from "libs/mjs";
import * as Utils from "libs/utils";

export function convertServerBoundingBoxToFrontend(
Expand Down Expand Up @@ -32,6 +34,17 @@ export function convertFrontendBoundingBoxToServer(
.getOrElse(null);
}

export function convertBoundariesToBoundingBox(boundary: Boundary): BoundingBoxObject {
const [width, height, depth] = V3.sub(boundary.upperBoundary, boundary.lowerBoundary);
return {
width,
height,
depth,
topLeft: boundary.lowerBoundary,
};
}

// Currently unused.
export function convertPointToVecInBoundingBox(boundingBox: ServerBoundingBox): BoundingBoxObject {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this function unused now?
But we can also check that when we refactor the bounding box/upper-lower boundary mess, maybe add a comment.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's unused. I'd leave it as is, though, since the cost of recreating the function (in case it's needed again) is higher than having the few additional unused lines in the code base. Getting rid of some bounding box types will be more rewarding :)

return {
width: boundingBox.width,
Expand Down
Loading