Skip to content

Commit

Permalink
Unify segementation and color layers (#4232)
Browse files Browse the repository at this point in the history
* merged ui of color and segmentation layers

* fix flow and added visibiltiy toggling of segmentation layer

* fixed rendering test

* added changelog entry

* small fixes

* made code pretty

* applied feedback
  • Loading branch information
MichaelBuessemeyer authored Aug 21, 2019
1 parent 92bceb6 commit fe30714
Show file tree
Hide file tree
Showing 9 changed files with 162 additions and 95 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 0 additions & 1 deletion frontend/javascripts/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
11 changes: 11 additions & 0 deletions frontend/javascripts/oxalis/controller/scene_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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),
Expand Down
1 change: 1 addition & 0 deletions frontend/javascripts/oxalis/default_state.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const defaultState: OxalisState = {
interpolation: false,
layers: {},
quality: 0,
isSegmentationDisabled: false,
loadingStrategy: "PROGRESSIVE_QUALITY",
segmentationOpacity: 20,
highlightHoveredCellId: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand Down
4 changes: 4 additions & 0 deletions frontend/javascripts/oxalis/geometries/plane.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
1 change: 1 addition & 0 deletions frontend/javascripts/oxalis/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ export type DatasetConfiguration = {|
},
+quality: 0 | 1 | 2,
+segmentationOpacity: number,
+isSegmentationDisabled: boolean,
+highlightHoveredCellId: boolean,
+renderIsosurfaces: boolean,
+position?: Vector3,
Expand Down
230 changes: 136 additions & 94 deletions frontend/javascripts/oxalis/view/settings/dataset_settings_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -116,89 +116,156 @@ class DatasetSettings extends React.PureComponent<DatasetSettingsProps, State> {
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,
) => (
<Tooltip title={isDisabled ? "Enable" : "Disable"} placement="top">
{/* This div is necessary for the tooltip to be displayed */}
<div style={{ display: "inline-block", marginRight: 8 }}>
<Switch size="small" onChange={onChange} checked={!isDisabled} />
</div>
</Tooltip>
);

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 (
<Histogram
data={histograms}
min={intensityRange[0]}
max={intensityRange[1]}
layerName={layerName}
/>
);
};

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 (
<Row style={{ marginTop: isDisabled ? 0 : 16 }}>
<Col span={24}>
{this.getEnableDisableLayerSwitch(isDisabled, onChange)}
<span style={{ fontWeight: 700 }}>{layerName}</span>
<Tag style={{ cursor: "default", marginLeft: 8 }}>{elementClass} Layer</Tag>
{this.getFindDataButton(layerName, isDisabled)}
</Col>
</Row>
);
};

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 (
<div key={layerName}>
<Row>
<Col span={24}>
{/* TODO Maybe use the new antd icons instead of the switch when upgrading antd. */}
<Tooltip title={isDisabled ? "Enable" : "Disable"} placement="top">
{/* This div is necessary for the tooltip to be displayed */}
<div style={{ display: "inline-block", marginRight: 8 }}>
<Switch
size="small"
onChange={(val, event) => {
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}
/>
</div>
</Tooltip>
<span style={{ fontWeight: 700 }}>{layerName}</span>
<Tag style={{ cursor: "default", marginLeft: 8 }}>{elementClass} Layer</Tag>
{this.getFindDataButton(layerName, isDisabled)}
</Col>
</Row>
{this.getLayerSettingsHeader(isDisabled, isColorLayer, layerName, elementClass)}
{isDisabled ? null : (
<React.Fragment>
{isHistogramSupported(elementClass) ? (
<Histogram
data={histograms}
min={intensityRange[0]}
max={intensityRange[1]}
layerName={layerName}
/>
) : null}
{isHistogramSupported(elementClass) && layerName != null && layer != null
? this.getHistogram(layerName, layer)
: null}
<NumberSliderSetting
label="Opacity"
min={0}
max={100}
value={alpha}
onChange={_.partial(this.props.onChangeLayer, layerName, "alpha")}
/>
<ColorSetting
label="Color"
value={Utils.rgbToHex(color)}
onChange={_.partial(this.props.onChangeLayer, layerName, "color")}
className="ant-btn"
value={
isColorLayer && layer != null
? layer.alpha
: this.props.datasetConfiguration.segmentationOpacity
}
onChange={
isColorLayer
? _.partial(this.props.onChangeLayer, layerName, "alpha")
: _.partial(this.props.onChange, "segmentationOpacity")
}
/>
{isColorLayer && layer != null ? (
<ColorSetting
label="Color"
value={Utils.rgbToHex(layer.color)}
onChange={_.partial(this.props.onChangeLayer, layerName, "color")}
className="ant-btn"
/>
) : (
<SwitchSetting
label={settings.highlightHoveredCellId}
value={this.props.datasetConfiguration.highlightHoveredCellId}
onChange={_.partial(this.props.onChange, "highlightHoveredCellId")}
/>
)}
{!isColorLayer &&
(this.props.controlMode === ControlModeEnum.VIEW || window.allowIsosurfaces) ? (
<SwitchSetting
label="Render Isosurfaces (Beta)"
value={this.props.datasetConfiguration.renderIsosurfaces}
onChange={_.partial(this.props.onChange, "renderIsosurfaces")}
/>
) : null}
</React.Fragment>
)}
{!isLastLayer && <Divider />}
</div>
);
};
Expand Down Expand Up @@ -240,66 +307,41 @@ class DatasetSettings extends React.PureComponent<DatasetSettingsProps, State> {
getSegmentationPanel() {
const segmentation = Model.getSegmentationLayer();
const segmentationLayerName = segmentation != null ? segmentation.name : null;
return (
<Panel header="Segmentation" key="2">
{segmentationLayerName ? (
<div style={{ overflow: "auto" }}>
{this.getFindDataButton(segmentationLayerName, false)}
</div>
) : null}
<NumberSliderSetting
label={settings.segmentationOpacity}
min={0}
max={100}
value={this.props.datasetConfiguration.segmentationOpacity}
onChange={_.partial(this.props.onChange, "segmentationOpacity")}
/>
<SwitchSetting
label={settings.highlightHoveredCellId}
value={this.props.datasetConfiguration.highlightHoveredCellId}
onChange={_.partial(this.props.onChange, "highlightHoveredCellId")}
/>

{this.props.controlMode === ControlModeEnum.VIEW || window.allowIsosurfaces ? (
<SwitchSetting
label="Render Isosurfaces (Beta)"
value={this.props.datasetConfiguration.renderIsosurfaces}
onChange={_.partial(this.props.onChange, "renderIsosurfaces")}
/>
) : null}
</Panel>
);
if (!segmentationLayerName) {
return null;
}
return this.getLayerSettings(segmentationLayerName, null, false);
}

renderPanelHeader = (hasInvisibleLayers: boolean) =>
hasInvisibleLayers ? (
<span>
Color Layers
Layers
<Tooltip title="Not all layers are currently visible.">
<Icon type="exclamation-circle-o" style={{ marginLeft: 16, color: "coral" }} />
</Tooltip>
</span>
) : (
"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 (
<Collapse bordered={false} defaultActiveKey={["1", "2", "3", "4"]}>
<Panel header={this.renderPanelHeader(hasInvisibleLayers)} key="1">
{colorSettings}
{this.props.hasSegmentation ? this.getSegmentationPanel() : null}
</Panel>
{this.props.hasSegmentation ? this.getSegmentationPanel() : null}
<Panel header="Data Rendering" key="3">
<DropdownSetting
label={
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ const datasetConfigOverrides: { [key: string]: DatasetConfiguration } = {
},
quality: 0,
segmentationOpacity: 0,
isSegmentationDisabled: false,
highlightHoveredCellId: true,
renderIsosurfaces: false,
renderMissingDataBlack: false,
Expand Down

0 comments on commit fe30714

Please sign in to comment.