From 8badcdd90282f468dc99749194b0137acb55a95d Mon Sep 17 00:00:00 2001 From: Youri K Date: Thu, 23 Apr 2020 17:34:26 +0200 Subject: [PATCH 01/20] [WIP] isosurfaces for volume tracings --- frontend/javascripts/admin/admin_rest_api.js | 5 +-- .../oxalis/model/sagas/isosurface_saga.js | 8 ++--- .../services/IsosurfaceService.scala | 2 +- .../controllers/VolumeTracingController.scala | 34 +++++++++++++++++-- .../volume/VolumeTracingService.scala | 23 +++++++++++-- ...alableminds.webknossos.tracingstore.routes | 2 ++ 6 files changed, 62 insertions(+), 12 deletions(-) diff --git a/frontend/javascripts/admin/admin_rest_api.js b/frontend/javascripts/admin/admin_rest_api.js index bc03bbf4de2..5cc0c6c6aae 100644 --- a/frontend/javascripts/admin/admin_rest_api.js +++ b/frontend/javascripts/admin/admin_rest_api.js @@ -1230,9 +1230,10 @@ export function computeIsosurface( const { position, zoomStep, segmentId, voxelDimensions, cubeSize } = isosurfaceRequest; return doWithToken(async token => { const { buffer, headers } = await Request.sendJSONReceiveArraybufferWithHeaders( - `${datastoreUrl}/data/datasets/${datasetId.owningOrganization}/${datasetId.name}/layers/${ + /*/data/datasets/${datasetId.owningOrganization}/${datasetId.name}/layers/${ layer.fallbackLayer != null ? layer.fallbackLayer : layer.name - }/isosurface?token=${token}`, + }*/ + `${datastoreUrl}/tracings/volume/7e9e66d6-3efa-4a91-9b0a-87c4b2958166/isosurface?token=${token}`, { data: { // The back-end needs a small padding at the border of the diff --git a/frontend/javascripts/oxalis/model/sagas/isosurface_saga.js b/frontend/javascripts/oxalis/model/sagas/isosurface_saga.js index a495b46e89c..03ff57746ed 100644 --- a/frontend/javascripts/oxalis/model/sagas/isosurface_saga.js +++ b/frontend/javascripts/oxalis/model/sagas/isosurface_saga.js @@ -132,18 +132,18 @@ function* ensureSuitableIsosurface( if (segmentId === 0) { return; } - const renderIsosurfaces = yield* select(state => state.datasetConfiguration.renderIsosurfaces); - const isControlModeSupported = yield* select( + const renderIsosurfaces = true; // yield* select(state => state.datasetConfiguration.renderIsosurfaces); + const isControlModeSupported = true; /* yield* select( state => state.temporaryConfiguration.controlMode === ControlModeEnum.VIEW || window.allowIsosurfaces, - ); + ); */ if (!renderIsosurfaces || !isControlModeSupported) { return; } const dataset = yield* select(state => state.dataset); const layer = Model.getSegmentationLayer(); if (!layer) { - return; + // return; } const position = seedPosition != null ? seedPosition : yield* select(state => getFlooredPosition(state.flycam)); diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/IsosurfaceService.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/IsosurfaceService.scala index a3c7807646a..54ada902396 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/IsosurfaceService.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/IsosurfaceService.scala @@ -186,7 +186,7 @@ class IsosurfaceService @Inject()( math.ceil(cuboid.depth / voxelDimensions.z).toInt) val offset = Vector3D(cuboid.topLeft.x, cuboid.topLeft.y, cuboid.topLeft.z) - val scale = Vector3D(cuboid.topLeft.resolution) * request.dataSource.scale.toVector + val scale = Vector3D(cuboid.topLeft.resolution) //* request.dataSource.scale.toVector val typedSegmentId = dataTypeFunctors.fromLong(request.segmentId) diff --git a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/controllers/VolumeTracingController.scala b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/controllers/VolumeTracingController.scala index 895a69b2006..b9132860961 100644 --- a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/controllers/VolumeTracingController.scala +++ b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/controllers/VolumeTracingController.scala @@ -1,12 +1,14 @@ package com.scalableminds.webknossos.tracingstore.controllers +import java.nio.{ByteBuffer, ByteOrder} + import akka.stream.scaladsl.Source import akka.util.ByteString import com.google.inject.Inject import com.scalableminds.webknossos.datastore.DataStoreConfig import com.scalableminds.webknossos.tracingstore.VolumeTracing.{VolumeTracing, VolumeTracingOpt, VolumeTracings} -import com.scalableminds.webknossos.datastore.models.WebKnossosDataRequest -import com.scalableminds.webknossos.datastore.services.{AccessTokenService, UserAccessRequest} +import com.scalableminds.webknossos.datastore.models.{WebKnossosDataRequest, WebKnossosIsosurfaceRequest} +import com.scalableminds.webknossos.datastore.services.{AccessTokenService, IsosurfaceRequest, UserAccessRequest} import com.scalableminds.webknossos.tracingstore.SkeletonTracing.{SkeletonTracing, SkeletonTracingOpt} import com.scalableminds.webknossos.tracingstore.{ TracingStoreAccessTokenService, @@ -146,4 +148,32 @@ class VolumeTracingController @Inject()(val tracingService: VolumeTracingService } } } + + /** + * Handles isosurface requests. + */ + def requestIsosurface(tracingId: String) = + Action.async(validateJson[WebKnossosIsosurfaceRequest]) { implicit request => + accessTokenService.validateAccess(UserAccessRequest.readTracing(tracingId)) { + AllowRemoteOrigin { + for { + // The client expects the isosurface as a flat float-array. Three consecutive floats form a 3D point, three + // consecutive 3D points (i.e., nine floats) form a triangle. + // There are no shared vertices between triangles. + (vertices, neighbors) <- tracingService.createIsosurface(tracingId, request.body) + } yield { + // We need four bytes for each float + val responseBuffer = ByteBuffer.allocate(vertices.length * 4).order(ByteOrder.LITTLE_ENDIAN) + responseBuffer.asFloatBuffer().put(vertices) + Ok(responseBuffer.array()).withHeaders(getNeighborIndices(neighbors): _*) + } + } + } + } + + private def getNeighborIndices(neighbors: List[Int]) = + List(("NEIGHBORS" -> formatNeighborList(neighbors)), ("Access-Control-Expose-Headers" -> "NEIGHBORS")) + + private def formatNeighborList(neighbors: List[Int]): String = + "[" + neighbors.mkString(", ") + "]" } diff --git a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingService.scala b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingService.scala index ba22a0f7d8e..4fb91593a9d 100644 --- a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingService.scala +++ b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingService.scala @@ -6,7 +6,7 @@ import java.nio.file.Paths import com.google.inject.Inject import com.scalableminds.util.geometry.Point3D import com.scalableminds.webknossos.datastore.dataformats.wkw.{WKWBucketStreamSink, WKWDataFormatHelper} -import com.scalableminds.webknossos.datastore.models.BucketPosition +import com.scalableminds.webknossos.datastore.models.{BucketPosition, WebKnossosIsosurfaceRequest} import com.scalableminds.webknossos.datastore.models.datasource.{DataSource, ElementClass, SegmentationLayer} import com.scalableminds.webknossos.tracingstore.VolumeTracing.VolumeTracing import com.scalableminds.webknossos.tracingstore.tracings._ @@ -15,13 +15,14 @@ import com.scalableminds.util.tools.{Fox, FoxImplicits, TextUtils} import com.scalableminds.webknossos.datastore.DataStoreConfig import com.scalableminds.webknossos.datastore.models.DataRequestCollection.DataRequestCollection import com.scalableminds.webknossos.datastore.models.requests.DataServiceDataRequest -import com.scalableminds.webknossos.datastore.services.BinaryDataService +import com.scalableminds.webknossos.datastore.services.{BinaryDataService, IsosurfaceRequest, IsosurfaceService} import com.scalableminds.webknossos.datastore.storage.TemporaryStore import com.scalableminds.webknossos.tracingstore.SkeletonTracing.SkeletonTracing import com.scalableminds.webknossos.tracingstore.{RedisTemporaryStore, TracingStoreConfig} import com.scalableminds.webknossos.wrap.WKWFile import com.typesafe.scalalogging.LazyLogging import net.liftweb.common.{Box, Empty, Failure, Full} +import play.api.i18n.Messages import play.api.libs.Files import play.api.libs.Files.TemporaryFileCreator import play.api.libs.iteratee.Concurrent.Channel @@ -36,7 +37,7 @@ import scala.util.Try class VolumeTracingService @Inject()( tracingDataStore: TracingDataStore, - config: TracingStoreConfig, + isosurfaceService: IsosurfaceService, val temporaryTracingStore: TemporaryTracingStore[VolumeTracing], val handledGroupIdStore: RedisTemporaryStore, val uncommittedUpdatesStore: RedisTemporaryStore, @@ -253,4 +254,20 @@ class VolumeTracingService @Inject()( updateActionGroupsJs = volumeTracings.map(versionedTupleToJson) } yield Json.toJson(updateActionGroupsJs) } + + def createIsosurface(tracingId: String, request: WebKnossosIsosurfaceRequest): Fox[(Array[Float], List[Int])] = + for { + tracing <- find(tracingId) ?~> "tracing.notFound" + segmentationLayer = volumeTracingLayer(tracingId, tracing) + isosurfaceRequest = IsosurfaceRequest( + null, + segmentationLayer, + request.cuboid(segmentationLayer), + request.segmentId, + request.voxelDimensions, + request.mapping, + request.mappingType + ) + result <- isosurfaceService.requestIsosurfaceViaActor(isosurfaceRequest) + } yield result } diff --git a/webknossos-tracingstore/conf/com.scalableminds.webknossos.tracingstore.routes b/webknossos-tracingstore/conf/com.scalableminds.webknossos.tracingstore.routes index c148dd0e8f7..ca07237d0b4 100644 --- a/webknossos-tracingstore/conf/com.scalableminds.webknossos.tracingstore.routes +++ b/webknossos-tracingstore/conf/com.scalableminds.webknossos.tracingstore.routes @@ -17,6 +17,8 @@ POST /volume/:tracingId/data @com.scalablemin GET /volume/:tracingId/duplicate @com.scalableminds.webknossos.tracingstore.controllers.VolumeTracingController.duplicate(tracingId: String, version: Option[Long]) GET /volume/:tracingId/updateActionLog @com.scalableminds.webknossos.tracingstore.controllers.VolumeTracingController.updateActionLog(tracingId: String) POST /volume/getMultiple @com.scalableminds.webknossos.tracingstore.controllers.VolumeTracingController.getMultiple +POST /volume/:tracingId/isosurface @com.scalableminds.webknossos.tracingstore.controllers.VolumeTracingController.requestIsosurface(tracingId: String) + # Skeleton tracings POST /skeleton/save @com.scalableminds.webknossos.tracingstore.controllers.SkeletonTracingController.save From b1ceca21cf7d9a288c7f0bebacc4799f2dfc1e5e Mon Sep 17 00:00:00 2001 From: Youri K Date: Fri, 24 Apr 2020 10:07:53 +0200 Subject: [PATCH 02/20] update changelog --- CHANGELOG.unreleased.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index 643a0cdaa50..5d28f117ac3 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -17,6 +17,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released - Added option to hide all unmapped segments to segmentation tab. [#4510](https://github.com/scalableminds/webknossos/pull/4510) - When wK changes datasource-properties.json files of datasets, now it creates a backup log of previous versions. [#4534](https://github.com/scalableminds/webknossos/pull/4534) - Isosurface generation now also supports hdf5-style mappings. [#4531](https://github.com/scalableminds/webknossos/pull/4531) +- Isosurface generation now also supports volume tracings without fallback layer. [#4567](https://github.com/scalableminds/webknossos/pull/4567) ### Changed - Reported datasets can now overwrite existing ones that are reported as missing, this ignores the isScratch precedence. [#4465](https://github.com/scalableminds/webknossos/pull/4465) From 894eb94dd4ee24770cf165ffe02c8ae2668c2291 Mon Sep 17 00:00:00 2001 From: Youri K Date: Fri, 24 Apr 2020 12:22:52 +0200 Subject: [PATCH 03/20] make datasource optional --- .../controllers/BinaryDataController.scala | 2 +- .../services/IsosurfaceService.scala | 23 +++++++++++-------- .../volume/VolumeTracingService.scala | 2 +- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/BinaryDataController.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/BinaryDataController.scala index 1beed6bcbe0..466bb22baa0 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/BinaryDataController.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/BinaryDataController.scala @@ -345,7 +345,7 @@ class BinaryDataController @Inject()( segmentationLayer <- tryo(dataLayer.asInstanceOf[SegmentationLayer]).toFox ?~> Messages( "dataLayer.mustBeSegmentation") isosurfaceRequest = IsosurfaceRequest( - dataSource, + Some(dataSource), segmentationLayer, request.body.cuboid(dataLayer), request.body.segmentId, diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/IsosurfaceService.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/IsosurfaceService.scala index 54ada902396..2a1c6165a46 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/IsosurfaceService.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/IsosurfaceService.scala @@ -26,7 +26,7 @@ import scala.concurrent.{Await, ExecutionContext} import scala.reflect.ClassTag case class IsosurfaceRequest( - dataSource: DataSource, + dataSource: Option[DataSource], dataLayer: SegmentationLayer, cuboid: Cuboid, segmentId: Long, @@ -99,9 +99,10 @@ class IsosurfaceService @Inject()( case Some(mappingName) => request.mappingType match { case Some("JSON") => - mappingService.applyMapping(DataServiceMappingRequest(request.dataSource, request.dataLayer, mappingName), - data, - dataTypeFunctors.fromLong) + mappingService.applyMapping( + DataServiceMappingRequest(request.dataSource.orNull, request.dataLayer, mappingName), + data, + dataTypeFunctors.fromLong) case _ => Fox.successful(data) } case _ => @@ -114,12 +115,13 @@ class IsosurfaceService @Inject()( request.mappingType match { case Some("HDF5") => val dataRequest = DataServiceDataRequest( - request.dataSource, + request.dataSource.orNull, request.dataLayer, request.mapping, request.cuboid, DataServiceRequestSettings(halfByte = false, request.mapping, None), - Vector3I(1, 1, 1)) + Vector3I(1, 1, 1) + ) agglomerateService.applyAgglomerate(dataRequest)(data) case _ => Fox.successful(data) @@ -163,9 +165,8 @@ class IsosurfaceService @Inject()( val back_yz = BoundingBox(Point3D(x, 0, 0), x, 1, z) val surfaceBoundingBoxes = List(front_xy, front_xz, front_yz, back_xy, back_xz, back_yz) surfaceBoundingBoxes.zipWithIndex.filter { - case (surfaceBoundingBox, index) => { + case (surfaceBoundingBox, index) => subVolumeContainsSegmentId(data, dataDimensions, surfaceBoundingBox, segmentId) - } }.map { case (surfaceBoundingBox, index) => index } @@ -174,7 +175,7 @@ class IsosurfaceService @Inject()( val cuboid = request.cuboid val voxelDimensions = Vector3D(request.voxelDimensions.x, request.voxelDimensions.y, request.voxelDimensions.z) - val dataRequest = DataServiceDataRequest(request.dataSource, + val dataRequest = DataServiceDataRequest(request.dataSource.orNull, request.dataLayer, request.mapping, cuboid, @@ -186,7 +187,9 @@ class IsosurfaceService @Inject()( math.ceil(cuboid.depth / voxelDimensions.z).toInt) val offset = Vector3D(cuboid.topLeft.x, cuboid.topLeft.y, cuboid.topLeft.z) - val scale = Vector3D(cuboid.topLeft.resolution) //* request.dataSource.scale.toVector + val scale = Vector3D(cuboid.topLeft.resolution) * request.dataSource + .map(_.scale.toVector) + .getOrElse(Vector3D(1, 1, 1)) val typedSegmentId = dataTypeFunctors.fromLong(request.segmentId) diff --git a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingService.scala b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingService.scala index 4fb91593a9d..eef40c32778 100644 --- a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingService.scala +++ b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingService.scala @@ -260,7 +260,7 @@ class VolumeTracingService @Inject()( tracing <- find(tracingId) ?~> "tracing.notFound" segmentationLayer = volumeTracingLayer(tracingId, tracing) isosurfaceRequest = IsosurfaceRequest( - null, + None, segmentationLayer, request.cuboid(segmentationLayer), request.segmentId, From e0767adb673edbefa2306b233cfe29d4073b1844 Mon Sep 17 00:00:00 2001 From: Youri K Date: Tue, 19 May 2020 19:29:47 +0200 Subject: [PATCH 04/20] add frontend part for isosurfaces for volumes --- frontend/javascripts/admin/admin_rest_api.js | 8 ++--- .../segmentation_plane_controller.js | 27 +++++++++++++++ .../skeletontracing_plane_controller.js | 8 +++-- .../volumetracing_plane_controller.js | 2 ++ .../oxalis/controller/scene_controller.js | 3 +- .../controller/viewmodes/plane_controller.js | 20 +---------- .../oxalis/model/sagas/isosurface_saga.js | 33 ++++++++++--------- .../view/settings/dataset_settings_view.js | 7 +--- 8 files changed, 58 insertions(+), 50 deletions(-) create mode 100644 frontend/javascripts/oxalis/controller/combinations/segmentation_plane_controller.js diff --git a/frontend/javascripts/admin/admin_rest_api.js b/frontend/javascripts/admin/admin_rest_api.js index 93f0f1f897a..79b9e36dba2 100644 --- a/frontend/javascripts/admin/admin_rest_api.js +++ b/frontend/javascripts/admin/admin_rest_api.js @@ -1228,18 +1228,14 @@ type IsosurfaceRequest = { }; export function computeIsosurface( - datastoreUrl: string, - datasetId: APIDatasetId, + requestUrl: string, layer: DataLayer, isosurfaceRequest: IsosurfaceRequest, ): Promise<{ buffer: ArrayBuffer, neighbors: Array }> { const { position, zoomStep, segmentId, voxelDimensions, cubeSize } = isosurfaceRequest; return doWithToken(async token => { const { buffer, headers } = await Request.sendJSONReceiveArraybufferWithHeaders( - /*/data/datasets/${datasetId.owningOrganization}/${datasetId.name}/layers/${ - layer.fallbackLayer != null ? layer.fallbackLayer : layer.name - }*/ - `${datastoreUrl}/tracings/volume/7e9e66d6-3efa-4a91-9b0a-87c4b2958166/isosurface?token=${token}`, + `${requestUrl}/isosurface?token=${token}`, { data: { // The back-end needs a small padding at the border of the diff --git a/frontend/javascripts/oxalis/controller/combinations/segmentation_plane_controller.js b/frontend/javascripts/oxalis/controller/combinations/segmentation_plane_controller.js new file mode 100644 index 00000000000..8fa57795d59 --- /dev/null +++ b/frontend/javascripts/oxalis/controller/combinations/segmentation_plane_controller.js @@ -0,0 +1,27 @@ +// @flow +import { type OrthoView, type Point2 } from "oxalis/constants"; +import Model from "oxalis/model"; +import { changeActiveIsosurfaceCellAction } from "oxalis/model/actions/segmentation_actions"; +import { calculateGlobalPos } from "oxalis/controller/viewmodes/plane_controller"; +import { getRequestLogZoomStep } from "oxalis/model/accessors/flycam_accessor"; +import Store from "oxalis/store"; + +function isosurfaceLeftClick(pos: Point2, plane: OrthoView, event: MouseEvent) { + if (!event.ctrlKey) { + return; + } + const segmentation = Model.getSegmentationLayer(); + if (!segmentation) { + return; + } + const position = calculateGlobalPos(pos); + const cellId = segmentation.cube.getMappedDataValue( + position, + getRequestLogZoomStep(Store.getState()), + ); + if (cellId > 0) { + Store.dispatch(changeActiveIsosurfaceCellAction(cellId, position)); + } +} + +export default isosurfaceLeftClick; diff --git a/frontend/javascripts/oxalis/controller/combinations/skeletontracing_plane_controller.js b/frontend/javascripts/oxalis/controller/combinations/skeletontracing_plane_controller.js index ff53956eb81..cff5da01585 100644 --- a/frontend/javascripts/oxalis/controller/combinations/skeletontracing_plane_controller.js +++ b/frontend/javascripts/oxalis/controller/combinations/skeletontracing_plane_controller.js @@ -42,6 +42,7 @@ import Store from "oxalis/store"; import api from "oxalis/api/internal_api"; import getSceneController from "oxalis/controller/scene_controller_provider"; import { renderToTexture } from "oxalis/view/rendering_utils"; +import isosurfaceLeftClick from "oxalis/controller/combinations/segmentation_plane_controller"; const OrthoViewToNumber: OrthoViewMap = { [OrthoViews.PLANE_XY]: 0, @@ -66,7 +67,7 @@ function simulateTracing(nodesPerTree: number = -1, nodesAlreadySet: number = 0) export function getPlaneMouseControls(planeView: PlaneView) { return { leftClick: (pos: Point2, plane: OrthoView, event: MouseEvent, isTouch: boolean) => - onClick(planeView, pos, event.shiftKey, event.altKey, event.ctrlKey, plane, isTouch), + onClick(planeView, pos, event.shiftKey, event.altKey, event.ctrlKey, plane, isTouch, event), rightClick: (pos: Point2, plane: OrthoView, event: MouseEvent) => { const { volume } = Store.getState().tracing; if (!volume || volume.activeTool !== VolumeToolEnum.BRUSH) { @@ -130,8 +131,9 @@ function onClick( ctrlPressed: boolean, plane: OrthoView, isTouch: boolean, + event?: MouseEvent, ): void { - if (!shiftPressed && !isTouch) { + if (!shiftPressed && !isTouch && !(ctrlPressed && event != null)) { // do nothing return; } @@ -177,6 +179,8 @@ function onClick( } else { Store.dispatch(setActiveNodeAction(nodeId)); } + } else if (ctrlPressed && event != null) { + isosurfaceLeftClick(position, plane, event); } } diff --git a/frontend/javascripts/oxalis/controller/combinations/volumetracing_plane_controller.js b/frontend/javascripts/oxalis/controller/combinations/volumetracing_plane_controller.js index d017dd89ef7..d70d9a8f671 100644 --- a/frontend/javascripts/oxalis/controller/combinations/volumetracing_plane_controller.js +++ b/frontend/javascripts/oxalis/controller/combinations/volumetracing_plane_controller.js @@ -36,6 +36,7 @@ import { movePlaneFlycamOrthoAction, setPositionAction } from "oxalis/model/acti import Model from "oxalis/model"; import Store from "oxalis/store"; import * as Utils from "libs/utils"; +import isosurfaceLeftClick from "oxalis/controller/combinations/segmentation_plane_controller"; // TODO: Build proper UI for this window.isAutomaticBrushEnabled = false; @@ -174,6 +175,7 @@ export function getPlaneMouseControls(_planeId: OrthoView): * { if (isAutomaticBrushEnabled()) { Store.dispatch(inferSegmentationInViewportAction(calculateGlobalPos(pos))); } + isosurfaceLeftClick(pos, plane, event); } }, diff --git a/frontend/javascripts/oxalis/controller/scene_controller.js b/frontend/javascripts/oxalis/controller/scene_controller.js index 573678927ec..13230544278 100644 --- a/frontend/javascripts/oxalis/controller/scene_controller.js +++ b/frontend/javascripts/oxalis/controller/scene_controller.js @@ -194,7 +194,8 @@ class SceneController { this.isosurfacesRootGroup.add(newGroup); newGroup.cellId = segmentationId; } - this.isosurfacesGroupsPerSegmentationId[segmentationId].add(mesh); + // this.isosurfacesGroupsPerSegmentationId[segmentationId].add(mesh); + this.rootNode.add(mesh); } removeIsosurfaceById(segmentationId: number): void { diff --git a/frontend/javascripts/oxalis/controller/viewmodes/plane_controller.js b/frontend/javascripts/oxalis/controller/viewmodes/plane_controller.js index 266f286ac6d..89e3e404285 100644 --- a/frontend/javascripts/oxalis/controller/viewmodes/plane_controller.js +++ b/frontend/javascripts/oxalis/controller/viewmodes/plane_controller.js @@ -10,7 +10,6 @@ import * as React from "react"; import _ from "lodash"; import { InputKeyboard, InputKeyboardNoLoop, InputMouse, type ModifierKeys } from "libs/input"; -import { changeActiveIsosurfaceCellAction } from "oxalis/model/actions/segmentation_actions"; import { document } from "libs/window"; import { getBaseVoxel, getBaseVoxelFactors } from "oxalis/model/scaleinfo"; import { getViewportScale, getInputCatcherRect } from "oxalis/model/accessors/view_mode_accessor"; @@ -51,6 +50,7 @@ import getSceneController from "oxalis/controller/scene_controller_provider"; import * as skeletonController from "oxalis/controller/combinations/skeletontracing_plane_controller"; import * as volumeController from "oxalis/controller/combinations/volumetracing_plane_controller"; import { downloadScreenshot } from "oxalis/view/rendering_utils"; +import isosurfaceLeftClick from "oxalis/controller/combinations/segmentation_plane_controller"; const MAX_BRUSH_CHANGE_VALUE = 5; const BRUSH_CHANGING_CONSTANT = 0.02; @@ -69,24 +69,6 @@ function ensureNonConflictingHandlers(skeletonControls: Object, volumeControls: } } -const isosurfaceLeftClick = (pos: Point2, plane: OrthoView, event: MouseEvent) => { - if (!event.shiftKey) { - return; - } - const segmentation = Model.getSegmentationLayer(); - if (!segmentation) { - return; - } - const position = calculateGlobalPos(pos); - const cellId = segmentation.cube.getMappedDataValue( - position, - getRequestLogZoomStep(Store.getState()), - ); - if (cellId > 0) { - Store.dispatch(changeActiveIsosurfaceCellAction(cellId, position)); - } -}; - type StateProps = {| tracing: Tracing, |}; diff --git a/frontend/javascripts/oxalis/model/sagas/isosurface_saga.js b/frontend/javascripts/oxalis/model/sagas/isosurface_saga.js index 03ff57746ed..dcc142b6546 100644 --- a/frontend/javascripts/oxalis/model/sagas/isosurface_saga.js +++ b/frontend/javascripts/oxalis/model/sagas/isosurface_saga.js @@ -6,7 +6,7 @@ import { changeActiveIsosurfaceCellAction, type ChangeActiveIsosurfaceCellAction, } from "oxalis/model/actions/segmentation_actions"; -import { ControlModeEnum, type Vector3 } from "oxalis/constants"; +import { type Vector3 } from "oxalis/constants"; import { type FlycamAction, FlycamActions } from "oxalis/model/actions/flycam_actions"; import type { ImportIsosurfaceFromStlAction, @@ -128,22 +128,19 @@ function* ensureSuitableIsosurface( maybeFlycamAction: ?FlycamAction, seedPosition?: Vector3, ): Saga { - const segmentId = yield* call(getCurrentCellId); + const segmentId = currentViewIsosurfaceCellId; if (segmentId === 0) { return; } - const renderIsosurfaces = true; // yield* select(state => state.datasetConfiguration.renderIsosurfaces); - const isControlModeSupported = true; /* yield* select( - state => - state.temporaryConfiguration.controlMode === ControlModeEnum.VIEW || window.allowIsosurfaces, - ); */ - if (!renderIsosurfaces || !isControlModeSupported) { + const renderIsosurfaces = yield* select(state => state.datasetConfiguration.renderIsosurfaces); + if (!renderIsosurfaces) { return; } const dataset = yield* select(state => state.dataset); + const volumeTracing = yield* select(state => state.tracing.volume); const layer = Model.getSegmentationLayer(); - if (!layer) { - // return; + if (!layer || (volumeTracing != null && volumeTracing.fallbackLayer != null)) { + return; } const position = seedPosition != null ? seedPosition : yield* select(state => getFlooredPosition(state.flycam)); @@ -218,11 +215,18 @@ function* maybeLoadIsosurface( const voxelDimensions = window.__isosurfaceVoxelDimensions || [4, 4, 4]; const dataStoreHost = yield* select(state => state.dataset.dataStore.url); + const tracingStoreHost = yield* select(state => state.tracing.tracingStore.url); + + const dataStoreUrl = `${dataStoreHost}/data/datasets/${dataset.owningOrganization}/${ + dataset.name + }/layers/${layer.fallbackLayer != null ? layer.fallbackLayer : layer.name}`; + const tracingStoreUrl = `${tracingStoreHost}/tracings/volume/${layer.name}`; + + const volumeTracing = yield* select(state => state.tracing.volume); const { buffer: responseBuffer, neighbors } = yield* call( computeIsosurface, - dataStoreHost, - dataset, + volumeTracing == null ? dataStoreUrl : tracingStoreUrl, layer, { position: clippedPosition, @@ -293,10 +297,7 @@ function* removeIsosurface(action: RemoveIsosurfaceAction): Saga { export default function* isosurfaceSaga(): Saga { yield* take("WK_READY"); yield _takeEvery(FlycamActions, ensureSuitableIsosurface); - yield _takeEvery( - ["CHANGE_ACTIVE_ISOSURFACE_CELL", "SET_ACTIVE_CELL"], - changeActiveIsosurfaceCell, - ); + yield _takeEvery("CHANGE_ACTIVE_ISOSURFACE_CELL", changeActiveIsosurfaceCell); yield _takeEvery("TRIGGER_ISOSURFACE_DOWNLOAD", downloadActiveIsosurfaceCell); yield _takeEvery("IMPORT_ISOSURFACE_FROM_STL", importIsosurfaceFromStl); yield _takeEvery("REMOVE_ISOSURFACE", removeIsosurface); diff --git a/frontend/javascripts/oxalis/view/settings/dataset_settings_view.js b/frontend/javascripts/oxalis/view/settings/dataset_settings_view.js index fb993440616..2616a715780 100644 --- a/frontend/javascripts/oxalis/view/settings/dataset_settings_view.js +++ b/frontend/javascripts/oxalis/view/settings/dataset_settings_view.js @@ -45,8 +45,6 @@ import Store, { import Toast from "libs/toast"; import * as Utils from "libs/utils"; import constants, { - type ControlMode, - ControlModeEnum, type ViewMode, type Vector3, } from "oxalis/constants"; @@ -69,7 +67,6 @@ type DatasetSettingsProps = {| ) => void, viewMode: ViewMode, histogramData: HistogramDataForAllLayers, - controlMode: ControlMode, onSetPosition: Vector3 => void, onZoomToResolution: Vector3 => number, onChangeUser: (key: $Keys, value: any) => void, @@ -312,8 +309,7 @@ class DatasetSettings extends React.PureComponent { onChange={_.partial(this.props.onChange, "highlightHoveredCellId")} /> )} - {!isColorLayer && - (this.props.controlMode === ControlModeEnum.VIEW || window.allowIsosurfaces) ? ( + {!isColorLayer ? ( ({ datasetConfiguration: state.datasetConfiguration, viewMode: state.temporaryConfiguration.viewMode, histogramData: state.temporaryConfiguration.histogramData, - controlMode: state.temporaryConfiguration.controlMode, dataset: state.dataset, tracing: state.tracing, }); From a1470c23fe33ff7b4597b8c139ec2596ede700cc Mon Sep 17 00:00:00 2001 From: Youri K Date: Tue, 2 Jun 2020 17:28:49 +0200 Subject: [PATCH 05/20] send scale to correctly display isosurfaces --- frontend/javascripts/admin/admin_rest_api.js | 4 +++- frontend/javascripts/oxalis/controller/scene_controller.js | 3 +-- frontend/javascripts/oxalis/model/sagas/isosurface_saga.js | 2 ++ .../oxalis/view/settings/dataset_settings_view.js | 5 +---- .../datastore/controllers/BinaryDataController.scala | 4 ++-- .../webknossos/datastore/models/DataRequests.scala | 3 ++- .../webknossos/datastore/services/IsosurfaceService.scala | 6 ++---- .../tracingstore/tracings/volume/VolumeTracingService.scala | 1 + 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/frontend/javascripts/admin/admin_rest_api.js b/frontend/javascripts/admin/admin_rest_api.js index 79b9e36dba2..e06f9c065da 100644 --- a/frontend/javascripts/admin/admin_rest_api.js +++ b/frontend/javascripts/admin/admin_rest_api.js @@ -1225,6 +1225,7 @@ type IsosurfaceRequest = { segmentId: number, voxelDimensions: Vector3, cubeSize: Vector3, + scale: Vector3, }; export function computeIsosurface( @@ -1232,7 +1233,7 @@ export function computeIsosurface( layer: DataLayer, isosurfaceRequest: IsosurfaceRequest, ): Promise<{ buffer: ArrayBuffer, neighbors: Array }> { - const { position, zoomStep, segmentId, voxelDimensions, cubeSize } = isosurfaceRequest; + const { position, zoomStep, segmentId, voxelDimensions, cubeSize, scale } = isosurfaceRequest; return doWithToken(async token => { const { buffer, headers } = await Request.sendJSONReceiveArraybufferWithHeaders( `${requestUrl}/isosurface?token=${token}`, @@ -1251,6 +1252,7 @@ export function computeIsosurface( mappingType: layer.activeMappingType, // "size" of each voxel (i.e., only every nth voxel is considered in each dimension) voxelDimensions, + scale, }, }, ); diff --git a/frontend/javascripts/oxalis/controller/scene_controller.js b/frontend/javascripts/oxalis/controller/scene_controller.js index 13230544278..573678927ec 100644 --- a/frontend/javascripts/oxalis/controller/scene_controller.js +++ b/frontend/javascripts/oxalis/controller/scene_controller.js @@ -194,8 +194,7 @@ class SceneController { this.isosurfacesRootGroup.add(newGroup); newGroup.cellId = segmentationId; } - // this.isosurfacesGroupsPerSegmentationId[segmentationId].add(mesh); - this.rootNode.add(mesh); + this.isosurfacesGroupsPerSegmentationId[segmentationId].add(mesh); } removeIsosurfaceById(segmentationId: number): void { diff --git a/frontend/javascripts/oxalis/model/sagas/isosurface_saga.js b/frontend/javascripts/oxalis/model/sagas/isosurface_saga.js index dcc142b6546..ce0af0038ff 100644 --- a/frontend/javascripts/oxalis/model/sagas/isosurface_saga.js +++ b/frontend/javascripts/oxalis/model/sagas/isosurface_saga.js @@ -214,6 +214,7 @@ function* maybeLoadIsosurface( threeDMap.set(clippedPosition, true); const voxelDimensions = window.__isosurfaceVoxelDimensions || [4, 4, 4]; + const scale = yield* select(state => state.dataset.dataSource.scale); const dataStoreHost = yield* select(state => state.dataset.dataStore.url); const tracingStoreHost = yield* select(state => state.tracing.tracingStore.url); @@ -234,6 +235,7 @@ function* maybeLoadIsosurface( segmentId, voxelDimensions, cubeSize, + scale, }, ); diff --git a/frontend/javascripts/oxalis/view/settings/dataset_settings_view.js b/frontend/javascripts/oxalis/view/settings/dataset_settings_view.js index 2616a715780..5d019662a24 100644 --- a/frontend/javascripts/oxalis/view/settings/dataset_settings_view.js +++ b/frontend/javascripts/oxalis/view/settings/dataset_settings_view.js @@ -44,10 +44,7 @@ import Store, { } from "oxalis/store"; import Toast from "libs/toast"; import * as Utils from "libs/utils"; -import constants, { - type ViewMode, - type Vector3, -} from "oxalis/constants"; +import constants, { type ViewMode, type Vector3 } from "oxalis/constants"; import messages, { settings } from "messages"; import Histogram, { isHistogramSupported } from "./histogram_view"; diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/BinaryDataController.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/BinaryDataController.scala index 466bb22baa0..ab80f17f51f 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/BinaryDataController.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/BinaryDataController.scala @@ -342,14 +342,14 @@ class BinaryDataController @Inject()( AllowRemoteOrigin { for { (dataSource, dataLayer) <- getDataSourceAndDataLayer(organizationName, dataSetName, dataLayerName) - segmentationLayer <- tryo(dataLayer.asInstanceOf[SegmentationLayer]).toFox ?~> Messages( - "dataLayer.mustBeSegmentation") + segmentationLayer <- tryo(dataLayer.asInstanceOf[SegmentationLayer]).toFox ?~> "dataLayer.mustBeSegmentation" isosurfaceRequest = IsosurfaceRequest( Some(dataSource), segmentationLayer, request.body.cuboid(dataLayer), request.body.segmentId, request.body.voxelDimensions, + request.body.scale, request.body.mapping, request.body.mappingType ) diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/models/DataRequests.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/models/DataRequests.scala index 9725f29c70f..b115a85717c 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/models/DataRequests.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/models/DataRequests.scala @@ -1,6 +1,6 @@ package com.scalableminds.webknossos.datastore.models -import com.scalableminds.util.geometry.{Point3D, Vector3I} +import com.scalableminds.util.geometry.{Point3D, Vector3D, Vector3I} import com.scalableminds.webknossos.datastore.models.datasource.DataLayer import com.scalableminds.webknossos.datastore.models.requests.{Cuboid, DataServiceRequestSettings} import play.api.libs.json.Json @@ -51,6 +51,7 @@ case class WebKnossosIsosurfaceRequest( cubeSize: Point3D, segmentId: Long, voxelDimensions: Vector3I, + scale: Vector3D, mapping: Option[String] = None, mappingType: Option[String] = None ) { diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/IsosurfaceService.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/IsosurfaceService.scala index 2a1c6165a46..249891e4174 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/IsosurfaceService.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/IsosurfaceService.scala @@ -31,6 +31,7 @@ case class IsosurfaceRequest( cuboid: Cuboid, segmentId: Long, voxelDimensions: Vector3I, + scale: Vector3D, mapping: Option[String] = None, mappingType: Option[String] = None ) @@ -187,10 +188,7 @@ class IsosurfaceService @Inject()( math.ceil(cuboid.depth / voxelDimensions.z).toInt) val offset = Vector3D(cuboid.topLeft.x, cuboid.topLeft.y, cuboid.topLeft.z) - val scale = Vector3D(cuboid.topLeft.resolution) * request.dataSource - .map(_.scale.toVector) - .getOrElse(Vector3D(1, 1, 1)) - + val scale = Vector3D(cuboid.topLeft.resolution) * request.scale val typedSegmentId = dataTypeFunctors.fromLong(request.segmentId) val vertexBuffer = mutable.ArrayBuffer[Vector3D]() diff --git a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingService.scala b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingService.scala index 61e1a082cf4..fe919772896 100644 --- a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingService.scala +++ b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingService.scala @@ -265,6 +265,7 @@ class VolumeTracingService @Inject()( request.cuboid(segmentationLayer), request.segmentId, request.voxelDimensions, + request.scale, request.mapping, request.mappingType ) From f3a66003eba679af49ebae257543572a114cfff2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20B=C3=BC=C3=9Femeyer?= Date: Wed, 29 Jul 2020 18:18:47 +0200 Subject: [PATCH 06/20] Add button to reload Isosurfaces of modified cells --- frontend/javascripts/libs/ThreeDMap.js | 18 ++++++++ frontend/javascripts/oxalis/api/api_latest.js | 5 +++ .../model/actions/annotation_actions.js | 9 ++++ .../oxalis/model/sagas/isosurface_saga.js | 45 +++++++++++++++++-- .../oxalis/view/td_view_controls.js | 25 +++++++++-- 5 files changed, 95 insertions(+), 7 deletions(-) diff --git a/frontend/javascripts/libs/ThreeDMap.js b/frontend/javascripts/libs/ThreeDMap.js index 3055e822aca..fc3f3942364 100644 --- a/frontend/javascripts/libs/ThreeDMap.js +++ b/frontend/javascripts/libs/ThreeDMap.js @@ -44,6 +44,24 @@ export default class ThreeDMap { .set(z, value); } + entries(): Array<[T, Vector3]> { + const entries: Array<[T, Vector3]> = []; + this.map.forEach((atX, x) => { + if (!atX) { + return; + } + atX.forEach((atY, y) => { + if (!atY) { + return; + } + atY.forEach((value, z) => { + entries.push([value, [x, y, z]]); + }); + }); + }); + return entries; + } + // This could be extended so the key is a Vector1 | Vector2 // if needed in the future delete(key: number): boolean { diff --git a/frontend/javascripts/oxalis/api/api_latest.js b/frontend/javascripts/oxalis/api/api_latest.js index 84ce07147c7..b5138ffec87 100644 --- a/frontend/javascripts/oxalis/api/api_latest.js +++ b/frontend/javascripts/oxalis/api/api_latest.js @@ -70,6 +70,7 @@ import { setTreeGroupsAction, } from "oxalis/model/actions/skeletontracing_actions"; import { setPositionAction, setRotationAction } from "oxalis/model/actions/flycam_actions"; +import { refreshIsosurfacesAction } from "oxalis/model/actions/annotation_actions"; import { updateUserSettingAction, updateDatasetSettingAction, @@ -805,6 +806,10 @@ class DataApi { return Store.getState().temporaryConfiguration.activeMapping.isMappingEnabled; } + refreshIsosurfaces(): void { + Store.dispatch(refreshIsosurfacesAction()); + } + /** * Returns the bounding box for a given layer name. */ diff --git a/frontend/javascripts/oxalis/model/actions/annotation_actions.js b/frontend/javascripts/oxalis/model/actions/annotation_actions.js index ea700b84114..912cfaf79d0 100644 --- a/frontend/javascripts/oxalis/model/actions/annotation_actions.js +++ b/frontend/javascripts/oxalis/model/actions/annotation_actions.js @@ -70,6 +70,10 @@ export type TriggerIsosurfaceDownloadAction = { type: "TRIGGER_ISOSURFACE_DOWNLOAD", }; +export type RefreshIsosurfacesAction = { + type: "REFRESH_ISOSURFACES", +}; + export type ImportIsosurfaceFromStlAction = { type: "IMPORT_ISOSURFACE_FROM_STL", buffer: ArrayBuffer, @@ -93,6 +97,7 @@ export type AnnotationActionTypes = | CreateMeshFromBufferAction | UpdateLocalMeshMetaDataAction | TriggerIsosurfaceDownloadAction + | RefreshIsosurfacesAction | ImportIsosurfaceFromStlAction | RemoveIsosurfaceAction; @@ -177,6 +182,10 @@ export const triggerIsosurfaceDownloadAction = (): TriggerIsosurfaceDownloadActi type: "TRIGGER_ISOSURFACE_DOWNLOAD", }); +export const refreshIsosurfacesAction = (): RefreshIsosurfacesAction => ({ + type: "REFRESH_ISOSURFACES", +}); + export const importIsosurfaceFromStlAction = ( buffer: ArrayBuffer, ): ImportIsosurfaceFromStlAction => ({ diff --git a/frontend/javascripts/oxalis/model/sagas/isosurface_saga.js b/frontend/javascripts/oxalis/model/sagas/isosurface_saga.js index ce0af0038ff..16bb8b7fa68 100644 --- a/frontend/javascripts/oxalis/model/sagas/isosurface_saga.js +++ b/frontend/javascripts/oxalis/model/sagas/isosurface_saga.js @@ -8,9 +8,10 @@ import { } from "oxalis/model/actions/segmentation_actions"; import { type Vector3 } from "oxalis/constants"; import { type FlycamAction, FlycamActions } from "oxalis/model/actions/flycam_actions"; -import type { - ImportIsosurfaceFromStlAction, - RemoveIsosurfaceAction, +import { + removeIsosurfaceAction, + type ImportIsosurfaceFromStlAction, + type RemoveIsosurfaceAction, } from "oxalis/model/actions/annotation_actions"; import { type Saga, @@ -33,9 +34,11 @@ import exportToStl from "libs/stl_exporter"; import getSceneController from "oxalis/controller/scene_controller_provider"; import parseStlBuffer from "libs/parse_stl_buffer"; import window from "libs/window"; +import { enforceVolumeTracing } from "oxalis/model/accessors/volumetracing_accessor"; const isosurfacesMap: Map> = new Map(); const cubeSize = [256, 256, 256]; +const modifiedCells: Set = new Set(); export function isIsosurfaceStl(buffer: ArrayBuffer): boolean { const dataView = new DataView(buffer); @@ -127,8 +130,9 @@ function* getCurrentCellId(): Saga { function* ensureSuitableIsosurface( maybeFlycamAction: ?FlycamAction, seedPosition?: Vector3, + cellId?: number, ): Saga { - const segmentId = currentViewIsosurfaceCellId; + const segmentId = cellId != null ? cellId : currentViewIsosurfaceCellId; if (segmentId === 0) { return; } @@ -296,6 +300,37 @@ function* removeIsosurface(action: RemoveIsosurfaceAction): Saga { } } +function* refreshIsosurfaces(): Saga { + const renderIsosurfaces = yield* select(state => state.datasetConfiguration.renderIsosurfaces); + if (!renderIsosurfaces) { + return; + } + // We reload all cells that got modified till the start of reloading. + // By that we avoid that removing cells that got annotated during reloading from the modifiedCells set. + const currentlyModifiedCells = new Set(modifiedCells); + modifiedCells.clear(); + // We first create an array containing information about all loaded isosurfaces as the map is manipulated within the loop. + for (const [cellId, threeDMap] of Array.from(isosurfacesMap.entries())) { + if (!currentlyModifiedCells.has(cellId)) { + continue; + } + // debugger; + const possibleEntry = threeDMap.entries().find(([value, _position]) => value); + if (!possibleEntry) { + return; + } + const [, possibleSeedPosition] = possibleEntry; + yield* put(removeIsosurfaceAction(cellId)); + // Reload the Isosurface. + yield* call(ensureSuitableIsosurface, null, possibleSeedPosition, cellId); + } +} + +function* noteEveryEditedCell(): Saga { + const activeCellId = yield* select(state => enforceVolumeTracing(state.tracing).activeCellId); + modifiedCells.add(activeCellId); +} + export default function* isosurfaceSaga(): Saga { yield* take("WK_READY"); yield _takeEvery(FlycamActions, ensureSuitableIsosurface); @@ -303,4 +338,6 @@ export default function* isosurfaceSaga(): Saga { yield _takeEvery("TRIGGER_ISOSURFACE_DOWNLOAD", downloadActiveIsosurfaceCell); yield _takeEvery("IMPORT_ISOSURFACE_FROM_STL", importIsosurfaceFromStl); yield _takeEvery("REMOVE_ISOSURFACE", removeIsosurface); + yield _takeEvery("REFRESH_ISOSURFACES", refreshIsosurfaces); + yield _takeEvery("START_EDITING", noteEveryEditedCell); } diff --git a/frontend/javascripts/oxalis/view/td_view_controls.js b/frontend/javascripts/oxalis/view/td_view_controls.js index d5d8b759f2c..50245ee6787 100644 --- a/frontend/javascripts/oxalis/view/td_view_controls.js +++ b/frontend/javascripts/oxalis/view/td_view_controls.js @@ -1,12 +1,18 @@ // @flow -import { Button } from "antd"; +import { Button, Icon, Tooltip } from "antd"; import * as React from "react"; +import { connect } from "react-redux"; +import type { OxalisState } from "oxalis/store"; import api from "oxalis/api/internal_api"; const ButtonGroup = Button.Group; -function TDViewControls() { +type Props = {| + renderIsosurfaces: boolean, +|}; + +function TDViewControls({ renderIsosurfaces }: Props) { return ( + {renderIsosurfaces ? ( + + + + ) : null} ); } -export default TDViewControls; +export function mapStateToProps(state: OxalisState): Props { + return { + renderIsosurfaces: state.datasetConfiguration.renderIsosurfaces, + }; +} + +export default connect(mapStateToProps)(TDViewControls); From 16f9a296ca7fd678168a8e239a99668df754e2d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20B=C3=BC=C3=9Femeyer?= Date: Thu, 30 Jul 2020 11:32:10 +0200 Subject: [PATCH 07/20] save before reloading & reload isosurface of all cubes --- .../oxalis/model/sagas/isosurface_saga.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/frontend/javascripts/oxalis/model/sagas/isosurface_saga.js b/frontend/javascripts/oxalis/model/sagas/isosurface_saga.js index 16bb8b7fa68..7699d3a29e4 100644 --- a/frontend/javascripts/oxalis/model/sagas/isosurface_saga.js +++ b/frontend/javascripts/oxalis/model/sagas/isosurface_saga.js @@ -35,6 +35,7 @@ import getSceneController from "oxalis/controller/scene_controller_provider"; import parseStlBuffer from "libs/parse_stl_buffer"; import window from "libs/window"; import { enforceVolumeTracing } from "oxalis/model/accessors/volumetracing_accessor"; +import { saveNowAction } from "oxalis/model/actions/save_actions"; const isosurfacesMap: Map> = new Map(); const cubeSize = [256, 256, 256]; @@ -305,6 +306,7 @@ function* refreshIsosurfaces(): Saga { if (!renderIsosurfaces) { return; } + yield* put(saveNowAction()); // We reload all cells that got modified till the start of reloading. // By that we avoid that removing cells that got annotated during reloading from the modifiedCells set. const currentlyModifiedCells = new Set(modifiedCells); @@ -314,15 +316,15 @@ function* refreshIsosurfaces(): Saga { if (!currentlyModifiedCells.has(cellId)) { continue; } - // debugger; - const possibleEntry = threeDMap.entries().find(([value, _position]) => value); - if (!possibleEntry) { - return; + const isoSurfacePositions = threeDMap.entries().filter(([value, _position]) => value); + if (isoSurfacePositions.length <= 0) { + continue; } - const [, possibleSeedPosition] = possibleEntry; yield* put(removeIsosurfaceAction(cellId)); + for (const [, position] of isoSurfacePositions) { + yield* call(ensureSuitableIsosurface, null, position, cellId); + } // Reload the Isosurface. - yield* call(ensureSuitableIsosurface, null, possibleSeedPosition, cellId); } } From 19cec69a0b915327de71e04891016d35efb38bfd Mon Sep 17 00:00:00 2001 From: Youri K Date: Tue, 11 Aug 2020 14:57:04 +0200 Subject: [PATCH 08/20] fix isosurface dependency injection --- .../datastore/DataStoreModule.scala | 2 +- .../controllers/BinaryDataController.scala | 9 +++- .../services/BinaryDataService.scala | 25 +++-------- .../services/BinaryDataServiceHolder.scala | 21 +++++++++ .../services/IsosurfaceService.scala | 17 +++----- .../volume/VolumeTracingService.scala | 43 +++++++++---------- 6 files changed, 62 insertions(+), 55 deletions(-) diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/DataStoreModule.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/DataStoreModule.scala index 21f7edbcf83..087d1dab1e4 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/DataStoreModule.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/DataStoreModule.scala @@ -20,7 +20,7 @@ class DataStoreModule(environment: Environment, configuration: Configuration) ex bind(classOf[BinaryDataServiceHolder]).asEagerSingleton() bind(classOf[MappingService]).asEagerSingleton() bind(classOf[AgglomerateService]).asEagerSingleton() - bind(classOf[IsosurfaceService]).asEagerSingleton() + bind(classOf[IsosurfaceServiceHolder]).asEagerSingleton() bind(classOf[SampleDataSourceService]).asEagerSingleton() } } diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/BinaryDataController.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/BinaryDataController.scala index ab80f17f51f..0086277ff48 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/BinaryDataController.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/BinaryDataController.scala @@ -40,13 +40,18 @@ class BinaryDataController @Inject()( accessTokenService: DataStoreAccessTokenService, binaryDataServiceHolder: BinaryDataServiceHolder, mappingService: MappingService, - isosurfaceService: IsosurfaceService, + isosurfaceServiceHolder: IsosurfaceServiceHolder, findDataService: FindDataService, actorSystem: ActorSystem )(implicit ec: ExecutionContext, bodyParsers: PlayBodyParsers) extends Controller { - val binaryDataService = binaryDataServiceHolder.binaryDataService + val binaryDataService: BinaryDataService = binaryDataServiceHolder.binaryDataService + isosurfaceServiceHolder.dataStoreIsosurfaceConfig = (binaryDataService, + mappingService, + config.Braingames.Binary.isosurfaceTimeout, + config.Braingames.Binary.isosurfaceActorPoolSize) + val isosurfaceService: IsosurfaceService = isosurfaceServiceHolder.dataStoreIsosurfaceService /** * Handles requests for raw binary data via HTTP POST from webKnossos. diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/BinaryDataService.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/BinaryDataService.scala index cccfc66c333..edc6b27c268 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/BinaryDataService.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/BinaryDataService.scala @@ -1,31 +1,18 @@ package com.scalableminds.webknossos.datastore.services -import scala.reflect.io.Directory + import java.io.File -import java.nio.{ByteBuffer, ByteOrder, LongBuffer} -import java.nio.file.{Files, Path, Paths, StandardCopyOption} +import java.nio.file.{Files, Path} import com.scalableminds.util.geometry.{Point3D, Vector3I} -import com.scalableminds.webknossos.datastore.models.BucketPosition -import com.scalableminds.webknossos.datastore.models.datasource.{Category, DataLayer, ElementClass} -import com.scalableminds.webknossos.datastore.models.requests.{ - DataReadInstruction, - DataServiceDataRequest, - DataServiceMappingRequest, - MappingReadInstruction -} -import com.scalableminds.webknossos.datastore.storage.{ - CachedAgglomerateFile, - CachedAgglomerateKey, - CachedCube, - DataCubeCache -} import com.scalableminds.util.tools.ExtendedTypes.ExtendedArraySeq import com.scalableminds.util.tools.{Fox, FoxImplicits} +import com.scalableminds.webknossos.datastore.models.BucketPosition +import com.scalableminds.webknossos.datastore.models.datasource.{Category, DataLayer, ElementClass} +import com.scalableminds.webknossos.datastore.models.requests.{DataReadInstruction, DataServiceDataRequest} +import com.scalableminds.webknossos.datastore.storage.{CachedAgglomerateKey, CachedCube, DataCubeCache} import com.typesafe.scalalogging.LazyLogging import net.liftweb.common.Full -import spire.math.UInt -import scala.collection.mutable import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration._ diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/BinaryDataServiceHolder.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/BinaryDataServiceHolder.scala index bb1afac973b..35f1af839bf 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/BinaryDataServiceHolder.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/BinaryDataServiceHolder.scala @@ -2,9 +2,13 @@ package com.scalableminds.webknossos.datastore.services import java.nio.file.Paths +import akka.actor.ActorSystem import com.scalableminds.webknossos.datastore.DataStoreConfig import javax.inject.Inject +import scala.concurrent.ExecutionContext +import scala.concurrent.duration.FiniteDuration + /* * The BinaryDataService needs to be instantiated as singleton to provide a shared DataCubeCache. * There is, however an additional instance for volume tracings in the TracingStore @@ -20,3 +24,20 @@ class BinaryDataServiceHolder @Inject()(config: DataStoreConfig, agglomerateServ agglomerateService) } + +class IsosurfaceServiceHolder @Inject()(actorSystem: ActorSystem)(implicit ec: ExecutionContext) { + var dataStoreIsosurfaceConfig: (BinaryDataService, MappingService, FiniteDuration, Int) = (null, null, null, 0) + lazy val dataStoreIsosurfaceService: IsosurfaceService = new IsosurfaceService(dataStoreIsosurfaceConfig._1, + dataStoreIsosurfaceConfig._2, + actorSystem, + dataStoreIsosurfaceConfig._3, + dataStoreIsosurfaceConfig._4) + + var tracingStoreIsosurfaceConfig: (BinaryDataService, FiniteDuration, Int) = (null, null, 0) + lazy val tracingStoreIsosurfaceService: IsosurfaceService = new IsosurfaceService(tracingStoreIsosurfaceConfig._1, + null, + actorSystem, + tracingStoreIsosurfaceConfig._2, + tracingStoreIsosurfaceConfig._3) + +} diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/IsosurfaceService.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/IsosurfaceService.scala index 249891e4174..ebb1ad40bfb 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/IsosurfaceService.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/IsosurfaceService.scala @@ -52,22 +52,19 @@ class IsosurfaceActor(val service: IsosurfaceService, val timeout: FiniteDuratio } } -class IsosurfaceService @Inject()( - actorSystem: ActorSystem, - dataServicesHolder: BinaryDataServiceHolder, - mappingService: MappingService, - config: DataStoreConfig, -)(implicit ec: ExecutionContext) +class IsosurfaceService(binaryDataService: BinaryDataService, + mappingService: MappingService, + actorSystem: ActorSystem, + isosurfaceTimeout: FiniteDuration, + isosurfaceActorPoolSize: Int)(implicit ec: ExecutionContext) extends FoxImplicits { - val binaryDataService: BinaryDataService = dataServicesHolder.binaryDataService val agglomerateService: AgglomerateService = binaryDataService.agglomerateService - implicit val timeout: Timeout = Timeout(config.Braingames.Binary.isosurfaceTimeout) + implicit val timeout: Timeout = Timeout(isosurfaceTimeout) val actor = actorSystem.actorOf( - RoundRobinPool(config.Braingames.Binary.isosurfaceActorPoolSize) - .props(Props(new IsosurfaceActor(this, timeout.duration)))) + RoundRobinPool(isosurfaceActorPoolSize).props(Props(new IsosurfaceActor(this, timeout.duration)))) def requestIsosurfaceViaActor(request: IsosurfaceRequest): Fox[(Array[Float], List[Int])] = actor.ask(request).mapTo[Box[(Array[Float], List[Int])]].recover { diff --git a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingService.scala b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingService.scala index b3221de982d..cc7e88881ed 100644 --- a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingService.scala +++ b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingService.scala @@ -5,44 +5,38 @@ import java.nio.file.Paths import com.google.inject.Inject import com.scalableminds.util.geometry.{BoundingBox, Point3D} -import com.scalableminds.webknossos.datastore.dataformats.wkw.{WKWBucketStreamSink, WKWDataFormatHelper} -import com.scalableminds.webknossos.datastore.models.{BucketPosition, WebKnossosIsosurfaceRequest} -import com.scalableminds.webknossos.datastore.models.datasource.{DataSource, ElementClass, SegmentationLayer} -import com.scalableminds.webknossos.tracingstore.VolumeTracing.VolumeTracing -import com.scalableminds.webknossos.tracingstore.tracings._ import com.scalableminds.util.io.{NamedStream, ZipIO} import com.scalableminds.util.tools.{Fox, FoxImplicits, TextUtils} -import com.scalableminds.webknossos.datastore.DataStoreConfig +import com.scalableminds.webknossos.datastore.dataformats.wkw.{WKWBucketStreamSink, WKWDataFormatHelper} import com.scalableminds.webknossos.datastore.models.DataRequestCollection.DataRequestCollection +import com.scalableminds.webknossos.datastore.models.datasource.{DataSource, SegmentationLayer} import com.scalableminds.webknossos.datastore.models.requests.DataServiceDataRequest -import com.scalableminds.webknossos.datastore.services.{BinaryDataService, IsosurfaceRequest, IsosurfaceService} -import com.scalableminds.webknossos.datastore.storage.TemporaryStore -import com.scalableminds.webknossos.tracingstore.SkeletonTracing.SkeletonTracing -import com.scalableminds.webknossos.tracingstore.{RedisTemporaryStore, TracingStoreConfig} +import com.scalableminds.webknossos.datastore.models.{BucketPosition, WebKnossosIsosurfaceRequest} +import com.scalableminds.webknossos.datastore.services.{ + BinaryDataService, + IsosurfaceRequest, + IsosurfaceService, + IsosurfaceServiceHolder +} +import com.scalableminds.webknossos.tracingstore.RedisTemporaryStore +import com.scalableminds.webknossos.tracingstore.VolumeTracing.VolumeTracing +import com.scalableminds.webknossos.tracingstore.geometry.NamedBoundingBox +import com.scalableminds.webknossos.tracingstore.tracings._ import com.scalableminds.webknossos.wrap.WKWFile import com.typesafe.scalalogging.LazyLogging import net.liftweb.common.{Box, Empty, Failure, Full} -import play.api.i18n.Messages import play.api.libs.Files import play.api.libs.Files.TemporaryFileCreator -import play.api.libs.iteratee.Concurrent.Channel - -import scala.concurrent.duration._ -import play.api.libs.iteratee.{Concurrent, Enumerator, Input} +import play.api.libs.iteratee.Enumerator import play.api.libs.json.{JsObject, Json} -import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.ExecutionContext.Implicits.global -import scala.util.Try -import com.scalableminds.webknossos.tracingstore.geometry.{ - NamedBoundingBox, - BoundingBox => ProtoBox, - Point3D => ProtoPoint -} +import scala.concurrent.Future +import scala.concurrent.duration._ class VolumeTracingService @Inject()( tracingDataStore: TracingDataStore, - isosurfaceService: IsosurfaceService, + isosurfaceServiceHolder: IsosurfaceServiceHolder, val temporaryTracingStore: TemporaryTracingStore[VolumeTracing], val handledGroupIdStore: RedisTemporaryStore, val uncommittedUpdatesStore: RedisTemporaryStore, @@ -70,6 +64,9 @@ class VolumeTracingService @Inject()( actually load anything from disk, unlike its “normal” instance in the datastore (only from the volume tracing store) */ val binaryDataService = new BinaryDataService(Paths.get(""), 10 seconds, 100, null) + isosurfaceServiceHolder.tracingStoreIsosurfaceConfig = (binaryDataService, 30 seconds, 1) + val isosurfaceService: IsosurfaceService = isosurfaceServiceHolder.tracingStoreIsosurfaceService + override def currentVersion(tracingId: String): Fox[Long] = tracingDataStore.volumes.getVersion(tracingId, mayBeEmpty = Some(true), emptyFallback = Some(0L)) From 5594a18df53a2df5d943411f2ff75a932ee5c2a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20B=C3=BC=C3=9Femeyer?= Date: Mon, 17 Aug 2020 11:07:01 +0200 Subject: [PATCH 09/20] add loading animation to isosurface refreshing --- frontend/javascripts/oxalis/api/api_latest.js | 17 +++++++++++++++-- frontend/javascripts/oxalis/default_state.js | 1 + .../oxalis/model/actions/annotation_actions.js | 9 +++++++++ .../oxalis/model/reducers/ui_reducer.js | 8 ++++++++ .../oxalis/model/sagas/isosurface_saga.js | 2 ++ frontend/javascripts/oxalis/store.js | 1 + .../javascripts/oxalis/view/td_view_controls.js | 7 +++---- 7 files changed, 39 insertions(+), 6 deletions(-) diff --git a/frontend/javascripts/oxalis/api/api_latest.js b/frontend/javascripts/oxalis/api/api_latest.js index 2fe20e162ba..f2323632ced 100644 --- a/frontend/javascripts/oxalis/api/api_latest.js +++ b/frontend/javascripts/oxalis/api/api_latest.js @@ -102,6 +102,7 @@ import messages from "messages"; import window, { location } from "libs/window"; import { type ElementClass } from "admin/api_flow_types"; import UserLocalStorage from "libs/user_local_storage"; +import { listenToStoreProperty } from "oxalis/model/helpers/listener_helpers"; type OutdatedDatasetConfigurationKeys = "segmentationOpacity" | "isSegmentationDisabled"; @@ -814,8 +815,20 @@ class DataApi { return Store.getState().temporaryConfiguration.activeMapping.isMappingEnabled; } - refreshIsosurfaces(): void { - Store.dispatch(refreshIsosurfacesAction()); + async refreshIsosurfaces(): Promise { + await Store.dispatch(refreshIsosurfacesAction()); + const isRefreshingComplete = new Promise(resolve => { + const unsubscribe = listenToStoreProperty( + state => state.uiInformation.refreshingIsosurfaces, + refreshingIsosurfaces => { + if (!refreshingIsosurfaces) { + unsubscribe(); + resolve(); + } + }, + ); + }); + return isRefreshingComplete; } /** diff --git a/frontend/javascripts/oxalis/default_state.js b/frontend/javascripts/oxalis/default_state.js index bc023f3ce7f..6602c84f943 100644 --- a/frontend/javascripts/oxalis/default_state.js +++ b/frontend/javascripts/oxalis/default_state.js @@ -202,6 +202,7 @@ const defaultState: OxalisState = { isImportingMesh: false, isInAnnotationView: false, hasOrganizations: false, + refreshingIsosurfaces: false, }, }; diff --git a/frontend/javascripts/oxalis/model/actions/annotation_actions.js b/frontend/javascripts/oxalis/model/actions/annotation_actions.js index 2e99b35fa22..e8865a3bbd5 100644 --- a/frontend/javascripts/oxalis/model/actions/annotation_actions.js +++ b/frontend/javascripts/oxalis/model/actions/annotation_actions.js @@ -79,6 +79,10 @@ export type RefreshIsosurfacesAction = { type: "REFRESH_ISOSURFACES", }; +export type FinishedRefreshingIsosurfacesAction = { + type: "FINISHED_REFRESHING_ISOSURFACES", +}; + export type ImportIsosurfaceFromStlAction = { type: "IMPORT_ISOSURFACE_FROM_STL", buffer: ArrayBuffer, @@ -104,6 +108,7 @@ export type AnnotationActionTypes = | UpdateLocalMeshMetaDataAction | TriggerIsosurfaceDownloadAction | RefreshIsosurfacesAction + | FinishedRefreshingIsosurfacesAction | ImportIsosurfaceFromStlAction | RemoveIsosurfaceAction; @@ -201,6 +206,10 @@ export const refreshIsosurfacesAction = (): RefreshIsosurfacesAction => ({ type: "REFRESH_ISOSURFACES", }); +export const finishedRefreshingIsosurfacesAction = (): FinishedRefreshingIsosurfacesAction => ({ + type: "FINISHED_REFRESHING_ISOSURFACES", +}); + export const importIsosurfaceFromStlAction = ( buffer: ArrayBuffer, ): ImportIsosurfaceFromStlAction => ({ diff --git a/frontend/javascripts/oxalis/model/reducers/ui_reducer.js b/frontend/javascripts/oxalis/model/reducers/ui_reducer.js index 77407410d90..ab62ed6487c 100644 --- a/frontend/javascripts/oxalis/model/reducers/ui_reducer.js +++ b/frontend/javascripts/oxalis/model/reducers/ui_reducer.js @@ -31,6 +31,14 @@ function UiReducer(state: OxalisState, action: Action): OxalisState { return updateKey(state, "uiInformation", { hasOrganizations: action.value }); } + case "REFRESH_ISOSURFACES": { + return updateKey(state, "uiInformation", { refreshingIsosurfaces: true }); + } + + case "FINISHED_REFRESHING_ISOSURFACES": { + return updateKey(state, "uiInformation", { refreshingIsosurfaces: false }); + } + default: return state; } diff --git a/frontend/javascripts/oxalis/model/sagas/isosurface_saga.js b/frontend/javascripts/oxalis/model/sagas/isosurface_saga.js index 7699d3a29e4..8750db341dd 100644 --- a/frontend/javascripts/oxalis/model/sagas/isosurface_saga.js +++ b/frontend/javascripts/oxalis/model/sagas/isosurface_saga.js @@ -10,6 +10,7 @@ import { type Vector3 } from "oxalis/constants"; import { type FlycamAction, FlycamActions } from "oxalis/model/actions/flycam_actions"; import { removeIsosurfaceAction, + refreshIsosurfacesAction, type ImportIsosurfaceFromStlAction, type RemoveIsosurfaceAction, } from "oxalis/model/actions/annotation_actions"; @@ -324,6 +325,7 @@ function* refreshIsosurfaces(): Saga { for (const [, position] of isoSurfacePositions) { yield* call(ensureSuitableIsosurface, null, position, cellId); } + yield* put(refreshIsosurfacesAction()); // Reload the Isosurface. } } diff --git a/frontend/javascripts/oxalis/store.js b/frontend/javascripts/oxalis/store.js index 0fd3e19ed0c..7400d5b0b48 100644 --- a/frontend/javascripts/oxalis/store.js +++ b/frontend/javascripts/oxalis/store.js @@ -441,6 +441,7 @@ type UiInformation = { +isImportingMesh: boolean, +isInAnnotationView: boolean, +hasOrganizations: boolean, + +refreshingIsosurfaces: boolean, }; export type OxalisState = {| diff --git a/frontend/javascripts/oxalis/view/td_view_controls.js b/frontend/javascripts/oxalis/view/td_view_controls.js index 50245ee6787..18ce41e705f 100644 --- a/frontend/javascripts/oxalis/view/td_view_controls.js +++ b/frontend/javascripts/oxalis/view/td_view_controls.js @@ -1,8 +1,9 @@ // @flow -import { Button, Icon, Tooltip } from "antd"; +import { Button, Tooltip } from "antd"; import * as React from "react"; import { connect } from "react-redux"; import type { OxalisState } from "oxalis/store"; +import { AsyncButton } from "components/async_clickables"; import api from "oxalis/api/internal_api"; @@ -32,9 +33,7 @@ function TDViewControls({ renderIsosurfaces }: Props) { {renderIsosurfaces ? ( - + ) : null} From d1df3507b037869f400d52e15171870590bf2c9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20B=C3=BC=C3=9Femeyer?= Date: Mon, 17 Aug 2020 14:40:33 +0200 Subject: [PATCH 10/20] remove isosurfaces from scene, when after fetching the first part --- frontend/javascripts/oxalis/api/api_latest.js | 4 +- .../segmentation_plane_controller.js | 2 +- .../skeletontracing_plane_controller.js | 2 +- .../volumetracing_plane_controller.js | 2 +- .../oxalis/controller/scene_controller.js | 1 + .../model/sagas/effect-generators.js.flow | 9 ++-- .../oxalis/model/sagas/isosurface_saga.js | 51 ++++++++++++------- 7 files changed, 46 insertions(+), 25 deletions(-) diff --git a/frontend/javascripts/oxalis/api/api_latest.js b/frontend/javascripts/oxalis/api/api_latest.js index 8a3fe4eb3fe..324fb321028 100644 --- a/frontend/javascripts/oxalis/api/api_latest.js +++ b/frontend/javascripts/oxalis/api/api_latest.js @@ -823,8 +823,8 @@ class DataApi { return Store.getState().temporaryConfiguration.activeMapping.isMappingEnabled; } - async refreshIsosurfaces(): Promise { - await Store.dispatch(refreshIsosurfacesAction()); + refreshIsosurfaces(): Promise { + Store.dispatch(refreshIsosurfacesAction()); const isRefreshingComplete = new Promise(resolve => { const unsubscribe = listenToStoreProperty( state => state.uiInformation.refreshingIsosurfaces, diff --git a/frontend/javascripts/oxalis/controller/combinations/segmentation_plane_controller.js b/frontend/javascripts/oxalis/controller/combinations/segmentation_plane_controller.js index 8fa57795d59..629c641dadd 100644 --- a/frontend/javascripts/oxalis/controller/combinations/segmentation_plane_controller.js +++ b/frontend/javascripts/oxalis/controller/combinations/segmentation_plane_controller.js @@ -7,7 +7,7 @@ import { getRequestLogZoomStep } from "oxalis/model/accessors/flycam_accessor"; import Store from "oxalis/store"; function isosurfaceLeftClick(pos: Point2, plane: OrthoView, event: MouseEvent) { - if (!event.ctrlKey) { + if (!event.shiftKey) { return; } const segmentation = Model.getSegmentationLayer(); diff --git a/frontend/javascripts/oxalis/controller/combinations/skeletontracing_plane_controller.js b/frontend/javascripts/oxalis/controller/combinations/skeletontracing_plane_controller.js index 2c66cfda0f2..edf20d4bbd0 100644 --- a/frontend/javascripts/oxalis/controller/combinations/skeletontracing_plane_controller.js +++ b/frontend/javascripts/oxalis/controller/combinations/skeletontracing_plane_controller.js @@ -232,7 +232,7 @@ function onClick( } else { Store.dispatch(setActiveNodeAction(nodeId)); } - } else if (ctrlPressed && event != null) { + } else if (shiftPressed && event != null) { isosurfaceLeftClick(position, plane, event); } } diff --git a/frontend/javascripts/oxalis/controller/combinations/volumetracing_plane_controller.js b/frontend/javascripts/oxalis/controller/combinations/volumetracing_plane_controller.js index 746c4260712..296bc6b9c36 100644 --- a/frontend/javascripts/oxalis/controller/combinations/volumetracing_plane_controller.js +++ b/frontend/javascripts/oxalis/controller/combinations/volumetracing_plane_controller.js @@ -169,12 +169,12 @@ export function getPlaneMouseControls(_planeId: OrthoView): * { ); if (cellId > 0) { Store.dispatch(setActiveCellAction(cellId)); + isosurfaceLeftClick(pos, plane, event); } } else if (event.ctrlKey) { if (isAutomaticBrushEnabled()) { Store.dispatch(inferSegmentationInViewportAction(calculateGlobalPos(pos))); } - isosurfaceLeftClick(pos, plane, event); } }, diff --git a/frontend/javascripts/oxalis/controller/scene_controller.js b/frontend/javascripts/oxalis/controller/scene_controller.js index 189d430cc99..2c52cc39378 100644 --- a/frontend/javascripts/oxalis/controller/scene_controller.js +++ b/frontend/javascripts/oxalis/controller/scene_controller.js @@ -186,6 +186,7 @@ class SceneController { .to({ opacity: 0.95 }, 500) .onUpdate(function onUpdate() { meshMaterial.opacity = this.opacity; + app.vent.trigger("rerender"); }) .start(); diff --git a/frontend/javascripts/oxalis/model/sagas/effect-generators.js.flow b/frontend/javascripts/oxalis/model/sagas/effect-generators.js.flow index 7a5538dcca9..c5d1a34bfa2 100644 --- a/frontend/javascripts/oxalis/model/sagas/effect-generators.js.flow +++ b/frontend/javascripts/oxalis/model/sagas/effect-generators.js.flow @@ -8,7 +8,7 @@ import { type Saga as _Saga, channel, effects -} from 'redux-saga'; +} from "redux-saga"; import { all as typedAll, race as typedRace, @@ -19,7 +19,7 @@ import { takeLeading as typedTakeLeading, throttle as typedThrottle, cancel as typedCancel, -} from 'redux-saga/effects'; +} from "redux-saga/effects"; import type { Action } from "oxalis/model/actions/actions"; import type { OxalisState } from "oxalis/store"; @@ -37,6 +37,7 @@ declare type Fn3 = (t1: T1, t2: T2, t3: T3) => Promise | Gener declare type Fn4 = (t1: T1, t2: T2, t3: T3, t4: T4) => Promise | Generator<*,R,*> | R; declare type Fn5 = (t1: T1, t2: T2, t3: T3, t4: T4, t5: T5) => Promise | Generator<*,R,*> | R; declare type Fn6 = (t1: T1, t2: T2, t3: T3, t4: T4, t5: T5, t6: T6) => Promise | Generator<*,R,*> | R; +declare type Fn7 = (t1: T1, t2: T2, t3: T3, t4: T4, t5: T5, t6: T6, t7:T7) => Promise | Generator<*,R,*> | R; /* ------------------ SELECT Stuff ------------------ */ @@ -69,6 +70,7 @@ declare type ContextCallFn = & (>(cfn: [C, Fn], t1: T1, t2: T2, t3: T3, t4: T4, ...rest: Array) => Generator<*, R, *>) & (>(cfn: [C, Fn], t1: T1, t2: T2, t3: T3, t4: T4, t5: T5, ...rest: Array) => Generator<*, R, *>) & (>(cfn: [C, Fn], t1: T1, t2: T2, t3: T3, t4: T4, t5: T5, t6: T6, ...rest: Array) => Generator<*, R, *>) + & (>(cfn: [C, Fn], t1: T1, t2: T2, t3: T3, t4: T4, t5: T5, t6: T6, t7: t7, ...rest: Array) => Generator<*, R, *>) // & (>(cfn: [C, Fn], t1: T, t2: T, t3: T, t4: T, t5: T, t6: T, ...args: Array) => Generator<*, R, *>); declare type CallFn = @@ -79,7 +81,8 @@ declare type CallFn = & (>(fn: Fn, t1: T1, t2: T2, t3: T3) => Generator<*, R, *>) & (>(fn: Fn, t1: T1, t2: T2, t3: T3, t4: T4) => Generator<*, R, *>) & (>(fn: Fn, t1: T1, t2: T2, t3: T3, t4: T4, t5: T5) => Generator<*, R, *>) - & (>(fn: Fn, t1: T1, t2: T2, t3: T3, t4: T4, t5: T5, t6: T6) => Generator<*, R, *>); + & (>(fn: Fn, t1: T1, t2: T2, t3: T3, t4: T4, t5: T5, t6: T6) => Generator<*, R, *>) + & (>(fn: Fn, t1: T1, t2: T2, t3: T3, t4: T4, t5: T5, t6: T6, t7: T7) => Generator<*, R, *>); // & (>(fn: Fn, ...args: Array) => Generator<*, R, *>); /* ------------------ CPS Stuff ------------------ */ diff --git a/frontend/javascripts/oxalis/model/sagas/isosurface_saga.js b/frontend/javascripts/oxalis/model/sagas/isosurface_saga.js index 67b07b552fe..d5341f92567 100644 --- a/frontend/javascripts/oxalis/model/sagas/isosurface_saga.js +++ b/frontend/javascripts/oxalis/model/sagas/isosurface_saga.js @@ -10,7 +10,7 @@ import { type Vector3 } from "oxalis/constants"; import { type FlycamAction, FlycamActions } from "oxalis/model/actions/flycam_actions"; import { removeIsosurfaceAction, - refreshIsosurfacesAction, + finishedRefreshingIsosurfacesAction, type ImportIsosurfaceFromStlAction, type RemoveIsosurfaceAction, } from "oxalis/model/actions/annotation_actions"; @@ -117,7 +117,7 @@ const MAXIMUM_BATCH_SIZE = 50; function* changeActiveIsosurfaceCell(action: ChangeActiveIsosurfaceCellAction): Saga { currentViewIsosurfaceCellId = action.cellId; - yield* call(ensureSuitableIsosurface, null, action.seedPosition); + yield* call(ensureSuitableIsosurface, null, action.seedPosition, currentViewIsosurfaceCellId); } // This function either returns the activeCellId of the current volume tracing @@ -135,6 +135,7 @@ function* ensureSuitableIsosurface( maybeFlycamAction: ?FlycamAction, seedPosition?: Vector3, cellId?: number, + removeExistingIsosurface: boolean = false, ): Saga { const segmentId = cellId != null ? cellId : currentViewIsosurfaceCellId; if (segmentId === 0) { @@ -167,6 +168,7 @@ function* ensureSuitableIsosurface( clippedPosition, zoomStep, resolutions, + removeExistingIsosurface, ); } @@ -177,9 +179,10 @@ function* loadIsosurfaceWithNeighbors( clippedPosition: Vector3, zoomStep: number, resolutions: Array, + removeExistingIsosurface: boolean, ): Saga { + let isInitialRequest = true; let positionsToRequest = [clippedPosition]; - while (positionsToRequest.length > 0) { const position = positionsToRequest.shift(); const neighbors = yield* call( @@ -190,7 +193,9 @@ function* loadIsosurfaceWithNeighbors( position, zoomStep, resolutions, + removeExistingIsosurface && isInitialRequest, ); + isInitialRequest = false; positionsToRequest = positionsToRequest.concat(neighbors); } } @@ -208,6 +213,7 @@ function* maybeLoadIsosurface( clippedPosition: Vector3, zoomStep: number, resolutions: Array, + removeExistingIsosurface: boolean, ): Saga> { const threeDMap = getMapForSegment(segmentId); @@ -252,8 +258,10 @@ function* maybeLoadIsosurface( if (hasBatchCounterExceededLimit(segmentId)) { return []; } - const vertices = new Float32Array(responseBuffer); + if (removeExistingIsosurface) { + getSceneController().removeIsosurfaceById(segmentId); + } getSceneController().addIsosurfaceFromVertices(vertices, segmentId); return neighbors.map(neighbor => @@ -292,9 +300,14 @@ function* importIsosurfaceFromStl(action: ImportIsosurfaceFromStlAction): Saga { +function* removeIsosurface( + action: RemoveIsosurfaceAction, + removeFromScene: boolean = true, +): Saga { const { cellId } = action; - getSceneController().removeIsosurfaceById(cellId); + if (removeFromScene) { + getSceneController().removeIsosurfaceById(cellId); + } removeMapForSegment(cellId); // Set batch counter to maximum so that potentially running requests are aborted @@ -308,6 +321,11 @@ function* removeIsosurface(action: RemoveIsosurfaceAction): Saga { } } +function* noteEveryEditedCell(): Saga { + const activeCellId = yield* select(state => enforceVolumeTracing(state.tracing).activeCellId); + modifiedCells.add(activeCellId); +} + function* refreshIsosurfaces(): Saga { const renderIsosurfaces = yield* select(state => state.datasetConfiguration.renderIsosurfaces); if (!renderIsosurfaces) { @@ -318,7 +336,7 @@ function* refreshIsosurfaces(): Saga { // By that we avoid that removing cells that got annotated during reloading from the modifiedCells set. const currentlyModifiedCells = new Set(modifiedCells); modifiedCells.clear(); - // We first create an array containing information about all loaded isosurfaces as the map is manipulated within the loop. + // First create an array containing information about all loaded isosurfaces as the map is manipulated within the loop. for (const [cellId, threeDMap] of Array.from(isosurfacesMap.entries())) { if (!currentlyModifiedCells.has(cellId)) { continue; @@ -327,18 +345,17 @@ function* refreshIsosurfaces(): Saga { if (isoSurfacePositions.length <= 0) { continue; } - yield* put(removeIsosurfaceAction(cellId)); + // Removing Isosurface from cache. + yield* call(removeIsosurface, removeIsosurfaceAction(cellId), false); + // The isosurface should only be removed once after re-fetching the isosurface first position. + let shouldBeRemoved = true; for (const [, position] of isoSurfacePositions) { - yield* call(ensureSuitableIsosurface, null, position, cellId); + // Reload the Isosurface. + yield* call(ensureSuitableIsosurface, null, position, cellId, shouldBeRemoved); + shouldBeRemoved = false; } - yield* put(refreshIsosurfacesAction()); - // Reload the Isosurface. } -} - -function* noteEveryEditedCell(): Saga { - const activeCellId = yield* select(state => enforceVolumeTracing(state.tracing).activeCellId); - modifiedCells.add(activeCellId); + yield* put(finishedRefreshingIsosurfacesAction()); } export default function* isosurfaceSaga(): Saga { @@ -349,5 +366,5 @@ export default function* isosurfaceSaga(): Saga { yield _takeEvery("IMPORT_ISOSURFACE_FROM_STL", importIsosurfaceFromStl); yield _takeEvery("REMOVE_ISOSURFACE", removeIsosurface); yield _takeEvery("REFRESH_ISOSURFACES", refreshIsosurfaces); - yield _takeEvery("START_EDITING", noteEveryEditedCell); + yield _takeEvery(["START_EDITING", "COPY_SEGMENTATION_LAYER"], noteEveryEditedCell); } From 87ddb09302c64eeba57774e9afaa6fd3fe0821ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20B=C3=BC=C3=9Femeyer?= Date: Mon, 17 Aug 2020 14:44:19 +0200 Subject: [PATCH 11/20] fetch isosurface of active cell id in if volume tracing exists --- .../segmentation_plane_controller.js | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/frontend/javascripts/oxalis/controller/combinations/segmentation_plane_controller.js b/frontend/javascripts/oxalis/controller/combinations/segmentation_plane_controller.js index 629c641dadd..543780f2678 100644 --- a/frontend/javascripts/oxalis/controller/combinations/segmentation_plane_controller.js +++ b/frontend/javascripts/oxalis/controller/combinations/segmentation_plane_controller.js @@ -10,15 +10,21 @@ function isosurfaceLeftClick(pos: Point2, plane: OrthoView, event: MouseEvent) { if (!event.shiftKey) { return; } - const segmentation = Model.getSegmentationLayer(); - if (!segmentation) { - return; - } + let cellId = 0; const position = calculateGlobalPos(pos); - const cellId = segmentation.cube.getMappedDataValue( - position, - getRequestLogZoomStep(Store.getState()), - ); + const volumeTracingMaybe = Store.getState().tracing.volume; + if (volumeTracingMaybe) { + cellId = volumeTracingMaybe.activeCellId; + } else { + const segmentation = Model.getSegmentationLayer(); + if (!segmentation) { + return; + } + cellId = segmentation.cube.getMappedDataValue( + position, + getRequestLogZoomStep(Store.getState()), + ); + } if (cellId > 0) { Store.dispatch(changeActiveIsosurfaceCellAction(cellId, position)); } From ad680eceeaa8adb101746615083e69245b5042ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20B=C3=BC=C3=9Femeyer?= Date: Mon, 17 Aug 2020 16:55:48 +0200 Subject: [PATCH 12/20] fix flow definition --- .../javascripts/oxalis/model/sagas/effect-generators.js.flow | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/javascripts/oxalis/model/sagas/effect-generators.js.flow b/frontend/javascripts/oxalis/model/sagas/effect-generators.js.flow index c5d1a34bfa2..f18ed56452a 100644 --- a/frontend/javascripts/oxalis/model/sagas/effect-generators.js.flow +++ b/frontend/javascripts/oxalis/model/sagas/effect-generators.js.flow @@ -37,7 +37,7 @@ declare type Fn3 = (t1: T1, t2: T2, t3: T3) => Promise | Gener declare type Fn4 = (t1: T1, t2: T2, t3: T3, t4: T4) => Promise | Generator<*,R,*> | R; declare type Fn5 = (t1: T1, t2: T2, t3: T3, t4: T4, t5: T5) => Promise | Generator<*,R,*> | R; declare type Fn6 = (t1: T1, t2: T2, t3: T3, t4: T4, t5: T5, t6: T6) => Promise | Generator<*,R,*> | R; -declare type Fn7 = (t1: T1, t2: T2, t3: T3, t4: T4, t5: T5, t6: T6, t7:T7) => Promise | Generator<*,R,*> | R; +declare type Fn7 = (t1: T1, t2: T2, t3: T3, t4: T4, t5: T5, t6: T6, t7: T7) => Promise | Generator<*,R,*> | R; /* ------------------ SELECT Stuff ------------------ */ @@ -70,7 +70,7 @@ declare type ContextCallFn = & (>(cfn: [C, Fn], t1: T1, t2: T2, t3: T3, t4: T4, ...rest: Array) => Generator<*, R, *>) & (>(cfn: [C, Fn], t1: T1, t2: T2, t3: T3, t4: T4, t5: T5, ...rest: Array) => Generator<*, R, *>) & (>(cfn: [C, Fn], t1: T1, t2: T2, t3: T3, t4: T4, t5: T5, t6: T6, ...rest: Array) => Generator<*, R, *>) - & (>(cfn: [C, Fn], t1: T1, t2: T2, t3: T3, t4: T4, t5: T5, t6: T6, t7: t7, ...rest: Array) => Generator<*, R, *>) + & (>(cfn: [C, Fn], t1: T1, t2: T2, t3: T3, t4: T4, t5: T5, t6: T6, t7: T7, ...rest: Array) => Generator<*, R, *>) // & (>(cfn: [C, Fn], t1: T, t2: T, t3: T, t4: T, t5: T, t6: T, ...args: Array) => Generator<*, R, *>); declare type CallFn = From f4eb1196333228dedce9f060bfe4a4d7b9142f9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20B=C3=BC=C3=9Femeyer?= Date: Tue, 1 Sep 2020 09:39:35 +0200 Subject: [PATCH 13/20] apply some feedback --- frontend/javascripts/oxalis/api/api_latest.js | 14 +------------- frontend/javascripts/oxalis/default_state.js | 2 +- .../oxalis/model/actions/annotation_actions.js | 6 +++--- .../oxalis/model/reducers/ui_reducer.js | 4 ++-- .../oxalis/model/sagas/isosurface_saga.js | 9 ++++----- frontend/javascripts/oxalis/store.js | 2 +- .../javascripts/oxalis/view/td_view_controls.js | 12 +++++++++--- 7 files changed, 21 insertions(+), 28 deletions(-) diff --git a/frontend/javascripts/oxalis/api/api_latest.js b/frontend/javascripts/oxalis/api/api_latest.js index 324fb321028..e4aad3f28bb 100644 --- a/frontend/javascripts/oxalis/api/api_latest.js +++ b/frontend/javascripts/oxalis/api/api_latest.js @@ -823,20 +823,8 @@ class DataApi { return Store.getState().temporaryConfiguration.activeMapping.isMappingEnabled; } - refreshIsosurfaces(): Promise { + refreshIsosurfaces() { Store.dispatch(refreshIsosurfacesAction()); - const isRefreshingComplete = new Promise(resolve => { - const unsubscribe = listenToStoreProperty( - state => state.uiInformation.refreshingIsosurfaces, - refreshingIsosurfaces => { - if (!refreshingIsosurfaces) { - unsubscribe(); - resolve(); - } - }, - ); - }); - return isRefreshingComplete; } /** diff --git a/frontend/javascripts/oxalis/default_state.js b/frontend/javascripts/oxalis/default_state.js index ea2cedde1b6..b05ad1f6d0a 100644 --- a/frontend/javascripts/oxalis/default_state.js +++ b/frontend/javascripts/oxalis/default_state.js @@ -203,7 +203,7 @@ const defaultState: OxalisState = { isImportingMesh: false, isInAnnotationView: false, hasOrganizations: false, - refreshingIsosurfaces: false, + isRefreshingIsosurfaces: false, }, }; diff --git a/frontend/javascripts/oxalis/model/actions/annotation_actions.js b/frontend/javascripts/oxalis/model/actions/annotation_actions.js index e8865a3bbd5..4b643e1b9c7 100644 --- a/frontend/javascripts/oxalis/model/actions/annotation_actions.js +++ b/frontend/javascripts/oxalis/model/actions/annotation_actions.js @@ -79,7 +79,7 @@ export type RefreshIsosurfacesAction = { type: "REFRESH_ISOSURFACES", }; -export type FinishedRefreshingIsosurfacesAction = { +export type FinishedisRefreshingIsosurfacesAction = { type: "FINISHED_REFRESHING_ISOSURFACES", }; @@ -108,7 +108,7 @@ export type AnnotationActionTypes = | UpdateLocalMeshMetaDataAction | TriggerIsosurfaceDownloadAction | RefreshIsosurfacesAction - | FinishedRefreshingIsosurfacesAction + | FinishedisRefreshingIsosurfacesAction | ImportIsosurfaceFromStlAction | RemoveIsosurfaceAction; @@ -206,7 +206,7 @@ export const refreshIsosurfacesAction = (): RefreshIsosurfacesAction => ({ type: "REFRESH_ISOSURFACES", }); -export const finishedRefreshingIsosurfacesAction = (): FinishedRefreshingIsosurfacesAction => ({ +export const finishedisRefreshingIsosurfacesAction = (): FinishedisRefreshingIsosurfacesAction => ({ type: "FINISHED_REFRESHING_ISOSURFACES", }); diff --git a/frontend/javascripts/oxalis/model/reducers/ui_reducer.js b/frontend/javascripts/oxalis/model/reducers/ui_reducer.js index ab62ed6487c..58dd830bc26 100644 --- a/frontend/javascripts/oxalis/model/reducers/ui_reducer.js +++ b/frontend/javascripts/oxalis/model/reducers/ui_reducer.js @@ -32,11 +32,11 @@ function UiReducer(state: OxalisState, action: Action): OxalisState { } case "REFRESH_ISOSURFACES": { - return updateKey(state, "uiInformation", { refreshingIsosurfaces: true }); + return updateKey(state, "uiInformation", { isRefreshingIsosurfaces: true }); } case "FINISHED_REFRESHING_ISOSURFACES": { - return updateKey(state, "uiInformation", { refreshingIsosurfaces: false }); + return updateKey(state, "uiInformation", { isRefreshingIsosurfaces: false }); } default: diff --git a/frontend/javascripts/oxalis/model/sagas/isosurface_saga.js b/frontend/javascripts/oxalis/model/sagas/isosurface_saga.js index d5341f92567..59a05fb2483 100644 --- a/frontend/javascripts/oxalis/model/sagas/isosurface_saga.js +++ b/frontend/javascripts/oxalis/model/sagas/isosurface_saga.js @@ -10,7 +10,7 @@ import { type Vector3 } from "oxalis/constants"; import { type FlycamAction, FlycamActions } from "oxalis/model/actions/flycam_actions"; import { removeIsosurfaceAction, - finishedRefreshingIsosurfacesAction, + finishedisRefreshingIsosurfacesAction, type ImportIsosurfaceFromStlAction, type RemoveIsosurfaceAction, } from "oxalis/model/actions/annotation_actions"; @@ -116,7 +116,7 @@ const MAXIMUM_BATCH_SIZE = 50; function* changeActiveIsosurfaceCell(action: ChangeActiveIsosurfaceCellAction): Saga { currentViewIsosurfaceCellId = action.cellId; - + debugger; yield* call(ensureSuitableIsosurface, null, action.seedPosition, currentViewIsosurfaceCellId); } @@ -146,9 +146,8 @@ function* ensureSuitableIsosurface( return; } const dataset = yield* select(state => state.dataset); - const volumeTracing = yield* select(state => state.tracing.volume); const layer = Model.getSegmentationLayer(); - if (!layer || (volumeTracing != null && volumeTracing.fallbackLayer != null)) { + if (!layer) { return; } const position = @@ -355,7 +354,7 @@ function* refreshIsosurfaces(): Saga { shouldBeRemoved = false; } } - yield* put(finishedRefreshingIsosurfacesAction()); + yield* put(finishedisRefreshingIsosurfacesAction()); } export default function* isosurfaceSaga(): Saga { diff --git a/frontend/javascripts/oxalis/store.js b/frontend/javascripts/oxalis/store.js index ea17fac687b..07d08fbd093 100644 --- a/frontend/javascripts/oxalis/store.js +++ b/frontend/javascripts/oxalis/store.js @@ -442,7 +442,7 @@ type UiInformation = { +isImportingMesh: boolean, +isInAnnotationView: boolean, +hasOrganizations: boolean, - +refreshingIsosurfaces: boolean, + +isRefreshingIsosurfaces: boolean, }; export type OxalisState = {| diff --git a/frontend/javascripts/oxalis/view/td_view_controls.js b/frontend/javascripts/oxalis/view/td_view_controls.js index 18ce41e705f..d3d55438dbd 100644 --- a/frontend/javascripts/oxalis/view/td_view_controls.js +++ b/frontend/javascripts/oxalis/view/td_view_controls.js @@ -3,7 +3,6 @@ import { Button, Tooltip } from "antd"; import * as React from "react"; import { connect } from "react-redux"; import type { OxalisState } from "oxalis/store"; -import { AsyncButton } from "components/async_clickables"; import api from "oxalis/api/internal_api"; @@ -11,9 +10,10 @@ const ButtonGroup = Button.Group; type Props = {| renderIsosurfaces: boolean, + isRefreshingIsosurfaces: boolean, |}; -function TDViewControls({ renderIsosurfaces }: Props) { +function TDViewControls({ renderIsosurfaces, isRefreshingIsosurfaces }: Props) { return ( {renderIsosurfaces ? ( - + {renderIsosurfaces ? ( - +