From 2faf82fb907af0952cb3c74cd510a0a59acb7c36 Mon Sep 17 00:00:00 2001 From: Philipp Otto Date: Tue, 30 Apr 2019 17:37:18 +0200 Subject: [PATCH 1/3] allow to enable auto-brush feature for whitelisted datasets --- conf/application.conf | 1 + frontend/javascripts/admin/api_flow_types.js | 1 + frontend/javascripts/messages.js | 1 + .../volumetracing_plane_controller.js | 6 ++- frontend/javascripts/oxalis/default_state.js | 1 + frontend/javascripts/oxalis/store.js | 1 + .../view/settings/user_settings_view.js | 43 ++++++++++++++++++- 7 files changed, 51 insertions(+), 3 deletions(-) diff --git a/conf/application.conf b/conf/application.conf index fc1b6bfd8da..70907592e69 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -93,6 +93,7 @@ features { # if it is false, / will be the login page, if not logged in, and the dashboard otherwise enableFrontpage = false isDemoInstance = false + autoBrushReadyDatasets = ["dsA"] } # Actor settings diff --git a/frontend/javascripts/admin/api_flow_types.js b/frontend/javascripts/admin/api_flow_types.js index 6a48e55698a..41e069f8c29 100644 --- a/frontend/javascripts/admin/api_flow_types.js +++ b/frontend/javascripts/admin/api_flow_types.js @@ -496,6 +496,7 @@ export type APIFeatureToggles = { +hideNavbarLogin: boolean, +addMissingDatasetButtonEnabled: boolean, +enableFrontpage: boolean, + autoBrushReadyDatasets: Array, }; // Tracing related datatypes diff --git a/frontend/javascripts/messages.js b/frontend/javascripts/messages.js index 8af11de92ba..6014079dc7c 100644 --- a/frontend/javascripts/messages.js +++ b/frontend/javascripts/messages.js @@ -36,6 +36,7 @@ export const settings = { but it will take more time until the best quality is shown).`, mergerMode: "Enable Merger Mode", gpuMemoryFactor: "Quality", + autoBrush: "Automatic Brush (Beta)", }; export default { diff --git a/frontend/javascripts/oxalis/controller/combinations/volumetracing_plane_controller.js b/frontend/javascripts/oxalis/controller/combinations/volumetracing_plane_controller.js index fe6896c68a1..d017dd89ef7 100644 --- a/frontend/javascripts/oxalis/controller/combinations/volumetracing_plane_controller.js +++ b/frontend/javascripts/oxalis/controller/combinations/volumetracing_plane_controller.js @@ -39,6 +39,8 @@ import * as Utils from "libs/utils"; // TODO: Build proper UI for this window.isAutomaticBrushEnabled = false; +const isAutomaticBrushEnabled = () => + window.isAutomaticBrushEnabled || Store.getState().temporaryConfiguration.isAutoBrushEnabled; // eslint-disable-next-line no-unused-vars const simulateTracing = async (): Promise => { @@ -95,7 +97,7 @@ export function getPlaneMouseControls(_planeId: OrthoView): * { if (!event.shiftKey && (tool === VolumeToolEnum.TRACE || tool === VolumeToolEnum.BRUSH)) { if (event.ctrlKey) { - if (window.isAutomaticBrushEnabled) { + if (isAutomaticBrushEnabled()) { return; } Store.dispatch(setContourTracingMode(ContourModeEnum.DRAW)); @@ -169,7 +171,7 @@ export function getPlaneMouseControls(_planeId: OrthoView): * { Store.dispatch(setActiveCellAction(cellId)); } } else if (event.ctrlKey) { - if (window.isAutomaticBrushEnabled) { + if (isAutomaticBrushEnabled()) { Store.dispatch(inferSegmentationInViewportAction(calculateGlobalPos(pos))); } } diff --git a/frontend/javascripts/oxalis/default_state.js b/frontend/javascripts/oxalis/default_state.js index 1ce90a4f1be..7041c467f74 100644 --- a/frontend/javascripts/oxalis/default_state.js +++ b/frontend/javascripts/oxalis/default_state.js @@ -87,6 +87,7 @@ const defaultState: OxalisState = { mappingSize: 0, }, isMergerModeEnabled: false, + isAutoBrushEnabled: false, gpuSetup: { smallestCommonBucketCapacity: Constants.GPU_FACTOR_MULTIPLIER * Constants.DEFAULT_GPU_MEMORY_FACTOR, diff --git a/frontend/javascripts/oxalis/store.js b/frontend/javascripts/oxalis/store.js index 694e2a82d91..ea85d7d4b48 100644 --- a/frontend/javascripts/oxalis/store.js +++ b/frontend/javascripts/oxalis/store.js @@ -286,6 +286,7 @@ export type TemporaryConfiguration = { +mappingSize: number, }, +isMergerModeEnabled: boolean, + +isAutoBrushEnabled: boolean, +gpuSetup: { // These rendering-related variables are set up // during startup and cannot change (with the current diff --git a/frontend/javascripts/oxalis/view/settings/user_settings_view.js b/frontend/javascripts/oxalis/view/settings/user_settings_view.js index 58b7aa285ef..c6321194f3d 100644 --- a/frontend/javascripts/oxalis/view/settings/user_settings_view.js +++ b/frontend/javascripts/oxalis/view/settings/user_settings_view.js @@ -3,6 +3,7 @@ * @flow */ +import features from "features"; import { Collapse } from "antd"; import type { Dispatch } from "redux"; import { connect } from "react-redux"; @@ -37,7 +38,10 @@ import { import { setUserBoundingBoxAction } from "oxalis/model/actions/annotation_actions"; import { setZoomStepAction } from "oxalis/model/actions/flycam_actions"; import { settings as settingsLabels } from "messages"; -import { updateUserSettingAction } from "oxalis/model/actions/settings_actions"; +import { + updateTemporarySettingAction, + updateUserSettingAction, +} from "oxalis/model/actions/settings_actions"; import { userSettings } from "libs/user_settings.schema"; import Constants, { type ControlMode, @@ -45,6 +49,7 @@ import Constants, { type ViewMode, type Vector6, } from "oxalis/constants"; +import Toast from "libs/toast"; import * as Utils from "libs/utils"; import MergerModeModalView from "./merger_mode_modal_view"; @@ -64,7 +69,9 @@ type UserSettingsViewProps = { onChangeRadius: (value: number) => void, onChangeZoomStep: (value: number) => void, onChangeEnableMergerMode: (active: boolean) => void, + onChangeEnableAutoBrush: (active: boolean) => void, isMergerModeEnabled: boolean, + isAutoBrushEnabled: boolean, viewMode: ViewMode, controlMode: ControlMode, dataset: APIDataset, @@ -109,6 +116,15 @@ class UserSettingsView extends PureComponent { } }; + handleAutoBrushChange = async (active: boolean) => { + this.props.onChangeEnableAutoBrush(active); + if (active) { + Toast.info( + "You enabled the experimental automatic brush feature. Activate the brush tool and use CTRL+Click to use it.", + ); + } + }; + getViewportOptions = () => { switch (this.props.viewMode) { case Constants.MODE_PLANE_TRACING: @@ -321,6 +337,7 @@ class UserSettingsView extends PureComponent { value={this.props.userConfiguration.brushSize} onChange={this.onChangeUser.brushSize} /> + {this.maybeGetAutoBrushUi()} { return panels; }; + maybeGetAutoBrushUi() { + const { autoBrushReadyDatasets } = features(); + if ( + autoBrushReadyDatasets == null || + !autoBrushReadyDatasets.includes(this.props.dataset.name) + ) { + return null; + } + + return ( + { + this.handleAutoBrushChange(value); + }} + /> + ); + } + render() { const { isMergerModeModalVisible, isMergerModeModalClosable } = this.state; const moveValueSetting = Constants.MODES_ARBITRARY.includes(this.props.viewMode) ? ( @@ -409,6 +446,7 @@ const mapStateToProps = (state: OxalisState) => ({ viewMode: state.temporaryConfiguration.viewMode, controlMode: state.temporaryConfiguration.controlMode, isMergerModeEnabled: state.temporaryConfiguration.isMergerModeEnabled, + isAutoBrushEnabled: state.temporaryConfiguration.isAutoBrushEnabled, dataset: state.dataset, }); @@ -437,6 +475,9 @@ const mapDispatchToProps = (dispatch: Dispatch<*>) => ({ onChangeEnableMergerMode(active: boolean) { dispatch(setMergerModeEnabledAction(active)); }, + onChangeEnableAutoBrush(active: boolean) { + dispatch(updateTemporarySettingAction("isAutoBrushEnabled", active)); + }, }); export default connect( From a05887e5cb1c5fadf36dff7c3ee06d26481c72c1 Mon Sep 17 00:00:00 2001 From: Philipp Otto Date: Thu, 2 May 2019 09:51:15 +0200 Subject: [PATCH 2/3] make auto-brush-toast closable with ESC key --- frontend/javascripts/libs/toast.js | 2 +- .../model/sagas/automatic_brush_saga.js | 23 +++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/frontend/javascripts/libs/toast.js b/frontend/javascripts/libs/toast.js index 178af82b8eb..609347a5233 100644 --- a/frontend/javascripts/libs/toast.js +++ b/frontend/javascripts/libs/toast.js @@ -90,7 +90,7 @@ const Toast = { notification[type](toastConfig); }, - info(message: string, config: ToastConfig = {}): void { + info(message: string | React$Element, config: ToastConfig = {}): void { return this.message("info", message, config); }, diff --git a/frontend/javascripts/oxalis/model/sagas/automatic_brush_saga.js b/frontend/javascripts/oxalis/model/sagas/automatic_brush_saga.js index 47bd4ea837f..f02b8ab8167 100644 --- a/frontend/javascripts/oxalis/model/sagas/automatic_brush_saga.js +++ b/frontend/javascripts/oxalis/model/sagas/automatic_brush_saga.js @@ -1,7 +1,7 @@ // @flow import * as tf from "@tensorflow/tfjs"; import memoizeOne from "memoize-one"; - +import React from "react"; import { type Saga, call, fork, select, take, _cancel } from "oxalis/model/sagas/effect-generators"; import floodfill from "libs/floodfill"; import { FlycamActions } from "oxalis/model/actions/flycam_actions"; @@ -18,6 +18,7 @@ import Model from "oxalis/model"; import Toast from "libs/toast"; import { enforceVolumeTracing } from "oxalis/model/accessors/volumetracing_accessor"; import Dimensions from "oxalis/model/dimensions"; +import Shortcut from "libs/shortcut_component"; import api from "oxalis/api/internal_api"; import { getPosition, @@ -138,9 +139,21 @@ export default function* inferSegmentInViewport( sticky: true, key: "automatic-brush", }; - const toastDescription = "Automatic brush is active. Close the toast to stop using it."; + const getEscapableToast = text => ( +
+ {text} + { + aborted = true; + Toast.close(toastConfig.key); + }} + /> +
+ ); - Toast.info(toastDescription, toastConfig); + const brushIsActiveText = "Automatic brush is active. Close the toast to stop using it."; + Toast.info(getEscapableToast(brushIsActiveText), toastConfig); const colorLayers = yield* call([Model, Model.getColorLayers]); const colorLayerName = colorLayers[0].name; @@ -209,7 +222,9 @@ export default function* inferSegmentInViewport( console.time("predict"); const thirdDimension = Dimensions.thirdDimensionForPlane(activeViewport); Toast.info( - `${toastDescription}\nLabeling ${["x", "y", "z"][thirdDimension]}=${z}.`, + getEscapableToast( + `${brushIsActiveText}\nLabeling ${["x", "y", "z"][thirdDimension]}=${z}.`, + ), toastConfig, ); predictions.set( From a903a3b7054b191597d2492ab28c0ee40b73eb0e Mon Sep 17 00:00:00 2001 From: Philipp Otto Date: Thu, 2 May 2019 14:38:58 +0200 Subject: [PATCH 3/3] update changelog and change default whitelist to be empty --- CHANGELOG.md | 2 +- conf/application.conf | 2 +- .../backend-snapshot-tests/misc.e2e.js.md | 1 + .../backend-snapshot-tests/misc.e2e.js.snap | Bin 1013 -> 1046 bytes 4 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33e8adab4be..9d0c66feb80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.md). ### Added - BossDB datasets can now be added to webKnossos using the webknossos-connect service. [#4036](https://github.com/scalableminds/webknossos/pull/4036) -- +- Added an auto-brush feature for selected datasets. [#4053](https://github.com/scalableminds/webknossos/pull/4053) ### Changed - The NML parser now rounds floating point values in node coordinates. [#4045](https://github.com/scalableminds/webknossos/pull/4045) diff --git a/conf/application.conf b/conf/application.conf index 70907592e69..7198537f41a 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -93,7 +93,7 @@ features { # if it is false, / will be the login page, if not logged in, and the dashboard otherwise enableFrontpage = false isDemoInstance = false - autoBrushReadyDatasets = ["dsA"] + autoBrushReadyDatasets = [] } # Actor settings diff --git a/frontend/javascripts/test/snapshots/public/test-bundle/test/backend-snapshot-tests/misc.e2e.js.md b/frontend/javascripts/test/snapshots/public/test-bundle/test/backend-snapshot-tests/misc.e2e.js.md index 00b186b1b18..efd6d6a5d98 100644 --- a/frontend/javascripts/test/snapshots/public/test-bundle/test/backend-snapshot-tests/misc.e2e.js.md +++ b/frontend/javascripts/test/snapshots/public/test-bundle/test/backend-snapshot-tests/misc.e2e.js.md @@ -66,6 +66,7 @@ Generated by [AVA](https://ava.li). addForeignDataset: false, addMissingDatasetButtonEnabled: false, allowOrganizationCreation: false, + autoBrushReadyDatasets: [], defaultOrganization: '', discussionBoard: 'https://support.webknossos.org', discussionBoardRequiresAdmin: false, diff --git a/frontend/javascripts/test/snapshots/public/test-bundle/test/backend-snapshot-tests/misc.e2e.js.snap b/frontend/javascripts/test/snapshots/public/test-bundle/test/backend-snapshot-tests/misc.e2e.js.snap index fdcc3a668dfdb5b444960ebb0b7baa67298713f9..70714a0212dc56667c02c7ffd17b7f208c2c304e 100644 GIT binary patch literal 1046 zcmV+x1nK)hRzV3L7T1N)f?>7vr@p{@#1L%iGx(8^wi>nK$#zZ@&Hg zX1=K*giLhfuifL{9N&MxcWSA|7zMv$213_An*Fo;*_RjJe)Ig}w;$WPio?dtdG1aP*ytAYL|Hk8{|xpj`r-1kB>h3{F=(kMuAe^a_On z!jlLa2v1@BRJ0I!uLUg*Vq|uqy9c-t4WWktYq%dTvu;069fgfyw^%HO%XWbBGT<`c zTflWd%FvdXqs#ILrx7jy$G8WZ@G*m@m^>vo2Zr#%F{7X{CYU@eJjxn8nlFyeKkIlz zInCtx6en#u_{ciZW*W!zz1EmG@D|`Zz%PI-RBr`v0ni1!2lyKBD5CoeG0Gh(^{m#J zC)K%F>M~JSjb|`-xbLxL%P!X!-FdR?Sai{~tddKXXkA;9W3u8XwnLK-YZj(m(40IA z=cU30@3n<5lj}Xn#Xl++(U*$3PN zWX(}OGe`U^A6nPw4Is7?Wh1zxV0dO|b9b8q!>gE);X$_(bd7M&51{)o;3vRMQ9xVE0d_qb zV-7jB0qrK>Rlpa3-vD`V!V=&j;B&x_fLr=AIqry)+TJt7b^9;EDp5pIm7Js_z&p#N zWW3SmRehxdU*mx{xXFtiAgz|@#&71Sz<&qVHsHfGeI#~G?<2kvRnM2r4NBZze=FTj zXI)w+eoL*78v)X&1(!+32i<~K1@VUUI}$ug65b{0cRIpTb6sk0b0MXWbKoypln1pa z8}tRAc{J{=`hPFbFf*q;!Bq!pe*mk=TzZb|*u;BUG!BY2&EzU=i!)p*0?*5klp8T6 QBW6l}16jxbrRfO(07hKRzVO!Owx>Anz1iI{Z)et-*{mzo zgVK}IlLrN9*1(wlgw6fYjUG`-g1@4dIX?9O|!X>s9W=FNQbn{R)= znQy8HAsgMgvwQM~llvbRuU^?-IrVOUQTRJyA$0SzncJPOzkU0I_bxnl@7cZUI2yf> zN9fB_KmS$!aPRunZHZsr`)(IUqwC{gynM7Ck73(^b`fwIFoSc`I9rjDbTRJt3WWl~ z69`)fAHn#=cp>!O30oY-$n8RR4{$3ULQezMa4%kN(|wJ48e4;Iu~>|j?EvL6;2PjZ zz)e8LGM3q+%O(-dB3uBD^(eOCV;Ub}>WttV7(zy4R>5G5Gj&!-%IZ9xFDB<-@g&h+ zBYi%@Nt5NO*x+PoldysO$J>g^t;%Ty>WLfBCgZ-E^+XebW847=gXGgUT2wO1J*v#C)|ga! zV<>gGIIO|5m_OL}#FFEdYYYAyS@ImZ;5$yqCyTUZEE!{};%T-+(+_JNrhU+yJ`3li z!UgX(MO&rUdxEJopXu4C$X>KWGopAOh(M<5c%FUs{t&350t_@Wut0`%!@Vr^S!tf3A>?8~&^EXiT0e1m; zd(_YD5kH%ZtZVcJ5Z8;d5nfU-JUgONX#T8 zK9xb}8NemLTN#6%P0&9D$`^p&0ryOLE^&G6`=3rXKArP!?{s>`l1ZkjkoR@MNu3_x zr=xxtmTBwa3-=dLE|uoo`6b6MmC9~u!I^Vt&7U`~N1A~DHsn~q{6h*@JdA+DWME;) zAtyGVtpY9szUe)UN5Kit1FitR0^Bg4$fO&N8~aWZ-|xK!t3(q;b$Wu10dJit#dy8P zTWM=8_!TTcg^omXLb3L)~pIloDzdJj00cq!HyjeZ9>j9rjC8V}VBh_^cFMx1gQ}uo}## j8)U~N@+DC}DAp8HtF$T3ais}789ma!mNEB$p$Py0cfRvG