diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md
index eeeac1fe665..c9656d6b48f 100644
--- a/CHANGELOG.unreleased.md
+++ b/CHANGELOG.unreleased.md
@@ -14,6 +14,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released
-
### Changed
+- The menu for viewing, editing and creating annotations for a dataset in the dashboard was cleaned up a bit. Creating a hybrid (skeleton + volume) annotation is now the default way of annotating a dataset. The other options are still available via a dropdown. [#4939](https://github.com/scalableminds/webknossos/pull/4939)
- For 2d-only datasets the view mode toggle is hidden. [#4952](https://github.com/scalableminds/webknossos/pull/4952)
### Fixed
diff --git a/app/models/binary/DataSetService.scala b/app/models/binary/DataSetService.scala
index ed8382eea18..db1e23b5a0e 100644
--- a/app/models/binary/DataSetService.scala
+++ b/app/models/binary/DataSetService.scala
@@ -241,22 +241,20 @@ class DataSetService @Inject()(organizationDAO: OrganizationDAO,
def dataSourceFor(dataSet: DataSet, organization: Option[Organization] = None, skipResolutions: Boolean = false)(
implicit ctx: DBAccessContext): Fox[InboxDataSource] =
- for {
+ (for {
organization <- Fox.fillOption(organization) {
organizationDAO.findOne(dataSet._organization)(GlobalAccessContext) ?~> "organization.notFound"
}
- dataLayersBox <- dataSetDataLayerDAO.findAllForDataSet(dataSet._id, skipResolutions).futureBox
+ dataLayers <- dataSetDataLayerDAO.findAllForDataSet(dataSet._id, skipResolutions)
dataSourceId = DataSourceId(dataSet.name, organization.name)
} yield {
- dataLayersBox match {
- case Full(dataLayers) if (dataLayers.nonEmpty) =>
- for {
- scale <- dataSet.scale
- } yield GenericDataSource[DataLayer](dataSourceId, dataLayers, scale)
- case _ =>
- Some(UnusableDataSource[DataLayer](dataSourceId, dataSet.status, dataSet.scale))
- }
- }
+ if (dataSet.isUsable)
+ for {
+ scale <- dataSet.scale.toFox ?~> "dataSet.source.usableButNoScale"
+ } yield GenericDataSource[DataLayer](dataSourceId, dataLayers, scale)
+ else
+ Fox.successful(UnusableDataSource[DataLayer](dataSourceId, dataSet.status, dataSet.scale))
+ }).flatten
def logoUrlFor(dataSet: DataSet, organization: Option[Organization]): Fox[String] =
dataSet.logoUrl match {
diff --git a/conf/messages b/conf/messages
index ed4e361b00f..5d4cbe63b51 100644
--- a/conf/messages
+++ b/conf/messages
@@ -94,6 +94,7 @@ dataSet.import.impossible.name=Import impossible. Dataset name can only consist
dataSet.name.alreadyTaken=This dataset name is already in use.
dataSet.name.notInSamples=This dataset name is not one of the available sample datasets.
dataSet.source.notFound=The data source for the data source couldn’t be found
+dataSet.source.usableButNoScale=Dataset {0} is marked as active but has no scale.
dataSet.import.success=The import of the dataset was successful
dataSet.import.failed=Failed to import the dataset
dataSet.import.fileAccessDenied=Cannot create organization folder. Please make sure webKnossos has write permissions in the “binaryData” directory
diff --git a/frontend/javascripts/admin/admin_rest_api.js b/frontend/javascripts/admin/admin_rest_api.js
index 3e5b68cf28a..55fba93c41d 100644
--- a/frontend/javascripts/admin/admin_rest_api.js
+++ b/frontend/javascripts/admin/admin_rest_api.js
@@ -761,7 +761,6 @@ export async function getDatasets(
const parameters = isUnreported != null ? `?isUnreported=${String(isUnreported)}` : "";
const datasets = await Request.receiveJSON(`/api/datasets${parameters}`);
assertResponseLimit(datasets);
-
return datasets;
}
diff --git a/frontend/javascripts/dashboard/advanced_dataset/dataset_action_view.js b/frontend/javascripts/dashboard/advanced_dataset/dataset_action_view.js
index 5b1c632c84f..4d49d75b87f 100644
--- a/frontend/javascripts/dashboard/advanced_dataset/dataset_action_view.js
+++ b/frontend/javascripts/dashboard/advanced_dataset/dataset_action_view.js
@@ -3,68 +3,136 @@ import { Dropdown, Menu, Icon, Tooltip } from "antd";
import { Link, withRouter } from "react-router-dom";
import * as React from "react";
-import type { APIMaybeUnimportedDataset, TracingType } from "types/api_flow_types";
+import type { APIMaybeUnimportedDataset, APIDataset } from "types/api_flow_types";
import { clearCache } from "admin/admin_rest_api";
+import {
+ getSegmentationLayer,
+ doesSupportVolumeWithFallback,
+} from "oxalis/model/accessors/dataset_accessor";
import Toast from "libs/toast";
import messages from "messages";
-const createTracingOverlayMenu = (dataset: APIMaybeUnimportedDataset, type: TracingType) => {
- const typeCapitalized = type.charAt(0).toUpperCase() + type.slice(1);
-
- const hasSegmentationLayer =
- dataset.dataSource.dataLayers != null
- ? dataset.dataSource.dataLayers.find(layer => layer.category === "segmentation") != null
- : false;
- const disabledLinkStyle = { pointerEvents: "none", color: "rgb(173, 173, 173)" };
+const getNewTracingMenu = (maybeUnimportedDataset: APIMaybeUnimportedDataset) => {
+ if (!maybeUnimportedDataset.isActive) {
+ // The dataset isn't imported. This menu won't be shown, anyway.
+ return
;
+ }
+ const dataset: APIDataset = maybeUnimportedDataset;
- return (
-
- );
+ );
+ };
+
+ const segmentationLayer = getSegmentationLayer(dataset);
+
+ if (segmentationLayer != null) {
+ if (doesSupportVolumeWithFallback(dataset)) {
+ return (
+
+ );
+ } else {
+ return (
+
+ );
+ }
+ } else {
+ return (
+
+ );
+ }
};
-export const createTracingOverlayMenuWithCallback = (
- dataset: APIMaybeUnimportedDataset,
- type: TracingType,
- onClick: (APIMaybeUnimportedDataset, TracingType, boolean) => Promise,
-) => {
- const typeCapitalized = type.charAt(0).toUpperCase() + type.slice(1);
+const disabledStyle = { pointerEvents: "none", color: "rgba(0, 0, 0, 0.25)" };
+function getDisabledWhenReloadingStyle(isReloading) {
+ return isReloading ? disabledStyle : null;
+}
+
+function NewAnnotationLink({ dataset, isReloading }) {
+ const newTracingMenu = getNewTracingMenu(dataset);
+ const withFallback = doesSupportVolumeWithFallback(dataset) ? "true" : "false";
+
return (
-
+
+ {isReloading ? null : (
+
+ )}
+
);
-};
+}
+
+function ImportLink({ dataset }) {
+ return (
+
+
+
+ Import
+
+
+
{dataset.dataSource.status}
+
+ );
+}
type Props = {
dataset: APIMaybeUnimportedDataset,
@@ -74,6 +142,28 @@ type State = {
isReloading: boolean,
};
+function LinkWithDisabled({
+ disabled,
+ ...rest
+}: {
+ disabled?: boolean,
+ style?: Object,
+ to: string,
+}) {
+ const maybeDisabledStyle = disabled ? disabledStyle : null;
+ const adaptedStyle =
+ rest.style != null
+ ? {
+ ...rest.style,
+ ...maybeDisabledStyle,
+ }
+ : maybeDisabledStyle;
+
+ return (
+ (disabled ? e.preventDefault() : null)} />
+ );
+}
+
class DatasetActionView extends React.PureComponent {
state = {
isReloading: false,
@@ -93,130 +183,55 @@ class DatasetActionView extends React.PureComponent {
render() {
const { dataset } = this.props;
const { isReloading } = this.state;
- const centerBackgroundImageStyle: { verticalAlign: string, filter?: string } = {
- verticalAlign: "middle",
- };
- if (isReloading) {
- // We need to explicitly grayscale the images when the dataset is being reloaded.
- centerBackgroundImageStyle.filter = "grayscale(100%) opacity(25%)";
- }
- const disabledWhenReloadingStyle = isReloading
- ? { pointerEvents: "none", color: "rgba(0, 0, 0, 0.25)" }
- : null;
- const disabledWhenReloadingAction = e => (isReloading ? e.preventDefault() : null);
-
- const volumeTracingMenu = (
-
-
- {" "}
- Start Volume Annotation
-
-
- );
- const hybridTracingMenu = (
-
-
-
- Start Hybrid Annotation
-
-
- );
+ const disabledWhenReloadingStyle = getDisabledWhenReloadingStyle(isReloading);
return (
- {dataset.isEditable && dataset.dataSource.dataLayers == null ? (
-
-
-
- Import
-
-
-
{dataset.dataSource.status}
-
- ) : null}
+ {dataset.isEditable && !dataset.isActive ?
: null}
{dataset.isActive ? (
) : null}
diff --git a/frontend/javascripts/dashboard/advanced_dataset/dataset_table.js b/frontend/javascripts/dashboard/advanced_dataset/dataset_table.js
index 9cc05f5274e..5a3ec77ecdf 100644
--- a/frontend/javascripts/dashboard/advanced_dataset/dataset_table.js
+++ b/frontend/javascripts/dashboard/advanced_dataset/dataset_table.js
@@ -88,7 +88,7 @@ class DatasetTable extends React.PureComponent {
const filterByHasLayers = datasets =>
this.props.isUserAdmin || this.props.isUserDatasetManager
? datasets
- : datasets.filter(dataset => dataset.dataSource.dataLayers != null);
+ : datasets.filter(dataset => dataset.isActive && dataset.dataSource.dataLayers.length > 0);
return filterByQuery(filterByMode(filterByHasLayers(this.props.datasets)));
}
@@ -189,7 +189,9 @@ class DatasetTable extends React.PureComponent {
key="scale"
width={230}
render={(__, dataset: APIMaybeUnimportedDataset) =>
- `${formatScale(dataset.dataSource.scale)} ${getDatasetExtentAsString(dataset)}`
+ `${
+ dataset.isActive ? formatScale(dataset.dataSource.scale) : ""
+ } ${getDatasetExtentAsString(dataset)}`
}
/>
{
dataIndex="dataSource.dataLayers"
render={(__, dataset: APIMaybeUnimportedDataset) => (
- {(dataset.dataSource.dataLayers || []).map(layer => (
+ {(dataset.isActive ? dataset.dataSource.dataLayers : []).map(layer => (
{layer.category} - {layer.elementClass}
diff --git a/frontend/javascripts/dashboard/dataset/dataset_cache_provider.js b/frontend/javascripts/dashboard/dataset/dataset_cache_provider.js
index 9a66388d522..0d9778f3eee 100644
--- a/frontend/javascripts/dashboard/dataset/dataset_cache_provider.js
+++ b/frontend/javascripts/dashboard/dataset/dataset_cache_provider.js
@@ -20,7 +20,7 @@ type Context = {
fetchDatasets: (options?: Options) => Promise
,
};
-const wkDatasetsCacheKey = "wk.datasets";
+const wkDatasetsCacheKey = "wk.datasets-v2";
export const datasetCache = {
set(datasets: APIMaybeUnimportedDataset[]): void {
UserLocalStorage.setItem(wkDatasetsCacheKey, JSON.stringify(datasets));
diff --git a/frontend/javascripts/oxalis/model/accessors/dataset_accessor.js b/frontend/javascripts/oxalis/model/accessors/dataset_accessor.js
index b078bd53383..fbd748d1a0c 100644
--- a/frontend/javascripts/oxalis/model/accessors/dataset_accessor.js
+++ b/frontend/javascripts/oxalis/model/accessors/dataset_accessor.js
@@ -427,15 +427,14 @@ export function getDatasetExtentAsString(
dataset: APIMaybeUnimportedDataset,
inVoxel: boolean = true,
): string {
- if (!dataset.dataSource.dataLayers || !dataset.dataSource.scale || !dataset.isActive) {
+ if (!dataset.isActive) {
return "";
}
- const importedDataset = ((dataset: any): APIDataset);
if (inVoxel) {
- const extentInVoxel = getDatasetExtentInVoxel(importedDataset);
+ const extentInVoxel = getDatasetExtentInVoxel(dataset);
return `${formatExtentWithLength(extentInVoxel, x => `${x}`)} voxel³`;
}
- const extent = getDatasetExtentInLength(importedDataset);
+ const extent = getDatasetExtentInLength(dataset);
return formatExtentWithLength(extent, formatNumberToLength);
}
@@ -522,7 +521,11 @@ export function isColorLayer(dataset: APIDataset, layerName: string): boolean {
return getLayerByName(dataset, layerName).category === "color";
}
-export function getSegmentationLayer(dataset: APIDataset): ?APISegmentationLayer {
+export function getSegmentationLayer(dataset: APIMaybeUnimportedDataset): ?APISegmentationLayer {
+ if (!dataset.isActive) {
+ return null;
+ }
+
// $FlowIssue[incompatible-type]
// $FlowIssue[prop-missing]
const segmentationLayers: Array = dataset.dataSource.dataLayers.filter(
@@ -538,6 +541,21 @@ export function hasSegmentation(dataset: APIDataset): boolean {
return getSegmentationLayer(dataset) != null;
}
+export function doesSupportVolumeWithFallback(dataset: APIMaybeUnimportedDataset): boolean {
+ if (!dataset.isActive) {
+ return false;
+ }
+ const segmentationLayer = getSegmentationLayer(dataset);
+ if (!segmentationLayer) {
+ return false;
+ }
+
+ const isUint64 =
+ segmentationLayer.elementClass === "uint64" || segmentationLayer.elementClass === "int64";
+ const isFallbackSupported = !isUint64;
+ return isFallbackSupported;
+}
+
export function getColorLayers(dataset: APIDataset): Array {
return dataset.dataSource.dataLayers.filter(dataLayer => isColorLayer(dataset, dataLayer.name));
}
diff --git a/frontend/javascripts/oxalis/view/action_bar_view.js b/frontend/javascripts/oxalis/view/action_bar_view.js
index 1c2162d864a..9367d2130e6 100644
--- a/frontend/javascripts/oxalis/view/action_bar_view.js
+++ b/frontend/javascripts/oxalis/view/action_bar_view.js
@@ -1,7 +1,8 @@
// @flow
-import { Alert, Dropdown } from "antd";
+import { Alert } from "antd";
import { connect } from "react-redux";
import * as React from "react";
+import _ from "lodash";
import type {
APIDataset,
@@ -19,6 +20,7 @@ import {
import { trackAction } from "oxalis/model/helpers/analytics";
import { updateUserSettingAction } from "oxalis/model/actions/settings_actions";
import AddNewLayoutModal from "oxalis/view/action-bar/add_new_layout_modal";
+import AuthenticationModal from "admin/auth/authentication_modal";
import ButtonComponent from "oxalis/view/components/button_component";
import Constants, { type ControlMode, ControlModeEnum, type ViewMode } from "oxalis/constants";
import DatasetPositionView from "oxalis/view/action-bar/dataset_position_view";
@@ -30,9 +32,10 @@ import TracingActionsView, {
import ViewDatasetActionsView from "oxalis/view/action-bar/view_dataset_actions_view";
import ViewModesView from "oxalis/view/action-bar/view_modes_view";
import VolumeActionsView from "oxalis/view/action-bar/volume_actions_view";
-import AuthenticationModal from "admin/auth/authentication_modal";
-import { createTracingOverlayMenuWithCallback } from "dashboard/advanced_dataset/dataset_action_view";
-import { is2dDataset } from "oxalis/model/accessors/dataset_accessor";
+import {
+ is2dDataset,
+ doesSupportVolumeWithFallback,
+} from "oxalis/model/accessors/dataset_accessor";
const VersionRestoreWarning = (
{
state = {
isNewLayoutModalVisible: false,
isAuthenticationModalVisible: false,
- useExistingSegmentation: true,
};
handleResetLayout = () => {
@@ -96,8 +97,12 @@ class ActionBarView extends React.PureComponent {
}
};
- createTracing = async (dataset: APIMaybeUnimportedDataset, useExistingSegmentation: boolean) => {
- const annotation = await createExplorational(dataset, "hybrid", useExistingSegmentation);
+ createTracing = async (dataset: APIMaybeUnimportedDataset) => {
+ // If the dataset supports creating an annotation with a fallback segmentation,
+ // use it (as the fallback can always be removed later)
+ const withFallback = doesSupportVolumeWithFallback(dataset);
+
+ const annotation = await createExplorational(dataset, "hybrid", withFallback);
trackAction("Create hybrid tracing (from view mode)");
location.href = `${location.origin}/annotations/${annotation.typ}/${annotation.id}${
location.hash
@@ -107,44 +112,24 @@ class ActionBarView extends React.PureComponent {
renderStartTracingButton(): React.Node {
const { activeUser, dataset } = this.props;
const needsAuthentication = activeUser == null;
- const hasSegmentationLayer = dataset.dataSource.dataLayers.some(
- layer => layer.category === "segmentation",
- );
- const handleCreateTracing = async (
- _dataset: APIMaybeUnimportedDataset,
- _type: TracingType,
- useExistingSegmentation: boolean,
- ) => {
+ const handleCreateTracing = async (_dataset: APIMaybeUnimportedDataset, _type: TracingType) => {
if (needsAuthentication) {
- this.setState({ isAuthenticationModalVisible: true, useExistingSegmentation });
+ this.setState({ isAuthenticationModalVisible: true });
} else {
- this.createTracing(dataset, useExistingSegmentation);
+ this.createTracing(dataset);
}
};
- if (hasSegmentationLayer) {
- return (
-
-
- Create Annotation
-
-
- );
- } else {
- return (
- handleCreateTracing(dataset, "hybrid", false)}
- >
- Create Annotation
-
- );
- }
+ return (
+ handleCreateTracing(dataset, "hybrid")}
+ >
+ Create Annotation
+
+ );
}
render() {
@@ -197,7 +182,7 @@ class ActionBarView extends React.PureComponent {
{
this.setState({ isAuthenticationModalVisible: false });
- this.createTracing(dataset, this.state.useExistingSegmentation);
+ this.createTracing(dataset);
}}
onCancel={() => this.setState({ isAuthenticationModalVisible: false })}
visible={this.state.isAuthenticationModalVisible}
diff --git a/frontend/javascripts/types/api_flow_types.js b/frontend/javascripts/types/api_flow_types.js
index 9c4b83a9633..5ad619aceb1 100644
--- a/frontend/javascripts/types/api_flow_types.js
+++ b/frontend/javascripts/types/api_flow_types.js
@@ -79,10 +79,7 @@ type APIDataSourceBase = {
+status?: string,
};
-export type APIMaybeUnimportedDataSource = APIDataSourceBase & {
- +dataLayers?: Array,
- +scale?: ?Vector3,
-};
+type APIUnimportedDatasource = APIDataSourceBase;
export type APIDataSource = APIDataSourceBase & {
+dataLayers: Array,
@@ -147,16 +144,18 @@ type APIDatasetBase = APIDatasetId & {
+publication: ?APIPublication,
};
-export type APIMaybeUnimportedDataset = APIDatasetBase & {
- +dataSource: APIMaybeUnimportedDataSource,
- +isActive: boolean,
-};
-
export type APIDataset = APIDatasetBase & {
+dataSource: APIDataSource,
+isActive: true,
};
+type APIUnimportedDataset = APIDatasetBase & {
+ +dataSource: APIUnimportedDatasource,
+ +isActive: false,
+};
+
+export type APIMaybeUnimportedDataset = APIUnimportedDataset | APIDataset;
+
export type APISampleDataset = {
+name: string,
+description: string,
diff --git a/frontend/javascripts/types/schemas/dataset_view_configuration_defaults.js b/frontend/javascripts/types/schemas/dataset_view_configuration_defaults.js
index 486e99cca6d..1168f294146 100644
--- a/frontend/javascripts/types/schemas/dataset_view_configuration_defaults.js
+++ b/frontend/javascripts/types/schemas/dataset_view_configuration_defaults.js
@@ -51,8 +51,7 @@ export const enforceValidatedDatasetViewConfiguration = (
const { layers } = datasetViewConfiguration;
const newLayerConfig = {};
- if (maybeUnimportedDataset.dataSource.dataLayers != null) {
- // $FlowFixMe[incompatible-type]
+ if (maybeUnimportedDataset.isActive) {
const dataset: APIDataset = maybeUnimportedDataset;
dataset.dataSource.dataLayers.forEach(layer => {
const layerConfigDefault = getDefaultLayerViewConfiguration(
diff --git a/frontend/stylesheets/_dashboard.less b/frontend/stylesheets/_dashboard.less
index f7349fec02b..fc3ba04a12d 100644
--- a/frontend/stylesheets/_dashboard.less
+++ b/frontend/stylesheets/_dashboard.less
@@ -239,7 +239,7 @@
}
.dataset-actions {
- a {
+ & > a {
display: block;
img {
width: 14px;