diff --git a/CHANGELOG.md b/CHANGELOG.md index dc77bc17402..0e8db8d7bd4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,10 +12,12 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.md). ### Added - Indicating active nml downloads with a loading icon. [#4228](https://github.com/scalableminds/webknossos/pull/4228) - Added possibility for users to see their own time statistics. [#4220](https://github.com/scalableminds/webknossos/pull/4220) +- The segmentation layer can now be turned invisible and also supports the find data feature. [#4232](https://github.com/scalableminds/webknossos/pull/4232) ### Changed - Each of the columns of the dataset table and explorative annotations table in the dashboard now have an individual fixed width, so the tables become scrollable on smaller screens. [#4207](https://github.com/scalableminds/webknossos/pull/4207) - When uploading a zipped annotation (such as volume / hybrid / collection), the zip name is used for the resulting explorative annotation, rather than the name of the contained NML file. [#4222](https://github.com/scalableminds/webknossos/pull/4222) +- Color and segmentation layer are not longer treated separately in the dataset settings in tracing/view mode. [#4232](https://github.com/scalableminds/webknossos/pull/4232) ### Fixed - Data for disabled or invisible layers will no longer be downloaded, saving bandwidth and speeding up webKnossos in general. [#4202](https://github.com/scalableminds/webknossos/pull/4202) diff --git a/frontend/javascripts/messages.js b/frontend/javascripts/messages.js index 90039a2bcf9..93f5d141829 100644 --- a/frontend/javascripts/messages.js +++ b/frontend/javascripts/messages.js @@ -17,7 +17,6 @@ export const settings = { fourBit: "4 Bit", interpolation: "Interpolation", quality: "Quality", - segmentationOpacity: "Segmentation Opacity", highlightHoveredCellId: "Highlight Hovered Cells", zoom: "Zoom", renderMissingDataBlack: "Render Missing Data Black", diff --git a/frontend/javascripts/oxalis/controller/scene_controller.js b/frontend/javascripts/oxalis/controller/scene_controller.js index ff3436178ec..005be0ff310 100644 --- a/frontend/javascripts/oxalis/controller/scene_controller.js +++ b/frontend/javascripts/oxalis/controller/scene_controller.js @@ -413,6 +413,12 @@ class SceneController { } } + setSegmentationVisibility(isVisible: boolean): void { + for (const plane of _.values(this.planes)) { + plane.setSegmentationVisibility(isVisible); + } + } + setIsMappingEnabled(isMappingEnabled: boolean): void { for (const plane of _.values(this.planes)) { plane.setIsMappingEnabled(isMappingEnabled); @@ -457,6 +463,11 @@ class SceneController { segmentationOpacity => this.setSegmentationAlpha(segmentationOpacity), ); + listenToStoreProperty( + storeState => storeState.datasetConfiguration.isSegmentationDisabled, + isSegmentationDisabled => this.setSegmentationVisibility(!isSegmentationDisabled), + ); + listenToStoreProperty( storeState => storeState.datasetConfiguration.interpolation, interpolation => this.setInterpolation(interpolation), diff --git a/frontend/javascripts/oxalis/default_state.js b/frontend/javascripts/oxalis/default_state.js index 3c1ca319679..60b96942e72 100644 --- a/frontend/javascripts/oxalis/default_state.js +++ b/frontend/javascripts/oxalis/default_state.js @@ -39,6 +39,7 @@ const defaultState: OxalisState = { interpolation: false, layers: {}, quality: 0, + isSegmentationDisabled: false, loadingStrategy: "PROGRESSIVE_QUALITY", segmentationOpacity: 20, highlightHoveredCellId: true, diff --git a/frontend/javascripts/oxalis/geometries/materials/plane_material_factory.js b/frontend/javascripts/oxalis/geometries/materials/plane_material_factory.js index c33d6164f50..49fdfafe709 100644 --- a/frontend/javascripts/oxalis/geometries/materials/plane_material_factory.js +++ b/frontend/javascripts/oxalis/geometries/materials/plane_material_factory.js @@ -300,6 +300,12 @@ class PlaneMaterialFactory { this.uniforms.alpha.value = alpha / 100; }; + this.material.setSegmentationVisibility = isVisible => { + this.uniforms.alpha.value = isVisible + ? Store.getState().datasetConfiguration.segmentationOpacity / 100 + : 0; + }; + this.material.setUseBilinearFiltering = isEnabled => { this.uniforms.useBilinearFiltering.value = isEnabled; }; diff --git a/frontend/javascripts/oxalis/geometries/plane.js b/frontend/javascripts/oxalis/geometries/plane.js index fb2e9cbd6db..4e8b40afd12 100644 --- a/frontend/javascripts/oxalis/geometries/plane.js +++ b/frontend/javascripts/oxalis/geometries/plane.js @@ -169,6 +169,10 @@ class Plane { this.plane.material.setSegmentationAlpha(alpha); } + setSegmentationVisibility(isVisible: boolean): void { + this.plane.material.setSegmentationVisibility(isVisible); + } + getMeshes = () => [this.plane, this.TDViewBorders, this.crosshair[0], this.crosshair[1]]; setLinearInterpolationEnabled = (enabled: boolean) => { diff --git a/frontend/javascripts/oxalis/store.js b/frontend/javascripts/oxalis/store.js index 5ff68bd3ff4..19522e2b59d 100644 --- a/frontend/javascripts/oxalis/store.js +++ b/frontend/javascripts/oxalis/store.js @@ -227,6 +227,7 @@ export type DatasetConfiguration = {| }, +quality: 0 | 1 | 2, +segmentationOpacity: number, + +isSegmentationDisabled: boolean, +highlightHoveredCellId: boolean, +renderIsosurfaces: boolean, +position?: Vector3, diff --git a/frontend/javascripts/oxalis/view/settings/dataset_settings_view.js b/frontend/javascripts/oxalis/view/settings/dataset_settings_view.js index 1d4ad221d6f..b3581f9905d 100644 --- a/frontend/javascripts/oxalis/view/settings/dataset_settings_view.js +++ b/frontend/javascripts/oxalis/view/settings/dataset_settings_view.js @@ -3,7 +3,7 @@ * @flow */ -import { Col, Collapse, Divider, Icon, Row, Select, Switch, Tag, Tooltip } from "antd"; +import { Col, Collapse, Icon, Row, Select, Switch, Tag, Tooltip } from "antd"; import type { Dispatch } from "redux"; import { connect } from "react-redux"; import * as React from "react"; @@ -116,89 +116,156 @@ class DatasetSettings extends React.PureComponent { Object.keys(layers).forEach(otherLayerName => this.props.onChangeLayer(otherLayerName, "isDisabled", !isVisible), ); + this.props.onChange("isSegmentationDisabled", !isVisible); }; isLayerExclusivelyVisible = (layerName: string): boolean => { const { layers } = this.props.datasetConfiguration; - return Object.keys(layers).every(otherLayerName => { + const isOnlyGivenLayerVisible = Object.keys(layers).every(otherLayerName => { const { isDisabled } = layers[otherLayerName]; return layerName === otherLayerName ? !isDisabled : isDisabled; }); + const { isSegmentationDisabled } = this.props.datasetConfiguration; + const segmentation = Model.getSegmentationLayer(); + const segmentationLayerName = segmentation != null ? segmentation.name : null; + if (layerName === segmentationLayerName) { + return isOnlyGivenLayerVisible && !isSegmentationDisabled; + } else { + return isOnlyGivenLayerVisible && isSegmentationDisabled; + } }; - getColorSettings = ( - [layerName, layer]: [string, DatasetLayerConfiguration], - layerIndex: number, - isLastLayer: boolean, - ) => { - const elementClass = getElementClass(this.props.dataset, layerName); - const { alpha, color, intensityRange, isDisabled } = layer; + getEnableDisableLayerSwitch = ( + isDisabled: boolean, + onChange: (boolean, SyntheticMouseEvent<>) => void, + ) => ( + + {/* This div is necessary for the tooltip to be displayed */} +
+ +
+
+ ); + + getHistogram = (layerName: string, layer: DatasetLayerConfiguration) => { + const { intensityRange } = layer; let histograms = [ { numberOfElements: 256, elementCounts: new Array(256).fill(0), min: 0, max: 255 }, ]; if (this.state.histograms && this.state.histograms[layerName]) { histograms = this.state.histograms[layerName]; } + return ( + + ); + }; + + getLayerSettingsHeader = ( + isDisabled: boolean, + isColorLayer: boolean, + layerName: string, + elementClass: string, + ) => { + const setSingleLayerVisibility = (isVisible: boolean) => { + if (isColorLayer) { + this.props.onChangeLayer(layerName, "isDisabled", !isVisible); + } else { + this.props.onChange("isSegmentationDisabled", !isVisible); + } + }; + const onChange = (value, event) => { + if (!event.ctrlKey) { + setSingleLayerVisibility(value); + return; + } + // If ctrl is pressed, toggle between "all layers visible" and + // "only selected layer visible". + if (this.isLayerExclusivelyVisible(layerName)) { + this.setVisibilityForAllLayers(true); + } else { + this.setVisibilityForAllLayers(false); + setSingleLayerVisibility(true); + } + }; + return ( + + + {this.getEnableDisableLayerSwitch(isDisabled, onChange)} + {layerName} + {elementClass} Layer + {this.getFindDataButton(layerName, isDisabled)} + + + ); + }; + + getLayerSettings = ( + layerName: string, + layer: ?DatasetLayerConfiguration, + isColorLayer: boolean = true, + ) => { + // Ensure that a color layer needs a layer. + if (isColorLayer && !layer) { + return null; + } + const elementClass = getElementClass(this.props.dataset, layerName); + const isDisabled = + isColorLayer && layer != null + ? layer.isDisabled + : this.props.datasetConfiguration.isSegmentationDisabled; return (
- - - {/* TODO Maybe use the new antd icons instead of the switch when upgrading antd. */} - - {/* This div is necessary for the tooltip to be displayed */} -
- { - if (!event.ctrlKey) { - this.props.onChangeLayer(layerName, "isDisabled", !val); - return; - } - // If ctrl is pressed, toggle between "all layers visible" and - // "only selected layer visible". - if (this.isLayerExclusivelyVisible(layerName)) { - this.setVisibilityForAllLayers(true); - } else { - this.setVisibilityForAllLayers(false); - this.props.onChangeLayer(layerName, "isDisabled", false); - } - }} - checked={!isDisabled} - /> -
-
- {layerName} - {elementClass} Layer - {this.getFindDataButton(layerName, isDisabled)} - -
+ {this.getLayerSettingsHeader(isDisabled, isColorLayer, layerName, elementClass)} {isDisabled ? null : ( - {isHistogramSupported(elementClass) ? ( - - ) : null} + {isHistogramSupported(elementClass) && layerName != null && layer != null + ? this.getHistogram(layerName, layer) + : null} - + {isColorLayer && layer != null ? ( + + ) : ( + + )} + {!isColorLayer && + (this.props.controlMode === ControlModeEnum.VIEW || window.allowIsosurfaces) ? ( + + ) : null} )} - {!isLastLayer && }
); }; @@ -240,66 +307,41 @@ class DatasetSettings extends React.PureComponent { getSegmentationPanel() { const segmentation = Model.getSegmentationLayer(); const segmentationLayerName = segmentation != null ? segmentation.name : null; - return ( - - {segmentationLayerName ? ( -
- {this.getFindDataButton(segmentationLayerName, false)} -
- ) : null} - - - - {this.props.controlMode === ControlModeEnum.VIEW || window.allowIsosurfaces ? ( - - ) : null} -
- ); + if (!segmentationLayerName) { + return null; + } + return this.getLayerSettings(segmentationLayerName, null, false); } renderPanelHeader = (hasInvisibleLayers: boolean) => hasInvisibleLayers ? ( - Color Layers + Layers ) : ( - "Color Layers" + "Layers" ); render() { const { layers } = this.props.datasetConfiguration; - const colorSettings = Object.entries(layers).map((entry, index) => + const colorSettings = Object.entries(layers).map(entry => { + const [layerName, layer] = entry; // $FlowFixMe Object.entries returns mixed for Flow - this.getColorSettings(entry, index, index === _.size(layers) - 1), - ); + return this.getLayerSettings(layerName, layer, true); + }); const hasInvisibleLayers = Object.keys(layers).find( layerName => layers[layerName].isDisabled || layers[layerName].alpha === 0, - ) != null; - + ) != null || this.props.datasetConfiguration.isSegmentationDisabled; return ( {colorSettings} + {this.props.hasSegmentation ? this.getSegmentationPanel() : null} - {this.props.hasSegmentation ? this.getSegmentationPanel() : null}