From 923a216298a3690bbc6603984207d323940bb6bc Mon Sep 17 00:00:00 2001 From: frcroth Date: Tue, 17 Oct 2023 13:03:33 +0200 Subject: [PATCH 01/43] Add additional coordinates to ad-hoc mesh request params --- .../controllers/BinaryDataController.scala | 1 + .../datastore/models/DataRequests.scala | 1 + .../datastore/services/AdHocMeshService.scala | 16 ++++++++++------ .../tracings/volume/VolumeTracingService.scala | 1 + 4 files changed, 13 insertions(+), 6 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 cab79f200e7..286c667ced3 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/BinaryDataController.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/BinaryDataController.scala @@ -275,6 +275,7 @@ class BinaryDataController @Inject()( request.body.scale, request.body.mapping, request.body.mappingType, + request.body.additionalCoordinates, request.body.findNeighbors ) // The client expects the ad-hoc mesh as a flat float-array. Three consecutive floats form a 3D point, three 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 27c7c63e888..f84b922a462 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/models/DataRequests.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/models/DataRequests.scala @@ -54,6 +54,7 @@ case class WebknossosAdHocMeshRequest( scale: Vec3Double, mapping: Option[String] = None, mappingType: Option[String] = None, + additionalCoordinates: Option[Seq[AdditionalCoordinate]] = None, findNeighbors: Boolean = true ) { def cuboid(dataLayer: DataLayer): Cuboid = diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/AdHocMeshService.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/AdHocMeshService.scala index c4d9141d8b0..3b4590a1077 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/AdHocMeshService.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/AdHocMeshService.scala @@ -7,6 +7,7 @@ import akka.routing.RoundRobinPool import akka.util.Timeout import com.scalableminds.util.geometry.{BoundingBox, Vec3Double, Vec3Int} import com.scalableminds.util.tools.{Fox, FoxImplicits} +import com.scalableminds.webknossos.datastore.models.AdditionalCoordinate import com.scalableminds.webknossos.datastore.models.datasource.{DataSource, ElementClass, SegmentationLayer} import com.scalableminds.webknossos.datastore.models.requests.{ Cuboid, @@ -31,6 +32,7 @@ case class AdHocMeshRequest(dataSource: Option[DataSource], scale: Vec3Double, mapping: Option[String] = None, mappingType: Option[String] = None, + additionalCoordinates: Option[Seq[AdditionalCoordinate]] = None, findNeighbors: Boolean = true) case class DataTypeFunctors[T, B]( @@ -172,12 +174,14 @@ class AdHocMeshService(binaryDataService: BinaryDataService, val subsamplingStrides = Vec3Double(request.subsamplingStrides.x, request.subsamplingStrides.y, request.subsamplingStrides.z) - val dataRequest = DataServiceDataRequest(request.dataSource.orNull, - request.dataLayer, - request.mapping, - cuboid, - DataServiceRequestSettings.default, - request.subsamplingStrides) + val dataRequest = DataServiceDataRequest( + request.dataSource.orNull, + request.dataLayer, + request.mapping, + cuboid, + DataServiceRequestSettings.default.copy(additionalCoordinates = request.additionalCoordinates), + request.subsamplingStrides + ) val dataDimensions = Vec3Int( math.ceil(cuboid.width / subsamplingStrides.x).toInt, 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 091ed9e79dd..c4d68e503a4 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 @@ -500,6 +500,7 @@ class VolumeTracingService @Inject()( request.scale, None, None, + request.additionalCoordinates, request.findNeighbors ) result <- adHocMeshingService.requestAdHocMeshViaActor(adHocMeshRequest) From 36bae08d175d16fdb19b9fb234faf4af3ee5b1dc Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Mon, 23 Oct 2023 18:30:49 +0200 Subject: [PATCH 02/43] WIP: pass add coordinates in context menu in view port --- .../oxalis/model/reducers/annotation_reducer.ts | 7 +++++-- .../javascripts/oxalis/model/sagas/mesh_saga.ts | 13 ++++++------- frontend/javascripts/oxalis/view/context_menu.tsx | 2 ++ .../segments_tab/segment_list_item.tsx | 3 ++- .../segments_tab/segments_view.tsx | 4 ++++ 5 files changed, 19 insertions(+), 10 deletions(-) diff --git a/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts b/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts index 5c34bec38b9..7028253091c 100644 --- a/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts +++ b/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts @@ -222,17 +222,20 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { } case "ADD_AD_HOC_ISOSURFACE": { - const { layerName, segmentId, seedPosition, mappingName, mappingType } = action; + const { layerName, segmentId, seedPosition, seedAdditionalCoordinates, mappingName, mappingType } = action; const meshInfo: MeshInformation = { segmentId: segmentId, seedPosition, + seedAdditionalCoordinates, isLoading: false, isVisible: true, isPrecomputed: false, mappingName, mappingType, }; - return updateKey4(state, "localSegmentationData", layerName, "meshes", segmentId, meshInfo); + const updatedKey = updateKey4(state, "localSegmentationData", layerName, "meshes", segmentId, meshInfo); + console.log(updatedKey) + return updatedKey; } case "ADD_PRECOMPUTED_ISOSURFACE": { diff --git a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts index c53edeb75de..1c3ad87a772 100644 --- a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts @@ -235,15 +235,14 @@ function* loadAdHocMesh( if (segmentId === 0 || layer == null) { return; } - if (_.size(layer.cube.additionalAxes) > 0) { // Also see https://github.com/scalableminds/webknossos/issues/7229 Toast.warning( "The current segmentation layer has more than 3 dimensions. Meshes are not properly supported in this case.", - ); - } - - yield* call([Model, Model.ensureSavedState]); + ); + } + + yield* call([Model, Model.ensureSavedState]); const meshExtraInfo = yield* call(getMeshExtraInfo, layer.name, maybeExtraInfo); @@ -288,6 +287,7 @@ function* loadFullAdHocMesh( let isInitialRequest = true; const { mappingName, mappingType } = meshExtraInfo; const clippedPosition = clipPositionToCubeBoundary(position); + console.log(additionalCoordinates) yield* put( addAdHocMeshAction( layer.name, @@ -566,8 +566,7 @@ function* _refreshMeshWithMap( let shouldBeRemoved = true; // Meshing for N-D segmentations is not yet supported. - // See https://github.com/scalableminds/webknossos/issues/7229 - const seedAdditionalCoordinates = undefined; + // See https://github.com/scalableminds/webknossos/issues/7229 //TODO for (const [, position] of meshPositions) { // Reload the mesh at the given position if it isn't already loaded there. // This is done to ensure that every voxel of the mesh is reloaded. diff --git a/frontend/javascripts/oxalis/view/context_menu.tsx b/frontend/javascripts/oxalis/view/context_menu.tsx index 28496d1dc52..8b35e9cbfd9 100644 --- a/frontend/javascripts/oxalis/view/context_menu.tsx +++ b/frontend/javascripts/oxalis/view/context_menu.tsx @@ -419,6 +419,7 @@ function getNodeContextMenuOptions({ volumeTracing, infoRows, allowUpdate, + additionalCoordinates, }: NodeContextMenuOptionsProps): ItemType[] { const state = Store.getState(); const isProofreadingActive = state.uiInformation.activeTool === AnnotationToolEnum.PROOFREAD; @@ -1369,6 +1370,7 @@ function ContextMenuInner(propsWithInputRef: Props) { }), }; + console.log(menu) if (inputRef == null || inputRef.current == null) return null; const refContent = inputRef.current; diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx index 690aae9c31c..37dbfa20192 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx @@ -129,7 +129,8 @@ const getComputeMeshAdHocMenuItem = ( andCloseContextMenu(); return; } - + console.log("segment list item") + console.log(segment) // TODO addCoor empty if newly created andCloseContextMenu( loadAdHocMesh(segment.id, segment.somePosition, segment.someAdditionalCoordinates), ); diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx index 06d56285882..9d4241121de 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx @@ -211,6 +211,8 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ seedPosition: Vector3, seedAdditionalCoordinates: AdditionalCoordinate[] | undefined, ) { + console.log("segments view") + console.log(seedAdditionalCoordinates) dispatch(loadAdHocMeshAction(segmentId, seedPosition, seedAdditionalCoordinates)); }, @@ -242,6 +244,8 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ }, setAdditionalCoordinates(additionalCoordinates: AdditionalCoordinate[] | undefined) { + console.log("setAdditionalCoordinates") + console.log(additionalCoordinates) dispatch(setAdditionalCoordinatesAction(additionalCoordinates || null)); }, From 5e17e917ea07a1ea37c7f44e4da6f92c58fc0dcb Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Tue, 24 Oct 2023 21:47:21 +0200 Subject: [PATCH 03/43] add 4D mesh to scene --- frontend/javascripts/admin/admin_rest_api.ts | 3 + .../controller/segment_mesh_controller.ts | 80 +++++++++++++++---- .../model/reducers/annotation_reducer.ts | 20 ++++- .../oxalis/model/sagas/mesh_saga.ts | 57 ++++++++++--- .../javascripts/oxalis/view/context_menu.tsx | 1 - .../segments_tab/segment_list_item.tsx | 4 +- .../segments_tab/segments_view.tsx | 8 +- 7 files changed, 136 insertions(+), 37 deletions(-) diff --git a/frontend/javascripts/admin/admin_rest_api.ts b/frontend/javascripts/admin/admin_rest_api.ts index 1b10f91b555..053a1cbe925 100644 --- a/frontend/javascripts/admin/admin_rest_api.ts +++ b/frontend/javascripts/admin/admin_rest_api.ts @@ -2147,6 +2147,7 @@ window.setMaintenance = setMaintenance; type MeshRequest = { // The position is in voxels in mag 1 position: Vector3; + additionalCoordinates: AdditionalCoordinate[] | undefined; mag: Vector3; segmentId: number; // Segment to build mesh for subsamplingStrides: Vector3; @@ -2167,6 +2168,7 @@ export function computeAdHocMesh( }> { const { position, + additionalCoordinates, cubeSize, mappingName, subsamplingStrides, @@ -2186,6 +2188,7 @@ export function computeAdHocMesh( // bounding box to calculate the mesh. This padding // is added here to the position and bbox size. position: V3.toArray(V3.sub(position, subsamplingStrides)), + additionalCoordinates: additionalCoordinates, cubeSize: V3.toArray(V3.add(cubeSize, subsamplingStrides)), // Name and type of mapping to apply before building mesh (optional) mapping: mappingName, diff --git a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts index 69ebb1c7e42..90f863dd04c 100644 --- a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts +++ b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts @@ -14,7 +14,10 @@ export default class SegmentMeshController { // meshesLODRootGroup holds lights and one group per segmentation id. // Each group can hold multiple meshes. meshesLODRootGroup: CustomLOD; - meshesGroupsPerSegmentationId: Record>> = {}; + meshesGroupsPerSegmentationId: Record< + string, + Record>> + > = {}; constructor() { this.meshesLODRootGroup = new CustomLOD(); @@ -22,7 +25,15 @@ export default class SegmentMeshController { } hasMesh(id: number, layerName: string): boolean { - const segments = this.meshesGroupsPerSegmentationId[layerName]; + const additionalCoordinatesObject = Store.getState().flycam.additionalCoordinates; + let additionalCoordinates = ""; + if (additionalCoordinatesObject != null) { + additionalCoordinates = additionalCoordinatesObject + ?.map((coordinate) => `${coordinate.name}=${coordinate.value}`) + .reduce((a: string, b: string) => a.concat(b)) as string; + } + + const segments = this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName]; if (!segments) { return false; } @@ -85,15 +96,35 @@ export default class SegmentMeshController { lod: number, layerName: string, ): void { - if (this.meshesGroupsPerSegmentationId[layerName] == null) { - this.meshesGroupsPerSegmentationId[layerName] = {}; + const additionalCoordinatesObject = Store.getState().flycam.additionalCoordinates; + let additionalCoordinates = ""; + if (additionalCoordinatesObject != null) { + additionalCoordinates = additionalCoordinatesObject + ?.map((coordinate) => `${coordinate.name}=${coordinate.value}`) + .reduce((a: string, b: string) => a.concat(b)) as string; } - if (this.meshesGroupsPerSegmentationId[layerName][segmentationId] == null) { - this.meshesGroupsPerSegmentationId[layerName][segmentationId] = {}; + + console.log("segemnt mesh controller"); + console.log(additionalCoordinates); + if (this.meshesGroupsPerSegmentationId[additionalCoordinates] == null) { + this.meshesGroupsPerSegmentationId[additionalCoordinates] = {}; } - if (this.meshesGroupsPerSegmentationId[layerName][segmentationId][lod] == null) { + + if (this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName] == null) { + this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName] = {}; + } + if ( + this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentationId] == null + ) { + this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentationId] = {}; + } + if ( + this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentationId][lod] == + null + ) { const newGroup = new THREE.Group(); - this.meshesGroupsPerSegmentationId[layerName][segmentationId][lod] = newGroup; + this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentationId][lod] = + newGroup; if (lod === NO_LOD_MESH_INDEX) { this.meshesLODRootGroup.addNoLODSupportedMesh(newGroup); } else { @@ -111,8 +142,10 @@ export default class SegmentMeshController { mesh.translateY(offset[1]); mesh.translateZ(offset[2]); } - - this.meshesGroupsPerSegmentationId[layerName][segmentationId][lod].add(mesh); + debugger; + this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentationId][lod].add( + mesh, + ); } removeMeshById(segmentationId: number, layerName: string): void { @@ -134,18 +167,37 @@ export default class SegmentMeshController { } getMeshGeometryInBestLOD(segmentId: number, layerName: string): THREE.Group { + const additionalCoordinatesObject = Store.getState().flycam.additionalCoordinates; + let additionalCoordinates = ""; + if (additionalCoordinatesObject != null) { + additionalCoordinates = additionalCoordinatesObject + ?.map((coordinate) => `${coordinate.name}=${coordinate.value}`) + .reduce((a: string, b: string) => a.concat(b)) as string; + } + const bestLod = Math.min( ...Object.keys(this.meshesGroupsPerSegmentationId[layerName][segmentId]).map((lodVal) => parseInt(lodVal), ), ); - return this.meshesGroupsPerSegmentationId[layerName][segmentId][bestLod]; + return this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentId][bestLod]; } setMeshVisibility(id: number, visibility: boolean, layerName: string): void { - _.forEach(this.meshesGroupsPerSegmentationId[layerName][id], (meshGroup) => { - meshGroup.visible = visibility; - }); + const additionalCoordinatesObject = Store.getState().flycam.additionalCoordinates; + let additionalCoordinates = ""; + if (additionalCoordinatesObject != null) { + additionalCoordinates = additionalCoordinatesObject + ?.map((coordinate) => `${coordinate.name}=${coordinate.value}`) + .reduce((a: string, b: string) => a.concat(b)) as string; + } + + _.forEach( + this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][id], + (meshGroup) => { + meshGroup.visible = visibility; + }, + ); } setMeshColor(id: number, layerName: string): void { diff --git a/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts b/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts index 7028253091c..05c6f38ba03 100644 --- a/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts +++ b/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts @@ -222,7 +222,14 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { } case "ADD_AD_HOC_ISOSURFACE": { - const { layerName, segmentId, seedPosition, seedAdditionalCoordinates, mappingName, mappingType } = action; + const { + layerName, + segmentId, + seedPosition, + seedAdditionalCoordinates, + mappingName, + mappingType, + } = action; const meshInfo: MeshInformation = { segmentId: segmentId, seedPosition, @@ -233,8 +240,15 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { mappingName, mappingType, }; - const updatedKey = updateKey4(state, "localSegmentationData", layerName, "meshes", segmentId, meshInfo); - console.log(updatedKey) + const updatedKey = updateKey4( + state, + "localSegmentationData", + layerName, + "meshes", + segmentId, + meshInfo, + ); + console.log(updatedKey); return updatedKey; } diff --git a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts index 1c3ad87a772..d468d166047 100644 --- a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts @@ -94,9 +94,9 @@ const MESH_CHUNK_THROTTLE_LIMIT = 50; * Ad Hoc Meshes * */ -// Maps from layerName and segmentId to a ThreeDMap that stores for each chunk +// Maps from additional coordinates, layerName and segmentId to a ThreeDMap that stores for each chunk // (at x, y, z) position whether the mesh chunk was loaded. -const adhocMeshesMapByLayer: Record>> = {}; +const adhocMeshesMapByLayer: Record>>> = {}; function marchingCubeSizeInMag1(): Vector3 { return (window as any).__marchingCubeSizeInMag1 != null ? (window as any).__marchingCubeSizeInMag1 @@ -112,8 +112,19 @@ export function isMeshSTL(buffer: ArrayBuffer): boolean { } function getOrAddMapForSegment(layerName: string, segmentId: number): ThreeDMap { - adhocMeshesMapByLayer[layerName] = adhocMeshesMapByLayer[layerName] || new Map(); - const meshesMap = adhocMeshesMapByLayer[layerName]; + const additionalCoordinatesObject = Store.getState().flycam.additionalCoordinates; + let additionalCoordinates = ""; + if (additionalCoordinatesObject != null) { + additionalCoordinates = additionalCoordinatesObject + ?.map((coordinate) => `${coordinate.name}=${coordinate.value}`) + .reduce((a: string, b: string) => a.concat(b)) as string; + } + + adhocMeshesMapByLayer[additionalCoordinates] = + adhocMeshesMapByLayer[additionalCoordinates] || new Map(); + adhocMeshesMapByLayer[additionalCoordinates][layerName] = + adhocMeshesMapByLayer[additionalCoordinates][layerName] || new Map(); + const meshesMap = adhocMeshesMapByLayer[additionalCoordinates][layerName]; const maybeMap = meshesMap.get(segmentId); if (maybeMap == null) { @@ -239,10 +250,10 @@ function* loadAdHocMesh( // Also see https://github.com/scalableminds/webknossos/issues/7229 Toast.warning( "The current segmentation layer has more than 3 dimensions. Meshes are not properly supported in this case.", - ); - } - - yield* call([Model, Model.ensureSavedState]); + ); + } + + yield* call([Model, Model.ensureSavedState]); const meshExtraInfo = yield* call(getMeshExtraInfo, layer.name, maybeExtraInfo); @@ -287,7 +298,7 @@ function* loadFullAdHocMesh( let isInitialRequest = true; const { mappingName, mappingType } = meshExtraInfo; const clippedPosition = clipPositionToCubeBoundary(position); - console.log(additionalCoordinates) + console.log(additionalCoordinates); yield* put( addAdHocMeshAction( layer.name, @@ -400,6 +411,8 @@ function* maybeLoadMeshChunk( useDataStore: boolean, findNeighbors: boolean, ): Saga { + + const additionalCoordinates = Store.getState().flycam.additionalCoordinates || undefined; const threeDMap = getOrAddMapForSegment(layer.name, segmentId); if (threeDMap.get(clippedPosition)) { @@ -451,6 +464,7 @@ function* maybeLoadMeshChunk( useDataStore ? dataStoreUrl : tracingStoreUrl, { position: clippedPosition, + additionalCoordinates, mag, segmentId, subsamplingStrides, @@ -494,15 +508,24 @@ function* refreshMeshes(): Saga { // By that we avoid to remove cells that got annotated during reloading from the modifiedCells set. const currentlyModifiedCells = new Set(modifiedCells); modifiedCells.clear(); + + const additionalCoordinatesObject = Store.getState().flycam.additionalCoordinates; + let additionalCoordinates = ""; + if (additionalCoordinatesObject != null) { + additionalCoordinates = additionalCoordinatesObject + ?.map((coordinate) => `${coordinate.name}=${coordinate.value}`) + .reduce((a: string, b: string) => a.concat(b)) as string; + } + const segmentationLayer = Model.getVisibleSegmentationLayer(); if (!segmentationLayer) { return; } - adhocMeshesMapByLayer[segmentationLayer.name] = - adhocMeshesMapByLayer[segmentationLayer.name] || new Map(); - const meshesMapForLayer = adhocMeshesMapByLayer[segmentationLayer.name]; + adhocMeshesMapByLayer[additionalCoordinates][segmentationLayer.name] = + adhocMeshesMapByLayer[additionalCoordinates][segmentationLayer.name] || new Map(); + const meshesMapForLayer = adhocMeshesMapByLayer[additionalCoordinates][segmentationLayer.name]; for (const [segmentId, threeDMap] of Array.from(meshesMapForLayer.entries())) { if (!currentlyModifiedCells.has(segmentId)) { @@ -514,6 +537,14 @@ function* refreshMeshes(): Saga { } function* refreshMesh(action: RefreshMeshAction): Saga { + const additionalCoordinatesObject = Store.getState().flycam.additionalCoordinates; + let additionalCoordinates = ""; + if (additionalCoordinatesObject != null) { + additionalCoordinates = additionalCoordinatesObject + ?.map((coordinate) => `${coordinate.name}=${coordinate.value}`) + .reduce((a: string, b: string) => a.concat(b)) as string; + } + const { segmentId, layerName } = action; const meshInfo = yield* select( @@ -532,7 +563,7 @@ function* refreshMesh(action: RefreshMeshAction): Saga { ), ); } else { - const threeDMap = adhocMeshesMapByLayer[action.layerName].get(segmentId); + const threeDMap = adhocMeshesMapByLayer[additionalCoordinates][action.layerName].get(segmentId); if (threeDMap == null) return; yield* call(_refreshMeshWithMap, segmentId, threeDMap, layerName); } diff --git a/frontend/javascripts/oxalis/view/context_menu.tsx b/frontend/javascripts/oxalis/view/context_menu.tsx index 8b35e9cbfd9..6771025fe7f 100644 --- a/frontend/javascripts/oxalis/view/context_menu.tsx +++ b/frontend/javascripts/oxalis/view/context_menu.tsx @@ -1370,7 +1370,6 @@ function ContextMenuInner(propsWithInputRef: Props) { }), }; - console.log(menu) if (inputRef == null || inputRef.current == null) return null; const refContent = inputRef.current; diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx index 37dbfa20192..334157652b6 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx @@ -129,8 +129,8 @@ const getComputeMeshAdHocMenuItem = ( andCloseContextMenu(); return; } - console.log("segment list item") - console.log(segment) // TODO addCoor empty if newly created + console.log("segment list item"); + console.log(segment); // TODO addCoor empty if newly created andCloseContextMenu( loadAdHocMesh(segment.id, segment.somePosition, segment.someAdditionalCoordinates), ); diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx index 9d4241121de..d232d8ff9cf 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx @@ -211,8 +211,8 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ seedPosition: Vector3, seedAdditionalCoordinates: AdditionalCoordinate[] | undefined, ) { - console.log("segments view") - console.log(seedAdditionalCoordinates) + console.log("segments view"); + console.log(seedAdditionalCoordinates); dispatch(loadAdHocMeshAction(segmentId, seedPosition, seedAdditionalCoordinates)); }, @@ -244,8 +244,8 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ }, setAdditionalCoordinates(additionalCoordinates: AdditionalCoordinate[] | undefined) { - console.log("setAdditionalCoordinates") - console.log(additionalCoordinates) + console.log("setAdditionalCoordinates"); + console.log(additionalCoordinates); dispatch(setAdditionalCoordinatesAction(additionalCoordinates || null)); }, From 30bf4986c5689a7f24775045fb00e7b93f51f6bd Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Wed, 25 Oct 2023 14:27:01 +0200 Subject: [PATCH 04/43] enable changing color and removing meshes --- .../controller/segment_mesh_controller.ts | 30 +++++++++++++++---- .../oxalis/model/sagas/mesh_saga.ts | 26 ++++++++++++++-- 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts index 90f863dd04c..07e9e790307 100644 --- a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts +++ b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts @@ -149,13 +149,26 @@ export default class SegmentMeshController { } removeMeshById(segmentationId: number, layerName: string): void { - if (this.meshesGroupsPerSegmentationId[layerName] == null) { + const additionalCoordinatesObject = Store.getState().flycam.additionalCoordinates; + let additionalCoordinates = ""; + if (additionalCoordinatesObject != null) { + additionalCoordinates = additionalCoordinatesObject + ?.map((coordinate) => `${coordinate.name}=${coordinate.value}`) + .reduce((a: string, b: string) => a.concat(b)) as string; + } + + // TODO I think it shouldnt be possible to remove meshes that arent visible currently. + // but if they are removed they should be removed for all timestamps + if (this.meshesGroupsPerSegmentationId[additionalCoordinates] == null) { return; } - if (this.meshesGroupsPerSegmentationId[layerName][segmentationId] == null) { + if (this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName] == null) { return; } - _.forEach(this.meshesGroupsPerSegmentationId[layerName][segmentationId], (meshGroup, lod) => { + if (this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentationId] == null) { + return; + } + _.forEach(this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentationId], (meshGroup, lod) => { const lodNumber = parseInt(lod); if (lodNumber !== NO_LOD_MESH_INDEX) { this.meshesLODRootGroup.removeLODMesh(meshGroup, lodNumber); @@ -163,7 +176,7 @@ export default class SegmentMeshController { this.meshesLODRootGroup.removeNoLODSupportedMesh(meshGroup); } }); - delete this.meshesGroupsPerSegmentationId[layerName][segmentationId]; + delete this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentationId]; } getMeshGeometryInBestLOD(segmentId: number, layerName: string): THREE.Group { @@ -201,8 +214,15 @@ export default class SegmentMeshController { } setMeshColor(id: number, layerName: string): void { + const additionalCoordinatesObject = Store.getState().flycam.additionalCoordinates; + let additionalCoordinates = ""; + if (additionalCoordinatesObject != null) { + additionalCoordinates = additionalCoordinatesObject + ?.map((coordinate) => `${coordinate.name}=${coordinate.value}`) + .reduce((a: string, b: string) => a.concat(b)) as string; + } const color = this.getColorObjectForSegment(id); - _.forEach(this.meshesGroupsPerSegmentationId[layerName][id], (meshGroup) => { + _.forEach(this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][id], (meshGroup) => { if (meshGroup) { for (const child of meshGroup.children) { // @ts-ignore diff --git a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts index d468d166047..ad6dcbd9a37 100644 --- a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts @@ -137,11 +137,19 @@ function getOrAddMapForSegment(layerName: string, segmentId: number): ThreeDMap< } function removeMapForSegment(layerName: string, segmentId: number): void { - if (adhocMeshesMapByLayer[layerName] == null) { + const additionalCoordinatesObject = Store.getState().flycam.additionalCoordinates; + let additionalCoordinates = ""; + if (additionalCoordinatesObject != null) { + additionalCoordinates = additionalCoordinatesObject + ?.map((coordinate) => `${coordinate.name}=${coordinate.value}`) + .reduce((a: string, b: string) => a.concat(b)) as string; + } + + if (adhocMeshesMapByLayer[additionalCoordinates][layerName] == null) { return; } - adhocMeshesMapByLayer[layerName].delete(segmentId); + adhocMeshesMapByLayer[additionalCoordinates][layerName].delete(segmentId); } function getZoomedCubeSize(zoomStep: number, resolutionInfo: ResolutionInfo): Vector3 { @@ -504,6 +512,7 @@ function* markEditedCellAsDirty(): Saga { function* refreshMeshes(): Saga { yield* put(saveNowAction()); + console.log("refresh") // We reload all cells that got modified till the start of reloading. // By that we avoid to remove cells that got annotated during reloading from the modifiedCells set. const currentlyModifiedCells = new Set(modifiedCells); @@ -537,6 +546,7 @@ function* refreshMeshes(): Saga { } function* refreshMesh(action: RefreshMeshAction): Saga { + console.log("refresh mesh") const additionalCoordinatesObject = Store.getState().flycam.additionalCoordinates; let additionalCoordinates = ""; if (additionalCoordinatesObject != null) { @@ -563,6 +573,7 @@ function* refreshMesh(action: RefreshMeshAction): Saga { ), ); } else { + if(adhocMeshesMapByLayer[additionalCoordinates] == null) return const threeDMap = adhocMeshesMapByLayer[additionalCoordinates][action.layerName].get(segmentId); if (threeDMap == null) return; yield* call(_refreshMeshWithMap, segmentId, threeDMap, layerName); @@ -598,13 +609,22 @@ function* _refreshMeshWithMap( // Meshing for N-D segmentations is not yet supported. // See https://github.com/scalableminds/webknossos/issues/7229 //TODO + + const additionalCoordinatesObject = Store.getState().flycam.additionalCoordinates; + let additionalCoordinates = ""; + if (additionalCoordinatesObject != null) { + additionalCoordinates = additionalCoordinatesObject + ?.map((coordinate) => `${coordinate.name}=${coordinate.value}`) + .reduce((a: string, b: string) => a.concat(b)) as string; + } + for (const [, position] of meshPositions) { // Reload the mesh at the given position if it isn't already loaded there. // This is done to ensure that every voxel of the mesh is reloaded. yield* call( loadAdHocMesh, position, - seedAdditionalCoordinates, + additionalCoordinatesObject, segmentId, shouldBeRemoved, layerName, From 811c3c1be673e0cb554956eb65c6d6167a4b506c Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Mon, 30 Oct 2023 12:35:22 +0100 Subject: [PATCH 05/43] WIP: only show meshes for current additional coordinates --- .../controller/segment_mesh_controller.ts | 25 +++++++++++++++++++ .../oxalis/model/sagas/mesh_saga.ts | 10 ++++++++ 2 files changed, 35 insertions(+) diff --git a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts index 07e9e790307..347a4f2fdca 100644 --- a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts +++ b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts @@ -9,6 +9,7 @@ import CustomLOD from "oxalis/controller/custom_lod"; import { getSegmentColorAsHSLA } from "oxalis/model/accessors/volumetracing_accessor"; import { NO_LOD_MESH_INDEX } from "oxalis/model/sagas/mesh_saga"; import Store from "oxalis/store"; +import { AdditionalCoordinate } from "types/api_flow_types"; export default class SegmentMeshController { // meshesLODRootGroup holds lights and one group per segmentation id. @@ -106,6 +107,7 @@ export default class SegmentMeshController { console.log("segemnt mesh controller"); console.log(additionalCoordinates); + console.log(segmentationId) if (this.meshesGroupsPerSegmentationId[additionalCoordinates] == null) { this.meshesGroupsPerSegmentationId[additionalCoordinates] = {}; } @@ -213,6 +215,29 @@ export default class SegmentMeshController { ); } + updateMeshVisibility(newAadditionalCoordinatesObject: AdditionalCoordinate[]){ + if(newAadditionalCoordinatesObject == null || newAadditionalCoordinatesObject.length === 0 ) return + const newAdditionalCoordinates = newAadditionalCoordinatesObject + ?.map((coordinate) => `${coordinate.name}=${coordinate.value}`) + .reduce((a: string, b: string) => a.concat(b)) as string; + + debugger + + Object.keys(this.meshesGroupsPerSegmentationId).forEach((additionalCoordinates) => { + const shouldBeVisible = additionalCoordinates === newAdditionalCoordinates; + Object.keys(this.meshesGroupsPerSegmentationId[additionalCoordinates]).forEach((layerName) => { + Object.keys(this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName]).forEach(meshGroup => { + _.forEach( + this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][parseInt(meshGroup)], + (meshGroup) => { + meshGroup.visible = shouldBeVisible; + }, + ); + }); + }); + }); + } + setMeshColor(id: number, layerName: string): void { const additionalCoordinatesObject = Store.getState().flycam.additionalCoordinates; let additionalCoordinates = ""; diff --git a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts index ad6dcbd9a37..94e8f138760 100644 --- a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts @@ -74,6 +74,7 @@ import { RemoveSegmentAction, UpdateSegmentAction } from "../actions/volumetraci import { ResolutionInfo } from "../helpers/resolution_info"; import { type AdditionalCoordinate } from "types/api_flow_types"; import Zip from "libs/zipjs_wrapper"; +import { FlycamAction } from "../actions/flycam_actions"; export const NO_LOD_MESH_INDEX = -1; const MAX_RETRY_COUNT = 5; @@ -1146,9 +1147,17 @@ function removeMesh(action: RemoveMeshAction, removeFromScene: boolean = true): function* handleMeshVisibilityChange(action: UpdateMeshVisibilityAction): Saga { const { id, visibility, layerName } = action; const { segmentMeshController } = yield* call(getSceneController); + // segmentMeshController.setMeshVisibility(id, visibility, layerName); } +function* handleAdditionalCoordinateUpdate(action: FlycamAction): Saga { + const { values } = action; + const { segmentMeshController } = yield* call(getSceneController); + // + segmentMeshController.updateMeshVisibility(values); +} + function* handleSegmentColorChange(action: UpdateSegmentAction): Saga { const { segmentMeshController } = yield* call(getSceneController); if ( @@ -1179,4 +1188,5 @@ export default function* meshSaga(): Saga { yield* takeEvery("UPDATE_ISOSURFACE_VISIBILITY", handleMeshVisibilityChange); yield* takeEvery(["START_EDITING", "COPY_SEGMENTATION_LAYER"], markEditedCellAsDirty); yield* takeEvery("UPDATE_SEGMENT", handleSegmentColorChange); + yield* takeEvery("SET_ADDITIONAL_COORDINATES", handleAdditionalCoordinateUpdate); } From 6545a0dcbc7c5285a485eb56c6507af445d39958 Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Mon, 30 Oct 2023 21:47:53 +0100 Subject: [PATCH 06/43] only show meshes in current additional coordinates --- .../controller/segment_mesh_controller.ts | 26 ++---------------- .../oxalis/model/sagas/mesh_saga.ts | 27 ++++++++++++++++++- 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts index 347a4f2fdca..520b8d8b6e5 100644 --- a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts +++ b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts @@ -144,7 +144,6 @@ export default class SegmentMeshController { mesh.translateY(offset[1]); mesh.translateZ(offset[2]); } - debugger; this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentationId][lod].add( mesh, ); @@ -207,6 +206,8 @@ export default class SegmentMeshController { .reduce((a: string, b: string) => a.concat(b)) as string; } + if(this.meshesGroupsPerSegmentationId[additionalCoordinates] == null) return + _.forEach( this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][id], (meshGroup) => { @@ -215,29 +216,6 @@ export default class SegmentMeshController { ); } - updateMeshVisibility(newAadditionalCoordinatesObject: AdditionalCoordinate[]){ - if(newAadditionalCoordinatesObject == null || newAadditionalCoordinatesObject.length === 0 ) return - const newAdditionalCoordinates = newAadditionalCoordinatesObject - ?.map((coordinate) => `${coordinate.name}=${coordinate.value}`) - .reduce((a: string, b: string) => a.concat(b)) as string; - - debugger - - Object.keys(this.meshesGroupsPerSegmentationId).forEach((additionalCoordinates) => { - const shouldBeVisible = additionalCoordinates === newAdditionalCoordinates; - Object.keys(this.meshesGroupsPerSegmentationId[additionalCoordinates]).forEach((layerName) => { - Object.keys(this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName]).forEach(meshGroup => { - _.forEach( - this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][parseInt(meshGroup)], - (meshGroup) => { - meshGroup.visible = shouldBeVisible; - }, - ); - }); - }); - }); - } - setMeshColor(id: number, layerName: string): void { const additionalCoordinatesObject = Store.getState().flycam.additionalCoordinates; let additionalCoordinates = ""; diff --git a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts index 94e8f138760..7c3d112d6ef 100644 --- a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts @@ -38,6 +38,7 @@ import { finishedLoadingMeshAction, startedLoadingMeshAction, TriggerMeshesDownloadAction, + updateMeshVisibilityAction, } from "oxalis/model/actions/annotation_actions"; import type { Saga } from "oxalis/model/sagas/effect-generators"; import { select } from "oxalis/model/sagas/effect-generators"; @@ -1155,7 +1156,31 @@ function* handleAdditionalCoordinateUpdate(action: FlycamAction): Saga { const { values } = action; const { segmentMeshController } = yield* call(getSceneController); // - segmentMeshController.updateMeshVisibility(values); + const meshRecords = segmentMeshController.meshesGroupsPerSegmentationId; + + if(values == null || values.length === 0 ) return + const newAdditionalCoordinates = values + ?.map((coordinate: AdditionalCoordinate) => `${coordinate.name}=${coordinate.value}`) + .reduce((a: string, b: string) => a.concat(b)) as string; + + let updateVisibilityActions: any = []; + Object.keys(meshRecords).forEach((additionalCoordinates) => { + const shouldBeVisible = additionalCoordinates === newAdditionalCoordinates; + Object.keys(meshRecords[additionalCoordinates]).forEach((layerName) => { + Object.keys(meshRecords[additionalCoordinates][layerName]).forEach(meshGroup => { + const meshId = parseInt(meshGroup); + _.forEach( + meshRecords[additionalCoordinates][layerName][meshId], + _ => { + updateVisibilityActions.push(updateMeshVisibilityAction(layerName, meshId, shouldBeVisible)) + segmentMeshController.setMeshVisibility(meshId, shouldBeVisible, layerName); + }, + ); + }); + }); + }); + console.log(updateVisibilityActions) + yield* all(updateVisibilityActions.map(e=>put(e))); } function* handleSegmentColorChange(action: UpdateSegmentAction): Saga { From 36e46b2aa40e4aeb8862fa4d5427473db1bd8322 Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Tue, 31 Oct 2023 15:28:54 +0100 Subject: [PATCH 07/43] fix mesh update after changing color of segment and ND mesh download --- .../oxalis/controller/segment_mesh_controller.ts | 4 +++- .../javascripts/oxalis/model/sagas/mesh_saga.ts | 14 ++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts index 520b8d8b6e5..957b79f225f 100644 --- a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts +++ b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts @@ -34,6 +34,8 @@ export default class SegmentMeshController { .reduce((a: string, b: string) => a.concat(b)) as string; } + if(this.meshesGroupsPerSegmentationId[additionalCoordinates] == null || this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName] == null) return false; + const segments = this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName]; if (!segments) { return false; @@ -190,7 +192,7 @@ export default class SegmentMeshController { } const bestLod = Math.min( - ...Object.keys(this.meshesGroupsPerSegmentationId[layerName][segmentId]).map((lodVal) => + ...Object.keys(this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentId]).map((lodVal) => parseInt(lodVal), ), ); diff --git a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts index 7c3d112d6ef..bc5b61c7353 100644 --- a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts @@ -71,7 +71,7 @@ import { getDracoLoader } from "libs/draco"; import messages from "messages"; import processTaskWithPool from "libs/async/task_pool"; import { getBaseSegmentationName } from "oxalis/view/right-border-tabs/segments_tab/segments_view_helper"; -import { RemoveSegmentAction, UpdateSegmentAction } from "../actions/volumetracing_actions"; +import { BatchUpdateGroupsAndSegmentsAction, RemoveSegmentAction, UpdateSegmentAction } from "../actions/volumetracing_actions"; import { ResolutionInfo } from "../helpers/resolution_info"; import { type AdditionalCoordinate } from "types/api_flow_types"; import Zip from "libs/zipjs_wrapper"; @@ -1188,10 +1188,15 @@ function* handleSegmentColorChange(action: UpdateSegmentAction): Saga { if ( "color" in action.segment && segmentMeshController.hasMesh(action.segmentId, action.layerName) - ) { - segmentMeshController.setMeshColor(action.segmentId, action.layerName); + ) { + segmentMeshController.setMeshColor(action.segmentId, action.layerName); + } + } + +function* handleBatchSegmentColorChange(batchAction: BatchUpdateGroupsAndSegmentsAction): Saga{ + const updateSegmentActions = batchAction.payload.filter(action => action.type === "UPDATE_SEGMENT").map(action => call(handleSegmentColorChange, action)) + yield* all(updateSegmentActions); } -} export default function* meshSaga(): Saga { // Buffer actions since they might be dispatched before WK_READY @@ -1213,5 +1218,6 @@ export default function* meshSaga(): Saga { yield* takeEvery("UPDATE_ISOSURFACE_VISIBILITY", handleMeshVisibilityChange); yield* takeEvery(["START_EDITING", "COPY_SEGMENTATION_LAYER"], markEditedCellAsDirty); yield* takeEvery("UPDATE_SEGMENT", handleSegmentColorChange); + yield* takeEvery("BATCH_UPDATE_GROUPS_AND_SEGMENTS", handleBatchSegmentColorChange); yield* takeEvery("SET_ADDITIONAL_COORDINATES", handleAdditionalCoordinateUpdate); } From 82b3ca282d83b9614f15c0967e2264c7c5691a50 Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Tue, 31 Oct 2023 17:10:49 +0100 Subject: [PATCH 08/43] commit before changing code structure --- frontend/javascripts/oxalis/api/api_latest.ts | 2 +- .../controller/segment_mesh_controller.ts | 75 ++++++++++++------- .../model/actions/annotation_actions.ts | 3 +- .../model/reducers/annotation_reducer.ts | 3 +- .../oxalis/model/sagas/mesh_saga.ts | 75 ++++++++++--------- 5 files changed, 95 insertions(+), 63 deletions(-) diff --git a/frontend/javascripts/oxalis/api/api_latest.ts b/frontend/javascripts/oxalis/api/api_latest.ts index de934e03e4e..8075f8d205a 100644 --- a/frontend/javascripts/oxalis/api/api_latest.ts +++ b/frontend/javascripts/oxalis/api/api_latest.ts @@ -2258,7 +2258,7 @@ class DataApi { ).name; if (Store.getState().localSegmentationData[effectiveLayerName].meshes[segmentId] != null) { - Store.dispatch(updateMeshVisibilityAction(effectiveLayerName, segmentId, isVisible)); + Store.dispatch(updateMeshVisibilityAction(effectiveLayerName, segmentId, isVisible, undefined)); } } diff --git a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts index 957b79f225f..d5ee5d74f59 100644 --- a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts +++ b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts @@ -34,7 +34,11 @@ export default class SegmentMeshController { .reduce((a: string, b: string) => a.concat(b)) as string; } - if(this.meshesGroupsPerSegmentationId[additionalCoordinates] == null || this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName] == null) return false; + if ( + this.meshesGroupsPerSegmentationId[additionalCoordinates] == null || + this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName] == null + ) + return false; const segments = this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName]; if (!segments) { @@ -107,9 +111,6 @@ export default class SegmentMeshController { .reduce((a: string, b: string) => a.concat(b)) as string; } - console.log("segemnt mesh controller"); - console.log(additionalCoordinates); - console.log(segmentationId) if (this.meshesGroupsPerSegmentationId[additionalCoordinates] == null) { this.meshesGroupsPerSegmentationId[additionalCoordinates] = {}; } @@ -168,17 +169,22 @@ export default class SegmentMeshController { if (this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName] == null) { return; } - if (this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentationId] == null) { + if ( + this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentationId] == null + ) { return; } - _.forEach(this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentationId], (meshGroup, lod) => { - const lodNumber = parseInt(lod); - if (lodNumber !== NO_LOD_MESH_INDEX) { - this.meshesLODRootGroup.removeLODMesh(meshGroup, lodNumber); - } else { - this.meshesLODRootGroup.removeNoLODSupportedMesh(meshGroup); - } - }); + _.forEach( + this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentationId], + (meshGroup, lod) => { + const lodNumber = parseInt(lod); + if (lodNumber !== NO_LOD_MESH_INDEX) { + this.meshesLODRootGroup.removeLODMesh(meshGroup, lodNumber); + } else { + this.meshesLODRootGroup.removeNoLODSupportedMesh(meshGroup); + } + }, + ); delete this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentationId]; } @@ -192,14 +198,16 @@ export default class SegmentMeshController { } const bestLod = Math.min( - ...Object.keys(this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentId]).map((lodVal) => - parseInt(lodVal), - ), + ...Object.keys( + this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentId], + ).map((lodVal) => parseInt(lodVal)), ); return this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentId][bestLod]; } - setMeshVisibility(id: number, visibility: boolean, layerName: string): void { + setMeshVisibility(id: number, visibility: boolean, layerName: string, additionalCoordinates?: AdditionalCoordinate[] | undefined): void { + //set all visibilities for not current add coor to false and the one + debugger; const additionalCoordinatesObject = Store.getState().flycam.additionalCoordinates; let additionalCoordinates = ""; if (additionalCoordinatesObject != null) { @@ -208,7 +216,21 @@ export default class SegmentMeshController { .reduce((a: string, b: string) => a.concat(b)) as string; } - if(this.meshesGroupsPerSegmentationId[additionalCoordinates] == null) return + if (this.meshesGroupsPerSegmentationId[additionalCoordinates] == null) { + Object.keys(this.meshesGroupsPerSegmentationId).forEach((additionalCoordinatesIter) => { + Object.keys(this.meshesGroupsPerSegmentationId[additionalCoordinatesIter]).forEach( + (layerNameIter) => { + _.forEach( + this.meshesGroupsPerSegmentationId[additionalCoordinatesIter][layerNameIter][id], + (meshGroup) => { + meshGroup.visible = false; + }, + ); + }, + ); + }); + return; + } _.forEach( this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][id], @@ -227,14 +249,17 @@ export default class SegmentMeshController { .reduce((a: string, b: string) => a.concat(b)) as string; } const color = this.getColorObjectForSegment(id); - _.forEach(this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][id], (meshGroup) => { - if (meshGroup) { - for (const child of meshGroup.children) { - // @ts-ignore - child.material.color = color; + _.forEach( + this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][id], + (meshGroup) => { + if (meshGroup) { + for (const child of meshGroup.children) { + // @ts-ignore + child.material.color = color; + } } - } - }); + }, + ); } getColorObjectForSegment(segmentId: number) { diff --git a/frontend/javascripts/oxalis/model/actions/annotation_actions.ts b/frontend/javascripts/oxalis/model/actions/annotation_actions.ts index 46b91390cf5..8821641a1c0 100644 --- a/frontend/javascripts/oxalis/model/actions/annotation_actions.ts +++ b/frontend/javascripts/oxalis/model/actions/annotation_actions.ts @@ -183,12 +183,13 @@ export const addUserBoundingBoxesAction = (userBoundingBoxes: Array +export const updateMeshVisibilityAction = (layerName: string, id: number, visibility: boolean, additionalCoordinates: AdditionalCoordinate[] | undefined) => ({ type: "UPDATE_ISOSURFACE_VISIBILITY", layerName, id, visibility, + additionalCoordinates } as const); export const maybeFetchMeshFilesAction = ( diff --git a/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts b/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts index 05c6f38ba03..dc6200d35f4 100644 --- a/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts +++ b/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts @@ -206,9 +206,10 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { } case "UPDATE_ISOSURFACE_VISIBILITY": { - const { layerName, id, visibility } = action; + const { layerName, id, visibility, additionalCoordinates } = action; const meshInfo: Partial = { isVisible: visibility, + seedAdditionalCoordinates: additionalCoordinates }; return updateKey4(state, "localSegmentationData", layerName, "meshes", id, meshInfo); } diff --git a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts index bc5b61c7353..0b6ed9dca54 100644 --- a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts @@ -1,5 +1,5 @@ import { saveAs } from "file-saver"; -import _ from "lodash"; +import _, { add } from "lodash"; import { V3 } from "libs/mjs"; import { chunkDynamically, sleep } from "libs/utils"; import ErrorHandling from "libs/error_handling"; @@ -71,7 +71,11 @@ import { getDracoLoader } from "libs/draco"; import messages from "messages"; import processTaskWithPool from "libs/async/task_pool"; import { getBaseSegmentationName } from "oxalis/view/right-border-tabs/segments_tab/segments_view_helper"; -import { BatchUpdateGroupsAndSegmentsAction, RemoveSegmentAction, UpdateSegmentAction } from "../actions/volumetracing_actions"; +import { + BatchUpdateGroupsAndSegmentsAction, + RemoveSegmentAction, + UpdateSegmentAction, +} from "../actions/volumetracing_actions"; import { ResolutionInfo } from "../helpers/resolution_info"; import { type AdditionalCoordinate } from "types/api_flow_types"; import Zip from "libs/zipjs_wrapper"; @@ -146,7 +150,7 @@ function removeMapForSegment(layerName: string, segmentId: number): void { ?.map((coordinate) => `${coordinate.name}=${coordinate.value}`) .reduce((a: string, b: string) => a.concat(b)) as string; } - + if (adhocMeshesMapByLayer[additionalCoordinates][layerName] == null) { return; } @@ -308,7 +312,6 @@ function* loadFullAdHocMesh( let isInitialRequest = true; const { mappingName, mappingType } = meshExtraInfo; const clippedPosition = clipPositionToCubeBoundary(position); - console.log(additionalCoordinates); yield* put( addAdHocMeshAction( layer.name, @@ -421,7 +424,6 @@ function* maybeLoadMeshChunk( useDataStore: boolean, findNeighbors: boolean, ): Saga { - const additionalCoordinates = Store.getState().flycam.additionalCoordinates || undefined; const threeDMap = getOrAddMapForSegment(layer.name, segmentId); @@ -514,7 +516,6 @@ function* markEditedCellAsDirty(): Saga { function* refreshMeshes(): Saga { yield* put(saveNowAction()); - console.log("refresh") // We reload all cells that got modified till the start of reloading. // By that we avoid to remove cells that got annotated during reloading from the modifiedCells set. const currentlyModifiedCells = new Set(modifiedCells); @@ -548,7 +549,6 @@ function* refreshMeshes(): Saga { } function* refreshMesh(action: RefreshMeshAction): Saga { - console.log("refresh mesh") const additionalCoordinatesObject = Store.getState().flycam.additionalCoordinates; let additionalCoordinates = ""; if (additionalCoordinatesObject != null) { @@ -575,7 +575,7 @@ function* refreshMesh(action: RefreshMeshAction): Saga { ), ); } else { - if(adhocMeshesMapByLayer[additionalCoordinates] == null) return + if (adhocMeshesMapByLayer[additionalCoordinates] == null) return; const threeDMap = adhocMeshesMapByLayer[additionalCoordinates][action.layerName].get(segmentId); if (threeDMap == null) return; yield* call(_refreshMeshWithMap, segmentId, threeDMap, layerName); @@ -1155,32 +1155,33 @@ function* handleMeshVisibilityChange(action: UpdateMeshVisibilityAction): Saga { const { values } = action; const { segmentMeshController } = yield* call(getSceneController); - // const meshRecords = segmentMeshController.meshesGroupsPerSegmentationId; - if(values == null || values.length === 0 ) return - const newAdditionalCoordinates = values + if (values == null || values.length === 0) return; + const newAdditionalCoordinates = values ?.map((coordinate: AdditionalCoordinate) => `${coordinate.name}=${coordinate.value}`) .reduce((a: string, b: string) => a.concat(b)) as string; - let updateVisibilityActions: any = []; - Object.keys(meshRecords).forEach((additionalCoordinates) => { - const shouldBeVisible = additionalCoordinates === newAdditionalCoordinates; - Object.keys(meshRecords[additionalCoordinates]).forEach((layerName) => { - Object.keys(meshRecords[additionalCoordinates][layerName]).forEach(meshGroup => { - const meshId = parseInt(meshGroup); - _.forEach( - meshRecords[additionalCoordinates][layerName][meshId], - _ => { - updateVisibilityActions.push(updateMeshVisibilityAction(layerName, meshId, shouldBeVisible)) - segmentMeshController.setMeshVisibility(meshId, shouldBeVisible, layerName); - }, - ); - }); + let updateVisibilityActions: any = []; + Object.keys(meshRecords).forEach((additionalCoordinates) => { + const shouldBeVisible = additionalCoordinates === newAdditionalCoordinates; + Object.keys(meshRecords[additionalCoordinates]).forEach((layerName) => { + Object.keys(meshRecords[additionalCoordinates][layerName]).forEach((meshGroup) => { + const meshId = parseInt(meshGroup); + _.forEach(meshRecords[additionalCoordinates][layerName][meshId], (_) => { + //TODO for multiple dimensions + const splitAddCoord = additionalCoordinates.split("="); + const addCoordObject = {name: splitAddCoord[0], value: parseInt(splitAddCoord[1])} + updateVisibilityActions.push( + updateMeshVisibilityAction(layerName, meshId, shouldBeVisible, [addCoordObject]), + ); + segmentMeshController.setMeshVisibility(meshId, shouldBeVisible, layerName); }); }); - console.log(updateVisibilityActions) - yield* all(updateVisibilityActions.map(e=>put(e))); + }); + }); + console.log(updateVisibilityActions); + yield* all(updateVisibilityActions.map((e) => put(e))); } function* handleSegmentColorChange(action: UpdateSegmentAction): Saga { @@ -1188,15 +1189,19 @@ function* handleSegmentColorChange(action: UpdateSegmentAction): Saga { if ( "color" in action.segment && segmentMeshController.hasMesh(action.segmentId, action.layerName) - ) { - segmentMeshController.setMeshColor(action.segmentId, action.layerName); - } - } - -function* handleBatchSegmentColorChange(batchAction: BatchUpdateGroupsAndSegmentsAction): Saga{ - const updateSegmentActions = batchAction.payload.filter(action => action.type === "UPDATE_SEGMENT").map(action => call(handleSegmentColorChange, action)) - yield* all(updateSegmentActions); + ) { + segmentMeshController.setMeshColor(action.segmentId, action.layerName); } +} + +function* handleBatchSegmentColorChange( + batchAction: BatchUpdateGroupsAndSegmentsAction, +): Saga { + const updateSegmentActions = batchAction.payload + .filter((action) => action.type === "UPDATE_SEGMENT") + .map((action) => call(handleSegmentColorChange, action)); + yield* all(updateSegmentActions); +} export default function* meshSaga(): Saga { // Buffer actions since they might be dispatched before WK_READY From 7b02f84e846bde981a0893ca2368761bf2246a6b Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Tue, 31 Oct 2023 17:40:36 +0100 Subject: [PATCH 09/43] only show meshes in current add coordinates --- .../controller/segment_mesh_controller.ts | 29 +++++-------------- .../model/reducers/annotation_reducer.ts | 1 - .../oxalis/model/sagas/mesh_saga.ts | 4 +-- 3 files changed, 9 insertions(+), 25 deletions(-) diff --git a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts index d5ee5d74f59..fffe27cdc40 100644 --- a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts +++ b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts @@ -205,35 +205,20 @@ export default class SegmentMeshController { return this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentId][bestLod]; } - setMeshVisibility(id: number, visibility: boolean, layerName: string, additionalCoordinates?: AdditionalCoordinate[] | undefined): void { - //set all visibilities for not current add coor to false and the one - debugger; - const additionalCoordinatesObject = Store.getState().flycam.additionalCoordinates; - let additionalCoordinates = ""; - if (additionalCoordinatesObject != null) { - additionalCoordinates = additionalCoordinatesObject + setMeshVisibility(id: number, visibility: boolean, layerName: string, additionalCoordinates?: AdditionalCoordinate[] | null): void { + let additionalCoordinatesString = ""; + if (additionalCoordinates != null) { + additionalCoordinatesString = additionalCoordinates ?.map((coordinate) => `${coordinate.name}=${coordinate.value}`) .reduce((a: string, b: string) => a.concat(b)) as string; } - if (this.meshesGroupsPerSegmentationId[additionalCoordinates] == null) { - Object.keys(this.meshesGroupsPerSegmentationId).forEach((additionalCoordinatesIter) => { - Object.keys(this.meshesGroupsPerSegmentationId[additionalCoordinatesIter]).forEach( - (layerNameIter) => { - _.forEach( - this.meshesGroupsPerSegmentationId[additionalCoordinatesIter][layerNameIter][id], - (meshGroup) => { - meshGroup.visible = false; - }, - ); - }, - ); - }); - return; + if (this.meshesGroupsPerSegmentationId[additionalCoordinatesString] == null) { + return; //TODO think about 3D only } _.forEach( - this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][id], + this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName][id], (meshGroup) => { meshGroup.visible = visibility; }, diff --git a/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts b/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts index dc6200d35f4..5513091b9ea 100644 --- a/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts +++ b/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts @@ -249,7 +249,6 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { segmentId, meshInfo, ); - console.log(updatedKey); return updatedKey; } diff --git a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts index 0b6ed9dca54..b0d0b482c91 100644 --- a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts @@ -1149,7 +1149,7 @@ function* handleMeshVisibilityChange(action: UpdateMeshVisibilityAction): Saga { @@ -1175,7 +1175,7 @@ function* handleAdditionalCoordinateUpdate(action: FlycamAction): Saga { updateVisibilityActions.push( updateMeshVisibilityAction(layerName, meshId, shouldBeVisible, [addCoordObject]), ); - segmentMeshController.setMeshVisibility(meshId, shouldBeVisible, layerName); + segmentMeshController.setMeshVisibility(meshId, shouldBeVisible, layerName, [addCoordObject]); }); }); }); From 89f5a564e35423cc28ca0de223a6e523aade5865 Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Wed, 1 Nov 2023 14:29:03 +0100 Subject: [PATCH 10/43] fix case where no additional coordinates are given --- .../controller/segment_mesh_controller.ts | 59 ++++++------------- .../oxalis/model/sagas/mesh_saga.ts | 15 ++--- 2 files changed, 25 insertions(+), 49 deletions(-) diff --git a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts index fffe27cdc40..e9ae419369e 100644 --- a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts +++ b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts @@ -26,13 +26,7 @@ export default class SegmentMeshController { } hasMesh(id: number, layerName: string): boolean { - const additionalCoordinatesObject = Store.getState().flycam.additionalCoordinates; - let additionalCoordinates = ""; - if (additionalCoordinatesObject != null) { - additionalCoordinates = additionalCoordinatesObject - ?.map((coordinate) => `${coordinate.name}=${coordinate.value}`) - .reduce((a: string, b: string) => a.concat(b)) as string; - } + const additionalCoordinates = this.getCoordinateString(); if ( this.meshesGroupsPerSegmentationId[additionalCoordinates] == null || @@ -95,6 +89,17 @@ export default class SegmentMeshController { return mesh; } + getCoordinateString(inputAdditionalCoordinates?: AdditionalCoordinate[] | null){ + const additionalCoordinatesObject = inputAdditionalCoordinates || Store.getState().flycam.additionalCoordinates; + let additionalCoordinates = ""; + if (additionalCoordinatesObject != null && additionalCoordinatesObject.length>0) { + additionalCoordinates = additionalCoordinatesObject + ?.map((coordinate) => `${coordinate.name}=${coordinate.value}`) + .reduce((a: string, b: string) => a.concat(b), "") as string; + } + return additionalCoordinates; + } + addMeshFromGeometry( geometry: THREE.BufferGeometry, segmentationId: number, @@ -103,14 +108,7 @@ export default class SegmentMeshController { lod: number, layerName: string, ): void { - const additionalCoordinatesObject = Store.getState().flycam.additionalCoordinates; - let additionalCoordinates = ""; - if (additionalCoordinatesObject != null) { - additionalCoordinates = additionalCoordinatesObject - ?.map((coordinate) => `${coordinate.name}=${coordinate.value}`) - .reduce((a: string, b: string) => a.concat(b)) as string; - } - + const additionalCoordinates = this.getCoordinateString(); if (this.meshesGroupsPerSegmentationId[additionalCoordinates] == null) { this.meshesGroupsPerSegmentationId[additionalCoordinates] = {}; } @@ -153,13 +151,7 @@ export default class SegmentMeshController { } removeMeshById(segmentationId: number, layerName: string): void { - const additionalCoordinatesObject = Store.getState().flycam.additionalCoordinates; - let additionalCoordinates = ""; - if (additionalCoordinatesObject != null) { - additionalCoordinates = additionalCoordinatesObject - ?.map((coordinate) => `${coordinate.name}=${coordinate.value}`) - .reduce((a: string, b: string) => a.concat(b)) as string; - } + const additionalCoordinates = this.getCoordinateString(); // TODO I think it shouldnt be possible to remove meshes that arent visible currently. // but if they are removed they should be removed for all timestamps @@ -189,13 +181,7 @@ export default class SegmentMeshController { } getMeshGeometryInBestLOD(segmentId: number, layerName: string): THREE.Group { - const additionalCoordinatesObject = Store.getState().flycam.additionalCoordinates; - let additionalCoordinates = ""; - if (additionalCoordinatesObject != null) { - additionalCoordinates = additionalCoordinatesObject - ?.map((coordinate) => `${coordinate.name}=${coordinate.value}`) - .reduce((a: string, b: string) => a.concat(b)) as string; - } + const additionalCoordinates = this.getCoordinateString(); const bestLod = Math.min( ...Object.keys( @@ -206,12 +192,7 @@ export default class SegmentMeshController { } setMeshVisibility(id: number, visibility: boolean, layerName: string, additionalCoordinates?: AdditionalCoordinate[] | null): void { - let additionalCoordinatesString = ""; - if (additionalCoordinates != null) { - additionalCoordinatesString = additionalCoordinates - ?.map((coordinate) => `${coordinate.name}=${coordinate.value}`) - .reduce((a: string, b: string) => a.concat(b)) as string; - } + const additionalCoordinatesString = this.getCoordinateString(additionalCoordinates); if (this.meshesGroupsPerSegmentationId[additionalCoordinatesString] == null) { return; //TODO think about 3D only @@ -226,13 +207,7 @@ export default class SegmentMeshController { } setMeshColor(id: number, layerName: string): void { - const additionalCoordinatesObject = Store.getState().flycam.additionalCoordinates; - let additionalCoordinates = ""; - if (additionalCoordinatesObject != null) { - additionalCoordinates = additionalCoordinatesObject - ?.map((coordinate) => `${coordinate.name}=${coordinate.value}`) - .reduce((a: string, b: string) => a.concat(b)) as string; - } + const additionalCoordinates = this.getCoordinateString(); const color = this.getColorObjectForSegment(id); _.forEach( this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][id], diff --git a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts index b0d0b482c91..37f63cccaef 100644 --- a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts @@ -120,7 +120,7 @@ export function isMeshSTL(buffer: ArrayBuffer): boolean { function getOrAddMapForSegment(layerName: string, segmentId: number): ThreeDMap { const additionalCoordinatesObject = Store.getState().flycam.additionalCoordinates; let additionalCoordinates = ""; - if (additionalCoordinatesObject != null) { + if (additionalCoordinatesObject != null && additionalCoordinatesObject?.length > 0) { additionalCoordinates = additionalCoordinatesObject ?.map((coordinate) => `${coordinate.name}=${coordinate.value}`) .reduce((a: string, b: string) => a.concat(b)) as string; @@ -497,7 +497,7 @@ function* maybeLoadMeshChunk( } catch (exception) { retryCount++; ErrorHandling.notify(exception as Error); - console.warn("Retrying mesh generation..."); + console.warn("Retrying mesh generation due to", exception); yield* call(sleep, RETRY_WAIT_TIME * 2 ** retryCount); } } @@ -1146,19 +1146,19 @@ function removeMesh(action: RemoveMeshAction, removeFromScene: boolean = true): } function* handleMeshVisibilityChange(action: UpdateMeshVisibilityAction): Saga { - const { id, visibility, layerName } = action; + const { id, visibility, layerName, additionalCoordinates } = action; const { segmentMeshController } = yield* call(getSceneController); // segmentMeshController.setMeshVisibility(id, visibility, layerName, additionalCoordinates); } function* handleAdditionalCoordinateUpdate(action: FlycamAction): Saga { - const { values } = action; + if(action.type === "SET_ADDITIONAL_COORDINATES"){ const { segmentMeshController } = yield* call(getSceneController); const meshRecords = segmentMeshController.meshesGroupsPerSegmentationId; - if (values == null || values.length === 0) return; - const newAdditionalCoordinates = values + if (action.values == null || action.values.length === 0) return; + const newAdditionalCoordinates = action.values ?.map((coordinate: AdditionalCoordinate) => `${coordinate.name}=${coordinate.value}`) .reduce((a: string, b: string) => a.concat(b)) as string; @@ -1183,6 +1183,7 @@ function* handleAdditionalCoordinateUpdate(action: FlycamAction): Saga { console.log(updateVisibilityActions); yield* all(updateVisibilityActions.map((e) => put(e))); } +} function* handleSegmentColorChange(action: UpdateSegmentAction): Saga { const { segmentMeshController } = yield* call(getSceneController); @@ -1224,5 +1225,5 @@ export default function* meshSaga(): Saga { yield* takeEvery(["START_EDITING", "COPY_SEGMENTATION_LAYER"], markEditedCellAsDirty); yield* takeEvery("UPDATE_SEGMENT", handleSegmentColorChange); yield* takeEvery("BATCH_UPDATE_GROUPS_AND_SEGMENTS", handleBatchSegmentColorChange); - yield* takeEvery("SET_ADDITIONAL_COORDINATES", handleAdditionalCoordinateUpdate); + yield* takeEvery("SET_ADDITIONAL_COORDINATES", handleAdditionalCoordinateUpdate); //TODO remove and while true } From 84f0bf07d5b598d5e1f37bad95fe43ec8db37cbf Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Wed, 1 Nov 2023 14:51:25 +0100 Subject: [PATCH 11/43] WIP: try to adjust segments view and add comment about batch actions --- frontend/javascripts/oxalis/model/sagas/mesh_saga.ts | 5 +++++ .../right-border-tabs/segments_tab/segments_view.tsx | 10 ++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts index 37f63cccaef..eb1a066e7ab 100644 --- a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts @@ -1198,6 +1198,11 @@ function* handleSegmentColorChange(action: UpdateSegmentAction): Saga { function* handleBatchSegmentColorChange( batchAction: BatchUpdateGroupsAndSegmentsAction, ): Saga { +// Manually unpack batched actions and handle these. +// In theory, this could happen automatically. See this issue in the corresponding (rather unmaintained) package: https://github.com/tshelburne/redux-batched-actions/pull/18 +// However, there seem to be some problems with that approach (e.g., too many updates, infinite recursion) and the discussion there didn't really reach a consensus +// about the correct solution. +// This is why we stick to the manual unpacking for now. const updateSegmentActions = batchAction.payload .filter((action) => action.type === "UPDATE_SEGMENT") .map((action) => call(handleSegmentColorChange, action)); diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx index d232d8ff9cf..f1612878294 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx @@ -164,12 +164,14 @@ const mapStateToProps = (state: OxalisState): StateProps => { const isVisibleButUneditableSegmentationLayerActive = visibleSegmentationLayer != null && visibleSegmentationLayer.tracingId == null; + // TODO add coord are undefined + const meshesForCurrentAdditionalCoordinates = visibleSegmentationLayer != null + ? Object.values(state.localSegmentationData[visibleSegmentationLayer.name].meshes).filter(mesh => mesh.seedAdditionalCoordinates === state.flycam.additionalCoordinates) + : EMPTY_OBJECT; + return { activeCellId: activeVolumeTracing?.activeCellId, - meshes: - visibleSegmentationLayer != null - ? state.localSegmentationData[visibleSegmentationLayer.name].meshes - : EMPTY_OBJECT, + meshes: meshesForCurrentAdditionalCoordinates, dataset: state.dataset, isJSONMappingEnabled: mappingInfo.mappingStatus === MappingStatusEnum.ENABLED && mappingInfo.mappingType === "JSON", From 15320c2e05c3293704252db0b9c06fce484686bc Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Mon, 6 Nov 2023 20:56:13 +0100 Subject: [PATCH 12/43] WIP: saving progress --- frontend/javascripts/oxalis/api/api_latest.ts | 4 +- .../controller/segment_mesh_controller.ts | 15 +- .../model/actions/annotation_actions.ts | 9 +- .../model/reducers/annotation_reducer.ts | 142 +++++++++++++++--- .../oxalis/model/sagas/mesh_saga.ts | 75 +++++---- frontend/javascripts/oxalis/store.ts | 2 +- .../segments_tab/segments_view.tsx | 34 ++++- 7 files changed, 213 insertions(+), 68 deletions(-) diff --git a/frontend/javascripts/oxalis/api/api_latest.ts b/frontend/javascripts/oxalis/api/api_latest.ts index 8075f8d205a..7aed79157b1 100644 --- a/frontend/javascripts/oxalis/api/api_latest.ts +++ b/frontend/javascripts/oxalis/api/api_latest.ts @@ -2258,7 +2258,9 @@ class DataApi { ).name; if (Store.getState().localSegmentationData[effectiveLayerName].meshes[segmentId] != null) { - Store.dispatch(updateMeshVisibilityAction(effectiveLayerName, segmentId, isVisible, undefined)); + Store.dispatch( + updateMeshVisibilityAction(effectiveLayerName, segmentId, isVisible, undefined), + ); } } diff --git a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts index e9ae419369e..b62870be344 100644 --- a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts +++ b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts @@ -89,10 +89,11 @@ export default class SegmentMeshController { return mesh; } - getCoordinateString(inputAdditionalCoordinates?: AdditionalCoordinate[] | null){ - const additionalCoordinatesObject = inputAdditionalCoordinates || Store.getState().flycam.additionalCoordinates; + getCoordinateString(inputAdditionalCoordinates?: AdditionalCoordinate[] | null) { + const additionalCoordinatesObject = + inputAdditionalCoordinates || Store.getState().flycam.additionalCoordinates; let additionalCoordinates = ""; - if (additionalCoordinatesObject != null && additionalCoordinatesObject.length>0) { + if (additionalCoordinatesObject != null && additionalCoordinatesObject.length > 0) { additionalCoordinates = additionalCoordinatesObject ?.map((coordinate) => `${coordinate.name}=${coordinate.value}`) .reduce((a: string, b: string) => a.concat(b), "") as string; @@ -191,7 +192,13 @@ export default class SegmentMeshController { return this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentId][bestLod]; } - setMeshVisibility(id: number, visibility: boolean, layerName: string, additionalCoordinates?: AdditionalCoordinate[] | null): void { + setMeshVisibility( + id: number, + visibility: boolean, + layerName: string, + additionalCoordinates?: AdditionalCoordinate[] | null, + ): void { + console.log("setMeshVis", id, visibility, layerName, additionalCoordinates); const additionalCoordinatesString = this.getCoordinateString(additionalCoordinates); if (this.meshesGroupsPerSegmentationId[additionalCoordinatesString] == null) { diff --git a/frontend/javascripts/oxalis/model/actions/annotation_actions.ts b/frontend/javascripts/oxalis/model/actions/annotation_actions.ts index 8821641a1c0..ca568c83877 100644 --- a/frontend/javascripts/oxalis/model/actions/annotation_actions.ts +++ b/frontend/javascripts/oxalis/model/actions/annotation_actions.ts @@ -183,13 +183,18 @@ export const addUserBoundingBoxesAction = (userBoundingBoxes: Array +export const updateMeshVisibilityAction = ( + layerName: string, + id: number, + visibility: boolean, + additionalCoordinates: AdditionalCoordinate[] | undefined, +) => ({ type: "UPDATE_ISOSURFACE_VISIBILITY", layerName, id, visibility, - additionalCoordinates + additionalCoordinates, } as const); export const maybeFetchMeshFilesAction = ( diff --git a/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts b/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts index 5513091b9ea..0b72ee8f223 100644 --- a/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts +++ b/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts @@ -2,7 +2,7 @@ import update from "immutability-helper"; import type { Action } from "oxalis/model/actions/actions"; import type { OxalisState, UserBoundingBox, MeshInformation } from "oxalis/store"; import { V3 } from "libs/mjs"; -import { updateKey, updateKey2, updateKey4 } from "oxalis/model/helpers/deep_update"; +import { updateKey, updateKey2, updateKey3, updateKey4 } from "oxalis/model/helpers/deep_update"; import { maybeGetSomeTracing } from "oxalis/model/accessors/tracing_accessor"; import * as Utils from "libs/utils"; import { getDisplayedDataExtentInPlaneMode } from "oxalis/model/accessors/view_mode_accessor"; @@ -206,19 +206,50 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { } case "UPDATE_ISOSURFACE_VISIBILITY": { - const { layerName, id, visibility, additionalCoordinates } = action; - const meshInfo: Partial = { - isVisible: visibility, - seedAdditionalCoordinates: additionalCoordinates - }; - return updateKey4(state, "localSegmentationData", layerName, "meshes", id, meshInfo); + const { layerName, id, visibility } = action; + const additionalCoordinates = state.flycam.additionalCoordinates; + const addCoordString = + additionalCoordinates != null && additionalCoordinates?.length > 0 + ? `${additionalCoordinates[0].name}=${additionalCoordinates[0].value}` + : ""; + if (state.localSegmentationData[layerName].meshes == null) return state; + return update(state, { + localSegmentationData: { + [layerName]: { + meshes: { + [addCoordString]: { + [id]: { + isVisible: { + $set: visibility, + }, + }, + }, + }, + }, + }, + }); } case "REMOVE_ISOSURFACE": { + //TODO fix me const { layerName, segmentId } = action; - const { [segmentId]: _, ...remainingMeshes } = state.localSegmentationData[layerName].meshes; - return updateKey2(state, "localSegmentationData", layerName, { - meshes: remainingMeshes, + const additionalCoordinates = state.flycam.additionalCoordinates; + const addCoordString = + additionalCoordinates != null && additionalCoordinates?.length > 0 + ? `${additionalCoordinates[0].name}=${additionalCoordinates[0].value}` + : ""; + const { [segmentId]: _, ...remainingMeshes } = + state.localSegmentationData[layerName].meshes[addCoordString]; + return update(state, { + localSegmentationData: { + [layerName]: { + meshes: { + [addCoordString]: { + $set: remainingMeshes, + }, + }, + }, + }, }); } @@ -241,14 +272,24 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { mappingName, mappingType, }; - const updatedKey = updateKey4( - state, - "localSegmentationData", - layerName, - "meshes", - segmentId, - meshInfo, - ); + const additionalCoordinates = state.flycam.additionalCoordinates; + const addCoordString = + additionalCoordinates != null && additionalCoordinates?.length > 0 + ? `${additionalCoordinates[0].name}=${additionalCoordinates[0].value}` + : ""; + const updatedKey = update(state, { + localSegmentationData: { + [layerName]: { + meshes: { + [addCoordString]: { + [segmentId]: { + $set: meshInfo, + }, + }, + }, + }, + }, + }); return updatedKey; } @@ -262,7 +303,25 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { isPrecomputed: true, meshFileName, }; - return updateKey4(state, "localSegmentationData", layerName, "meshes", segmentId, meshInfo); + const additionalCoordinates = state.flycam.additionalCoordinates; + const addCoordString = + additionalCoordinates != null && additionalCoordinates?.length > 0 + ? `${additionalCoordinates[0].name}=${additionalCoordinates[0].value}` + : ""; + const updatedKey = update(state, { + localSegmentationData: { + [layerName]: { + meshes: { + [addCoordString]: { + [segmentId]: { + $set: meshInfo, + }, + }, + }, + }, + }, + }); + return updatedKey; } case "STARTED_LOADING_ISOSURFACE": { @@ -270,7 +329,28 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { const meshInfo: Partial = { isLoading: true, }; - return updateKey4(state, "localSegmentationData", layerName, "meshes", segmentId, meshInfo); + const additionalCoordinates = state.flycam.additionalCoordinates; + const addCoordString = + additionalCoordinates != null && additionalCoordinates?.length > 0 + ? `${additionalCoordinates[0].name}=${additionalCoordinates[0].value}` + : ""; + const updatedKey = update(state, { + localSegmentationData: { + [layerName]: { + meshes: { + [addCoordString]: { + [segmentId]: { + isLoading:{ + $set: true, + } + }, + }, + }, + }, + }, + }); + return updatedKey; + //return updateKey4(state, "localSegmentationData", layerName, "meshes", segmentId, meshInfo); } case "FINISHED_LOADING_ISOSURFACE": { @@ -278,7 +358,27 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { const meshInfo: Partial = { isLoading: false, }; - return updateKey4(state, "localSegmentationData", layerName, "meshes", segmentId, meshInfo); + const additionalCoordinates = state.flycam.additionalCoordinates; + const addCoordString = + additionalCoordinates != null && additionalCoordinates?.length > 0 + ? `${additionalCoordinates[0].name}=${additionalCoordinates[0].value}` + : ""; + const updatedKey = update(state, { + localSegmentationData: { + [layerName]: { + meshes: { + [addCoordString]: { + [segmentId]: { + isLoading: { + $set: false, + }, + }, + }, + }, + }, + }, + }); + return updatedKey; } case "UPDATE_MESH_FILE_LIST": { diff --git a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts index eb1a066e7ab..833d157157d 100644 --- a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts @@ -1148,41 +1148,50 @@ function removeMesh(action: RemoveMeshAction, removeFromScene: boolean = true): function* handleMeshVisibilityChange(action: UpdateMeshVisibilityAction): Saga { const { id, visibility, layerName, additionalCoordinates } = action; const { segmentMeshController } = yield* call(getSceneController); - // - segmentMeshController.setMeshVisibility(id, visibility, layerName, additionalCoordinates); + segmentMeshController.setMeshVisibility(id, visibility, layerName, additionalCoordinates); //use yield* call + console.log(Store.getState().localSegmentationData); } function* handleAdditionalCoordinateUpdate(action: FlycamAction): Saga { - if(action.type === "SET_ADDITIONAL_COORDINATES"){ - const { segmentMeshController } = yield* call(getSceneController); - const meshRecords = segmentMeshController.meshesGroupsPerSegmentationId; - - if (action.values == null || action.values.length === 0) return; - const newAdditionalCoordinates = action.values - ?.map((coordinate: AdditionalCoordinate) => `${coordinate.name}=${coordinate.value}`) - .reduce((a: string, b: string) => a.concat(b)) as string; - - let updateVisibilityActions: any = []; - Object.keys(meshRecords).forEach((additionalCoordinates) => { - const shouldBeVisible = additionalCoordinates === newAdditionalCoordinates; - Object.keys(meshRecords[additionalCoordinates]).forEach((layerName) => { - Object.keys(meshRecords[additionalCoordinates][layerName]).forEach((meshGroup) => { - const meshId = parseInt(meshGroup); - _.forEach(meshRecords[additionalCoordinates][layerName][meshId], (_) => { - //TODO for multiple dimensions - const splitAddCoord = additionalCoordinates.split("="); - const addCoordObject = {name: splitAddCoord[0], value: parseInt(splitAddCoord[1])} - updateVisibilityActions.push( - updateMeshVisibilityAction(layerName, meshId, shouldBeVisible, [addCoordObject]), + if (action.type === "SET_ADDITIONAL_COORDINATES") { + const { segmentMeshController } = yield* call(getSceneController); + const meshRecords = yield* call({ + context: segmentMeshController, + fn: () => segmentMeshController.meshesGroupsPerSegmentationId, + }); + + if (action.values == null || action.values.length === 0) return; + const newAdditionalCoordinates = action.values + ?.map((coordinate: AdditionalCoordinate) => `${coordinate.name}=${coordinate.value}`) + .reduce((a: string, b: string) => a.concat(b)) as string; + + let updateVisibilityActions: any = []; + + // maybe use for(const i of Object.keys(...)) + Object.keys(meshRecords).forEach((additionalCoordinates) => { + const shouldBeVisible = additionalCoordinates === newAdditionalCoordinates; + //TODO put unpacked nested vars into vars and use Object.entries + Object.keys(meshRecords[additionalCoordinates]).forEach((layerName) => { + Object.keys(meshRecords[additionalCoordinates][layerName]).forEach((meshGroup) => { + const meshId = parseInt(meshGroup); + Object.entries(meshRecords[additionalCoordinates][layerName][meshId]).forEach( + ([key, value]) => { + //TODO for multiple dimensions_.forEach( + const splitAddCoord = additionalCoordinates.split("="); + const addCoordObject = { name: splitAddCoord[0], value: parseInt(splitAddCoord[1]) }; + updateVisibilityActions.push( + updateMeshVisibilityAction(layerName, meshId, shouldBeVisible, [addCoordObject]), + ); + segmentMeshController.setMeshVisibility(meshId, shouldBeVisible, layerName, [ + addCoordObject, + ]); + }, ); - segmentMeshController.setMeshVisibility(meshId, shouldBeVisible, layerName, [addCoordObject]); }); }); }); - }); - console.log(updateVisibilityActions); - yield* all(updateVisibilityActions.map((e) => put(e))); -} + yield* all(updateVisibilityActions.map((e) => put(e))); + } } function* handleSegmentColorChange(action: UpdateSegmentAction): Saga { @@ -1198,11 +1207,11 @@ function* handleSegmentColorChange(action: UpdateSegmentAction): Saga { function* handleBatchSegmentColorChange( batchAction: BatchUpdateGroupsAndSegmentsAction, ): Saga { -// Manually unpack batched actions and handle these. -// In theory, this could happen automatically. See this issue in the corresponding (rather unmaintained) package: https://github.com/tshelburne/redux-batched-actions/pull/18 -// However, there seem to be some problems with that approach (e.g., too many updates, infinite recursion) and the discussion there didn't really reach a consensus -// about the correct solution. -// This is why we stick to the manual unpacking for now. + // Manually unpack batched actions and handle these. + // In theory, this could happen automatically. See this issue in the corresponding (rather unmaintained) package: https://github.com/tshelburne/redux-batched-actions/pull/18 + // However, there seem to be some problems with that approach (e.g., too many updates, infinite recursion) and the discussion there didn't really reach a consensus + // about the correct solution. + // This is why we stick to the manual unpacking for now. const updateSegmentActions = batchAction.payload .filter((action) => action.type === "UPDATE_SEGMENT") .map((action) => call(handleSegmentColorChange, action)); diff --git a/frontend/javascripts/oxalis/store.ts b/frontend/javascripts/oxalis/store.ts index ce9b49bf90d..b26fb539b84 100644 --- a/frontend/javascripts/oxalis/store.ts +++ b/frontend/javascripts/oxalis/store.ts @@ -568,7 +568,7 @@ export type OxalisState = { readonly localSegmentationData: Record< string, { - readonly meshes: Record; + readonly meshes: Record>; readonly availableMeshFiles: Array | null | undefined; readonly currentMeshFile: APIMeshFile | null | undefined; // Note that for a volume tracing, this information should be stored diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx index f1612878294..b741526ab08 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx @@ -164,10 +164,23 @@ const mapStateToProps = (state: OxalisState): StateProps => { const isVisibleButUneditableSegmentationLayerActive = visibleSegmentationLayer != null && visibleSegmentationLayer.tracingId == null; - // TODO add coord are undefined - const meshesForCurrentAdditionalCoordinates = visibleSegmentationLayer != null - ? Object.values(state.localSegmentationData[visibleSegmentationLayer.name].meshes).filter(mesh => mesh.seedAdditionalCoordinates === state.flycam.additionalCoordinates) - : EMPTY_OBJECT; + const additionalCoordinates = state.flycam.additionalCoordinates; + const addCoordString = + additionalCoordinates != null + ? `${additionalCoordinates[0].name}=${additionalCoordinates[0].value}` + : ""; + + // TODO add coord are undefined + let meshesForCurrentAdditionalCoordinates = EMPTY_OBJECT; + if (visibleSegmentationLayer != null) { + const meshRecords = state.localSegmentationData[visibleSegmentationLayer?.name].meshes; + meshesForCurrentAdditionalCoordinates = + meshRecords != null && + Object.keys(meshRecords).length > 0 && + Object.keys(meshRecords[addCoordString]).length > 0 + ? Object.values(meshRecords[addCoordString]) + : EMPTY_OBJECT; + } return { activeCellId: activeVolumeTracing?.activeCellId, @@ -1301,9 +1314,18 @@ class SegmentsView extends React.Component { groupId: number | null, isVisible: boolean, ) => { + const state = Store.getState(); + const additionalCoordinates = state.flycam.additionalCoordinates; this.handlePerSegment(groupId, (segment) => { - if (Store.getState().localSegmentationData[layerName].meshes[segment.id] != null) { - Store.dispatch(updateMeshVisibilityAction(layerName, segment.id, isVisible)); + if (state.localSegmentationData[layerName].meshes[segment.id] != null) { + Store.dispatch( + updateMeshVisibilityAction( + layerName, + segment.id, + isVisible, + additionalCoordinates || undefined, + ), + ); } }); }; From 38df01e0d8fa9db98c1847d627daeabe8d820be4 Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Tue, 7 Nov 2023 19:46:53 +0100 Subject: [PATCH 13/43] store add coordinates in localsegmentation data in store state --- .../model/reducers/annotation_reducer.ts | 44 ++++++++----------- .../oxalis/model/reducers/flycam_reducer.ts | 30 ++++++++++++- .../oxalis/model/sagas/mesh_saga.ts | 2 +- .../segments_tab/segment_list_item.tsx | 4 +- .../segments_tab/segments_view.tsx | 1 + 5 files changed, 50 insertions(+), 31 deletions(-) diff --git a/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts b/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts index 0b72ee8f223..7446515e3e4 100644 --- a/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts +++ b/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts @@ -206,23 +206,22 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { } case "UPDATE_ISOSURFACE_VISIBILITY": { - const { layerName, id, visibility } = action; - const additionalCoordinates = state.flycam.additionalCoordinates; + const { layerName, id, visibility, additionalCoordinates } = action; const addCoordString = additionalCoordinates != null && additionalCoordinates?.length > 0 ? `${additionalCoordinates[0].name}=${additionalCoordinates[0].value}` : ""; - if (state.localSegmentationData[layerName].meshes == null) return state; + // assumption: set_additional_coordinates action is handled before return update(state, { localSegmentationData: { [layerName]: { meshes: { [addCoordString]: { - [id]: { - isVisible: { - $set: visibility, - }, + [id]: { + isVisible: { + $set: visibility, }, + }, }, }, }, @@ -245,8 +244,8 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { [layerName]: { meshes: { [addCoordString]: { - $set: remainingMeshes, - }, + $set: remainingMeshes, + }, }, }, }, @@ -282,9 +281,9 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { [layerName]: { meshes: { [addCoordString]: { - [segmentId]: { - $set: meshInfo, - }, + [segmentId]: { + $set: meshInfo, + }, }, }, }, @@ -314,8 +313,8 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { meshes: { [addCoordString]: { [segmentId]: { - $set: meshInfo, - }, + $set: meshInfo, + }, }, }, }, @@ -326,9 +325,6 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { case "STARTED_LOADING_ISOSURFACE": { const { layerName, segmentId } = action; - const meshInfo: Partial = { - isLoading: true, - }; const additionalCoordinates = state.flycam.additionalCoordinates; const addCoordString = additionalCoordinates != null && additionalCoordinates?.length > 0 @@ -340,24 +336,20 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { meshes: { [addCoordString]: { [segmentId]: { - isLoading:{ + isLoading: { $set: true, - } }, }, + }, }, }, }, }); return updatedKey; - //return updateKey4(state, "localSegmentationData", layerName, "meshes", segmentId, meshInfo); } case "FINISHED_LOADING_ISOSURFACE": { const { layerName, segmentId } = action; - const meshInfo: Partial = { - isLoading: false, - }; const additionalCoordinates = state.flycam.additionalCoordinates; const addCoordString = additionalCoordinates != null && additionalCoordinates?.length > 0 @@ -369,11 +361,11 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { meshes: { [addCoordString]: { [segmentId]: { - isLoading: { - $set: false, - }, + isLoading: { + $set: false, }, }, + }, }, }, }, diff --git a/frontend/javascripts/oxalis/model/reducers/flycam_reducer.ts b/frontend/javascripts/oxalis/model/reducers/flycam_reducer.ts index f139928df7d..ad24e896b7e 100644 --- a/frontend/javascripts/oxalis/model/reducers/flycam_reducer.ts +++ b/frontend/javascripts/oxalis/model/reducers/flycam_reducer.ts @@ -12,7 +12,10 @@ import { } from "oxalis/model/accessors/flycam_accessor"; import Dimensions from "oxalis/model/dimensions"; import * as Utils from "libs/utils"; -import { getUnifiedAdditionalCoordinates } from "../accessors/dataset_accessor"; +import { + getUnifiedAdditionalCoordinates, + getVisibleSegmentationLayer, +} from "../accessors/dataset_accessor"; function cloneMatrix(m: Matrix4x4): Matrix4x4 { return [ @@ -267,11 +270,34 @@ function FlycamReducer(state: OxalisState, action: Action): OxalisState { return { name, value: fallbackValue }; }); - return update(state, { + const updatedFlycamState = update(state, { flycam: { additionalCoordinates: { $set: values }, }, }); + + const visibleSegmentationLayer = getVisibleSegmentationLayer(state); + const additionalCoordinateString = `${values[0].name}=${values[0].value}`; + if ( + visibleSegmentationLayer == null || + state.localSegmentationData[visibleSegmentationLayer.name].meshes[ + additionalCoordinateString + ] != null + ) { + return updatedFlycamState; + } + + const updatedLocalSegmentationState = update(updatedFlycamState, { + localSegmentationData: { + [visibleSegmentationLayer.name]: { + meshes: { + [additionalCoordinateString]: { $set: [] }, + }, + }, + }, + }); + + return updatedLocalSegmentationState; } case "SET_ROTATION": { diff --git a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts index 833d157157d..af063bfb67e 100644 --- a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts @@ -560,7 +560,7 @@ function* refreshMesh(action: RefreshMeshAction): Saga { const { segmentId, layerName } = action; const meshInfo = yield* select( - (state) => state.localSegmentationData[layerName].meshes[segmentId], + (state) => state.localSegmentationData[layerName].meshes[additionalCoordinates][segmentId], ); if (meshInfo.isPrecomputed) { diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx index 334157652b6..e43023776ef 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx @@ -231,9 +231,9 @@ function _MeshInfoItem(props: { setAdditionalCoordinates: (additionalCoordinates: AdditionalCoordinate[] | undefined) => void; }) { const dispatch = useDispatch(); - const onChangeMeshVisibility = (layerName: string, id: number, isVisible: boolean) => { - dispatch(updateMeshVisibilityAction(layerName, id, isVisible)); + console.log(mesh?.seedAdditionalCoordinates) // todo these are undefined + dispatch(updateMeshVisibilityAction(layerName, id, isVisible, mesh?.seedAdditionalCoordinates)); }; const { segment, isSelectedInList, isHovered, mesh } = props; diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx index b741526ab08..4e19e381256 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx @@ -1316,6 +1316,7 @@ class SegmentsView extends React.Component { ) => { const state = Store.getState(); const additionalCoordinates = state.flycam.additionalCoordinates; + console.log("segments view", additionalCoordinates) this.handlePerSegment(groupId, (segment) => { if (state.localSegmentationData[layerName].meshes[segment.id] != null) { Store.dispatch( From 299f5f97f6ca7850660a9652d5e112266ef6ede4 Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Wed, 8 Nov 2023 13:32:52 +0100 Subject: [PATCH 14/43] WIP: fixed segment list, mesh loading and reloading --- frontend/javascripts/oxalis/model/sagas/mesh_saga.ts | 10 ++++++---- .../segments_tab/segment_list_item.tsx | 1 - .../right-border-tabs/segments_tab/segments_view.tsx | 12 ++++-------- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts index af063bfb67e..a8cb92356b9 100644 --- a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts @@ -196,6 +196,7 @@ function getNeighborPosition(clippedPosition: Vector3, neighborId: number): Vect } function* loadAdHocMeshFromAction(action: LoadAdHocMeshAction): Saga { + console.log("load ad hoc mesh from action", action.seedAdditionalCoordinates) //todo undefined yield* call( loadAdHocMesh, action.seedPosition, @@ -544,7 +545,7 @@ function* refreshMeshes(): Saga { continue; } - yield* call(_refreshMeshWithMap, segmentId, threeDMap, segmentationLayer.name); + yield* call(_refreshMeshWithMap, segmentId, threeDMap, segmentationLayer.name, additionalCoordinates); } } @@ -578,7 +579,7 @@ function* refreshMesh(action: RefreshMeshAction): Saga { if (adhocMeshesMapByLayer[additionalCoordinates] == null) return; const threeDMap = adhocMeshesMapByLayer[additionalCoordinates][action.layerName].get(segmentId); if (threeDMap == null) return; - yield* call(_refreshMeshWithMap, segmentId, threeDMap, layerName); + yield* call(_refreshMeshWithMap, segmentId, threeDMap, layerName, additionalCoordinates); } } @@ -586,9 +587,10 @@ function* _refreshMeshWithMap( segmentId: number, threeDMap: ThreeDMap, layerName: string, + additionalCoordinateString: string, ): Saga { const meshInfo = yield* select( - (state) => state.localSegmentationData[layerName].meshes[segmentId], + (state) => state.localSegmentationData[layerName].meshes[additionalCoordinateString][segmentId], ); yield* call( [ErrorHandling, ErrorHandling.assert], @@ -596,7 +598,7 @@ function* _refreshMeshWithMap( "_refreshMeshWithMap was called for a precomputed mesh.", ); if (meshInfo.isPrecomputed) return; - const { mappingName, mappingType } = meshInfo; + const { mappingName, mappingType, seedAdditionalCoordinates } = meshInfo; const meshPositions = threeDMap.entries().filter(([value, _position]) => value); if (meshPositions.length === 0) { diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx index e43023776ef..e5f4880c907 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx @@ -232,7 +232,6 @@ function _MeshInfoItem(props: { }) { const dispatch = useDispatch(); const onChangeMeshVisibility = (layerName: string, id: number, isVisible: boolean) => { - console.log(mesh?.seedAdditionalCoordinates) // todo these are undefined dispatch(updateMeshVisibilityAction(layerName, id, isVisible, mesh?.seedAdditionalCoordinates)); }; diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx index 4e19e381256..9ca380005b6 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx @@ -178,7 +178,7 @@ const mapStateToProps = (state: OxalisState): StateProps => { meshRecords != null && Object.keys(meshRecords).length > 0 && Object.keys(meshRecords[addCoordString]).length > 0 - ? Object.values(meshRecords[addCoordString]) + ? meshRecords[addCoordString] : EMPTY_OBJECT; } @@ -223,12 +223,9 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ loadAdHocMesh( segmentId: number, - seedPosition: Vector3, - seedAdditionalCoordinates: AdditionalCoordinate[] | undefined, + seedPosition: Vector3 ) { - console.log("segments view"); - console.log(seedAdditionalCoordinates); - dispatch(loadAdHocMeshAction(segmentId, seedPosition, seedAdditionalCoordinates)); + dispatch(loadAdHocMeshAction(segmentId, seedPosition, Store.getState().flycam.additionalCoordinates||undefined)); }, loadPrecomputedMesh( @@ -1316,7 +1313,6 @@ class SegmentsView extends React.Component { ) => { const state = Store.getState(); const additionalCoordinates = state.flycam.additionalCoordinates; - console.log("segments view", additionalCoordinates) this.handlePerSegment(groupId, (segment) => { if (state.localSegmentationData[layerName].meshes[segment.id] != null) { Store.dispatch( @@ -1334,7 +1330,7 @@ class SegmentsView extends React.Component { handleLoadMeshesAdHoc = (groupId: number | null) => { this.handlePerSegment(groupId, (segment) => { if (segment.somePosition == null) return; - this.props.loadAdHocMesh(segment.id, segment.somePosition, segment.someAdditionalCoordinates); + this.props.loadAdHocMesh(segment.id, segment.somePosition); }); }; From 247bb4420aba39a9e02510aba6f02b34abf40530 Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Mon, 13 Nov 2023 12:34:54 +0100 Subject: [PATCH 15/43] add string conversion for additional coordinates and remove some Store.getState() occurences --- .../oxalis/model/accessors/flycam_accessor.ts | 16 ++++++++++ .../oxalis/model/sagas/mesh_saga.ts | 11 +++++-- .../segments_tab/segments_view.tsx | 32 ++++++++----------- 3 files changed, 38 insertions(+), 21 deletions(-) diff --git a/frontend/javascripts/oxalis/model/accessors/flycam_accessor.ts b/frontend/javascripts/oxalis/model/accessors/flycam_accessor.ts index 30fd6d95cbf..3d651be94e6 100644 --- a/frontend/javascripts/oxalis/model/accessors/flycam_accessor.ts +++ b/frontend/javascripts/oxalis/model/accessors/flycam_accessor.ts @@ -38,6 +38,7 @@ import { MAX_ZOOM_STEP_DIFF } from "oxalis/model/bucket_data_handling/loading_st import { getMatrixScale, rotateOnAxis } from "../reducers/flycam_reducer"; import { SmallerOrHigherInfo } from "../helpers/resolution_info"; import { getBaseVoxel } from "oxalis/model/scaleinfo"; +import { AdditionalCoordinate } from "types/api_flow_types"; export const ZOOM_STEP_INTERVAL = 1.1; @@ -274,6 +275,21 @@ function _getPosition(flycam: Flycam): Vector3 { return [matrix[12], matrix[13], matrix[14]]; } +export function getAdditionalCoordinatesAsStringFromFC(flycam: Flycam): string { + return getAdditionalCoordinatesAsString(flycam.additionalCoordinates); +} + +export function getAdditionalCoordinatesAsString( + additionalCoordinates: AdditionalCoordinate[] | null, +): string { + if (additionalCoordinates != null && additionalCoordinates.length > 0) { + return additionalCoordinates + ?.map((coordinate) => `${coordinate.name}=${coordinate.value}`) + .reduce((a: string, b: string) => a.concat(b, ";"), "") as string; + } + return ""; +} + function _getFlooredPosition(flycam: Flycam): Vector3 { return map3((x) => Math.floor(x), _getPosition(flycam)); } diff --git a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts index a8cb92356b9..ec21347e478 100644 --- a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts @@ -196,7 +196,6 @@ function getNeighborPosition(clippedPosition: Vector3, neighborId: number): Vect } function* loadAdHocMeshFromAction(action: LoadAdHocMeshAction): Saga { - console.log("load ad hoc mesh from action", action.seedAdditionalCoordinates) //todo undefined yield* call( loadAdHocMesh, action.seedPosition, @@ -545,7 +544,13 @@ function* refreshMeshes(): Saga { continue; } - yield* call(_refreshMeshWithMap, segmentId, threeDMap, segmentationLayer.name, additionalCoordinates); + yield* call( + _refreshMeshWithMap, + segmentId, + threeDMap, + segmentationLayer.name, + additionalCoordinates, + ); } } @@ -587,7 +592,7 @@ function* _refreshMeshWithMap( segmentId: number, threeDMap: ThreeDMap, layerName: string, - additionalCoordinateString: string, + additionalCoordinateString: string, ): Saga { const meshInfo = yield* select( (state) => state.localSegmentationData[layerName].meshes[additionalCoordinateString][segmentId], diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx index 9ca380005b6..4a96da37f53 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx @@ -223,9 +223,10 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ loadAdHocMesh( segmentId: number, - seedPosition: Vector3 + seedPosition: Vector3, + additionalCoordinates: AdditionalCoordinate[] | undefined, ) { - dispatch(loadAdHocMeshAction(segmentId, seedPosition, Store.getState().flycam.additionalCoordinates||undefined)); + dispatch(loadAdHocMeshAction(segmentId, seedPosition, additionalCoordinates)); }, loadPrecomputedMesh( @@ -703,6 +704,7 @@ class SegmentsView extends React.Component { handleSegmentDropdownMenuVisibility = (isVisible: boolean, segmentId: number | null = null) => { const newActiveSegmentDropdown = isVisible ? segmentId : null; + console.log("on show dropdown", this.props.meshes[1]); this.setState({ activeDropdownSegmentId: newActiveSegmentDropdown }); }; @@ -1210,6 +1212,7 @@ class SegmentsView extends React.Component { areSelectedSegmentsMeshesVisible = () => { const selectedSegments = this.getSelectedSegments(); const meshes = this.props.meshes; + console.log("are selected meshes visible", meshes); const isSomeMeshLoadedAndInvisible = selectedSegments.some((segment) => { const segmentMesh = meshes[segment.id]; return segmentMesh != null && !meshes[segment.id].isVisible; @@ -1268,14 +1271,11 @@ class SegmentsView extends React.Component { } handleRefreshMeshes = (groupId: number | null) => { - const { visibleSegmentationLayer } = this.props; + const { visibleSegmentationLayer, meshes } = this.props; if (visibleSegmentationLayer == null) return; this.handlePerSegment(groupId, (segment) => { - if ( - Store.getState().localSegmentationData[visibleSegmentationLayer.name].meshes[segment.id] != - null - ) { + if (meshes[segment.id] != null) { Store.dispatch(refreshMeshAction(visibleSegmentationLayer.name, segment.id)); } }); @@ -1294,13 +1294,10 @@ class SegmentsView extends React.Component { }; handleRemoveMeshes = (groupId: number | null) => { - const { visibleSegmentationLayer } = this.props; + const { visibleSegmentationLayer, meshes } = this.props; if (visibleSegmentationLayer == null) return; this.handlePerSegment(groupId, (segment) => { - if ( - Store.getState().localSegmentationData[visibleSegmentationLayer.name].meshes[segment.id] != - null - ) { + if (meshes[segment.id] != null) { Store.dispatch(removeMeshAction(visibleSegmentationLayer.name, segment.id)); } }); @@ -1311,10 +1308,10 @@ class SegmentsView extends React.Component { groupId: number | null, isVisible: boolean, ) => { - const state = Store.getState(); - const additionalCoordinates = state.flycam.additionalCoordinates; + const { flycam, meshes } = this.props; + const additionalCoordinates = flycam.additionalCoordinates; this.handlePerSegment(groupId, (segment) => { - if (state.localSegmentationData[layerName].meshes[segment.id] != null) { + if (meshes[segment.id] != null) { Store.dispatch( updateMeshVisibilityAction( layerName, @@ -1330,7 +1327,7 @@ class SegmentsView extends React.Component { handleLoadMeshesAdHoc = (groupId: number | null) => { this.handlePerSegment(groupId, (segment) => { if (segment.somePosition == null) return; - this.props.loadAdHocMesh(segment.id, segment.somePosition); + this.props.loadAdHocMesh(segment.id, segment.somePosition, this.props.flycam.additionalCoordinates || undefined); }); }; @@ -1815,8 +1812,7 @@ class SegmentsView extends React.Component { const relevantSegments = groupId != null ? this.getSegmentsOfGroupRecursively(groupId) : this.getSelectedSegments(); if (relevantSegments == null || relevantSegments.length === 0) return false; - const meshesOfLayer = - Store.getState().localSegmentationData[visibleSegmentationLayer.name].meshes; + const meshesOfLayer = this.props.meshes; return relevantSegments.some((segment) => meshesOfLayer[segment.id] != null); }; From adc3b6ddd27de7d9b3d347688d198c6736dfe685 Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Mon, 13 Nov 2023 18:57:28 +0100 Subject: [PATCH 16/43] fix segment multiselect --- .../oxalis/model/sagas/mesh_saga.ts | 9 +++--- .../segments_tab/segments_view.tsx | 29 ++++++++++++------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts index ec21347e478..60f641f35d4 100644 --- a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts @@ -633,7 +633,7 @@ function* _refreshMeshWithMap( yield* call( loadAdHocMesh, position, - additionalCoordinatesObject, + additionalCoordinatesObject || undefined, segmentId, shouldBeRemoved, layerName, @@ -1161,6 +1161,7 @@ function* handleMeshVisibilityChange(action: UpdateMeshVisibilityAction): Saga { if (action.type === "SET_ADDITIONAL_COORDINATES") { + debugger; const { segmentMeshController } = yield* call(getSceneController); const meshRecords = yield* call({ context: segmentMeshController, @@ -1170,9 +1171,9 @@ function* handleAdditionalCoordinateUpdate(action: FlycamAction): Saga { if (action.values == null || action.values.length === 0) return; const newAdditionalCoordinates = action.values ?.map((coordinate: AdditionalCoordinate) => `${coordinate.name}=${coordinate.value}`) - .reduce((a: string, b: string) => a.concat(b)) as string; + .reduce((a: string, b: string) => a.concat(b, ";")) as string; - let updateVisibilityActions: any = []; + let updateVisibilityActions: UpdateMeshVisibilityAction[] = []; // maybe use for(const i of Object.keys(...)) Object.keys(meshRecords).forEach((additionalCoordinates) => { @@ -1221,7 +1222,7 @@ function* handleBatchSegmentColorChange( // This is why we stick to the manual unpacking for now. const updateSegmentActions = batchAction.payload .filter((action) => action.type === "UPDATE_SEGMENT") - .map((action) => call(handleSegmentColorChange, action)); + .map((action) => call(handleSegmentColorChange, action as UpdateSegmentAction)); yield* all(updateSegmentActions); } diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx index 4a96da37f53..df6562ac455 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx @@ -36,7 +36,7 @@ import { } from "antd"; import features from "features"; import Toast from "libs/toast"; -import _, { isNumber } from "lodash"; +import _, { add, isNumber } from "lodash"; import memoizeOne from "memoize-one"; import type { Vector3 } from "oxalis/constants"; import { MappingStatusEnum } from "oxalis/constants"; @@ -46,7 +46,10 @@ import { getResolutionInfoOfVisibleSegmentationLayer, getVisibleSegmentationLayer, } from "oxalis/model/accessors/dataset_accessor"; -import { getPosition } from "oxalis/model/accessors/flycam_accessor"; +import { + getAdditionalCoordinatesAsString, + getPosition, +} from "oxalis/model/accessors/flycam_accessor"; import { getActiveSegmentationTracing, getVisibleSegments, @@ -257,9 +260,12 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ }, setAdditionalCoordinates(additionalCoordinates: AdditionalCoordinate[] | undefined) { - console.log("setAdditionalCoordinates"); - console.log(additionalCoordinates); - dispatch(setAdditionalCoordinatesAction(additionalCoordinates || null)); + if ( + getAdditionalCoordinatesAsString(Store.getState().flycam.additionalCoordinates) !== + getAdditionalCoordinatesAsString(additionalCoordinates || null) + ) { + dispatch(setAdditionalCoordinatesAction(additionalCoordinates || null)); + } }, updateSegments( @@ -692,8 +698,9 @@ class SegmentsView extends React.Component { return; } this.props.setPosition(segment.somePosition); - if (segment.someAdditionalCoordinates) { - this.props.setAdditionalCoordinates(segment.someAdditionalCoordinates); + const segmentAdditionalCoordinates = segment.someAdditionalCoordinates; + if (segmentAdditionalCoordinates != null) { + this.props.setAdditionalCoordinates(segmentAdditionalCoordinates); } }; @@ -704,7 +711,6 @@ class SegmentsView extends React.Component { handleSegmentDropdownMenuVisibility = (isVisible: boolean, segmentId: number | null = null) => { const newActiveSegmentDropdown = isVisible ? segmentId : null; - console.log("on show dropdown", this.props.meshes[1]); this.setState({ activeDropdownSegmentId: newActiveSegmentDropdown }); }; @@ -1212,7 +1218,6 @@ class SegmentsView extends React.Component { areSelectedSegmentsMeshesVisible = () => { const selectedSegments = this.getSelectedSegments(); const meshes = this.props.meshes; - console.log("are selected meshes visible", meshes); const isSomeMeshLoadedAndInvisible = selectedSegments.some((segment) => { const segmentMesh = meshes[segment.id]; return segmentMesh != null && !meshes[segment.id].isVisible; @@ -1327,7 +1332,11 @@ class SegmentsView extends React.Component { handleLoadMeshesAdHoc = (groupId: number | null) => { this.handlePerSegment(groupId, (segment) => { if (segment.somePosition == null) return; - this.props.loadAdHocMesh(segment.id, segment.somePosition, this.props.flycam.additionalCoordinates || undefined); + this.props.loadAdHocMesh( + segment.id, + segment.somePosition, + this.props.flycam.additionalCoordinates || undefined, + ); }); }; From 38cdce097448cca4c355a6be43267e4eb3bad19a Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Mon, 13 Nov 2023 20:22:56 +0100 Subject: [PATCH 17/43] making sgement mesh controller react class component, probably reverting --- .../controller/segment_mesh_controller.ts | 120 ++++++++++-------- 1 file changed, 66 insertions(+), 54 deletions(-) diff --git a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts index b62870be344..8150d524b49 100644 --- a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts +++ b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts @@ -8,33 +8,47 @@ import type { Vector3 } from "oxalis/constants"; import CustomLOD from "oxalis/controller/custom_lod"; import { getSegmentColorAsHSLA } from "oxalis/model/accessors/volumetracing_accessor"; import { NO_LOD_MESH_INDEX } from "oxalis/model/sagas/mesh_saga"; -import Store from "oxalis/store"; +import Store, { Flycam, OxalisState } from "oxalis/store"; import { AdditionalCoordinate } from "types/api_flow_types"; +import { getAdditionalCoordinatesAsString, getAdditionalCoordinatesAsStringFromFC } from "oxalis/model/accessors/flycam_accessor"; +import { connect } from "react-redux"; +import React from "react"; -export default class SegmentMeshController { +type StateProps = { + flycam: Flycam; +} + +type State = { // meshesLODRootGroup holds lights and one group per segmentation id. // Each group can hold multiple meshes. meshesLODRootGroup: CustomLOD; meshesGroupsPerSegmentationId: Record< string, - Record>> - > = {}; + Record>>>; +} + +class SegmentMeshController extends React.PureComponent{ - constructor() { - this.meshesLODRootGroup = new CustomLOD(); + state: State = { + meshesLODRootGroup: new CustomLOD(), + meshesGroupsPerSegmentationId: {} + }; + + componentDidMount(): void { this.addLights(); } + hasMesh(id: number, layerName: string): boolean { - const additionalCoordinates = this.getCoordinateString(); + const additionalCoordinates = getAdditionalCoordinatesAsStringFromFC(Store.getState().flycam); if ( - this.meshesGroupsPerSegmentationId[additionalCoordinates] == null || - this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName] == null + this.state.meshesGroupsPerSegmentationId[additionalCoordinates] == null || + this.state.meshesGroupsPerSegmentationId[additionalCoordinates][layerName] == null ) return false; - const segments = this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName]; + const segments = this.state.meshesGroupsPerSegmentationId[additionalCoordinates][layerName]; if (!segments) { return false; } @@ -89,18 +103,6 @@ export default class SegmentMeshController { return mesh; } - getCoordinateString(inputAdditionalCoordinates?: AdditionalCoordinate[] | null) { - const additionalCoordinatesObject = - inputAdditionalCoordinates || Store.getState().flycam.additionalCoordinates; - let additionalCoordinates = ""; - if (additionalCoordinatesObject != null && additionalCoordinatesObject.length > 0) { - additionalCoordinates = additionalCoordinatesObject - ?.map((coordinate) => `${coordinate.name}=${coordinate.value}`) - .reduce((a: string, b: string) => a.concat(b), "") as string; - } - return additionalCoordinates; - } - addMeshFromGeometry( geometry: THREE.BufferGeometry, segmentationId: number, @@ -109,30 +111,30 @@ export default class SegmentMeshController { lod: number, layerName: string, ): void { - const additionalCoordinates = this.getCoordinateString(); - if (this.meshesGroupsPerSegmentationId[additionalCoordinates] == null) { - this.meshesGroupsPerSegmentationId[additionalCoordinates] = {}; + const additionalCoordinates = getAdditionalCoordinatesAsStringFromFC(this.props.flycam); + if (this.state.meshesGroupsPerSegmentationId[additionalCoordinates] == null) { + this.state.meshesGroupsPerSegmentationId[additionalCoordinates] = {}; } - if (this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName] == null) { - this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName] = {}; + if (this.state.meshesGroupsPerSegmentationId[additionalCoordinates][layerName] == null) { + this.state.meshesGroupsPerSegmentationId[additionalCoordinates][layerName] = {}; } if ( - this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentationId] == null + this.state.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentationId] == null ) { - this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentationId] = {}; + this.state.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentationId] = {}; } if ( - this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentationId][lod] == + this.state.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentationId][lod] == null ) { const newGroup = new THREE.Group(); - this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentationId][lod] = + this.state.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentationId][lod] = newGroup; if (lod === NO_LOD_MESH_INDEX) { - this.meshesLODRootGroup.addNoLODSupportedMesh(newGroup); + this.state.meshesLODRootGroup.addNoLODSupportedMesh(newGroup); } else { - this.meshesLODRootGroup.addLODMesh(newGroup, lod); + this.state.meshesLODRootGroup.addLODMesh(newGroup, lod); } // @ts-ignore newGroup.cellId = segmentationId; @@ -146,50 +148,50 @@ export default class SegmentMeshController { mesh.translateY(offset[1]); mesh.translateZ(offset[2]); } - this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentationId][lod].add( + this.state.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentationId][lod].add( mesh, ); } removeMeshById(segmentationId: number, layerName: string): void { - const additionalCoordinates = this.getCoordinateString(); + const additionalCoordinates = getAdditionalCoordinatesAsStringFromFC(Store.getState().flycam); // TODO I think it shouldnt be possible to remove meshes that arent visible currently. // but if they are removed they should be removed for all timestamps - if (this.meshesGroupsPerSegmentationId[additionalCoordinates] == null) { + if (this.state.meshesGroupsPerSegmentationId[additionalCoordinates] == null) { return; } - if (this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName] == null) { + if (this.state.meshesGroupsPerSegmentationId[additionalCoordinates][layerName] == null) { return; } if ( - this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentationId] == null + this.state.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentationId] == null ) { return; } _.forEach( - this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentationId], + this.state.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentationId], (meshGroup, lod) => { const lodNumber = parseInt(lod); if (lodNumber !== NO_LOD_MESH_INDEX) { - this.meshesLODRootGroup.removeLODMesh(meshGroup, lodNumber); + this.state.meshesLODRootGroup.removeLODMesh(meshGroup, lodNumber); } else { - this.meshesLODRootGroup.removeNoLODSupportedMesh(meshGroup); + this.state.meshesLODRootGroup.removeNoLODSupportedMesh(meshGroup); } }, ); - delete this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentationId]; + delete this.state.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentationId]; } getMeshGeometryInBestLOD(segmentId: number, layerName: string): THREE.Group { - const additionalCoordinates = this.getCoordinateString(); + const additionalCoordinates = getAdditionalCoordinatesAsStringFromFC(this.props.flycam); const bestLod = Math.min( ...Object.keys( - this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentId], + this.state.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentId], ).map((lodVal) => parseInt(lodVal)), ); - return this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentId][bestLod]; + return this.state.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentId][bestLod]; } setMeshVisibility( @@ -199,14 +201,14 @@ export default class SegmentMeshController { additionalCoordinates?: AdditionalCoordinate[] | null, ): void { console.log("setMeshVis", id, visibility, layerName, additionalCoordinates); - const additionalCoordinatesString = this.getCoordinateString(additionalCoordinates); + const additionalCoordinatesString = getAdditionalCoordinatesAsString(additionalCoordinates||null); - if (this.meshesGroupsPerSegmentationId[additionalCoordinatesString] == null) { + if (this.state.meshesGroupsPerSegmentationId[additionalCoordinatesString] == null) { return; //TODO think about 3D only } _.forEach( - this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName][id], + this.state.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName][id], (meshGroup) => { meshGroup.visible = visibility; }, @@ -214,10 +216,10 @@ export default class SegmentMeshController { } setMeshColor(id: number, layerName: string): void { - const additionalCoordinates = this.getCoordinateString(); + const additionalCoordinates = getAdditionalCoordinatesAsStringFromFC(this.props.flycam); const color = this.getColorObjectForSegment(id); _.forEach( - this.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][id], + this.state.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][id], (meshGroup) => { if (meshGroup) { for (const child of meshGroup.children) { @@ -261,9 +263,19 @@ export default class SegmentMeshController { pointLight.position.y = -25; pointLight.position.z = 10; - this.meshesLODRootGroup.add(ambientLight); - this.meshesLODRootGroup.add(directionalLight); - this.meshesLODRootGroup.add(directionalLight2); - this.meshesLODRootGroup.add(pointLight); + this.state.meshesLODRootGroup.add(ambientLight); + this.state.meshesLODRootGroup.add(directionalLight); + this.state.meshesLODRootGroup.add(directionalLight2); + this.state.meshesLODRootGroup.add(pointLight); } + } + +const mapStateToProps = (state: OxalisState): StateProps=>{ + return { + flycam: state.flycam + } +} + +const connector = connect(mapStateToProps); +export default connector(SegmentMeshController); \ No newline at end of file From a0b736bb67a1aa4a5864c4dba032c8640540f61c Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Mon, 13 Nov 2023 22:24:01 +0100 Subject: [PATCH 18/43] move additional coordinate to string method to flycam accessor --- .../controller/segment_mesh_controller.ts | 179 ++++++++++-------- .../model/reducers/annotation_reducer.ts | 33 +--- .../oxalis/model/reducers/flycam_reducer.ts | 3 +- .../oxalis/model/sagas/mesh_saga.ts | 107 +++++------ .../javascripts/oxalis/view/context_menu.tsx | 1 - .../segments_tab/segments_view.tsx | 8 +- 6 files changed, 159 insertions(+), 172 deletions(-) diff --git a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts index 8150d524b49..017d177c01f 100644 --- a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts +++ b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts @@ -8,54 +8,52 @@ import type { Vector3 } from "oxalis/constants"; import CustomLOD from "oxalis/controller/custom_lod"; import { getSegmentColorAsHSLA } from "oxalis/model/accessors/volumetracing_accessor"; import { NO_LOD_MESH_INDEX } from "oxalis/model/sagas/mesh_saga"; -import Store, { Flycam, OxalisState } from "oxalis/store"; +import Store from "oxalis/store"; import { AdditionalCoordinate } from "types/api_flow_types"; -import { getAdditionalCoordinatesAsString, getAdditionalCoordinatesAsStringFromFC } from "oxalis/model/accessors/flycam_accessor"; -import { connect } from "react-redux"; -import React from "react"; +import { getAdditionalCoordinatesAsString } from "oxalis/model/accessors/flycam_accessor"; -type StateProps = { - flycam: Flycam; -} - -type State = { +export default class SegmentMeshController { // meshesLODRootGroup holds lights and one group per segmentation id. // Each group can hold multiple meshes. meshesLODRootGroup: CustomLOD; meshesGroupsPerSegmentationId: Record< string, - Record>>>; -} - -class SegmentMeshController extends React.PureComponent{ - - state: State = { - meshesLODRootGroup: new CustomLOD(), - meshesGroupsPerSegmentationId: {} - }; + Record>> + > = {}; - componentDidMount(): void { + constructor() { + this.meshesLODRootGroup = new CustomLOD(); this.addLights(); } - - hasMesh(id: number, layerName: string): boolean { - const additionalCoordinates = getAdditionalCoordinatesAsStringFromFC(Store.getState().flycam); + hasMesh( + id: number, + layerName: string, + additionalCoordinates?: AdditionalCoordinate[] | null, + ): boolean { + const additionalCoordinatesString = getAdditionalCoordinatesAsString( + additionalCoordinates || null, + ); if ( - this.state.meshesGroupsPerSegmentationId[additionalCoordinates] == null || - this.state.meshesGroupsPerSegmentationId[additionalCoordinates][layerName] == null + this.meshesGroupsPerSegmentationId[additionalCoordinatesString] == null || + this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName] == null ) return false; - const segments = this.state.meshesGroupsPerSegmentationId[additionalCoordinates][layerName]; + const segments = this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName]; if (!segments) { return false; } return segments[id] != null; } - addMeshFromVertices(vertices: Float32Array, segmentationId: number, layerName: string): void { + addMeshFromVertices( + vertices: Float32Array, + segmentationId: number, + layerName: string, + additionalCoordinates?: AdditionalCoordinate[], + ): void { let bufferGeometry = new THREE.BufferGeometry(); bufferGeometry.setAttribute("position", new THREE.BufferAttribute(vertices, 3)); @@ -69,6 +67,7 @@ class SegmentMeshController extends React.PureComponent{ null, NO_LOD_MESH_INDEX, layerName, + additionalCoordinates || null, ); } @@ -110,31 +109,39 @@ class SegmentMeshController extends React.PureComponent{ scale: Vector3 | null = null, lod: number, layerName: string, + additionalCoordinates: AdditionalCoordinate[] | null, ): void { - const additionalCoordinates = getAdditionalCoordinatesAsStringFromFC(this.props.flycam); - if (this.state.meshesGroupsPerSegmentationId[additionalCoordinates] == null) { - this.state.meshesGroupsPerSegmentationId[additionalCoordinates] = {}; + console.log("addmeshfromg", additionalCoordinates); + const additionalCoordinatesString = getAdditionalCoordinatesAsString( + additionalCoordinates || null, + ); + if (this.meshesGroupsPerSegmentationId[additionalCoordinatesString] == null) { + this.meshesGroupsPerSegmentationId[additionalCoordinatesString] = {}; } - if (this.state.meshesGroupsPerSegmentationId[additionalCoordinates][layerName] == null) { - this.state.meshesGroupsPerSegmentationId[additionalCoordinates][layerName] = {}; + if (this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName] == null) { + this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName] = {}; } if ( - this.state.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentationId] == null + this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName][segmentationId] == + null ) { - this.state.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentationId] = {}; + this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName][segmentationId] = + {}; } if ( - this.state.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentationId][lod] == - null + this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName][segmentationId][ + lod + ] == null ) { const newGroup = new THREE.Group(); - this.state.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentationId][lod] = - newGroup; + this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName][segmentationId][ + lod + ] = newGroup; if (lod === NO_LOD_MESH_INDEX) { - this.state.meshesLODRootGroup.addNoLODSupportedMesh(newGroup); + this.meshesLODRootGroup.addNoLODSupportedMesh(newGroup); } else { - this.state.meshesLODRootGroup.addLODMesh(newGroup, lod); + this.meshesLODRootGroup.addLODMesh(newGroup, lod); } // @ts-ignore newGroup.cellId = segmentationId; @@ -148,50 +155,65 @@ class SegmentMeshController extends React.PureComponent{ mesh.translateY(offset[1]); mesh.translateZ(offset[2]); } - this.state.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentationId][lod].add( - mesh, - ); + this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName][segmentationId][ + lod + ].add(mesh); } - removeMeshById(segmentationId: number, layerName: string): void { - const additionalCoordinates = getAdditionalCoordinatesAsStringFromFC(Store.getState().flycam); + removeMeshById( + segmentationId: number, + layerName: string, + additionalCoordinates: AdditionalCoordinate[] | null, + ): void { + const additionalCoordinatesString = getAdditionalCoordinatesAsString(additionalCoordinates); // TODO I think it shouldnt be possible to remove meshes that arent visible currently. // but if they are removed they should be removed for all timestamps - if (this.state.meshesGroupsPerSegmentationId[additionalCoordinates] == null) { + if (this.meshesGroupsPerSegmentationId[additionalCoordinatesString] == null) { return; } - if (this.state.meshesGroupsPerSegmentationId[additionalCoordinates][layerName] == null) { + if (this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName] == null) { return; } if ( - this.state.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentationId] == null + this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName][segmentationId] == + null ) { return; } _.forEach( - this.state.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentationId], + this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName][segmentationId], (meshGroup, lod) => { const lodNumber = parseInt(lod); if (lodNumber !== NO_LOD_MESH_INDEX) { - this.state.meshesLODRootGroup.removeLODMesh(meshGroup, lodNumber); + this.meshesLODRootGroup.removeLODMesh(meshGroup, lodNumber); } else { - this.state.meshesLODRootGroup.removeNoLODSupportedMesh(meshGroup); + this.meshesLODRootGroup.removeNoLODSupportedMesh(meshGroup); } }, ); - delete this.state.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentationId]; + delete this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName][ + segmentationId + ]; } - getMeshGeometryInBestLOD(segmentId: number, layerName: string): THREE.Group { - const additionalCoordinates = getAdditionalCoordinatesAsStringFromFC(this.props.flycam); + getMeshGeometryInBestLOD( + segmentId: number, + layerName: string, + additionalCoordinates?: AdditionalCoordinate[] | null, + ): THREE.Group { + const additionalCoordinatesString = getAdditionalCoordinatesAsString( + additionalCoordinates || null, + ); const bestLod = Math.min( ...Object.keys( - this.state.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentId], + this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName][segmentId], ).map((lodVal) => parseInt(lodVal)), ); - return this.state.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][segmentId][bestLod]; + return this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName][segmentId][ + bestLod + ]; } setMeshVisibility( @@ -200,26 +222,41 @@ class SegmentMeshController extends React.PureComponent{ layerName: string, additionalCoordinates?: AdditionalCoordinate[] | null, ): void { - console.log("setMeshVis", id, visibility, layerName, additionalCoordinates); - const additionalCoordinatesString = getAdditionalCoordinatesAsString(additionalCoordinates||null); + console.log( + "setMeshVis", + id, + visibility, + layerName, + additionalCoordinates, + this.meshesGroupsPerSegmentationId, + ); + const additionalCoordinatesString = getAdditionalCoordinatesAsString( + additionalCoordinates || null, + ); - if (this.state.meshesGroupsPerSegmentationId[additionalCoordinatesString] == null) { + if (this.meshesGroupsPerSegmentationId[additionalCoordinatesString] == null) { return; //TODO think about 3D only } _.forEach( - this.state.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName][id], + this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName][id], (meshGroup) => { meshGroup.visible = visibility; }, ); } - setMeshColor(id: number, layerName: string): void { - const additionalCoordinates = getAdditionalCoordinatesAsStringFromFC(this.props.flycam); + setMeshColor( + id: number, + layerName: string, + additionalCoordinates?: AdditionalCoordinate[] | null, + ): void { + const additionalCoordinatesString = getAdditionalCoordinatesAsString( + additionalCoordinates || null, + ); const color = this.getColorObjectForSegment(id); _.forEach( - this.state.meshesGroupsPerSegmentationId[additionalCoordinates][layerName][id], + this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName][id], (meshGroup) => { if (meshGroup) { for (const child of meshGroup.children) { @@ -263,19 +300,9 @@ class SegmentMeshController extends React.PureComponent{ pointLight.position.y = -25; pointLight.position.z = 10; - this.state.meshesLODRootGroup.add(ambientLight); - this.state.meshesLODRootGroup.add(directionalLight); - this.state.meshesLODRootGroup.add(directionalLight2); - this.state.meshesLODRootGroup.add(pointLight); + this.meshesLODRootGroup.add(ambientLight); + this.meshesLODRootGroup.add(directionalLight); + this.meshesLODRootGroup.add(directionalLight2); + this.meshesLODRootGroup.add(pointLight); } - } - -const mapStateToProps = (state: OxalisState): StateProps=>{ - return { - flycam: state.flycam - } -} - -const connector = connect(mapStateToProps); -export default connector(SegmentMeshController); \ No newline at end of file diff --git a/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts b/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts index 7446515e3e4..f63b610f874 100644 --- a/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts +++ b/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts @@ -2,12 +2,13 @@ import update from "immutability-helper"; import type { Action } from "oxalis/model/actions/actions"; import type { OxalisState, UserBoundingBox, MeshInformation } from "oxalis/store"; import { V3 } from "libs/mjs"; -import { updateKey, updateKey2, updateKey3, updateKey4 } from "oxalis/model/helpers/deep_update"; +import { updateKey, updateKey2 } from "oxalis/model/helpers/deep_update"; import { maybeGetSomeTracing } from "oxalis/model/accessors/tracing_accessor"; import * as Utils from "libs/utils"; import { getDisplayedDataExtentInPlaneMode } from "oxalis/model/accessors/view_mode_accessor"; import { convertServerAnnotationToFrontendAnnotation } from "oxalis/model/reducers/reducer_helpers"; import _ from "lodash"; +import { getAdditionalCoordinatesAsString } from "../accessors/flycam_accessor"; const updateTracing = (state: OxalisState, shape: Partial): OxalisState => updateKey(state, "tracing", shape); @@ -207,10 +208,7 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { case "UPDATE_ISOSURFACE_VISIBILITY": { const { layerName, id, visibility, additionalCoordinates } = action; - const addCoordString = - additionalCoordinates != null && additionalCoordinates?.length > 0 - ? `${additionalCoordinates[0].name}=${additionalCoordinates[0].value}` - : ""; + const addCoordString = getAdditionalCoordinatesAsString(additionalCoordinates || null); // assumption: set_additional_coordinates action is handled before return update(state, { localSegmentationData: { @@ -233,10 +231,7 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { //TODO fix me const { layerName, segmentId } = action; const additionalCoordinates = state.flycam.additionalCoordinates; - const addCoordString = - additionalCoordinates != null && additionalCoordinates?.length > 0 - ? `${additionalCoordinates[0].name}=${additionalCoordinates[0].value}` - : ""; + const addCoordString = getAdditionalCoordinatesAsString(additionalCoordinates); const { [segmentId]: _, ...remainingMeshes } = state.localSegmentationData[layerName].meshes[addCoordString]; return update(state, { @@ -272,10 +267,7 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { mappingType, }; const additionalCoordinates = state.flycam.additionalCoordinates; - const addCoordString = - additionalCoordinates != null && additionalCoordinates?.length > 0 - ? `${additionalCoordinates[0].name}=${additionalCoordinates[0].value}` - : ""; + const addCoordString = getAdditionalCoordinatesAsString(additionalCoordinates); const updatedKey = update(state, { localSegmentationData: { [layerName]: { @@ -303,10 +295,7 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { meshFileName, }; const additionalCoordinates = state.flycam.additionalCoordinates; - const addCoordString = - additionalCoordinates != null && additionalCoordinates?.length > 0 - ? `${additionalCoordinates[0].name}=${additionalCoordinates[0].value}` - : ""; + const addCoordString = getAdditionalCoordinatesAsString(additionalCoordinates); const updatedKey = update(state, { localSegmentationData: { [layerName]: { @@ -326,10 +315,7 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { case "STARTED_LOADING_ISOSURFACE": { const { layerName, segmentId } = action; const additionalCoordinates = state.flycam.additionalCoordinates; - const addCoordString = - additionalCoordinates != null && additionalCoordinates?.length > 0 - ? `${additionalCoordinates[0].name}=${additionalCoordinates[0].value}` - : ""; + const addCoordString = getAdditionalCoordinatesAsString(additionalCoordinates); const updatedKey = update(state, { localSegmentationData: { [layerName]: { @@ -351,10 +337,7 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { case "FINISHED_LOADING_ISOSURFACE": { const { layerName, segmentId } = action; const additionalCoordinates = state.flycam.additionalCoordinates; - const addCoordString = - additionalCoordinates != null && additionalCoordinates?.length > 0 - ? `${additionalCoordinates[0].name}=${additionalCoordinates[0].value}` - : ""; + const addCoordString = getAdditionalCoordinatesAsString(additionalCoordinates); const updatedKey = update(state, { localSegmentationData: { [layerName]: { diff --git a/frontend/javascripts/oxalis/model/reducers/flycam_reducer.ts b/frontend/javascripts/oxalis/model/reducers/flycam_reducer.ts index ad24e896b7e..0ce073ae0cf 100644 --- a/frontend/javascripts/oxalis/model/reducers/flycam_reducer.ts +++ b/frontend/javascripts/oxalis/model/reducers/flycam_reducer.ts @@ -7,6 +7,7 @@ import type { OxalisState } from "oxalis/store"; import type { Vector3 } from "oxalis/constants"; import { getBaseVoxelFactors } from "oxalis/model/scaleinfo"; import { + getAdditionalCoordinatesAsString, getValidZoomRangeForUser, ZOOM_STEP_INTERVAL, } from "oxalis/model/accessors/flycam_accessor"; @@ -277,7 +278,7 @@ function FlycamReducer(state: OxalisState, action: Action): OxalisState { }); const visibleSegmentationLayer = getVisibleSegmentationLayer(state); - const additionalCoordinateString = `${values[0].name}=${values[0].value}`; + const additionalCoordinateString = getAdditionalCoordinatesAsString(values); if ( visibleSegmentationLayer == null || state.localSegmentationData[visibleSegmentationLayer.name].meshes[ diff --git a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts index 60f641f35d4..f7297cba315 100644 --- a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts @@ -1,5 +1,5 @@ import { saveAs } from "file-saver"; -import _, { add } from "lodash"; +import _ from "lodash"; import { V3 } from "libs/mjs"; import { chunkDynamically, sleep } from "libs/utils"; import ErrorHandling from "libs/error_handling"; @@ -80,6 +80,7 @@ import { ResolutionInfo } from "../helpers/resolution_info"; import { type AdditionalCoordinate } from "types/api_flow_types"; import Zip from "libs/zipjs_wrapper"; import { FlycamAction } from "../actions/flycam_actions"; +import { getAdditionalCoordinatesAsString } from "../accessors/flycam_accessor"; export const NO_LOD_MESH_INDEX = -1; const MAX_RETRY_COUNT = 5; @@ -119,12 +120,7 @@ export function isMeshSTL(buffer: ArrayBuffer): boolean { function getOrAddMapForSegment(layerName: string, segmentId: number): ThreeDMap { const additionalCoordinatesObject = Store.getState().flycam.additionalCoordinates; - let additionalCoordinates = ""; - if (additionalCoordinatesObject != null && additionalCoordinatesObject?.length > 0) { - additionalCoordinates = additionalCoordinatesObject - ?.map((coordinate) => `${coordinate.name}=${coordinate.value}`) - .reduce((a: string, b: string) => a.concat(b)) as string; - } + let additionalCoordinates = getAdditionalCoordinatesAsString(additionalCoordinatesObject); adhocMeshesMapByLayer[additionalCoordinates] = adhocMeshesMapByLayer[additionalCoordinates] || new Map(); @@ -143,13 +139,9 @@ function getOrAddMapForSegment(layerName: string, segmentId: number): ThreeDMap< } function removeMapForSegment(layerName: string, segmentId: number): void { - const additionalCoordinatesObject = Store.getState().flycam.additionalCoordinates; - let additionalCoordinates = ""; - if (additionalCoordinatesObject != null) { - additionalCoordinates = additionalCoordinatesObject - ?.map((coordinate) => `${coordinate.name}=${coordinate.value}`) - .reduce((a: string, b: string) => a.concat(b)) as string; - } + let additionalCoordinates = getAdditionalCoordinatesAsString( + Store.getState().flycam.additionalCoordinates, + ); if (adhocMeshesMapByLayer[additionalCoordinates][layerName] == null) { return; @@ -424,7 +416,7 @@ function* maybeLoadMeshChunk( useDataStore: boolean, findNeighbors: boolean, ): Saga { - const additionalCoordinates = Store.getState().flycam.additionalCoordinates || undefined; + const additionalCoordinates = yield* select((state) => state.flycam.additionalCoordinates); const threeDMap = getOrAddMapForSegment(layer.name, segmentId); if (threeDMap.get(clippedPosition)) { @@ -476,7 +468,7 @@ function* maybeLoadMeshChunk( useDataStore ? dataStoreUrl : tracingStoreUrl, { position: clippedPosition, - additionalCoordinates, + additionalCoordinates: additionalCoordinates || undefined, mag, segmentId, subsamplingStrides, @@ -489,10 +481,15 @@ function* maybeLoadMeshChunk( const vertices = new Float32Array(responseBuffer); if (removeExistingMesh) { - segmentMeshController.removeMeshById(segmentId, layer.name); + segmentMeshController.removeMeshById(segmentId, layer.name, additionalCoordinates); } - segmentMeshController.addMeshFromVertices(vertices, segmentId, layer.name); + segmentMeshController.addMeshFromVertices( + vertices, + segmentId, + layer.name, + additionalCoordinates || undefined, + ); return neighbors.map((neighbor) => getNeighborPosition(clippedPosition, neighbor)); } catch (exception) { retryCount++; @@ -521,14 +518,8 @@ function* refreshMeshes(): Saga { const currentlyModifiedCells = new Set(modifiedCells); modifiedCells.clear(); - const additionalCoordinatesObject = Store.getState().flycam.additionalCoordinates; - let additionalCoordinates = ""; - if (additionalCoordinatesObject != null) { - additionalCoordinates = additionalCoordinatesObject - ?.map((coordinate) => `${coordinate.name}=${coordinate.value}`) - .reduce((a: string, b: string) => a.concat(b)) as string; - } - + const additionalCoordinatesObject = yield* select((state) => state.flycam.additionalCoordinates); + const additionalCoordinates = getAdditionalCoordinatesAsString(additionalCoordinatesObject); const segmentationLayer = Model.getVisibleSegmentationLayer(); if (!segmentationLayer) { @@ -549,19 +540,14 @@ function* refreshMeshes(): Saga { segmentId, threeDMap, segmentationLayer.name, - additionalCoordinates, + additionalCoordinatesObject, ); } } function* refreshMesh(action: RefreshMeshAction): Saga { - const additionalCoordinatesObject = Store.getState().flycam.additionalCoordinates; - let additionalCoordinates = ""; - if (additionalCoordinatesObject != null) { - additionalCoordinates = additionalCoordinatesObject - ?.map((coordinate) => `${coordinate.name}=${coordinate.value}`) - .reduce((a: string, b: string) => a.concat(b)) as string; - } + const additionalCoordinatesObject = yield* select((state) => state.flycam.additionalCoordinates); + let additionalCoordinates = getAdditionalCoordinatesAsString(additionalCoordinatesObject); const { segmentId, layerName } = action; @@ -584,7 +570,7 @@ function* refreshMesh(action: RefreshMeshAction): Saga { if (adhocMeshesMapByLayer[additionalCoordinates] == null) return; const threeDMap = adhocMeshesMapByLayer[additionalCoordinates][action.layerName].get(segmentId); if (threeDMap == null) return; - yield* call(_refreshMeshWithMap, segmentId, threeDMap, layerName, additionalCoordinates); + yield* call(_refreshMeshWithMap, segmentId, threeDMap, layerName, additionalCoordinatesObject); } } @@ -592,8 +578,9 @@ function* _refreshMeshWithMap( segmentId: number, threeDMap: ThreeDMap, layerName: string, - additionalCoordinateString: string, + additionalCoordinates: AdditionalCoordinate[] | null, ): Saga { + const additionalCoordinateString = getAdditionalCoordinatesAsString(additionalCoordinates); const meshInfo = yield* select( (state) => state.localSegmentationData[layerName].meshes[additionalCoordinateString][segmentId], ); @@ -603,7 +590,7 @@ function* _refreshMeshWithMap( "_refreshMeshWithMap was called for a precomputed mesh.", ); if (meshInfo.isPrecomputed) return; - const { mappingName, mappingType, seedAdditionalCoordinates } = meshInfo; + const { mappingName, mappingType } = meshInfo; const meshPositions = threeDMap.entries().filter(([value, _position]) => value); if (meshPositions.length === 0) { @@ -619,21 +606,13 @@ function* _refreshMeshWithMap( // Meshing for N-D segmentations is not yet supported. // See https://github.com/scalableminds/webknossos/issues/7229 //TODO - const additionalCoordinatesObject = Store.getState().flycam.additionalCoordinates; - let additionalCoordinates = ""; - if (additionalCoordinatesObject != null) { - additionalCoordinates = additionalCoordinatesObject - ?.map((coordinate) => `${coordinate.name}=${coordinate.value}`) - .reduce((a: string, b: string) => a.concat(b)) as string; - } - for (const [, position] of meshPositions) { // Reload the mesh at the given position if it isn't already loaded there. // This is done to ensure that every voxel of the mesh is reloaded. yield* call( loadAdHocMesh, position, - additionalCoordinatesObject || undefined, + additionalCoordinates || undefined, segmentId, shouldBeRemoved, layerName, @@ -752,6 +731,7 @@ function* loadPrecomputedMeshForSegmentId( ); yield* put(startedLoadingMeshAction(layerName, id)); const dataset = yield* select((state) => state.dataset); + const additionalCoordinates = yield* select((state) => state.flycam.additionalCoordinates); const availableMeshFiles = yield* call( dispatchMaybeFetchMeshFilesAsync, @@ -800,6 +780,7 @@ function* loadPrecomputedMeshForSegmentId( availableChunksMap, loadingOrder, scale, + additionalCoordinates, ); try { @@ -907,6 +888,7 @@ function _getLoadChunksTasks( availableChunksMap: ChunksMap, loadingOrder: number[], scale: Vector3 | null, + additionalCoordinates: AdditionalCoordinate[] | null, ) { const { segmentMeshController } = getSceneController(); const { meshFileName } = meshFile; @@ -991,6 +973,7 @@ function _getLoadChunksTasks( scale, lod, layerName, + additionalCoordinates, ); } @@ -1036,6 +1019,7 @@ function _getLoadChunksTasks( null, lod, layerName, + additionalCoordinates, ); }, ); @@ -1146,7 +1130,11 @@ function removeMesh(action: RemoveMeshAction, removeFromScene: boolean = true): const segmentId = action.segmentId; if (removeFromScene) { - getSceneController().segmentMeshController.removeMeshById(segmentId, layerName); + getSceneController().segmentMeshController.removeMeshById( + segmentId, + layerName, + Store.getState().flycam.additionalCoordinates, + ); } removeMapForSegment(layerName, segmentId); @@ -1156,7 +1144,6 @@ function* handleMeshVisibilityChange(action: UpdateMeshVisibilityAction): Saga { @@ -1169,9 +1156,7 @@ function* handleAdditionalCoordinateUpdate(action: FlycamAction): Saga { }); if (action.values == null || action.values.length === 0) return; - const newAdditionalCoordinates = action.values - ?.map((coordinate: AdditionalCoordinate) => `${coordinate.name}=${coordinate.value}`) - .reduce((a: string, b: string) => a.concat(b, ";")) as string; + const newAdditionalCoordinates = getAdditionalCoordinatesAsString(action.values); let updateVisibilityActions: UpdateMeshVisibilityAction[] = []; @@ -1182,19 +1167,15 @@ function* handleAdditionalCoordinateUpdate(action: FlycamAction): Saga { Object.keys(meshRecords[additionalCoordinates]).forEach((layerName) => { Object.keys(meshRecords[additionalCoordinates][layerName]).forEach((meshGroup) => { const meshId = parseInt(meshGroup); - Object.entries(meshRecords[additionalCoordinates][layerName][meshId]).forEach( - ([key, value]) => { - //TODO for multiple dimensions_.forEach( - const splitAddCoord = additionalCoordinates.split("="); - const addCoordObject = { name: splitAddCoord[0], value: parseInt(splitAddCoord[1]) }; - updateVisibilityActions.push( - updateMeshVisibilityAction(layerName, meshId, shouldBeVisible, [addCoordObject]), - ); - segmentMeshController.setMeshVisibility(meshId, shouldBeVisible, layerName, [ - addCoordObject, - ]); - }, + //TODO for multiple dimensions_.forEach( + const splitAddCoord = additionalCoordinates.split("="); + const addCoordObject = { name: splitAddCoord[0], value: parseInt(splitAddCoord[1]) }; + updateVisibilityActions.push( + updateMeshVisibilityAction(layerName, meshId, shouldBeVisible, [addCoordObject]), ); + segmentMeshController.setMeshVisibility(meshId, shouldBeVisible, layerName, [ + addCoordObject, + ]); }); }); }); diff --git a/frontend/javascripts/oxalis/view/context_menu.tsx b/frontend/javascripts/oxalis/view/context_menu.tsx index 6771025fe7f..28496d1dc52 100644 --- a/frontend/javascripts/oxalis/view/context_menu.tsx +++ b/frontend/javascripts/oxalis/view/context_menu.tsx @@ -419,7 +419,6 @@ function getNodeContextMenuOptions({ volumeTracing, infoRows, allowUpdate, - additionalCoordinates, }: NodeContextMenuOptionsProps): ItemType[] { const state = Store.getState(); const isProofreadingActive = state.uiInformation.activeTool === AnnotationToolEnum.PROOFREAD; diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx index df6562ac455..4649f07244b 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx @@ -36,7 +36,7 @@ import { } from "antd"; import features from "features"; import Toast from "libs/toast"; -import _, { add, isNumber } from "lodash"; +import _, { isNumber } from "lodash"; import memoizeOne from "memoize-one"; import type { Vector3 } from "oxalis/constants"; import { MappingStatusEnum } from "oxalis/constants"; @@ -167,11 +167,7 @@ const mapStateToProps = (state: OxalisState): StateProps => { const isVisibleButUneditableSegmentationLayerActive = visibleSegmentationLayer != null && visibleSegmentationLayer.tracingId == null; - const additionalCoordinates = state.flycam.additionalCoordinates; - const addCoordString = - additionalCoordinates != null - ? `${additionalCoordinates[0].name}=${additionalCoordinates[0].value}` - : ""; + const addCoordString = getAdditionalCoordinatesAsString(state.flycam.additionalCoordinates); // TODO add coord are undefined let meshesForCurrentAdditionalCoordinates = EMPTY_OBJECT; From ffba849154fa6684911789aa0e2c0cdf98fec75c Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Tue, 14 Nov 2023 21:02:06 +0100 Subject: [PATCH 19/43] fix context menu and refactor mesh visibility change in mesh saga --- .../oxalis/model/sagas/mesh_saga.ts | 127 +++++++++++------- .../oxalis/model/sagas/root_saga.ts | 3 +- .../javascripts/oxalis/view/context_menu.tsx | 2 +- 3 files changed, 78 insertions(+), 54 deletions(-) diff --git a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts index f7297cba315..bf2d5f45f04 100644 --- a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts @@ -118,15 +118,18 @@ export function isMeshSTL(buffer: ArrayBuffer): boolean { return isMesh; } -function getOrAddMapForSegment(layerName: string, segmentId: number): ThreeDMap { - const additionalCoordinatesObject = Store.getState().flycam.additionalCoordinates; - let additionalCoordinates = getAdditionalCoordinatesAsString(additionalCoordinatesObject); - - adhocMeshesMapByLayer[additionalCoordinates] = - adhocMeshesMapByLayer[additionalCoordinates] || new Map(); - adhocMeshesMapByLayer[additionalCoordinates][layerName] = - adhocMeshesMapByLayer[additionalCoordinates][layerName] || new Map(); - const meshesMap = adhocMeshesMapByLayer[additionalCoordinates][layerName]; +function getOrAddMapForSegment( + layerName: string, + segmentId: number, + additionalCoordinates?: AdditionalCoordinate[] | null, +): ThreeDMap { + let additionalCoordinatesString = getAdditionalCoordinatesAsString(additionalCoordinates || null); + + adhocMeshesMapByLayer[additionalCoordinatesString] = + adhocMeshesMapByLayer[additionalCoordinatesString] || new Map(); + adhocMeshesMapByLayer[additionalCoordinatesString][layerName] = + adhocMeshesMapByLayer[additionalCoordinatesString][layerName] || new Map(); + const meshesMap = adhocMeshesMapByLayer[additionalCoordinatesString][layerName]; const maybeMap = meshesMap.get(segmentId); if (maybeMap == null) { @@ -138,16 +141,18 @@ function getOrAddMapForSegment(layerName: string, segmentId: number): ThreeDMap< return maybeMap; } -function removeMapForSegment(layerName: string, segmentId: number): void { - let additionalCoordinates = getAdditionalCoordinatesAsString( - Store.getState().flycam.additionalCoordinates, - ); +function removeMapForSegment( + layerName: string, + segmentId: number, + additionalCoordinates?: AdditionalCoordinate[] | null, +): void { + let additionalCoordinatesString = getAdditionalCoordinatesAsString(additionalCoordinates || null); - if (adhocMeshesMapByLayer[additionalCoordinates][layerName] == null) { + if (adhocMeshesMapByLayer[additionalCoordinatesString][layerName] == null) { return; } - adhocMeshesMapByLayer[additionalCoordinates][layerName].delete(segmentId); + adhocMeshesMapByLayer[additionalCoordinatesString][layerName].delete(segmentId); } function getZoomedCubeSize(zoomStep: number, resolutionInfo: ResolutionInfo): Vector3 { @@ -417,7 +422,7 @@ function* maybeLoadMeshChunk( findNeighbors: boolean, ): Saga { const additionalCoordinates = yield* select((state) => state.flycam.additionalCoordinates); - const threeDMap = getOrAddMapForSegment(layer.name, segmentId); + const threeDMap = getOrAddMapForSegment(layer.name, segmentId, additionalCoordinates); if (threeDMap.get(clippedPosition)) { return []; @@ -1125,19 +1130,20 @@ function* handleRemoveSegment(action: RemoveSegmentAction) { yield* put(removeMeshAction(action.layerName, action.segmentId)); } -function removeMesh(action: RemoveMeshAction, removeFromScene: boolean = true): void { +function* removeMesh(action: RemoveMeshAction, removeFromScene: boolean = true): Saga { const { layerName } = action; const segmentId = action.segmentId; + const additionalCoordinates = yield* select((state) => state.flycam.additionalCoordinates); if (removeFromScene) { getSceneController().segmentMeshController.removeMeshById( segmentId, layerName, - Store.getState().flycam.additionalCoordinates, + additionalCoordinates, ); } - removeMapForSegment(layerName, segmentId); + removeMapForSegment(layerName, segmentId, additionalCoordinates); } function* handleMeshVisibilityChange(action: UpdateMeshVisibilityAction): Saga { @@ -1146,40 +1152,58 @@ function* handleMeshVisibilityChange(action: UpdateMeshVisibilityAction): Saga { - if (action.type === "SET_ADDITIONAL_COORDINATES") { - debugger; - const { segmentMeshController } = yield* call(getSceneController); - const meshRecords = yield* call({ - context: segmentMeshController, - fn: () => segmentMeshController.meshesGroupsPerSegmentationId, - }); +export function* handleAdditionalCoordinateUpdate(): Saga { + yield* take("WK_READY"); - if (action.values == null || action.values.length === 0) return; - const newAdditionalCoordinates = getAdditionalCoordinatesAsString(action.values); - - let updateVisibilityActions: UpdateMeshVisibilityAction[] = []; - - // maybe use for(const i of Object.keys(...)) - Object.keys(meshRecords).forEach((additionalCoordinates) => { - const shouldBeVisible = additionalCoordinates === newAdditionalCoordinates; - //TODO put unpacked nested vars into vars and use Object.entries - Object.keys(meshRecords[additionalCoordinates]).forEach((layerName) => { - Object.keys(meshRecords[additionalCoordinates][layerName]).forEach((meshGroup) => { - const meshId = parseInt(meshGroup); - //TODO for multiple dimensions_.forEach( - const splitAddCoord = additionalCoordinates.split("="); - const addCoordObject = { name: splitAddCoord[0], value: parseInt(splitAddCoord[1]) }; - updateVisibilityActions.push( - updateMeshVisibilityAction(layerName, meshId, shouldBeVisible, [addCoordObject]), - ); - segmentMeshController.setMeshVisibility(meshId, shouldBeVisible, layerName, [ - addCoordObject, - ]); - }); + let previousAdditionalCoordinates = yield* select((state) => state.flycam.additionalCoordinates); + + while (true) { + const action = (yield* take(["SET_ADDITIONAL_COORDINATES"]) as any) as FlycamAction; + //satisfy TS + if (action.type === "SET_ADDITIONAL_COORDINATES") { + const { segmentMeshController } = yield* call(getSceneController); + const meshRecords = yield* call({ + context: segmentMeshController, + fn: () => segmentMeshController.meshesGroupsPerSegmentationId, }); - }); - yield* all(updateVisibilityActions.map((e) => put(e))); + + if (action.values == null || action.values.length === 0) break; + const newAdditionalCoordinates = getAdditionalCoordinatesAsString(action.values); + + for (const additionalCoordinates of [action.values, previousAdditionalCoordinates]) { + const currentAdditionalCoordinatesAsString = + getAdditionalCoordinatesAsString(additionalCoordinates); + const shouldBeVisible = currentAdditionalCoordinatesAsString === newAdditionalCoordinates; + const recordsOfLayers = meshRecords[currentAdditionalCoordinatesAsString]; + if (recordsOfLayers != null) { + for (const [layerName, recordsForOneLayer] of Object.entries(recordsOfLayers)) { + const segmentIds = Object.keys(recordsForOneLayer); + for (const segmentIdAsString of segmentIds) { + const segmentId = parseInt(segmentIdAsString); + yield* put( + updateMeshVisibilityAction( + layerName, + segmentId, + shouldBeVisible, + additionalCoordinates || undefined, + ), + ); + yield* call( + { + context: segmentMeshController, + fn: segmentMeshController.setMeshVisibility, + }, + segmentId, + shouldBeVisible, + layerName, + additionalCoordinates, + ); + } + } + } + } + previousAdditionalCoordinates = yield* select((state) => state.flycam.additionalCoordinates); + } } } @@ -1228,5 +1252,4 @@ export default function* meshSaga(): Saga { yield* takeEvery(["START_EDITING", "COPY_SEGMENTATION_LAYER"], markEditedCellAsDirty); yield* takeEvery("UPDATE_SEGMENT", handleSegmentColorChange); yield* takeEvery("BATCH_UPDATE_GROUPS_AND_SEGMENTS", handleBatchSegmentColorChange); - yield* takeEvery("SET_ADDITIONAL_COORDINATES", handleAdditionalCoordinateUpdate); //TODO remove and while true } diff --git a/frontend/javascripts/oxalis/model/sagas/root_saga.ts b/frontend/javascripts/oxalis/model/sagas/root_saga.ts index 849fcbf6fdd..200a885957a 100644 --- a/frontend/javascripts/oxalis/model/sagas/root_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/root_saga.ts @@ -8,7 +8,7 @@ import AnnotationSagas from "oxalis/model/sagas/annotation_saga"; import { watchDataRelevantChanges } from "oxalis/model/sagas/prefetch_saga"; import SkeletontracingSagas from "oxalis/model/sagas/skeletontracing_saga"; import ErrorHandling from "libs/error_handling"; -import meshSaga from "oxalis/model/sagas/mesh_saga"; +import meshSaga, { handleAdditionalCoordinateUpdate } from "oxalis/model/sagas/mesh_saga"; import { watchMaximumRenderableLayers, watchZ1Downsampling } from "oxalis/model/sagas/dataset_saga"; import { watchToolDeselection, watchToolReset } from "oxalis/model/sagas/annotation_tool_saga"; import SettingsSaga from "oxalis/model/sagas/settings_saga"; @@ -67,6 +67,7 @@ function* restartableSaga(): Saga { call(watchZ1Downsampling), call(warnIfEmailIsUnverified), call(listenToErrorEscalation), + call(handleAdditionalCoordinateUpdate), ]); } catch (err) { rootSagaCrashed = true; diff --git a/frontend/javascripts/oxalis/view/context_menu.tsx b/frontend/javascripts/oxalis/view/context_menu.tsx index 28496d1dc52..38f731042b8 100644 --- a/frontend/javascripts/oxalis/view/context_menu.tsx +++ b/frontend/javascripts/oxalis/view/context_menu.tsx @@ -1476,7 +1476,7 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ dispatch(removeMeshAction(layerName, meshId)); }, hideMesh(layerName: string, meshId: number) { - dispatch(updateMeshVisibilityAction(layerName, meshId, false)); + dispatch(updateMeshVisibilityAction(layerName, meshId, false, Store.getState().flycam.additionalCoordinates||undefined)); }, setPosition(position: Vector3) { dispatch(setPositionAction(position)); From e5a1e9dfc0b17aeeed47112a139c25054bf6f1bf Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Wed, 15 Nov 2023 11:20:56 +0100 Subject: [PATCH 20/43] fix yarn.lock --- yarn.lock | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/yarn.lock b/yarn.lock index e8aed34c58c..4171017d98b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3388,10 +3388,20 @@ camelcase@^5.3.1: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -caniuse-lite@^1.0.30001317, caniuse-lite@^1.0.30001400, caniuse-lite@^1.0.30001517: - version "1.0.30001561" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001561.tgz" - integrity sha512-NTt0DNoKe958Q0BE0j0c1V9jbUzhBxHIEJy7asmGrpE0yG63KTV7PLHPnK2E1O9RsQrQ081I3NLuXGS6zht3cw== +caniuse-lite@^1.0.30001317: + version "1.0.30001412" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001412.tgz" + integrity sha512-+TeEIee1gS5bYOiuf+PS/kp2mrXic37Hl66VY6EAfxasIk5fELTktK2oOezYed12H8w7jt3s512PpulQidPjwA== + +caniuse-lite@^1.0.30001400: + version "1.0.30001441" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001441.tgz#987437b266260b640a23cd18fbddb509d7f69f3e" + integrity sha512-OyxRR4Vof59I3yGWXws6i908EtGbMzVUi3ganaZQHmydk1iwDhRnvaPG2WaR0KcqrDFKrxVZHULT396LEPhXfg== + +caniuse-lite@^1.0.30001517: + version "1.0.30001518" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001518.tgz#b3ca93904cb4699c01218246c4d77a71dbe97150" + integrity sha512-rup09/e3I0BKjncL+FesTayKtPrdwKhUufQFd3riFw1hHg8JmIFoInYfB102cFcY/pPgGmdyl/iy+jgiDi2vdA== caseless@~0.12.0: version "0.12.0" @@ -14213,4 +14223,4 @@ zustand@^3.7.2: zwitch@^2.0.0, zwitch@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7" - integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A== + integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A== \ No newline at end of file From 4a61f357ac7b2b52817c555e7789dd032e82545e Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Wed, 15 Nov 2023 13:04:19 +0100 Subject: [PATCH 21/43] disable segment stats in nd datasets for now --- frontend/javascripts/oxalis/view/context_menu.tsx | 9 ++++++--- .../right-border-tabs/segments_tab/segments_view.tsx | 3 ++- yarn.lock | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/frontend/javascripts/oxalis/view/context_menu.tsx b/frontend/javascripts/oxalis/view/context_menu.tsx index c049bc028c0..1545bcdd8a6 100644 --- a/frontend/javascripts/oxalis/view/context_menu.tsx +++ b/frontend/javascripts/oxalis/view/context_menu.tsx @@ -1162,7 +1162,8 @@ function ContextMenuInner(propsWithInputRef: Props) { contextMenuPosition == null || volumeTracing == null || !hasNoFallbackLayer || - !volumeTracing.hasSegmentIndex + !volumeTracing.hasSegmentIndex || + props.additionalCoordinates != null // TODO change once statistics are available for nd-datasets ) { return []; } else { @@ -1300,7 +1301,9 @@ function ContextMenuInner(propsWithInputRef: Props) { ); - if (hasNoFallbackLayer && volumeTracing?.hasSegmentIndex && isHoveredSegmentOrMesh) { + const areSegmentStatisticsAvailable = hasNoFallbackLayer && volumeTracing?.hasSegmentIndex && isHoveredSegmentOrMesh && props.additionalCoordinates == null; // TODO change once statistics are available for nd-datasets + + if (areSegmentStatisticsAvailable) { infoRows.push( getInfoMenuItem( "volumeInfo", @@ -1314,7 +1317,7 @@ function ContextMenuInner(propsWithInputRef: Props) { ); } - if (hasNoFallbackLayer && volumeTracing?.hasSegmentIndex && isHoveredSegmentOrMesh) { + if (areSegmentStatisticsAvailable) { infoRows.push( getInfoMenuItem( "boundingBoxPositionInfo", diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx index 8e03306577d..e941f3ef536 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx @@ -1089,7 +1089,8 @@ class SegmentsView extends React.Component { visibleSegmentationLayer == null || !("fallbackLayer" in visibleSegmentationLayer) || visibleSegmentationLayer.fallbackLayer != null || - !this.props.activeVolumeTracing?.hasSegmentIndex + !this.props.activeVolumeTracing?.hasSegmentIndex || + (this.props.flycam.additionalCoordinates != null) // TODO change once statistics are available for nd-datasets ) { //in this case there is a fallback layer return null; diff --git a/yarn.lock b/yarn.lock index 4171017d98b..0565ada23f8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14223,4 +14223,4 @@ zustand@^3.7.2: zwitch@^2.0.0, zwitch@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7" - integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A== \ No newline at end of file + integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A== From 6e7134d958e1f668bb95b6409c5264769178f1e6 Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Wed, 15 Nov 2023 14:27:22 +0100 Subject: [PATCH 22/43] fix visibility toggle and color change --- .../controller/segment_mesh_controller.ts | 28 +++++++------------ .../oxalis/model/sagas/mesh_saga.ts | 6 ++-- .../segments_tab/segment_list_item.tsx | 3 +- .../segments_tab/segments_view.tsx | 6 +++- 4 files changed, 21 insertions(+), 22 deletions(-) diff --git a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts index 017d177c01f..5578de8e465 100644 --- a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts +++ b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts @@ -246,26 +246,18 @@ export default class SegmentMeshController { ); } - setMeshColor( - id: number, - layerName: string, - additionalCoordinates?: AdditionalCoordinate[] | null, - ): void { - const additionalCoordinatesString = getAdditionalCoordinatesAsString( - additionalCoordinates || null, - ); + setMeshColor(id: number, layerName: string): void { const color = this.getColorObjectForSegment(id); - _.forEach( - this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName][id], - (meshGroup) => { - if (meshGroup) { - for (const child of meshGroup.children) { - // @ts-ignore - child.material.color = color; - } + // if in nd-dataset, set the color for all additional coordinates + for (const recordsOfLayers of Object.values(this.meshesGroupsPerSegmentationId)) { + const meshDataForOneSegment = recordsOfLayers[layerName][id]; + if (meshDataForOneSegment != null) { + for (const meshGroup of Object.values(meshDataForOneSegment)) { + // @ts-ignore + meshGroup.children.forEach((child) => (child.material.color = color)); } - }, - ); + } + } } getColorObjectForSegment(segmentId: number) { diff --git a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts index 1645d806747..a949946ceee 100644 --- a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts @@ -1149,7 +1149,7 @@ function* removeMesh(action: RemoveMeshAction, removeFromScene: boolean = true): function* handleMeshVisibilityChange(action: UpdateMeshVisibilityAction): Saga { const { id, visibility, layerName, additionalCoordinates } = action; const { segmentMeshController } = yield* call(getSceneController); - segmentMeshController.setMeshVisibility(id, visibility, layerName, additionalCoordinates); //use yield* call + segmentMeshController.setMeshVisibility(id, visibility, layerName, additionalCoordinates); } export function* handleAdditionalCoordinateUpdate(): Saga { @@ -1209,9 +1209,11 @@ export function* handleAdditionalCoordinateUpdate(): Saga { function* handleSegmentColorChange(action: UpdateSegmentAction): Saga { const { segmentMeshController } = yield* call(getSceneController); + const additionalCoordinates = yield* select((state) => state.flycam.additionalCoordinates); + debugger; if ( "color" in action.segment && - segmentMeshController.hasMesh(action.segmentId, action.layerName) + segmentMeshController.hasMesh(action.segmentId, action.layerName, additionalCoordinates) ) { segmentMeshController.setMeshColor(action.segmentId, action.layerName); } diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx index e5f4880c907..424464a56ed 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx @@ -31,6 +31,7 @@ import { ChangeColorMenuItemContent } from "components/color_picker"; import { MenuItemType } from "antd/lib/menu/hooks/useItems"; import { withMappingActivationConfirmation } from "./segments_view_helper"; import { type AdditionalCoordinate } from "types/api_flow_types"; +import { getAdditionalCoordinatesAsString } from "oxalis/model/accessors/flycam_accessor"; function ColoredDotIconForSegment({ segmentColorHSLA }: { segmentColorHSLA: Vector4 }) { const hslaCss = hslaToCSS(segmentColorHSLA); @@ -237,7 +238,7 @@ function _MeshInfoItem(props: { const { segment, isSelectedInList, isHovered, mesh } = props; - if (!mesh) { + if (!mesh || getAdditionalCoordinatesAsString(mesh.seedAdditionalCoordinates || null) !== getAdditionalCoordinatesAsString(Store.getState().flycam.additionalCoordinates)) { if (isSelectedInList) { return (
) => ({ seedPosition: Vector3, additionalCoordinates: AdditionalCoordinate[] | undefined, ) { + //TODO check that segment exists dispatch(loadAdHocMeshAction(segmentId, seedPosition, additionalCoordinates)); }, @@ -260,6 +261,7 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ getAdditionalCoordinatesAsString(Store.getState().flycam.additionalCoordinates) !== getAdditionalCoordinatesAsString(additionalCoordinates || null) ) { + //TODO only do this if the segment is present in current add coord. dispatch(setAdditionalCoordinatesAction(additionalCoordinates || null)); } }, @@ -1332,7 +1334,9 @@ class SegmentsView extends React.Component { handleLoadMeshesAdHoc = (groupId: number | null) => { this.handlePerSegment(groupId, (segment) => { - if (segment.somePosition == null) return; + debugger + const currentAdditionalCoordinates = getAdditionalCoordinatesAsString(this.props.flycam.additionalCoordinates); + if (segment.somePosition == null || getAdditionalCoordinatesAsString(segment.someAdditionalCoordinates||null) !== currentAdditionalCoordinates) return; this.props.loadAdHocMesh( segment.id, segment.somePosition, From 6ee80c56484fb27707684f0fab6bc337f2dd18b8 Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Mon, 20 Nov 2023 22:21:32 +0100 Subject: [PATCH 23/43] fix loading of meshes without voxels and removing of meshes --- frontend/javascripts/oxalis/api/api_latest.ts | 4 +- .../controller/segment_mesh_controller.ts | 72 +++++++++++-------- .../model/actions/annotation_actions.ts | 2 +- .../model/reducers/annotation_reducer.ts | 17 ++--- .../oxalis/model/sagas/mesh_saga.ts | 20 ++++-- .../segments_tab/segments_view.tsx | 5 +- 6 files changed, 68 insertions(+), 52 deletions(-) diff --git a/frontend/javascripts/oxalis/api/api_latest.ts b/frontend/javascripts/oxalis/api/api_latest.ts index 7aed79157b1..de934e03e4e 100644 --- a/frontend/javascripts/oxalis/api/api_latest.ts +++ b/frontend/javascripts/oxalis/api/api_latest.ts @@ -2258,9 +2258,7 @@ class DataApi { ).name; if (Store.getState().localSegmentationData[effectiveLayerName].meshes[segmentId] != null) { - Store.dispatch( - updateMeshVisibilityAction(effectiveLayerName, segmentId, isVisible, undefined), - ); + Store.dispatch(updateMeshVisibilityAction(effectiveLayerName, segmentId, isVisible)); } } diff --git a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts index 5578de8e465..813d2a0dc02 100644 --- a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts +++ b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts @@ -45,6 +45,7 @@ export default class SegmentMeshController { if (!segments) { return false; } + debugger; return segments[id] != null; } @@ -54,6 +55,8 @@ export default class SegmentMeshController { layerName: string, additionalCoordinates?: AdditionalCoordinate[], ): void { + debugger; + if (vertices.length === 0) return; let bufferGeometry = new THREE.BufferGeometry(); bufferGeometry.setAttribute("position", new THREE.BufferAttribute(vertices, 3)); @@ -111,7 +114,6 @@ export default class SegmentMeshController { layerName: string, additionalCoordinates: AdditionalCoordinate[] | null, ): void { - console.log("addmeshfromg", additionalCoordinates); const additionalCoordinatesString = getAdditionalCoordinatesAsString( additionalCoordinates || null, ); @@ -150,6 +152,7 @@ export default class SegmentMeshController { } } const mesh = this.constructMesh(segmentationId, geometry); + debugger; if (offset) { mesh.translateX(offset[0]); mesh.translateY(offset[1]); @@ -165,36 +168,47 @@ export default class SegmentMeshController { layerName: string, additionalCoordinates: AdditionalCoordinate[] | null, ): void { - const additionalCoordinatesString = getAdditionalCoordinatesAsString(additionalCoordinates); - - // TODO I think it shouldnt be possible to remove meshes that arent visible currently. - // but if they are removed they should be removed for all timestamps - if (this.meshesGroupsPerSegmentationId[additionalCoordinatesString] == null) { - return; + // either remove a mesh for specific additional coordinates, or remove all meshes for a segment per default. + let additionalCoordinatesToRemoveMeshes; + if (additionalCoordinates == null) { + additionalCoordinatesToRemoveMeshes = Object.keys(this.meshesGroupsPerSegmentationId); + } else { + additionalCoordinatesToRemoveMeshes = [ + getAdditionalCoordinatesAsString(additionalCoordinates), + ]; } - if (this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName] == null) { - return; - } - if ( - this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName][segmentationId] == - null - ) { - return; + for (const additionalCoordinatesString of additionalCoordinatesToRemoveMeshes) { + // TODO I think it shouldnt be possible to remove meshes that arent visible currently. + // but if they are removed they should be removed for all timestamps + + if (this.meshesGroupsPerSegmentationId[additionalCoordinatesString] == null) { + return; + } + if (this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName] == null) { + return; + } + if ( + this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName][ + segmentationId + ] == null + ) { + return; + } + _.forEach( + this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName][segmentationId], + (meshGroup, lod) => { + const lodNumber = parseInt(lod); + if (lodNumber !== NO_LOD_MESH_INDEX) { + this.meshesLODRootGroup.removeLODMesh(meshGroup, lodNumber); + } else { + this.meshesLODRootGroup.removeNoLODSupportedMesh(meshGroup); + } + }, + ); + delete this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName][ + segmentationId + ]; } - _.forEach( - this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName][segmentationId], - (meshGroup, lod) => { - const lodNumber = parseInt(lod); - if (lodNumber !== NO_LOD_MESH_INDEX) { - this.meshesLODRootGroup.removeLODMesh(meshGroup, lodNumber); - } else { - this.meshesLODRootGroup.removeNoLODSupportedMesh(meshGroup); - } - }, - ); - delete this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName][ - segmentationId - ]; } getMeshGeometryInBestLOD( diff --git a/frontend/javascripts/oxalis/model/actions/annotation_actions.ts b/frontend/javascripts/oxalis/model/actions/annotation_actions.ts index 3e8ad0bc5e1..45306c8de86 100644 --- a/frontend/javascripts/oxalis/model/actions/annotation_actions.ts +++ b/frontend/javascripts/oxalis/model/actions/annotation_actions.ts @@ -187,7 +187,7 @@ export const updateMeshVisibilityAction = ( layerName: string, id: number, visibility: boolean, - additionalCoordinates: AdditionalCoordinate[] | undefined, + additionalCoordinates?: AdditionalCoordinate[] | undefined, ) => ({ type: "UPDATE_MESH_VISIBILITY", diff --git a/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts b/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts index 672c9d22cbc..3e6f86ebcb1 100644 --- a/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts +++ b/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts @@ -228,19 +228,20 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { } case "REMOVE_MESH": { - //TODO fix me const { layerName, segmentId } = action; - const additionalCoordinates = state.flycam.additionalCoordinates; - const addCoordString = getAdditionalCoordinatesAsString(additionalCoordinates); - const { [segmentId]: _, ...remainingMeshes } = - state.localSegmentationData[layerName].meshes[addCoordString]; + const newMeshes: Record> = {}; + for (const additionalCoordString of Object.keys( + state.localSegmentationData[layerName].meshes, + )) { + const { [segmentId]: _, ...remainingMeshes } = + state.localSegmentationData[layerName].meshes[additionalCoordString]; + newMeshes[additionalCoordString] = remainingMeshes; + } return update(state, { localSegmentationData: { [layerName]: { meshes: { - [addCoordString]: { - $set: remainingMeshes, - }, + $set: newMeshes, }, }, }, diff --git a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts index a949946ceee..b408483852a 100644 --- a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts @@ -294,6 +294,12 @@ function* loadAdHocMesh( action.layerName === layer.name, ), }); + + const { segmentMeshController } = getSceneController(); + debugger; + if (!segmentMeshController.hasMesh(segmentId, layer.name, seedAdditionalCoordinates)) { + yield* put(removeMeshAction(layer.name, segmentId)); + } } function* loadFullAdHocMesh( @@ -628,7 +634,12 @@ function* _refreshMeshWithMap( ); shouldBeRemoved = false; } - + //let segment mesh controller check for geometries and remove mesh if there are none + const { segmentMeshController } = getSceneController(); + debugger; + if (!segmentMeshController.hasMesh(segmentId, layerName, additionalCoordinates)) { + yield* put(removeMeshAction(layerName, meshInfo.segmentId)); + } yield* put(finishedLoadingMeshAction(layerName, segmentId)); } @@ -1136,11 +1147,7 @@ function* removeMesh(action: RemoveMeshAction, removeFromScene: boolean = true): const additionalCoordinates = yield* select((state) => state.flycam.additionalCoordinates); if (removeFromScene) { - getSceneController().segmentMeshController.removeMeshById( - segmentId, - layerName, - additionalCoordinates, - ); + getSceneController().segmentMeshController.removeMeshById(segmentId, layerName, null); } removeMapForSegment(layerName, segmentId, additionalCoordinates); @@ -1210,7 +1217,6 @@ export function* handleAdditionalCoordinateUpdate(): Saga { function* handleSegmentColorChange(action: UpdateSegmentAction): Saga { const { segmentMeshController } = yield* call(getSceneController); const additionalCoordinates = yield* select((state) => state.flycam.additionalCoordinates); - debugger; if ( "color" in action.segment && segmentMeshController.hasMesh(action.segmentId, action.layerName, additionalCoordinates) diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx index 30563177251..2b704219031 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx @@ -169,7 +169,6 @@ const mapStateToProps = (state: OxalisState): StateProps => { const addCoordString = getAdditionalCoordinatesAsString(state.flycam.additionalCoordinates); - // TODO add coord are undefined let meshesForCurrentAdditionalCoordinates = EMPTY_OBJECT; if (visibleSegmentationLayer != null) { const meshRecords = state.localSegmentationData[visibleSegmentationLayer?.name].meshes; @@ -1334,9 +1333,7 @@ class SegmentsView extends React.Component { handleLoadMeshesAdHoc = (groupId: number | null) => { this.handlePerSegment(groupId, (segment) => { - debugger - const currentAdditionalCoordinates = getAdditionalCoordinatesAsString(this.props.flycam.additionalCoordinates); - if (segment.somePosition == null || getAdditionalCoordinatesAsString(segment.someAdditionalCoordinates||null) !== currentAdditionalCoordinates) return; + if (segment.somePosition == null) return; this.props.loadAdHocMesh( segment.id, segment.somePosition, From 3649d70b2a9da00e028834640d5338d3b8ecc1a2 Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Wed, 22 Nov 2023 14:00:32 +0100 Subject: [PATCH 24/43] fix edge case after removing mesh and mesh download --- .../controller/segment_mesh_controller.ts | 11 ------- .../model/reducers/annotation_reducer.ts | 2 +- .../model/reducers/volumetracing_reducer.ts | 7 ++-- .../oxalis/model/sagas/mesh_saga.ts | 32 +++++++++---------- .../javascripts/oxalis/view/context_menu.tsx | 15 +++++++-- .../segments_tab/segment_list_item.tsx | 14 +++++--- .../segments_tab/segments_view.tsx | 3 +- 7 files changed, 45 insertions(+), 39 deletions(-) diff --git a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts index 813d2a0dc02..98b0eee1231 100644 --- a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts +++ b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts @@ -45,7 +45,6 @@ export default class SegmentMeshController { if (!segments) { return false; } - debugger; return segments[id] != null; } @@ -55,7 +54,6 @@ export default class SegmentMeshController { layerName: string, additionalCoordinates?: AdditionalCoordinate[], ): void { - debugger; if (vertices.length === 0) return; let bufferGeometry = new THREE.BufferGeometry(); bufferGeometry.setAttribute("position", new THREE.BufferAttribute(vertices, 3)); @@ -152,7 +150,6 @@ export default class SegmentMeshController { } } const mesh = this.constructMesh(segmentationId, geometry); - debugger; if (offset) { mesh.translateX(offset[0]); mesh.translateY(offset[1]); @@ -236,14 +233,6 @@ export default class SegmentMeshController { layerName: string, additionalCoordinates?: AdditionalCoordinate[] | null, ): void { - console.log( - "setMeshVis", - id, - visibility, - layerName, - additionalCoordinates, - this.meshesGroupsPerSegmentationId, - ); const additionalCoordinatesString = getAdditionalCoordinatesAsString( additionalCoordinates || null, ); diff --git a/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts b/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts index 3e6f86ebcb1..9764e0babeb 100644 --- a/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts +++ b/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts @@ -209,7 +209,6 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { case "UPDATE_MESH_VISIBILITY": { const { layerName, id, visibility, additionalCoordinates } = action; const addCoordString = getAdditionalCoordinatesAsString(additionalCoordinates || null); - // assumption: set_additional_coordinates action is handled before return update(state, { localSegmentationData: { [layerName]: { @@ -248,6 +247,7 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { }); } + // Mesh information is stored in three places: the state in the store, segment_view_controller and within the mesh_saga. case "ADD_AD_HOC_MESH": { const { layerName, diff --git a/frontend/javascripts/oxalis/model/reducers/volumetracing_reducer.ts b/frontend/javascripts/oxalis/model/reducers/volumetracing_reducer.ts index 1c4de983c8f..ac8c20fecc0 100644 --- a/frontend/javascripts/oxalis/model/reducers/volumetracing_reducer.ts +++ b/frontend/javascripts/oxalis/model/reducers/volumetracing_reducer.ts @@ -40,7 +40,7 @@ import { import { updateKey2 } from "oxalis/model/helpers/deep_update"; import DiffableMap from "libs/diffable_map"; import * as Utils from "libs/utils"; -import type { ServerVolumeTracing } from "types/api_flow_types"; +import type { AdditionalCoordinate, ServerVolumeTracing } from "types/api_flow_types"; import { SetMappingAction, SetMappingEnabledAction, @@ -161,10 +161,13 @@ function handleUpdateSegment(state: OxalisState, action: UpdateSegmentAction) { const oldSegment = segments.getNullable(segmentId); let somePosition; + let someAdditionalCoordinates: AdditionalCoordinate[] | undefined; if (segment.somePosition) { somePosition = Utils.floor3(segment.somePosition); + someAdditionalCoordinates = segment.someAdditionalCoordinates; } else if (oldSegment != null) { somePosition = oldSegment.somePosition; + someAdditionalCoordinates = oldSegment.someAdditionalCoordinates; } else { // UPDATE_SEGMENT was called for a non-existing segment without providing // a position. This is necessary to define custom colors for segments @@ -179,7 +182,7 @@ function handleUpdateSegment(state: OxalisState, action: UpdateSegmentAction) { name: null, color: null, groupId: null, - someAdditionalCoordinates: [], + someAdditionalCoordinates: someAdditionalCoordinates, ...oldSegment, ...segment, somePosition, diff --git a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts index b408483852a..0d122e9782f 100644 --- a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts @@ -124,7 +124,6 @@ function getOrAddMapForSegment( additionalCoordinates?: AdditionalCoordinate[] | null, ): ThreeDMap { let additionalCoordinatesString = getAdditionalCoordinatesAsString(additionalCoordinates || null); - adhocMeshesMapByLayer[additionalCoordinatesString] = adhocMeshesMapByLayer[additionalCoordinatesString] || new Map(); adhocMeshesMapByLayer[additionalCoordinatesString][layerName] = @@ -144,15 +143,14 @@ function getOrAddMapForSegment( function removeMapForSegment( layerName: string, segmentId: number, - additionalCoordinates?: AdditionalCoordinate[] | null, + additionalCoordinateString: string, ): void { - let additionalCoordinatesString = getAdditionalCoordinatesAsString(additionalCoordinates || null); - - if (adhocMeshesMapByLayer[additionalCoordinatesString][layerName] == null) { + if (adhocMeshesMapByLayer[additionalCoordinateString] == null) return; + if (adhocMeshesMapByLayer[additionalCoordinateString][layerName] == null) { return; } - adhocMeshesMapByLayer[additionalCoordinatesString][layerName].delete(segmentId); + adhocMeshesMapByLayer[additionalCoordinateString][layerName].delete(segmentId); } function getZoomedCubeSize(zoomStep: number, resolutionInfo: ResolutionInfo): Vector3 { @@ -257,12 +255,6 @@ function* loadAdHocMesh( if (segmentId === 0 || layer == null) { return; } - if (_.size(layer.cube.additionalAxes) > 0) { - // Also see https://github.com/scalableminds/webknossos/issues/7229 - Toast.warning( - "The current segmentation layer has more than 3 dimensions. Meshes are not properly supported in this case.", - ); - } yield* call([Model, Model.ensureSavedState]); @@ -296,7 +288,6 @@ function* loadAdHocMesh( }); const { segmentMeshController } = getSceneController(); - debugger; if (!segmentMeshController.hasMesh(segmentId, layer.name, seedAdditionalCoordinates)) { yield* put(removeMeshAction(layer.name, segmentId)); } @@ -636,7 +627,6 @@ function* _refreshMeshWithMap( } //let segment mesh controller check for geometries and remove mesh if there are none const { segmentMeshController } = getSceneController(); - debugger; if (!segmentMeshController.hasMesh(segmentId, layerName, additionalCoordinates)) { yield* put(removeMeshAction(layerName, meshInfo.segmentId)); } @@ -1063,7 +1053,12 @@ function sortByDistanceTo( */ function* downloadMeshCellById(cellName: string, segmentId: number, layerName: string): Saga { const { segmentMeshController } = getSceneController(); - const geometry = segmentMeshController.getMeshGeometryInBestLOD(segmentId, layerName); + const additionalCoordinates = yield* select((state) => state.flycam.additionalCoordinates); + const geometry = segmentMeshController.getMeshGeometryInBestLOD( + segmentId, + layerName, + additionalCoordinates, + ); if (geometry == null) { const errorMessage = messages["tracing.not_mesh_available_to_download"]; @@ -1088,11 +1083,13 @@ function* downloadMeshCellsAsZIP( ): Saga { const { segmentMeshController } = getSceneController(); const zipWriter = new Zip.ZipWriter(new Zip.BlobWriter("application/zip")); + const additionalCoordinates = yield* select((state) => state.flycam.additionalCoordinates); try { const addFileToZipWriterPromises = segments.map((element) => { const geometry = segmentMeshController.getMeshGeometryInBestLOD( element.segmentId, element.layerName, + additionalCoordinates, ); if (geometry == null) { @@ -1144,13 +1141,14 @@ function* handleRemoveSegment(action: RemoveSegmentAction) { function* removeMesh(action: RemoveMeshAction, removeFromScene: boolean = true): Saga { const { layerName } = action; const segmentId = action.segmentId; - const additionalCoordinates = yield* select((state) => state.flycam.additionalCoordinates); if (removeFromScene) { getSceneController().segmentMeshController.removeMeshById(segmentId, layerName, null); } - removeMapForSegment(layerName, segmentId, additionalCoordinates); + for (const addCoordString of Object.keys(adhocMeshesMapByLayer)) { + removeMapForSegment(layerName, segmentId, addCoordString); + } } function* handleMeshVisibilityChange(action: UpdateMeshVisibilityAction): Saga { diff --git a/frontend/javascripts/oxalis/view/context_menu.tsx b/frontend/javascripts/oxalis/view/context_menu.tsx index 1545bcdd8a6..040e5a330b5 100644 --- a/frontend/javascripts/oxalis/view/context_menu.tsx +++ b/frontend/javascripts/oxalis/view/context_menu.tsx @@ -1301,7 +1301,11 @@ function ContextMenuInner(propsWithInputRef: Props) { ); - const areSegmentStatisticsAvailable = hasNoFallbackLayer && volumeTracing?.hasSegmentIndex && isHoveredSegmentOrMesh && props.additionalCoordinates == null; // TODO change once statistics are available for nd-datasets + const areSegmentStatisticsAvailable = + hasNoFallbackLayer && + volumeTracing?.hasSegmentIndex && + isHoveredSegmentOrMesh && + props.additionalCoordinates == null; // TODO change once statistics are available for nd-datasets if (areSegmentStatisticsAvailable) { infoRows.push( @@ -1521,7 +1525,14 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ dispatch(removeMeshAction(layerName, meshId)); }, hideMesh(layerName: string, meshId: number) { - dispatch(updateMeshVisibilityAction(layerName, meshId, false, Store.getState().flycam.additionalCoordinates||undefined)); + dispatch( + updateMeshVisibilityAction( + layerName, + meshId, + false, + Store.getState().flycam.additionalCoordinates || undefined, + ), + ); }, setPosition(position: Vector3) { dispatch(setPositionAction(position)); diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx index 424464a56ed..f10fd7d9448 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx @@ -130,10 +130,12 @@ const getComputeMeshAdHocMenuItem = ( andCloseContextMenu(); return; } - console.log("segment list item"); - console.log(segment); // TODO addCoor empty if newly created andCloseContextMenu( - loadAdHocMesh(segment.id, segment.somePosition, segment.someAdditionalCoordinates), + loadAdHocMesh( + segment.id, + segment.somePosition, + Store.getState().flycam.additionalCoordinates || undefined, + ), //TODO does this work? some postion? ); }, disabled, @@ -238,7 +240,11 @@ function _MeshInfoItem(props: { const { segment, isSelectedInList, isHovered, mesh } = props; - if (!mesh || getAdditionalCoordinatesAsString(mesh.seedAdditionalCoordinates || null) !== getAdditionalCoordinatesAsString(Store.getState().flycam.additionalCoordinates)) { + if ( + !mesh || + getAdditionalCoordinatesAsString(mesh.seedAdditionalCoordinates || null) !== + getAdditionalCoordinatesAsString(Store.getState().flycam.additionalCoordinates) + ) { if (isSelectedInList) { return (
) => ({ seedPosition: Vector3, additionalCoordinates: AdditionalCoordinate[] | undefined, ) { - //TODO check that segment exists dispatch(loadAdHocMeshAction(segmentId, seedPosition, additionalCoordinates)); }, @@ -1091,7 +1090,7 @@ class SegmentsView extends React.Component { !("fallbackLayer" in visibleSegmentationLayer) || visibleSegmentationLayer.fallbackLayer != null || !this.props.activeVolumeTracing?.hasSegmentIndex || - (this.props.flycam.additionalCoordinates != null) // TODO change once statistics are available for nd-datasets + this.props.flycam.additionalCoordinates != null // TODO change once statistics are available for nd-datasets ) { //in this case there is a fallback layer return null; From b457ac85a6275af7709d6c9c9b9654a61afa5e7c Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Wed, 22 Nov 2023 14:54:59 +0100 Subject: [PATCH 25/43] add some comments and make code prettier --- .../oxalis/controller/segment_mesh_controller.ts | 16 ++++++---------- .../oxalis/model/accessors/flycam_accessor.ts | 4 ---- .../oxalis/model/reducers/annotation_reducer.ts | 12 ++++-------- .../javascripts/oxalis/model/sagas/mesh_saga.ts | 13 +++++++------ frontend/javascripts/oxalis/store.ts | 4 ++-- .../segments_tab/segment_list_item.tsx | 2 +- .../segments_tab/segments_view.tsx | 1 - 7 files changed, 20 insertions(+), 32 deletions(-) diff --git a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts index 98b0eee1231..8a84e7d21fc 100644 --- a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts +++ b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts @@ -16,6 +16,9 @@ export default class SegmentMeshController { // meshesLODRootGroup holds lights and one group per segmentation id. // Each group can hold multiple meshes. meshesLODRootGroup: CustomLOD; + + // meshesGroupsPerSegmentationId holds a record for every additionalCoordinatesString, then + // (nested) for each layerName, and then at the lowest level a group for each segment ID. meshesGroupsPerSegmentationId: Record< string, Record>> @@ -175,16 +178,9 @@ export default class SegmentMeshController { ]; } for (const additionalCoordinatesString of additionalCoordinatesToRemoveMeshes) { - // TODO I think it shouldnt be possible to remove meshes that arent visible currently. - // but if they are removed they should be removed for all timestamps - - if (this.meshesGroupsPerSegmentationId[additionalCoordinatesString] == null) { - return; - } - if (this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName] == null) { - return; - } if ( + this.meshesGroupsPerSegmentationId[additionalCoordinatesString] == null || + this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName] == null || this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName][ segmentationId ] == null @@ -238,7 +234,7 @@ export default class SegmentMeshController { ); if (this.meshesGroupsPerSegmentationId[additionalCoordinatesString] == null) { - return; //TODO think about 3D only + return; } _.forEach( diff --git a/frontend/javascripts/oxalis/model/accessors/flycam_accessor.ts b/frontend/javascripts/oxalis/model/accessors/flycam_accessor.ts index fa15da3e2bb..9f43022bc7a 100644 --- a/frontend/javascripts/oxalis/model/accessors/flycam_accessor.ts +++ b/frontend/javascripts/oxalis/model/accessors/flycam_accessor.ts @@ -273,10 +273,6 @@ function _getPosition(flycam: Flycam): Vector3 { return [matrix[12], matrix[13], matrix[14]]; } -export function getAdditionalCoordinatesAsStringFromFC(flycam: Flycam): string { - return getAdditionalCoordinatesAsString(flycam.additionalCoordinates); -} - export function getAdditionalCoordinatesAsString( additionalCoordinates: AdditionalCoordinate[] | null, ): string { diff --git a/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts b/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts index 9764e0babeb..e4d5286bccd 100644 --- a/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts +++ b/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts @@ -267,8 +267,7 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { mappingName, mappingType, }; - const additionalCoordinates = state.flycam.additionalCoordinates; - const addCoordString = getAdditionalCoordinatesAsString(additionalCoordinates); + const addCoordString = getAdditionalCoordinatesAsString(state.flycam.additionalCoordinates); const updatedKey = update(state, { localSegmentationData: { [layerName]: { @@ -295,8 +294,7 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { isPrecomputed: true, meshFileName, }; - const additionalCoordinates = state.flycam.additionalCoordinates; - const addCoordString = getAdditionalCoordinatesAsString(additionalCoordinates); + const addCoordString = getAdditionalCoordinatesAsString(state.flycam.additionalCoordinates); const updatedKey = update(state, { localSegmentationData: { [layerName]: { @@ -315,8 +313,7 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { case "STARTED_LOADING_MESH": { const { layerName, segmentId } = action; - const additionalCoordinates = state.flycam.additionalCoordinates; - const addCoordString = getAdditionalCoordinatesAsString(additionalCoordinates); + const addCoordString = getAdditionalCoordinatesAsString(state.flycam.additionalCoordinates); const updatedKey = update(state, { localSegmentationData: { [layerName]: { @@ -337,8 +334,7 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { case "FINISHED_LOADING_MESH": { const { layerName, segmentId } = action; - const additionalCoordinates = state.flycam.additionalCoordinates; - const addCoordString = getAdditionalCoordinatesAsString(additionalCoordinates); + const addCoordString = getAdditionalCoordinatesAsString(state.flycam.additionalCoordinates); const updatedKey = update(state, { localSegmentationData: { [layerName]: { diff --git a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts index 0d122e9782f..55d1d60bc5e 100644 --- a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts @@ -145,8 +145,10 @@ function removeMapForSegment( segmentId: number, additionalCoordinateString: string, ): void { - if (adhocMeshesMapByLayer[additionalCoordinateString] == null) return; - if (adhocMeshesMapByLayer[additionalCoordinateString][layerName] == null) { + if ( + adhocMeshesMapByLayer[additionalCoordinateString] == null || + adhocMeshesMapByLayer[additionalCoordinateString][layerName] == null + ) { return; } @@ -287,6 +289,8 @@ function* loadAdHocMesh( ), }); + // If no voxels were added to the scene (e.g. because the segment doesn't have any voxels in this n-dimension), + // remove it from the store's state aswell. const { segmentMeshController } = getSceneController(); if (!segmentMeshController.hasMesh(segmentId, layer.name, seedAdditionalCoordinates)) { yield* put(removeMeshAction(layer.name, segmentId)); @@ -605,9 +609,6 @@ function* _refreshMeshWithMap( // The mesh should only be removed once after re-fetching the mesh first position. let shouldBeRemoved = true; - // Meshing for N-D segmentations is not yet supported. - // See https://github.com/scalableminds/webknossos/issues/7229 //TODO - for (const [, position] of meshPositions) { // Reload the mesh at the given position if it isn't already loaded there. // This is done to ensure that every voxel of the mesh is reloaded. @@ -625,7 +626,7 @@ function* _refreshMeshWithMap( ); shouldBeRemoved = false; } - //let segment mesh controller check for geometries and remove mesh if there are none + // see comment in l. 292 const { segmentMeshController } = getSceneController(); if (!segmentMeshController.hasMesh(segmentId, layerName, additionalCoordinates)) { yield* put(removeMeshAction(layerName, meshInfo.segmentId)); diff --git a/frontend/javascripts/oxalis/store.ts b/frontend/javascripts/oxalis/store.ts index 68c8fc56784..7a6fe4b4a9b 100644 --- a/frontend/javascripts/oxalis/store.ts +++ b/frontend/javascripts/oxalis/store.ts @@ -568,9 +568,9 @@ export type OxalisState = { readonly activeOrganization: APIOrganization | null; readonly uiInformation: UiInformation; readonly localSegmentationData: Record< - string, + string, //layerName { - readonly meshes: Record>; + readonly meshes: Record>; //string represents additional coordinates, number is the segment ID readonly availableMeshFiles: Array | null | undefined; readonly currentMeshFile: APIMeshFile | null | undefined; // Note that for a volume tracing, this information should be stored diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx index f10fd7d9448..061a3079354 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx @@ -135,7 +135,7 @@ const getComputeMeshAdHocMenuItem = ( segment.id, segment.somePosition, Store.getState().flycam.additionalCoordinates || undefined, - ), //TODO does this work? some postion? + ), ); }, disabled, diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx index 5291d001d92..75ff61d4ca8 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx @@ -259,7 +259,6 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ getAdditionalCoordinatesAsString(Store.getState().flycam.additionalCoordinates) !== getAdditionalCoordinatesAsString(additionalCoordinates || null) ) { - //TODO only do this if the segment is present in current add coord. dispatch(setAdditionalCoordinatesAction(additionalCoordinates || null)); } }, From e971675c54c92a55e8c37de12835c1efa4435b76 Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Wed, 22 Nov 2023 15:02:47 +0100 Subject: [PATCH 26/43] add changelog --- CHANGELOG.unreleased.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index 0efdabeaf8e..ba9e73f8a52 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -15,6 +15,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released - Added support for reading uint24 rgb layers in datasets with zarr2/zarr3/n5/neuroglancerPrecomputed format, as used for voxelytics predictions. [#7413](https://github.com/scalableminds/webknossos/pull/7413) - Adding a remote dataset can now be done by providing a Neuroglancer URI. [#7416](https://github.com/scalableminds/webknossos/pull/7416) - Added a filter to the Task List->Stats column to quickly filter for tasks with "Prending", "In-Progress" or "Finished" instances. [#7430](https://github.com/scalableminds/webknossos/pull/7430) +- Adhoc mesh rendering is now available for ND datasets.[#7394](https://github.com/scalableminds/webknossos/pull/7394) ### Changed - An appropriate error is returned when requesting an API version that is higher that the current version. [#7424](https://github.com/scalableminds/webknossos/pull/7424) From 74da01a2fb7a2e87a732d2b22f858b753fc067dc Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Mon, 27 Nov 2023 18:14:56 +0100 Subject: [PATCH 27/43] refactor segment mesh controller and getAddCoordAsString() --- .../controller/segment_mesh_controller.ts | 157 ++++++++---------- .../oxalis/model/accessors/flycam_accessor.ts | 2 +- .../model/reducers/annotation_reducer.ts | 2 +- .../oxalis/model/sagas/mesh_saga.ts | 2 +- .../segments_tab/segment_list_item.tsx | 2 +- .../segments_tab/segments_view.tsx | 2 +- 6 files changed, 76 insertions(+), 91 deletions(-) diff --git a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts index 8a84e7d21fc..e294c5e7f89 100644 --- a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts +++ b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts @@ -34,21 +34,10 @@ export default class SegmentMeshController { layerName: string, additionalCoordinates?: AdditionalCoordinate[] | null, ): boolean { - const additionalCoordinatesString = getAdditionalCoordinatesAsString( - additionalCoordinates || null, + return ( + this.getMeshGroups(getAdditionalCoordinatesAsString(additionalCoordinates), layerName, id) != + null ); - - if ( - this.meshesGroupsPerSegmentationId[additionalCoordinatesString] == null || - this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName] == null - ) - return false; - - const segments = this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName]; - if (!segments) { - return false; - } - return segments[id] != null; } addMeshFromVertices( @@ -115,42 +104,23 @@ export default class SegmentMeshController { layerName: string, additionalCoordinates: AdditionalCoordinate[] | null, ): void { - const additionalCoordinatesString = getAdditionalCoordinatesAsString( - additionalCoordinates || null, + const additionalCoordinatesString = getAdditionalCoordinatesAsString(additionalCoordinates); + const newGroup = new THREE.Group(); + const keys = [additionalCoordinatesString, layerName, segmentationId, lod]; + _.set( + this.meshesGroupsPerSegmentationId, + keys, + _.get(this.meshesGroupsPerSegmentationId, keys, newGroup), ); - if (this.meshesGroupsPerSegmentationId[additionalCoordinatesString] == null) { - this.meshesGroupsPerSegmentationId[additionalCoordinatesString] = {}; - } - - if (this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName] == null) { - this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName] = {}; - } - if ( - this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName][segmentationId] == - null - ) { - this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName][segmentationId] = - {}; + if (lod === NO_LOD_MESH_INDEX) { + this.meshesLODRootGroup.addNoLODSupportedMesh(newGroup); + } else { + this.meshesLODRootGroup.addLODMesh(newGroup, lod); } - if ( - this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName][segmentationId][ - lod - ] == null - ) { - const newGroup = new THREE.Group(); - this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName][segmentationId][ - lod - ] = newGroup; - if (lod === NO_LOD_MESH_INDEX) { - this.meshesLODRootGroup.addNoLODSupportedMesh(newGroup); - } else { - this.meshesLODRootGroup.addLODMesh(newGroup, lod); - } - // @ts-ignore - newGroup.cellId = segmentationId; - if (scale != null) { - newGroup.scale.copy(new THREE.Vector3(...scale)); - } + // @ts-ignore + newGroup.cellId = segmentationId; + if (scale != null) { + newGroup.scale.copy(new THREE.Vector3(...scale)); } const mesh = this.constructMesh(segmentationId, geometry); if (offset) { @@ -158,9 +128,7 @@ export default class SegmentMeshController { mesh.translateY(offset[1]); mesh.translateZ(offset[2]); } - this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName][segmentationId][ - lod - ].add(mesh); + this.addMeshToMeshGroups(additionalCoordinatesString, layerName, segmentationId, lod, mesh); } removeMeshById( @@ -168,7 +136,7 @@ export default class SegmentMeshController { layerName: string, additionalCoordinates: AdditionalCoordinate[] | null, ): void { - // either remove a mesh for specific additional coordinates, or remove all meshes for a segment per default. + // either remove a mesh for specific additional coordinates, or remove all meshes for a segment per default by passing null as additionalCoordinates. let additionalCoordinatesToRemoveMeshes; if (additionalCoordinates == null) { additionalCoordinatesToRemoveMeshes = Object.keys(this.meshesGroupsPerSegmentationId); @@ -178,17 +146,11 @@ export default class SegmentMeshController { ]; } for (const additionalCoordinatesString of additionalCoordinatesToRemoveMeshes) { - if ( - this.meshesGroupsPerSegmentationId[additionalCoordinatesString] == null || - this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName] == null || - this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName][ - segmentationId - ] == null - ) { + if (this.getMeshGroups(additionalCoordinatesString, layerName, segmentationId) == null) { return; } _.forEach( - this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName][segmentationId], + this.getMeshGroups(additionalCoordinatesString, layerName, segmentationId), (meshGroup, lod) => { const lodNumber = parseInt(lod); if (lodNumber !== NO_LOD_MESH_INDEX) { @@ -198,9 +160,7 @@ export default class SegmentMeshController { } }, ); - delete this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName][ - segmentationId - ]; + this.removeMeshFromMeshGroups(additionalCoordinatesString, layerName, segmentationId); } } @@ -209,18 +169,13 @@ export default class SegmentMeshController { layerName: string, additionalCoordinates?: AdditionalCoordinate[] | null, ): THREE.Group { - const additionalCoordinatesString = getAdditionalCoordinatesAsString( - additionalCoordinates || null, - ); - + const additionalCoordKey = getAdditionalCoordinatesAsString(additionalCoordinates); const bestLod = Math.min( - ...Object.keys( - this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName][segmentId], - ).map((lodVal) => parseInt(lodVal)), + ...Object.keys(this.getMeshGroups(additionalCoordKey, layerName, segmentId)).map((lodVal) => + parseInt(lodVal), + ), ); - return this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName][segmentId][ - bestLod - ]; + return this.getMeshGroupsByLOD(additionalCoordinates, layerName, segmentId, bestLod); } setMeshVisibility( @@ -229,20 +184,14 @@ export default class SegmentMeshController { layerName: string, additionalCoordinates?: AdditionalCoordinate[] | null, ): void { - const additionalCoordinatesString = getAdditionalCoordinatesAsString( - additionalCoordinates || null, - ); - - if (this.meshesGroupsPerSegmentationId[additionalCoordinatesString] == null) { - return; - } - - _.forEach( - this.meshesGroupsPerSegmentationId[additionalCoordinatesString][layerName][id], - (meshGroup) => { - meshGroup.visible = visibility; - }, + console.log( + "if mesh visibility is broken, look into segment mesh controller l. 197.", + "we stopped checking the records", ); + const additionalCoordKey = getAdditionalCoordinatesAsString(additionalCoordinates); + _.forEach(this.getMeshGroups(additionalCoordKey, layerName, id), (meshGroup) => { + meshGroup.visible = visibility; + }); } setMeshColor(id: number, layerName: string): void { @@ -296,4 +245,40 @@ export default class SegmentMeshController { this.meshesLODRootGroup.add(directionalLight2); this.meshesLODRootGroup.add(pointLight); } + + getMeshGroupsByLOD( + additionalCoordinates: AdditionalCoordinate[] | null | undefined, + layerName: string, + segmentationId: number, + lod: number, + ) { + const additionalCoordKey = getAdditionalCoordinatesAsString(additionalCoordinates); + const keys = [additionalCoordKey, layerName, segmentationId, lod]; + return _.get(this.meshesGroupsPerSegmentationId, keys, null); + } + + getMeshGroups(additionalCoordKey: string, layerName: string, segmentationId: number) { + const keys = [additionalCoordKey, layerName, segmentationId]; + return _.get(this.meshesGroupsPerSegmentationId, keys, null); + } + + addMeshToMeshGroups( + additionalCoordKey: string, + layerName: string, + segmentationId: number, + lod: number, + mesh: THREE.Mesh, + ) { + this.meshesGroupsPerSegmentationId[additionalCoordKey][layerName][segmentationId][lod].add( + mesh, + ); + } + + removeMeshFromMeshGroups( + additionalCoordinateKey: string, + layerName: string, + segmentationId: number, + ) { + delete this.meshesGroupsPerSegmentationId[additionalCoordinateKey][layerName][segmentationId]; + } } diff --git a/frontend/javascripts/oxalis/model/accessors/flycam_accessor.ts b/frontend/javascripts/oxalis/model/accessors/flycam_accessor.ts index 9f43022bc7a..0334090cbe1 100644 --- a/frontend/javascripts/oxalis/model/accessors/flycam_accessor.ts +++ b/frontend/javascripts/oxalis/model/accessors/flycam_accessor.ts @@ -274,7 +274,7 @@ function _getPosition(flycam: Flycam): Vector3 { } export function getAdditionalCoordinatesAsString( - additionalCoordinates: AdditionalCoordinate[] | null, + additionalCoordinates: AdditionalCoordinate[] | null | undefined, ): string { if (additionalCoordinates != null && additionalCoordinates.length > 0) { return additionalCoordinates diff --git a/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts b/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts index e4d5286bccd..bef88dee8c1 100644 --- a/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts +++ b/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts @@ -208,7 +208,7 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { case "UPDATE_MESH_VISIBILITY": { const { layerName, id, visibility, additionalCoordinates } = action; - const addCoordString = getAdditionalCoordinatesAsString(additionalCoordinates || null); + const addCoordString = getAdditionalCoordinatesAsString(additionalCoordinates); return update(state, { localSegmentationData: { [layerName]: { diff --git a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts index 55d1d60bc5e..858312f82ac 100644 --- a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts @@ -123,7 +123,7 @@ function getOrAddMapForSegment( segmentId: number, additionalCoordinates?: AdditionalCoordinate[] | null, ): ThreeDMap { - let additionalCoordinatesString = getAdditionalCoordinatesAsString(additionalCoordinates || null); + let additionalCoordinatesString = getAdditionalCoordinatesAsString(additionalCoordinates); adhocMeshesMapByLayer[additionalCoordinatesString] = adhocMeshesMapByLayer[additionalCoordinatesString] || new Map(); adhocMeshesMapByLayer[additionalCoordinatesString][layerName] = diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx index 061a3079354..ad891e42ae6 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx @@ -242,7 +242,7 @@ function _MeshInfoItem(props: { if ( !mesh || - getAdditionalCoordinatesAsString(mesh.seedAdditionalCoordinates || null) !== + getAdditionalCoordinatesAsString(mesh.seedAdditionalCoordinates) !== getAdditionalCoordinatesAsString(Store.getState().flycam.additionalCoordinates) ) { if (isSelectedInList) { diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx index 75ff61d4ca8..3fc5e935370 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx @@ -257,7 +257,7 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ setAdditionalCoordinates(additionalCoordinates: AdditionalCoordinate[] | undefined) { if ( getAdditionalCoordinatesAsString(Store.getState().flycam.additionalCoordinates) !== - getAdditionalCoordinatesAsString(additionalCoordinates || null) + getAdditionalCoordinatesAsString(additionalCoordinates) ) { dispatch(setAdditionalCoordinatesAction(additionalCoordinates || null)); } From 0397da44f4b59d1eab9a3dd38740598739867eef Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Tue, 28 Nov 2023 22:06:17 +0100 Subject: [PATCH 28/43] WIP: refactor typing of localSegmentationData.meshes --- frontend/javascripts/oxalis/api/api_latest.ts | 22 +++++++++++-- frontend/javascripts/oxalis/constants.ts | 1 + .../controller/segment_mesh_controller.ts | 4 --- .../oxalis/controller/url_manager.ts | 11 ++++--- .../model/reducers/annotation_reducer.ts | 31 ++++++++++++++++--- .../oxalis/model/reducers/flycam_reducer.ts | 25 +-------------- .../oxalis/model/sagas/mesh_saga.ts | 14 ++++++++- frontend/javascripts/oxalis/store.ts | 2 +- .../segments_tab/segment_list_item.tsx | 3 +- .../segments_tab/segments_view.tsx | 18 +++++------ 10 files changed, 78 insertions(+), 53 deletions(-) diff --git a/frontend/javascripts/oxalis/api/api_latest.ts b/frontend/javascripts/oxalis/api/api_latest.ts index de934e03e4e..f5cce350785 100644 --- a/frontend/javascripts/oxalis/api/api_latest.ts +++ b/frontend/javascripts/oxalis/api/api_latest.ts @@ -139,6 +139,7 @@ import Constants, { AnnotationToolEnum, TDViewDisplayModeEnum, MappingStatusEnum, + EMPTY_OBJECT, } from "oxalis/constants"; import DataLayer from "oxalis/model/data_layer"; import type { OxalisModel } from "oxalis/model"; @@ -173,6 +174,7 @@ import { setLayerTransformsAction } from "oxalis/model/actions/dataset_actions"; import { ResolutionInfo } from "oxalis/model/helpers/resolution_info"; import { type AdditionalCoordinate } from "types/api_flow_types"; import { getMaximumGroupId } from "oxalis/model/reducers/skeletontracing_reducer_helpers"; +import { EMPTY_KEY_VALUE } from "oxalis/model/bucket_data_handling/abstract_cuckoo_table"; type TransformSpec = | { type: "scale"; args: [Vector3, Vector3] } @@ -2257,8 +2259,15 @@ class DataApi { layerName, ).name; - if (Store.getState().localSegmentationData[effectiveLayerName].meshes[segmentId] != null) { + if ( + Store.getState().localSegmentationData[effectiveLayerName].meshes != null && + Store.getState().localSegmentationData[effectiveLayerName].meshes![segmentId] != null + ) { Store.dispatch(updateMeshVisibilityAction(effectiveLayerName, segmentId, isVisible)); + } else { + throw new Error( + `Error while setting mesh visibility. Mesh for segment ${segmentId} was not found in OxalisState.localSegmentationData.`, + ); } } @@ -2275,8 +2284,15 @@ class DataApi { layerName, ).name; - if (Store.getState().localSegmentationData[effectiveLayerName].meshes[segmentId] != null) { + if ( + Store.getState().localSegmentationData[effectiveLayerName].meshes != null && + Store.getState().localSegmentationData[effectiveLayerName].meshes![segmentId] != null + ) { Store.dispatch(removeMeshAction(effectiveLayerName, segmentId)); + } else { + throw new Error( + `Error while removing mesh. Mesh for segment ${segmentId} was not found in OxalisState.localSegmentationData.`, + ); } } @@ -2293,7 +2309,7 @@ class DataApi { layerName, ).name; const segmentIds = Object.keys( - Store.getState().localSegmentationData[effectiveLayerName].meshes, + Store.getState().localSegmentationData[effectiveLayerName].meshes || EMPTY_OBJECT, ); for (const segmentId of segmentIds) { diff --git a/frontend/javascripts/oxalis/constants.ts b/frontend/javascripts/oxalis/constants.ts index ee602fed5de..462dccd8902 100644 --- a/frontend/javascripts/oxalis/constants.ts +++ b/frontend/javascripts/oxalis/constants.ts @@ -381,3 +381,4 @@ export enum BLEND_MODES { export const Identity4x4 = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]); export const IdentityTransform = { type: "affine", affineMatrix: Identity4x4 } as const; +export const EMPTY_OBJECT = {}; diff --git a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts index e294c5e7f89..b525ae4c825 100644 --- a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts +++ b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts @@ -184,10 +184,6 @@ export default class SegmentMeshController { layerName: string, additionalCoordinates?: AdditionalCoordinate[] | null, ): void { - console.log( - "if mesh visibility is broken, look into segment mesh controller l. 197.", - "we stopped checking the records", - ); const additionalCoordKey = getAdditionalCoordinatesAsString(additionalCoordinates); _.forEach(this.getMeshGroups(additionalCoordKey, layerName, id), (meshGroup) => { meshGroup.visible = visibility; diff --git a/frontend/javascripts/oxalis/controller/url_manager.ts b/frontend/javascripts/oxalis/controller/url_manager.ts index f80356ff1b6..e36b8402bd6 100644 --- a/frontend/javascripts/oxalis/controller/url_manager.ts +++ b/frontend/javascripts/oxalis/controller/url_manager.ts @@ -11,7 +11,7 @@ import type { OxalisState, MappingType, MeshInformation } from "oxalis/store"; import Store from "oxalis/store"; import * as Utils from "libs/utils"; import type { ViewMode, Vector3 } from "oxalis/constants"; -import constants, { ViewModeValues, MappingStatusEnum } from "oxalis/constants"; +import constants, { ViewModeValues, MappingStatusEnum, EMPTY_OBJECT } from "oxalis/constants"; import window, { location } from "libs/window"; import ErrorHandling from "libs/error_handling"; import Toast from "libs/toast"; @@ -274,9 +274,12 @@ class UrlManager { for (const layerName of Object.keys(state.localSegmentationData)) { const { meshes: localMeshes, currentMeshFile } = state.localSegmentationData[layerName]; const currentMeshFileName = currentMeshFile?.meshFileName; - const meshes = Utils.values(localMeshes) - .filter(({ isVisible }) => isVisible) - .map(mapMeshInfoToUrlMeshDescriptor); + const meshes = + localMeshes != null + ? Utils.values(localMeshes as Record) + .filter(({ isVisible }) => isVisible) + .map(mapMeshInfoToUrlMeshDescriptor) + : []; if (currentMeshFileName != null || meshes.length > 0) { stateByLayer[layerName] = { diff --git a/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts b/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts index bef88dee8c1..e90b7bba779 100644 --- a/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts +++ b/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts @@ -229,11 +229,14 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { case "REMOVE_MESH": { const { layerName, segmentId } = action; const newMeshes: Record> = {}; + if (state.localSegmentationData[layerName].meshes == null) { + throw Error("No mesh data found in state.localSegmentationData."); + } for (const additionalCoordString of Object.keys( - state.localSegmentationData[layerName].meshes, + state.localSegmentationData[layerName].meshes!, )) { - const { [segmentId]: _, ...remainingMeshes } = - state.localSegmentationData[layerName].meshes[additionalCoordString]; + const { [segmentId]: _, ...remainingMeshes } = state.localSegmentationData[layerName] + .meshes![additionalCoordString] as Record; newMeshes[additionalCoordString] = remainingMeshes; } return update(state, { @@ -268,7 +271,27 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { mappingType, }; const addCoordString = getAdditionalCoordinatesAsString(state.flycam.additionalCoordinates); - const updatedKey = update(state, { + + let stateWithCurrentAddCoords = state; + + // maybe add current add. coord. as key in meshes records + if ( + state.localSegmentationData[layerName] == null || + state.localSegmentationData[layerName].meshes == null || + state.localSegmentationData[layerName].meshes![addCoordString] == null + ) { + stateWithCurrentAddCoords = update(state, { + localSegmentationData: { + [layerName]: { + meshes: { + [addCoordString]: { $set: [] }, + }, + }, + }, + }); + } + + const updatedKey = update(stateWithCurrentAddCoords, { localSegmentationData: { [layerName]: { meshes: { diff --git a/frontend/javascripts/oxalis/model/reducers/flycam_reducer.ts b/frontend/javascripts/oxalis/model/reducers/flycam_reducer.ts index 0ce073ae0cf..a215b73d278 100644 --- a/frontend/javascripts/oxalis/model/reducers/flycam_reducer.ts +++ b/frontend/javascripts/oxalis/model/reducers/flycam_reducer.ts @@ -271,34 +271,11 @@ function FlycamReducer(state: OxalisState, action: Action): OxalisState { return { name, value: fallbackValue }; }); - const updatedFlycamState = update(state, { + return update(state, { flycam: { additionalCoordinates: { $set: values }, }, }); - - const visibleSegmentationLayer = getVisibleSegmentationLayer(state); - const additionalCoordinateString = getAdditionalCoordinatesAsString(values); - if ( - visibleSegmentationLayer == null || - state.localSegmentationData[visibleSegmentationLayer.name].meshes[ - additionalCoordinateString - ] != null - ) { - return updatedFlycamState; - } - - const updatedLocalSegmentationState = update(updatedFlycamState, { - localSegmentationData: { - [visibleSegmentationLayer.name]: { - meshes: { - [additionalCoordinateString]: { $set: [] }, - }, - }, - }, - }); - - return updatedLocalSegmentationState; } case "SET_ROTATION": { diff --git a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts index 858312f82ac..ff38b4dbf06 100644 --- a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts @@ -557,8 +557,20 @@ function* refreshMesh(action: RefreshMeshAction): Saga { const { segmentId, layerName } = action; + const meshData = yield* select((state) => state.localSegmentationData[layerName].meshes); + if (meshData == null) { + throw new Error("Mesh refreshing failed due to lack of mesh data in localSegmentationData."); + } + const meshDataForAddCoord = yield* select( + (state) => state.localSegmentationData[layerName].meshes![additionalCoordinates], + ); + if (meshDataForAddCoord == null) { + throw new Error( + `Mesh refreshing failed due to lack of mesh data for add. coord. ${additionalCoordinates} in localSegmentationData.`, + ); + } const meshInfo = yield* select( - (state) => state.localSegmentationData[layerName].meshes[additionalCoordinates][segmentId], + (state) => state.localSegmentationData[layerName].meshes![additionalCoordinates]![segmentId], ); if (meshInfo.isPrecomputed) { diff --git a/frontend/javascripts/oxalis/store.ts b/frontend/javascripts/oxalis/store.ts index 7a6fe4b4a9b..c5126a51b0f 100644 --- a/frontend/javascripts/oxalis/store.ts +++ b/frontend/javascripts/oxalis/store.ts @@ -570,7 +570,7 @@ export type OxalisState = { readonly localSegmentationData: Record< string, //layerName { - readonly meshes: Record>; //string represents additional coordinates, number is the segment ID + readonly meshes: Record | undefined> | undefined; //string represents additional coordinates, number is the segment ID readonly availableMeshFiles: Array | null | undefined; readonly currentMeshFile: APIMeshFile | null | undefined; // Note that for a volume tracing, this information should be stored diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx index ad891e42ae6..34b5edd8692 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx @@ -233,6 +233,7 @@ function _MeshInfoItem(props: { setPosition: (arg0: Vector3) => void; setAdditionalCoordinates: (additionalCoordinates: AdditionalCoordinate[] | undefined) => void; }) { + const additionalCoordinates = useSelector((state: OxalisState) =>state.flycam.additionalCoordinates) const dispatch = useDispatch(); const onChangeMeshVisibility = (layerName: string, id: number, isVisible: boolean) => { dispatch(updateMeshVisibilityAction(layerName, id, isVisible, mesh?.seedAdditionalCoordinates)); @@ -243,7 +244,7 @@ function _MeshInfoItem(props: { if ( !mesh || getAdditionalCoordinatesAsString(mesh.seedAdditionalCoordinates) !== - getAdditionalCoordinatesAsString(Store.getState().flycam.additionalCoordinates) + getAdditionalCoordinatesAsString(additionalCoordinates) ) { if (isSelectedInList) { return ( diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx index 3fc5e935370..9fedc4fdb7c 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx @@ -39,7 +39,7 @@ import Toast from "libs/toast"; import _, { isNumber } from "lodash"; import memoizeOne from "memoize-one"; import type { Vector3 } from "oxalis/constants"; -import { MappingStatusEnum } from "oxalis/constants"; +import { EMPTY_OBJECT, MappingStatusEnum } from "oxalis/constants"; import { getSegmentIdForPosition } from "oxalis/controller/combinations/volume_handlers"; import { getMappingInfo, @@ -153,7 +153,6 @@ type StateProps = { resolutionInfoOfVisibleSegmentationLayer: ResolutionInfo; }; -const EMPTY_OBJECT = {}; const mapStateToProps = (state: OxalisState): StateProps => { const visibleSegmentationLayer = getVisibleSegmentationLayer(state); const activeVolumeTracing = getActiveSegmentationTracing(state); @@ -169,20 +168,17 @@ const mapStateToProps = (state: OxalisState): StateProps => { const addCoordString = getAdditionalCoordinatesAsString(state.flycam.additionalCoordinates); - let meshesForCurrentAdditionalCoordinates = EMPTY_OBJECT; + let meshesForCurrentAdditionalCoordinates; if (visibleSegmentationLayer != null) { - const meshRecords = state.localSegmentationData[visibleSegmentationLayer?.name].meshes; - meshesForCurrentAdditionalCoordinates = - meshRecords != null && - Object.keys(meshRecords).length > 0 && - Object.keys(meshRecords[addCoordString]).length > 0 - ? meshRecords[addCoordString] - : EMPTY_OBJECT; + const meshRecords = state.localSegmentationData[visibleSegmentationLayer.name].meshes; + if(meshRecords != null && meshRecords[addCoordString] != null){ + meshesForCurrentAdditionalCoordinates = meshRecords[addCoordString]; + } } return { activeCellId: activeVolumeTracing?.activeCellId, - meshes: meshesForCurrentAdditionalCoordinates, + meshes: meshesForCurrentAdditionalCoordinates || EMPTY_OBJECT, // satisfy ts dataset: state.dataset, isJSONMappingEnabled: mappingInfo.mappingStatus === MappingStatusEnum.ENABLED && mappingInfo.mappingType === "JSON", From 2ed040459c345f2654d971b6727079f64b6b6cbf Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Wed, 29 Nov 2023 16:36:14 +0100 Subject: [PATCH 29/43] refactor code, commit before revertig stuff in mesh saga --- .../model/accessors/volumetracing_accessor.ts | 21 +++ .../oxalis/model/sagas/mesh_saga.ts | 166 ++++++++++-------- .../action-bar/create_animation_modal.tsx | 7 +- .../segments_tab/segments_view.tsx | 12 +- 4 files changed, 121 insertions(+), 85 deletions(-) diff --git a/frontend/javascripts/oxalis/model/accessors/volumetracing_accessor.ts b/frontend/javascripts/oxalis/model/accessors/volumetracing_accessor.ts index e6f816f015f..3dc37f027d4 100644 --- a/frontend/javascripts/oxalis/model/accessors/volumetracing_accessor.ts +++ b/frontend/javascripts/oxalis/model/accessors/volumetracing_accessor.ts @@ -4,6 +4,7 @@ import type { APIAnnotationCompact, APIDataset, APISegmentationLayer, + AdditionalCoordinate, AnnotationLayerDescriptor, ServerTracing, ServerVolumeTracing, @@ -34,6 +35,7 @@ import { MAX_ZOOM_STEP_DIFF } from "oxalis/model/bucket_data_handling/loading_st import { getFlooredPosition, getActiveMagIndexForLayer, + getAdditionalCoordinatesAsString, } from "oxalis/model/accessors/flycam_accessor"; import { reuseInstanceOnEquality } from "oxalis/model/accessors/accessor_helpers"; import { V3 } from "libs/mjs"; @@ -664,3 +666,22 @@ export function hasAgglomerateMapping(state: OxalisState) { return AGGLOMERATE_STATES.YES; } + +export function getMeshesForAdditionalCoordinates( + state: OxalisState, + additionalCoordinates: AdditionalCoordinate[] | null, +) { + const visibleSegmentationLayer = getVisibleSegmentationLayer(state); + const addCoordKey = getAdditionalCoordinatesAsString(additionalCoordinates); + if (visibleSegmentationLayer != null) { + const meshRecords = state.localSegmentationData[visibleSegmentationLayer.name].meshes; + if (meshRecords != null && meshRecords[addCoordKey] != null) { + return meshRecords[addCoordKey]; + } + } + return null; +} + +export function getMeshesForCurrentAdditionalCoordinates(state: OxalisState) { + return getMeshesForAdditionalCoordinates(state, state.flycam.additionalCoordinates); +} diff --git a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts index ff38b4dbf06..876dac10e5c 100644 --- a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts @@ -7,7 +7,7 @@ import type { APIDataset, APIMeshFile, APISegmentationLayer } from "types/api_fl import { mergeBufferGeometries, mergeVertices } from "libs/BufferGeometryUtils"; import Deferred from "libs/async/deferred"; -import Store from "oxalis/store"; +import Store, { OxalisState } from "oxalis/store"; import { getResolutionInfo, getMappingInfo, @@ -551,44 +551,60 @@ function* refreshMeshes(): Saga { } } +const selectMeshDataFromState = ( + state: OxalisState, + layerName: string, + addCoordKey: string, + segmentId: number, +) => { + if (state.localSegmentationData[layerName].meshes == null) { + throw new Error("There is no mesh data in localSegmentationData."); + } + if (state.localSegmentationData[layerName].meshes![addCoordKey] == null) { + throw new Error( + `There is no mesh data for add. coord. ${addCoordKey} in localSegmentationData.`, + ); + } + return state.localSegmentationData[layerName].meshes![addCoordKey]![segmentId]; +}; + function* refreshMesh(action: RefreshMeshAction): Saga { const additionalCoordinatesObject = yield* select((state) => state.flycam.additionalCoordinates); let additionalCoordinates = getAdditionalCoordinatesAsString(additionalCoordinatesObject); const { segmentId, layerName } = action; - const meshData = yield* select((state) => state.localSegmentationData[layerName].meshes); - if (meshData == null) { - throw new Error("Mesh refreshing failed due to lack of mesh data in localSegmentationData."); - } - const meshDataForAddCoord = yield* select( - (state) => state.localSegmentationData[layerName].meshes![additionalCoordinates], - ); - if (meshDataForAddCoord == null) { - throw new Error( - `Mesh refreshing failed due to lack of mesh data for add. coord. ${additionalCoordinates} in localSegmentationData.`, + try { + const meshInfo = yield* select((state) => + selectMeshDataFromState(state, layerName, additionalCoordinates, segmentId), ); - } - const meshInfo = yield* select( - (state) => state.localSegmentationData[layerName].meshes![additionalCoordinates]![segmentId], - ); - if (meshInfo.isPrecomputed) { - yield* put(removeMeshAction(layerName, meshInfo.segmentId)); - yield* put( - loadPrecomputedMeshAction( - meshInfo.segmentId, - meshInfo.seedPosition, - meshInfo.seedAdditionalCoordinates, - meshInfo.meshFileName, + if (meshInfo.isPrecomputed) { + yield* put(removeMeshAction(layerName, meshInfo.segmentId)); + yield* put( + loadPrecomputedMeshAction( + meshInfo.segmentId, + meshInfo.seedPosition, + meshInfo.seedAdditionalCoordinates, + meshInfo.meshFileName, + layerName, + ), + ); + } else { + if (adhocMeshesMapByLayer[additionalCoordinates] == null) return; + const threeDMap = + adhocMeshesMapByLayer[additionalCoordinates][action.layerName].get(segmentId); + if (threeDMap == null) return; + yield* call( + _refreshMeshWithMap, + segmentId, + threeDMap, layerName, - ), - ); - } else { - if (adhocMeshesMapByLayer[additionalCoordinates] == null) return; - const threeDMap = adhocMeshesMapByLayer[additionalCoordinates][action.layerName].get(segmentId); - if (threeDMap == null) return; - yield* call(_refreshMeshWithMap, segmentId, threeDMap, layerName, additionalCoordinatesObject); + additionalCoordinatesObject, + ); + } + } catch (error) { + console.error("Mesh refreshing failed due to the following error:", error); } } @@ -598,52 +614,54 @@ function* _refreshMeshWithMap( layerName: string, additionalCoordinates: AdditionalCoordinate[] | null, ): Saga { - const additionalCoordinateString = getAdditionalCoordinatesAsString(additionalCoordinates); - const meshInfo = yield* select( - (state) => state.localSegmentationData[layerName].meshes[additionalCoordinateString][segmentId], - ); - yield* call( - [ErrorHandling, ErrorHandling.assert], - !meshInfo.isPrecomputed, - "_refreshMeshWithMap was called for a precomputed mesh.", - ); - if (meshInfo.isPrecomputed) return; - const { mappingName, mappingType } = meshInfo; - const meshPositions = threeDMap.entries().filter(([value, _position]) => value); - - if (meshPositions.length === 0) { - return; - } - - yield* put(startedLoadingMeshAction(layerName, segmentId)); - // Remove mesh from cache. - yield* call(removeMesh, removeMeshAction(layerName, segmentId), false); - // The mesh should only be removed once after re-fetching the mesh first position. - let shouldBeRemoved = true; - - for (const [, position] of meshPositions) { - // Reload the mesh at the given position if it isn't already loaded there. - // This is done to ensure that every voxel of the mesh is reloaded. + const addCoordKey = getAdditionalCoordinatesAsString(additionalCoordinates); + try { + const meshInfo = yield* select((state) => + selectMeshDataFromState(state, layerName, addCoordKey, segmentId), + ); yield* call( - loadAdHocMesh, - position, - additionalCoordinates || undefined, - segmentId, - shouldBeRemoved, - layerName, - { - mappingName, - mappingType, - }, + [ErrorHandling, ErrorHandling.assert], + !meshInfo.isPrecomputed, + "_refreshMeshWithMap was called for a precomputed mesh.", ); - shouldBeRemoved = false; - } - // see comment in l. 292 - const { segmentMeshController } = getSceneController(); - if (!segmentMeshController.hasMesh(segmentId, layerName, additionalCoordinates)) { - yield* put(removeMeshAction(layerName, meshInfo.segmentId)); - } - yield* put(finishedLoadingMeshAction(layerName, segmentId)); + if (meshInfo.isPrecomputed) return; + const { mappingName, mappingType } = meshInfo; + const meshPositions = threeDMap.entries().filter(([value, _position]) => value); + + if (meshPositions.length === 0) { + return; + } + + yield* put(startedLoadingMeshAction(layerName, segmentId)); + // Remove mesh from cache. + yield* call(removeMesh, removeMeshAction(layerName, segmentId), false); + // The mesh should only be removed once after re-fetching the mesh first position. + let shouldBeRemoved = true; + + for (const [, position] of meshPositions) { + // Reload the mesh at the given position if it isn't already loaded there. + // This is done to ensure that every voxel of the mesh is reloaded. + yield* call( + loadAdHocMesh, + position, + additionalCoordinates || undefined, + segmentId, + shouldBeRemoved, + layerName, + { + mappingName, + mappingType, + }, + ); + shouldBeRemoved = false; + } + // see comment in l. 292 + const { segmentMeshController } = getSceneController(); + if (!segmentMeshController.hasMesh(segmentId, layerName, additionalCoordinates)) { + yield* put(removeMeshAction(layerName, meshInfo.segmentId)); + } + yield* put(finishedLoadingMeshAction(layerName, segmentId)); + } catch (error) {} } /* diff --git a/frontend/javascripts/oxalis/view/action-bar/create_animation_modal.tsx b/frontend/javascripts/oxalis/view/action-bar/create_animation_modal.tsx index 1d39df13755..ee3320354a6 100644 --- a/frontend/javascripts/oxalis/view/action-bar/create_animation_modal.tsx +++ b/frontend/javascripts/oxalis/view/action-bar/create_animation_modal.tsx @@ -5,7 +5,7 @@ import React, { useState } from "react"; import { startRenderAnimationJob } from "admin/admin_rest_api"; import Toast from "libs/toast"; import _ from "lodash"; -import Store, { OxalisState, UserBoundingBox } from "oxalis/store"; +import Store, { MeshInformation, OxalisState, UserBoundingBox } from "oxalis/store"; import { getColorLayers, @@ -179,7 +179,10 @@ function CreateAnimationModal(props: Props) { if (visibleSegmentationLayer) { const availableMeshes = state.localSegmentationData[visibleSegmentationLayer.name].meshes; - meshSegmentIds = Object.values(availableMeshes) + if(availableMeshes == null){ + throw new Error("There is no mesh data in localSegmentationData."); + } + meshSegmentIds = Object.values(availableMeshes as Record) .filter((mesh) => mesh.isVisible && mesh.isPrecomputed) .map((mesh) => mesh.segmentId); diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx index 9fedc4fdb7c..b2537b2f0e0 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx @@ -52,6 +52,8 @@ import { } from "oxalis/model/accessors/flycam_accessor"; import { getActiveSegmentationTracing, + getMeshesForAdditionalCoordinates, + getMeshesForCurrentAdditionalCoordinates, getVisibleSegments, hasEditableMapping, } from "oxalis/model/accessors/volumetracing_accessor"; @@ -166,15 +168,7 @@ const mapStateToProps = (state: OxalisState): StateProps => { const isVisibleButUneditableSegmentationLayerActive = visibleSegmentationLayer != null && visibleSegmentationLayer.tracingId == null; - const addCoordString = getAdditionalCoordinatesAsString(state.flycam.additionalCoordinates); - - let meshesForCurrentAdditionalCoordinates; - if (visibleSegmentationLayer != null) { - const meshRecords = state.localSegmentationData[visibleSegmentationLayer.name].meshes; - if(meshRecords != null && meshRecords[addCoordString] != null){ - meshesForCurrentAdditionalCoordinates = meshRecords[addCoordString]; - } - } + const meshesForCurrentAdditionalCoordinates = getMeshesForCurrentAdditionalCoordinates(state); return { activeCellId: activeVolumeTracing?.activeCellId, From 41fb5732bd8e1b1b7b51e51d1331e2d2db580434 Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Wed, 29 Nov 2023 17:40:43 +0100 Subject: [PATCH 30/43] WIP: refactor code after code review --- .../model/accessors/volumetracing_accessor.ts | 29 ++- .../oxalis/model/sagas/mesh_saga.ts | 173 ++++++++---------- .../segments_tab/segments_view.tsx | 5 +- 3 files changed, 100 insertions(+), 107 deletions(-) diff --git a/frontend/javascripts/oxalis/model/accessors/volumetracing_accessor.ts b/frontend/javascripts/oxalis/model/accessors/volumetracing_accessor.ts index 3dc37f027d4..b858de885ae 100644 --- a/frontend/javascripts/oxalis/model/accessors/volumetracing_accessor.ts +++ b/frontend/javascripts/oxalis/model/accessors/volumetracing_accessor.ts @@ -670,18 +670,31 @@ export function hasAgglomerateMapping(state: OxalisState) { export function getMeshesForAdditionalCoordinates( state: OxalisState, additionalCoordinates: AdditionalCoordinate[] | null, + layerName: string, ) { - const visibleSegmentationLayer = getVisibleSegmentationLayer(state); const addCoordKey = getAdditionalCoordinatesAsString(additionalCoordinates); - if (visibleSegmentationLayer != null) { - const meshRecords = state.localSegmentationData[visibleSegmentationLayer.name].meshes; - if (meshRecords != null && meshRecords[addCoordKey] != null) { - return meshRecords[addCoordKey]; - } + const meshRecords = state.localSegmentationData[layerName].meshes; + if (meshRecords != null && meshRecords[addCoordKey] != null) { + return meshRecords[addCoordKey]; } return null; } -export function getMeshesForCurrentAdditionalCoordinates(state: OxalisState) { - return getMeshesForAdditionalCoordinates(state, state.flycam.additionalCoordinates); +export function getMeshesForCurrentAdditionalCoordinates(state: OxalisState, layerName: string) { + return getMeshesForAdditionalCoordinates(state, state.flycam.additionalCoordinates, layerName); +} + +export function getMeshInfoForSegment( + state: OxalisState, + additionalCoordinates: AdditionalCoordinate[] | null, + layerName: string, + segmentId: number, +) { + const meshesForAddCoords = getMeshesForAdditionalCoordinates( + state, + additionalCoordinates, + layerName, + ); + if (meshesForAddCoords == null) return null; + return meshesForAddCoords[segmentId]; } diff --git a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts index 876dac10e5c..1339d190902 100644 --- a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts @@ -63,6 +63,7 @@ import window from "libs/window"; import { getActiveSegmentationTracing, getEditableMappingForVolumeTracingId, + getMeshInfoForSegment, getTracingForSegmentationLayer, } from "oxalis/model/accessors/volumetracing_accessor"; import { saveNowAction } from "oxalis/model/actions/save_actions"; @@ -124,10 +125,10 @@ function getOrAddMapForSegment( additionalCoordinates?: AdditionalCoordinate[] | null, ): ThreeDMap { let additionalCoordinatesString = getAdditionalCoordinatesAsString(additionalCoordinates); - adhocMeshesMapByLayer[additionalCoordinatesString] = - adhocMeshesMapByLayer[additionalCoordinatesString] || new Map(); - adhocMeshesMapByLayer[additionalCoordinatesString][layerName] = - adhocMeshesMapByLayer[additionalCoordinatesString][layerName] || new Map(); + + const keys = [additionalCoordinatesString, layerName]; + // create new map if adhocMeshesMapByLayer[additionalCoordinatesString][layerName] doesn't exist yet. + _.set(adhocMeshesMapByLayer, keys, _.get(adhocMeshesMapByLayer, keys, new Map())); const meshesMap = adhocMeshesMapByLayer[additionalCoordinatesString][layerName]; const maybeMap = meshesMap.get(segmentId); @@ -551,60 +552,36 @@ function* refreshMeshes(): Saga { } } -const selectMeshDataFromState = ( - state: OxalisState, - layerName: string, - addCoordKey: string, - segmentId: number, -) => { - if (state.localSegmentationData[layerName].meshes == null) { - throw new Error("There is no mesh data in localSegmentationData."); - } - if (state.localSegmentationData[layerName].meshes![addCoordKey] == null) { - throw new Error( - `There is no mesh data for add. coord. ${addCoordKey} in localSegmentationData.`, - ); - } - return state.localSegmentationData[layerName].meshes![addCoordKey]![segmentId]; -}; - function* refreshMesh(action: RefreshMeshAction): Saga { - const additionalCoordinatesObject = yield* select((state) => state.flycam.additionalCoordinates); - let additionalCoordinates = getAdditionalCoordinatesAsString(additionalCoordinatesObject); + const additionalCoordinates = yield* select((state) => state.flycam.additionalCoordinates); + let addCoordKey = getAdditionalCoordinatesAsString(additionalCoordinates); const { segmentId, layerName } = action; - try { - const meshInfo = yield* select((state) => - selectMeshDataFromState(state, layerName, additionalCoordinates, segmentId), - ); + const meshInfo = yield* select((state) => + getMeshInfoForSegment(state, additionalCoordinates, layerName, segmentId), + ); - if (meshInfo.isPrecomputed) { - yield* put(removeMeshAction(layerName, meshInfo.segmentId)); - yield* put( - loadPrecomputedMeshAction( - meshInfo.segmentId, - meshInfo.seedPosition, - meshInfo.seedAdditionalCoordinates, - meshInfo.meshFileName, - layerName, - ), - ); - } else { - if (adhocMeshesMapByLayer[additionalCoordinates] == null) return; - const threeDMap = - adhocMeshesMapByLayer[additionalCoordinates][action.layerName].get(segmentId); - if (threeDMap == null) return; - yield* call( - _refreshMeshWithMap, - segmentId, - threeDMap, + if (meshInfo == null) { + throw new Error("Mesh refreshing failed due to lack of mesh data in store."); + } + + if (meshInfo.isPrecomputed) { + yield* put(removeMeshAction(layerName, meshInfo.segmentId)); + yield* put( + loadPrecomputedMeshAction( + meshInfo.segmentId, + meshInfo.seedPosition, + meshInfo.seedAdditionalCoordinates, + meshInfo.meshFileName, layerName, - additionalCoordinatesObject, - ); - } - } catch (error) { - console.error("Mesh refreshing failed due to the following error:", error); + ), + ); + } else { + if (adhocMeshesMapByLayer[addCoordKey] == null) return; + const threeDMap = adhocMeshesMapByLayer[addCoordKey][action.layerName].get(segmentId); + if (threeDMap == null) return; + yield* call(_refreshMeshWithMap, segmentId, threeDMap, layerName, additionalCoordinates); } } @@ -614,54 +591,54 @@ function* _refreshMeshWithMap( layerName: string, additionalCoordinates: AdditionalCoordinate[] | null, ): Saga { - const addCoordKey = getAdditionalCoordinatesAsString(additionalCoordinates); - try { - const meshInfo = yield* select((state) => - selectMeshDataFromState(state, layerName, addCoordKey, segmentId), - ); - yield* call( - [ErrorHandling, ErrorHandling.assert], - !meshInfo.isPrecomputed, - "_refreshMeshWithMap was called for a precomputed mesh.", - ); - if (meshInfo.isPrecomputed) return; - const { mappingName, mappingType } = meshInfo; - const meshPositions = threeDMap.entries().filter(([value, _position]) => value); + const meshInfo = yield* select((state) => + getMeshInfoForSegment(state, additionalCoordinates, layerName, segmentId), + ); + if (meshInfo == null) { + throw new Error("Mesh refreshing failed due to lack of mesh data in store."); + } + yield* call( + [ErrorHandling, ErrorHandling.assert], + !meshInfo.isPrecomputed, + "_refreshMeshWithMap was called for a precomputed mesh.", + ); + if (meshInfo.isPrecomputed) return; + const { mappingName, mappingType } = meshInfo; + const meshPositions = threeDMap.entries().filter(([value, _position]) => value); - if (meshPositions.length === 0) { - return; - } + if (meshPositions.length === 0) { + return; + } - yield* put(startedLoadingMeshAction(layerName, segmentId)); - // Remove mesh from cache. - yield* call(removeMesh, removeMeshAction(layerName, segmentId), false); - // The mesh should only be removed once after re-fetching the mesh first position. - let shouldBeRemoved = true; - - for (const [, position] of meshPositions) { - // Reload the mesh at the given position if it isn't already loaded there. - // This is done to ensure that every voxel of the mesh is reloaded. - yield* call( - loadAdHocMesh, - position, - additionalCoordinates || undefined, - segmentId, - shouldBeRemoved, - layerName, - { - mappingName, - mappingType, - }, - ); - shouldBeRemoved = false; - } - // see comment in l. 292 - const { segmentMeshController } = getSceneController(); - if (!segmentMeshController.hasMesh(segmentId, layerName, additionalCoordinates)) { - yield* put(removeMeshAction(layerName, meshInfo.segmentId)); - } - yield* put(finishedLoadingMeshAction(layerName, segmentId)); - } catch (error) {} + yield* put(startedLoadingMeshAction(layerName, segmentId)); + // Remove mesh from cache. + yield* call(removeMesh, removeMeshAction(layerName, segmentId), false); + // The mesh should only be removed once after re-fetching the mesh first position. + let shouldBeRemoved = true; + + for (const [, position] of meshPositions) { + // Reload the mesh at the given position if it isn't already loaded there. + // This is done to ensure that every voxel of the mesh is reloaded. + yield* call( + loadAdHocMesh, + position, + additionalCoordinates || undefined, + segmentId, + shouldBeRemoved, + layerName, + { + mappingName, + mappingType, + }, + ); + shouldBeRemoved = false; + } + // see comment in l. 292 + const { segmentMeshController } = getSceneController(); + if (!segmentMeshController.hasMesh(segmentId, layerName, additionalCoordinates)) { + yield* put(removeMeshAction(layerName, meshInfo.segmentId)); + } + yield* put(finishedLoadingMeshAction(layerName, segmentId)); } /* diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx index b2537b2f0e0..f8c4b76a8c0 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx @@ -168,7 +168,10 @@ const mapStateToProps = (state: OxalisState): StateProps => { const isVisibleButUneditableSegmentationLayerActive = visibleSegmentationLayer != null && visibleSegmentationLayer.tracingId == null; - const meshesForCurrentAdditionalCoordinates = getMeshesForCurrentAdditionalCoordinates(state); + + const meshesForCurrentAdditionalCoordinates = + visibleSegmentationLayer != null ? + getMeshesForCurrentAdditionalCoordinates(state, visibleSegmentationLayer?.name) : undefined; return { activeCellId: activeVolumeTracing?.activeCellId, From 281a8053085a41bb3da7ba5c677888842bedc584 Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Wed, 29 Nov 2023 17:44:54 +0100 Subject: [PATCH 31/43] lint :) --- frontend/javascripts/oxalis/api/api_latest.ts | 1 - frontend/javascripts/oxalis/controller/url_manager.ts | 2 +- .../oxalis/model/accessors/volumetracing_accessor.ts | 2 +- .../javascripts/oxalis/model/reducers/flycam_reducer.ts | 6 +----- frontend/javascripts/oxalis/model/sagas/mesh_saga.ts | 2 +- .../view/right-border-tabs/segments_tab/segments_view.tsx | 1 - 6 files changed, 4 insertions(+), 10 deletions(-) diff --git a/frontend/javascripts/oxalis/api/api_latest.ts b/frontend/javascripts/oxalis/api/api_latest.ts index f5cce350785..a5d5fde7c9e 100644 --- a/frontend/javascripts/oxalis/api/api_latest.ts +++ b/frontend/javascripts/oxalis/api/api_latest.ts @@ -174,7 +174,6 @@ import { setLayerTransformsAction } from "oxalis/model/actions/dataset_actions"; import { ResolutionInfo } from "oxalis/model/helpers/resolution_info"; import { type AdditionalCoordinate } from "types/api_flow_types"; import { getMaximumGroupId } from "oxalis/model/reducers/skeletontracing_reducer_helpers"; -import { EMPTY_KEY_VALUE } from "oxalis/model/bucket_data_handling/abstract_cuckoo_table"; type TransformSpec = | { type: "scale"; args: [Vector3, Vector3] } diff --git a/frontend/javascripts/oxalis/controller/url_manager.ts b/frontend/javascripts/oxalis/controller/url_manager.ts index e36b8402bd6..f012f77ebdb 100644 --- a/frontend/javascripts/oxalis/controller/url_manager.ts +++ b/frontend/javascripts/oxalis/controller/url_manager.ts @@ -11,7 +11,7 @@ import type { OxalisState, MappingType, MeshInformation } from "oxalis/store"; import Store from "oxalis/store"; import * as Utils from "libs/utils"; import type { ViewMode, Vector3 } from "oxalis/constants"; -import constants, { ViewModeValues, MappingStatusEnum, EMPTY_OBJECT } from "oxalis/constants"; +import constants, { ViewModeValues, MappingStatusEnum } from "oxalis/constants"; import window, { location } from "libs/window"; import ErrorHandling from "libs/error_handling"; import Toast from "libs/toast"; diff --git a/frontend/javascripts/oxalis/model/accessors/volumetracing_accessor.ts b/frontend/javascripts/oxalis/model/accessors/volumetracing_accessor.ts index b858de885ae..3d4881e305a 100644 --- a/frontend/javascripts/oxalis/model/accessors/volumetracing_accessor.ts +++ b/frontend/javascripts/oxalis/model/accessors/volumetracing_accessor.ts @@ -674,7 +674,7 @@ export function getMeshesForAdditionalCoordinates( ) { const addCoordKey = getAdditionalCoordinatesAsString(additionalCoordinates); const meshRecords = state.localSegmentationData[layerName].meshes; - if (meshRecords != null && meshRecords[addCoordKey] != null) { + if (meshRecords?.[addCoordKey] != null) { return meshRecords[addCoordKey]; } return null; diff --git a/frontend/javascripts/oxalis/model/reducers/flycam_reducer.ts b/frontend/javascripts/oxalis/model/reducers/flycam_reducer.ts index a215b73d278..f139928df7d 100644 --- a/frontend/javascripts/oxalis/model/reducers/flycam_reducer.ts +++ b/frontend/javascripts/oxalis/model/reducers/flycam_reducer.ts @@ -7,16 +7,12 @@ import type { OxalisState } from "oxalis/store"; import type { Vector3 } from "oxalis/constants"; import { getBaseVoxelFactors } from "oxalis/model/scaleinfo"; import { - getAdditionalCoordinatesAsString, getValidZoomRangeForUser, ZOOM_STEP_INTERVAL, } from "oxalis/model/accessors/flycam_accessor"; import Dimensions from "oxalis/model/dimensions"; import * as Utils from "libs/utils"; -import { - getUnifiedAdditionalCoordinates, - getVisibleSegmentationLayer, -} from "../accessors/dataset_accessor"; +import { getUnifiedAdditionalCoordinates } from "../accessors/dataset_accessor"; function cloneMatrix(m: Matrix4x4): Matrix4x4 { return [ diff --git a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts index 1339d190902..29766c21d31 100644 --- a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts @@ -7,7 +7,7 @@ import type { APIDataset, APIMeshFile, APISegmentationLayer } from "types/api_fl import { mergeBufferGeometries, mergeVertices } from "libs/BufferGeometryUtils"; import Deferred from "libs/async/deferred"; -import Store, { OxalisState } from "oxalis/store"; +import Store from "oxalis/store"; import { getResolutionInfo, getMappingInfo, diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx index f8c4b76a8c0..ea199807273 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx @@ -52,7 +52,6 @@ import { } from "oxalis/model/accessors/flycam_accessor"; import { getActiveSegmentationTracing, - getMeshesForAdditionalCoordinates, getMeshesForCurrentAdditionalCoordinates, getVisibleSegments, hasEditableMapping, From 18b15661fb6ee6a3d0797935a6f6fd249d84e80a Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Mon, 4 Dec 2023 16:49:07 +0100 Subject: [PATCH 32/43] WIP: work on code review --- .../controller/segment_mesh_controller.ts | 2 +- .../model/reducers/annotation_reducer.ts | 16 +- .../oxalis/model/sagas/mesh_saga.ts | 139 +++++++++--------- 3 files changed, 82 insertions(+), 75 deletions(-) diff --git a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts index b525ae4c825..f910c23793a 100644 --- a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts +++ b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts @@ -134,7 +134,7 @@ export default class SegmentMeshController { removeMeshById( segmentationId: number, layerName: string, - additionalCoordinates: AdditionalCoordinate[] | null, + additionalCoordinates: AdditionalCoordinate[] | null | undefined, ): void { // either remove a mesh for specific additional coordinates, or remove all meshes for a segment per default by passing null as additionalCoordinates. let additionalCoordinatesToRemoveMeshes; diff --git a/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts b/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts index e90b7bba779..8946b184e5c 100644 --- a/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts +++ b/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts @@ -232,12 +232,12 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { if (state.localSegmentationData[layerName].meshes == null) { throw Error("No mesh data found in state.localSegmentationData."); } - for (const additionalCoordString of Object.keys( + for (const additionalCoordKey of Object.keys( state.localSegmentationData[layerName].meshes!, )) { const { [segmentId]: _, ...remainingMeshes } = state.localSegmentationData[layerName] - .meshes![additionalCoordString] as Record; - newMeshes[additionalCoordString] = remainingMeshes; + .meshes![additionalCoordKey] as Record; + newMeshes[additionalCoordKey] = remainingMeshes; } return update(state, { localSegmentationData: { @@ -270,7 +270,9 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { mappingName, mappingType, }; - const addCoordString = getAdditionalCoordinatesAsString(state.flycam.additionalCoordinates); + const additionalCoordKey = getAdditionalCoordinatesAsString( + state.flycam.additionalCoordinates, + ); let stateWithCurrentAddCoords = state; @@ -278,13 +280,13 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { if ( state.localSegmentationData[layerName] == null || state.localSegmentationData[layerName].meshes == null || - state.localSegmentationData[layerName].meshes![addCoordString] == null + state.localSegmentationData[layerName].meshes![additionalCoordKey] == null ) { stateWithCurrentAddCoords = update(state, { localSegmentationData: { [layerName]: { meshes: { - [addCoordString]: { $set: [] }, + [additionalCoordKey]: { $set: [] }, }, }, }, @@ -295,7 +297,7 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { localSegmentationData: { [layerName]: { meshes: { - [addCoordString]: { + [additionalCoordKey]: { [segmentId]: { $set: meshInfo, }, diff --git a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts index 29766c21d31..e5901695692 100644 --- a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts @@ -124,12 +124,12 @@ function getOrAddMapForSegment( segmentId: number, additionalCoordinates?: AdditionalCoordinate[] | null, ): ThreeDMap { - let additionalCoordinatesString = getAdditionalCoordinatesAsString(additionalCoordinates); + let additionalCoordKey = getAdditionalCoordinatesAsString(additionalCoordinates); - const keys = [additionalCoordinatesString, layerName]; + const keys = [additionalCoordKey, layerName]; // create new map if adhocMeshesMapByLayer[additionalCoordinatesString][layerName] doesn't exist yet. _.set(adhocMeshesMapByLayer, keys, _.get(adhocMeshesMapByLayer, keys, new Map())); - const meshesMap = adhocMeshesMapByLayer[additionalCoordinatesString][layerName]; + const meshesMap = adhocMeshesMapByLayer[additionalCoordKey][layerName]; const maybeMap = meshesMap.get(segmentId); if (maybeMap == null) { @@ -144,16 +144,16 @@ function getOrAddMapForSegment( function removeMapForSegment( layerName: string, segmentId: number, - additionalCoordinateString: string, + additionalCoordinateKey: string, ): void { if ( - adhocMeshesMapByLayer[additionalCoordinateString] == null || - adhocMeshesMapByLayer[additionalCoordinateString][layerName] == null + adhocMeshesMapByLayer[additionalCoordinateKey] == null || + adhocMeshesMapByLayer[additionalCoordinateKey][layerName] == null ) { return; } - adhocMeshesMapByLayer[additionalCoordinateString][layerName].delete(segmentId); + adhocMeshesMapByLayer[additionalCoordinateKey][layerName].delete(segmentId); } function getZoomedCubeSize(zoomStep: number, resolutionInfo: ResolutionInfo): Vector3 { @@ -289,12 +289,19 @@ function* loadAdHocMesh( action.layerName === layer.name, ), }); + removeMeshWithoutVoxelsFromStore(segmentId, layer.name, seedAdditionalCoordinates); +} +function removeMeshWithoutVoxelsFromStore( + segmentId: number, + layerName: string, + additionalCoordinates: AdditionalCoordinate[] | undefined | null, +) { // If no voxels were added to the scene (e.g. because the segment doesn't have any voxels in this n-dimension), // remove it from the store's state aswell. const { segmentMeshController } = getSceneController(); - if (!segmentMeshController.hasMesh(segmentId, layer.name, seedAdditionalCoordinates)) { - yield* put(removeMeshAction(layer.name, segmentId)); + if (!segmentMeshController.hasMesh(segmentId, layerName, additionalCoordinates)) { + Store.dispatch(removeMeshAction(layerName, segmentId)); } } @@ -356,6 +363,10 @@ function* loadFullAdHocMesh( ) : [clippedPosition]; + if (positionsToRequest.length === 0) { + const { segmentMeshController } = getSceneController(); + segmentMeshController.removeMeshById(segmentId, layer.name, additionalCoordinates); + } while (positionsToRequest.length > 0) { const currentPosition = positionsToRequest.shift(); if (currentPosition == null) { @@ -525,17 +536,17 @@ function* refreshMeshes(): Saga { const currentlyModifiedCells = new Set(modifiedCells); modifiedCells.clear(); - const additionalCoordinatesObject = yield* select((state) => state.flycam.additionalCoordinates); - const additionalCoordinates = getAdditionalCoordinatesAsString(additionalCoordinatesObject); + const additionalCoordinates = yield* select((state) => state.flycam.additionalCoordinates); + const additionalCoordKey = getAdditionalCoordinatesAsString(additionalCoordinates); const segmentationLayer = Model.getVisibleSegmentationLayer(); if (!segmentationLayer) { return; } - adhocMeshesMapByLayer[additionalCoordinates][segmentationLayer.name] = - adhocMeshesMapByLayer[additionalCoordinates][segmentationLayer.name] || new Map(); - const meshesMapForLayer = adhocMeshesMapByLayer[additionalCoordinates][segmentationLayer.name]; + adhocMeshesMapByLayer[additionalCoordKey][segmentationLayer.name] = + adhocMeshesMapByLayer[additionalCoordKey][segmentationLayer.name] || new Map(); + const meshesMapForLayer = adhocMeshesMapByLayer[additionalCoordKey][segmentationLayer.name]; for (const [segmentId, threeDMap] of Array.from(meshesMapForLayer.entries())) { if (!currentlyModifiedCells.has(segmentId)) { @@ -547,14 +558,14 @@ function* refreshMeshes(): Saga { segmentId, threeDMap, segmentationLayer.name, - additionalCoordinatesObject, + additionalCoordinates, ); } } function* refreshMesh(action: RefreshMeshAction): Saga { const additionalCoordinates = yield* select((state) => state.flycam.additionalCoordinates); - let addCoordKey = getAdditionalCoordinatesAsString(additionalCoordinates); + let additionalCoordKey = getAdditionalCoordinatesAsString(additionalCoordinates); const { segmentId, layerName } = action; @@ -578,9 +589,11 @@ function* refreshMesh(action: RefreshMeshAction): Saga { ), ); } else { - if (adhocMeshesMapByLayer[addCoordKey] == null) return; - const threeDMap = adhocMeshesMapByLayer[addCoordKey][action.layerName].get(segmentId); - if (threeDMap == null) return; + if (adhocMeshesMapByLayer[additionalCoordKey] == null) return; + const threeDMap = adhocMeshesMapByLayer[additionalCoordKey][action.layerName].get(segmentId); + if (threeDMap == null) { + return; + } yield* call(_refreshMeshWithMap, segmentId, threeDMap, layerName, additionalCoordinates); } } @@ -610,7 +623,7 @@ function* _refreshMeshWithMap( return; } - yield* put(startedLoadingMeshAction(layerName, segmentId)); + //yield* put(startedLoadingMeshAction(layerName, segmentId)); TODO can i leave this out? // Remove mesh from cache. yield* call(removeMesh, removeMeshAction(layerName, segmentId), false); // The mesh should only be removed once after re-fetching the mesh first position. @@ -633,12 +646,8 @@ function* _refreshMeshWithMap( ); shouldBeRemoved = false; } - // see comment in l. 292 - const { segmentMeshController } = getSceneController(); - if (!segmentMeshController.hasMesh(segmentId, layerName, additionalCoordinates)) { - yield* put(removeMeshAction(layerName, meshInfo.segmentId)); - } - yield* put(finishedLoadingMeshAction(layerName, segmentId)); + //yield* put(finishedLoadingMeshAction(layerName, segmentId)); TODO can I leave this out? + removeMeshWithoutVoxelsFromStore(segmentId, layerName, additionalCoordinates); } /* @@ -1169,54 +1178,50 @@ export function* handleAdditionalCoordinateUpdate(): Saga { yield* take("WK_READY"); let previousAdditionalCoordinates = yield* select((state) => state.flycam.additionalCoordinates); + const { segmentMeshController } = yield* call(getSceneController); while (true) { const action = (yield* take(["SET_ADDITIONAL_COORDINATES"]) as any) as FlycamAction; //satisfy TS - if (action.type === "SET_ADDITIONAL_COORDINATES") { - const { segmentMeshController } = yield* call(getSceneController); - const meshRecords = yield* call({ - context: segmentMeshController, - fn: () => segmentMeshController.meshesGroupsPerSegmentationId, - }); - - if (action.values == null || action.values.length === 0) break; - const newAdditionalCoordinates = getAdditionalCoordinatesAsString(action.values); - - for (const additionalCoordinates of [action.values, previousAdditionalCoordinates]) { - const currentAdditionalCoordinatesAsString = - getAdditionalCoordinatesAsString(additionalCoordinates); - const shouldBeVisible = currentAdditionalCoordinatesAsString === newAdditionalCoordinates; - const recordsOfLayers = meshRecords[currentAdditionalCoordinatesAsString]; - if (recordsOfLayers != null) { - for (const [layerName, recordsForOneLayer] of Object.entries(recordsOfLayers)) { - const segmentIds = Object.keys(recordsForOneLayer); - for (const segmentIdAsString of segmentIds) { - const segmentId = parseInt(segmentIdAsString); - yield* put( - updateMeshVisibilityAction( - layerName, - segmentId, - shouldBeVisible, - additionalCoordinates || undefined, - ), - ); - yield* call( - { - context: segmentMeshController, - fn: segmentMeshController.setMeshVisibility, - }, - segmentId, - shouldBeVisible, - layerName, - additionalCoordinates, - ); - } - } + if (action.type !== "SET_ADDITIONAL_COORDINATES") { + throw new Error("Unexpected action type"); + } + const meshRecords = segmentMeshController.meshesGroupsPerSegmentationId; + + if (action.values == null || action.values.length === 0) break; + const newAdditionalCoordinates = getAdditionalCoordinatesAsString(action.values); + + for (const additionalCoordinates of [action.values, previousAdditionalCoordinates]) { + const currentAdditionalCoordinatesAsString = + getAdditionalCoordinatesAsString(additionalCoordinates); + const shouldBeVisible = currentAdditionalCoordinatesAsString === newAdditionalCoordinates; + const recordsOfLayers = meshRecords[currentAdditionalCoordinatesAsString] || {}; + for (const [layerName, recordsForOneLayer] of Object.entries(recordsOfLayers)) { + const segmentIds = Object.keys(recordsForOneLayer); + for (const segmentIdAsString of segmentIds) { + const segmentId = parseInt(segmentIdAsString); + yield* put( + updateMeshVisibilityAction( + layerName, + segmentId, + shouldBeVisible, + additionalCoordinates || undefined, + ), + ); + yield* call( + { + context: segmentMeshController, + fn: segmentMeshController.setMeshVisibility, + }, + segmentId, + shouldBeVisible, + layerName, + additionalCoordinates, + ); } } - previousAdditionalCoordinates = yield* select((state) => state.flycam.additionalCoordinates); } + previousAdditionalCoordinates = yield* select((state) => state.flycam.additionalCoordinates); } } From 369d661cc477de791055b13e0a8a924273db38f0 Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Mon, 4 Dec 2023 21:17:46 +0100 Subject: [PATCH 33/43] WIP: fix mesh download --- .../controller/segment_mesh_controller.ts | 4 +- .../oxalis/model/actions/flycam_actions.ts | 2 +- .../oxalis/model/sagas/mesh_saga.ts | 37 ++++++++++++------- .../action-bar/create_animation_modal.tsx | 2 +- .../segments_tab/segment_list_item.tsx | 4 +- .../segments_tab/segments_view.tsx | 10 ++--- 6 files changed, 35 insertions(+), 24 deletions(-) diff --git a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts index f910c23793a..07aa14f1a64 100644 --- a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts +++ b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts @@ -60,7 +60,7 @@ export default class SegmentMeshController { null, NO_LOD_MESH_INDEX, layerName, - additionalCoordinates || null, + additionalCoordinates, ); } @@ -102,7 +102,7 @@ export default class SegmentMeshController { scale: Vector3 | null = null, lod: number, layerName: string, - additionalCoordinates: AdditionalCoordinate[] | null, + additionalCoordinates: AdditionalCoordinate[] | null | undefined, ): void { const additionalCoordinatesString = getAdditionalCoordinatesAsString(additionalCoordinates); const newGroup = new THREE.Group(); diff --git a/frontend/javascripts/oxalis/model/actions/flycam_actions.ts b/frontend/javascripts/oxalis/model/actions/flycam_actions.ts index 317ef7cf0a7..dacc8b1ef0d 100644 --- a/frontend/javascripts/oxalis/model/actions/flycam_actions.ts +++ b/frontend/javascripts/oxalis/model/actions/flycam_actions.ts @@ -80,7 +80,7 @@ export const setPositionAction = (position: Vector3, dimensionToSkip?: number | dimensionToSkip, } as const); -export const setAdditionalCoordinatesAction = (values: AdditionalCoordinate[] | null) => +export const setAdditionalCoordinatesAction = (values: AdditionalCoordinate[] | null | undefined) => ({ type: "SET_ADDITIONAL_COORDINATES", values, diff --git a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts index e5901695692..791b88c27af 100644 --- a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts @@ -351,7 +351,8 @@ function* loadFullAdHocMesh( const usePositionsFromSegmentStats = volumeTracing?.hasSegmentIndex && !volumeTracing.mappingIsEditable && - visibleSegmentationLayer?.tracingId != null; + visibleSegmentationLayer?.tracingId != null && + additionalCoordinates == null; // TODO remove in https://github.com/scalableminds/webknossos/pull/7411 let positionsToRequest = usePositionsFromSegmentStats ? yield* getChunkPositionsFromSegmentStats( tracingStoreHost, @@ -364,6 +365,7 @@ function* loadFullAdHocMesh( : [clippedPosition]; if (positionsToRequest.length === 0) { + //TODO may be null const { segmentMeshController } = getSceneController(); segmentMeshController.removeMeshById(segmentId, layer.name, additionalCoordinates); } @@ -1103,21 +1105,25 @@ function* downloadMeshCellsAsZIP( const additionalCoordinates = yield* select((state) => state.flycam.additionalCoordinates); try { const addFileToZipWriterPromises = segments.map((element) => { - const geometry = segmentMeshController.getMeshGeometryInBestLOD( - element.segmentId, - element.layerName, - additionalCoordinates, - ); + if ( + segmentMeshController.hasMesh(element.segmentId, element.layerName, additionalCoordinates) + ) { + const geometry = segmentMeshController.getMeshGeometryInBestLOD( + element.segmentId, + element.layerName, + additionalCoordinates, + ); - if (geometry == null) { - const errorMessage = messages["tracing.not_mesh_available_to_download"]; - Toast.error(errorMessage, { - sticky: false, - }); - return; + if (geometry == null) { + const errorMessage = messages["tracing.not_mesh_available_to_download"]; + Toast.error(errorMessage, { + sticky: false, + }); + return; + } + const stlDataReader = new Zip.BlobReader(getSTLBlob(geometry, element.segmentId)); + return zipWriter.add(`${element.segmentName}-${element.segmentId}.stl`, stlDataReader); } - const stlDataReader = new Zip.BlobReader(getSTLBlob(geometry, element.segmentId)); - return zipWriter.add(`${element.segmentName}-${element.segmentId}.stl`, stlDataReader); }); yield all(addFileToZipWriterPromises); const result = yield* call([zipWriter, zipWriter.close]); @@ -1175,6 +1181,9 @@ function* handleMeshVisibilityChange(action: UpdateMeshVisibilityAction): Saga { + // We want to prevent iterating through all additional coordinates to adjust the mesh visibility, so we store the + // previous additional coordinates in this method. Thus we have to catch SET_ADDITIONAL_COORDINATES actions in a + // while-true loop and register this saga in the root saga instead of calling from the mesh saga. yield* take("WK_READY"); let previousAdditionalCoordinates = yield* select((state) => state.flycam.additionalCoordinates); diff --git a/frontend/javascripts/oxalis/view/action-bar/create_animation_modal.tsx b/frontend/javascripts/oxalis/view/action-bar/create_animation_modal.tsx index ee3320354a6..7fdf6ceb109 100644 --- a/frontend/javascripts/oxalis/view/action-bar/create_animation_modal.tsx +++ b/frontend/javascripts/oxalis/view/action-bar/create_animation_modal.tsx @@ -179,7 +179,7 @@ function CreateAnimationModal(props: Props) { if (visibleSegmentationLayer) { const availableMeshes = state.localSegmentationData[visibleSegmentationLayer.name].meshes; - if(availableMeshes == null){ + if (availableMeshes == null) { throw new Error("There is no mesh data in localSegmentationData."); } meshSegmentIds = Object.values(availableMeshes as Record) diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx index 34b5edd8692..c5eab639667 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx @@ -233,7 +233,9 @@ function _MeshInfoItem(props: { setPosition: (arg0: Vector3) => void; setAdditionalCoordinates: (additionalCoordinates: AdditionalCoordinate[] | undefined) => void; }) { - const additionalCoordinates = useSelector((state: OxalisState) =>state.flycam.additionalCoordinates) + const additionalCoordinates = useSelector( + (state: OxalisState) => state.flycam.additionalCoordinates, + ); const dispatch = useDispatch(); const onChangeMeshVisibility = (layerName: string, id: number, isVisible: boolean) => { dispatch(updateMeshVisibilityAction(layerName, id, isVisible, mesh?.seedAdditionalCoordinates)); diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx index ea199807273..219860fd192 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx @@ -167,10 +167,10 @@ const mapStateToProps = (state: OxalisState): StateProps => { const isVisibleButUneditableSegmentationLayerActive = visibleSegmentationLayer != null && visibleSegmentationLayer.tracingId == null; - - const meshesForCurrentAdditionalCoordinates = - visibleSegmentationLayer != null ? - getMeshesForCurrentAdditionalCoordinates(state, visibleSegmentationLayer?.name) : undefined; + const meshesForCurrentAdditionalCoordinates = + visibleSegmentationLayer != null + ? getMeshesForCurrentAdditionalCoordinates(state, visibleSegmentationLayer?.name) + : undefined; return { activeCellId: activeVolumeTracing?.activeCellId, @@ -251,7 +251,7 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ getAdditionalCoordinatesAsString(Store.getState().flycam.additionalCoordinates) !== getAdditionalCoordinatesAsString(additionalCoordinates) ) { - dispatch(setAdditionalCoordinatesAction(additionalCoordinates || null)); + dispatch(setAdditionalCoordinatesAction(additionalCoordinates)); } }, From 52749901b4ac86403a10b5173425bea4550c54c0 Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Mon, 4 Dec 2023 22:31:17 +0100 Subject: [PATCH 34/43] WIP: improve var naming --- .../model/actions/annotation_actions.ts | 2 +- .../model/reducers/annotation_reducer.ts | 22 ++++++++++++------- .../oxalis/model/sagas/mesh_saga.ts | 6 ++--- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/frontend/javascripts/oxalis/model/actions/annotation_actions.ts b/frontend/javascripts/oxalis/model/actions/annotation_actions.ts index 45306c8de86..c2f72b2a05e 100644 --- a/frontend/javascripts/oxalis/model/actions/annotation_actions.ts +++ b/frontend/javascripts/oxalis/model/actions/annotation_actions.ts @@ -187,7 +187,7 @@ export const updateMeshVisibilityAction = ( layerName: string, id: number, visibility: boolean, - additionalCoordinates?: AdditionalCoordinate[] | undefined, + additionalCoordinates?: AdditionalCoordinate[] | undefined | null, ) => ({ type: "UPDATE_MESH_VISIBILITY", diff --git a/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts b/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts index 8946b184e5c..cf5dd4662f4 100644 --- a/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts +++ b/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts @@ -208,12 +208,12 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { case "UPDATE_MESH_VISIBILITY": { const { layerName, id, visibility, additionalCoordinates } = action; - const addCoordString = getAdditionalCoordinatesAsString(additionalCoordinates); + const additionalCoordKey = getAdditionalCoordinatesAsString(additionalCoordinates); return update(state, { localSegmentationData: { [layerName]: { meshes: { - [addCoordString]: { + [additionalCoordKey]: { [id]: { isVisible: { $set: visibility, @@ -319,12 +319,14 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { isPrecomputed: true, meshFileName, }; - const addCoordString = getAdditionalCoordinatesAsString(state.flycam.additionalCoordinates); + const additionalCoordKey = getAdditionalCoordinatesAsString( + state.flycam.additionalCoordinates, + ); const updatedKey = update(state, { localSegmentationData: { [layerName]: { meshes: { - [addCoordString]: { + [additionalCoordKey]: { [segmentId]: { $set: meshInfo, }, @@ -338,12 +340,14 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { case "STARTED_LOADING_MESH": { const { layerName, segmentId } = action; - const addCoordString = getAdditionalCoordinatesAsString(state.flycam.additionalCoordinates); + const additionalCoordKey = getAdditionalCoordinatesAsString( + state.flycam.additionalCoordinates, + ); const updatedKey = update(state, { localSegmentationData: { [layerName]: { meshes: { - [addCoordString]: { + [additionalCoordKey]: { [segmentId]: { isLoading: { $set: true, @@ -359,12 +363,14 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { case "FINISHED_LOADING_MESH": { const { layerName, segmentId } = action; - const addCoordString = getAdditionalCoordinatesAsString(state.flycam.additionalCoordinates); + const additionalCoordKey = getAdditionalCoordinatesAsString( + state.flycam.additionalCoordinates, + ); const updatedKey = update(state, { localSegmentationData: { [layerName]: { meshes: { - [addCoordString]: { + [additionalCoordKey]: { [segmentId]: { isLoading: { $set: false, diff --git a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts index 791b88c27af..d3e3093d22b 100644 --- a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts @@ -1169,8 +1169,8 @@ function* removeMesh(action: RemoveMeshAction, removeFromScene: boolean = true): getSceneController().segmentMeshController.removeMeshById(segmentId, layerName, null); } - for (const addCoordString of Object.keys(adhocMeshesMapByLayer)) { - removeMapForSegment(layerName, segmentId, addCoordString); + for (const additionalCoordKey of Object.keys(adhocMeshesMapByLayer)) { + removeMapForSegment(layerName, segmentId, additionalCoordKey); } } @@ -1214,7 +1214,7 @@ export function* handleAdditionalCoordinateUpdate(): Saga { layerName, segmentId, shouldBeVisible, - additionalCoordinates || undefined, + additionalCoordinates, ), ); yield* call( From d0432d2c2645f5a882ae214d1442561002284cec Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Wed, 6 Dec 2023 12:40:56 +0100 Subject: [PATCH 35/43] only remove mesh in current dimension --- .../controller/segment_mesh_controller.ts | 2 +- .../model/reducers/annotation_reducer.ts | 21 +++++---- .../oxalis/model/sagas/mesh_saga.ts | 44 +++++++++++-------- 3 files changed, 39 insertions(+), 28 deletions(-) diff --git a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts index 07aa14f1a64..664f9d71a61 100644 --- a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts +++ b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts @@ -44,7 +44,7 @@ export default class SegmentMeshController { vertices: Float32Array, segmentationId: number, layerName: string, - additionalCoordinates?: AdditionalCoordinate[], + additionalCoordinates?: AdditionalCoordinate[] | undefined | null, ): void { if (vertices.length === 0) return; let bufferGeometry = new THREE.BufferGeometry(); diff --git a/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts b/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts index cf5dd4662f4..ed02443d184 100644 --- a/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts +++ b/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts @@ -229,21 +229,24 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { case "REMOVE_MESH": { const { layerName, segmentId } = action; const newMeshes: Record> = {}; - if (state.localSegmentationData[layerName].meshes == null) { + const additionalCoordKey = getAdditionalCoordinatesAsString( + state.flycam.additionalCoordinates, + ); + if ( + state.localSegmentationData[layerName].meshes == null || + state.localSegmentationData[layerName].meshes![additionalCoordKey] == null + ) { throw Error("No mesh data found in state.localSegmentationData."); } - for (const additionalCoordKey of Object.keys( - state.localSegmentationData[layerName].meshes!, - )) { - const { [segmentId]: _, ...remainingMeshes } = state.localSegmentationData[layerName] - .meshes![additionalCoordKey] as Record; - newMeshes[additionalCoordKey] = remainingMeshes; - } + const { [segmentId]: _, ...remainingMeshes } = state.localSegmentationData[layerName].meshes![ + additionalCoordKey + ] as Record; + newMeshes[additionalCoordKey] = remainingMeshes; return update(state, { localSegmentationData: { [layerName]: { meshes: { - $set: newMeshes, + $merge: newMeshes, }, }, }, diff --git a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts index d3e3093d22b..83d2c721e25 100644 --- a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts @@ -365,9 +365,10 @@ function* loadFullAdHocMesh( : [clippedPosition]; if (positionsToRequest.length === 0) { - //TODO may be null - const { segmentMeshController } = getSceneController(); - segmentMeshController.removeMeshById(segmentId, layer.name, additionalCoordinates); + //if no positions are requested, remove the mesh, + //so that the old one isn't displayed anymore + console.log("371"); + yield* put(removeMeshAction(layer.name, segmentId)); } while (positionsToRequest.length > 0) { const currentPosition = positionsToRequest.shift(); @@ -508,7 +509,7 @@ function* maybeLoadMeshChunk( vertices, segmentId, layer.name, - additionalCoordinates || undefined, + additionalCoordinates, ); return neighbors.map((neighbor) => getNeighborPosition(clippedPosition, neighbor)); } catch (exception) { @@ -1155,23 +1156,31 @@ function* downloadMeshCells(action: TriggerMeshesDownloadAction): Saga { } function* handleRemoveSegment(action: RemoveSegmentAction) { - // The dispatched action will make sure that the mesh entry is removed from the - // store **and** from the scene. Otherwise, the store will still contain a reference - // to the mesh even though it's not in the scene, anymore. - yield* put(removeMeshAction(action.layerName, action.segmentId)); + const additionalCoordinates = yield* select((state) => state.flycam.additionalCoordinates); + const additionalCoordKey = getAdditionalCoordinatesAsString(additionalCoordinates); + const { layerName, segmentId } = action; + if (adhocMeshesMapByLayer[additionalCoordKey]?.[layerName]?.get(segmentId) != null) { + // The dispatched action will make sure that the mesh entry is removed from the + // store **and** from the scene. Otherwise, the store will still contain a reference + // to the mesh even though it's not in the scene, anymore. + yield* put(removeMeshAction(action.layerName, action.segmentId)); + } } function* removeMesh(action: RemoveMeshAction, removeFromScene: boolean = true): Saga { + const additionalCoordinates = yield* select((state) => state.flycam.additionalCoordinates); + const additionalCoordKey = getAdditionalCoordinatesAsString(additionalCoordinates); const { layerName } = action; const segmentId = action.segmentId; if (removeFromScene) { - getSceneController().segmentMeshController.removeMeshById(segmentId, layerName, null); - } - - for (const additionalCoordKey of Object.keys(adhocMeshesMapByLayer)) { - removeMapForSegment(layerName, segmentId, additionalCoordKey); + getSceneController().segmentMeshController.removeMeshById( + segmentId, + layerName, + additionalCoordinates, + ); } + removeMapForSegment(layerName, segmentId, additionalCoordKey); } function* handleMeshVisibilityChange(action: UpdateMeshVisibilityAction): Saga { @@ -1198,13 +1207,12 @@ export function* handleAdditionalCoordinateUpdate(): Saga { const meshRecords = segmentMeshController.meshesGroupsPerSegmentationId; if (action.values == null || action.values.length === 0) break; - const newAdditionalCoordinates = getAdditionalCoordinatesAsString(action.values); + const newAdditionalCoordKey = getAdditionalCoordinatesAsString(action.values); for (const additionalCoordinates of [action.values, previousAdditionalCoordinates]) { - const currentAdditionalCoordinatesAsString = - getAdditionalCoordinatesAsString(additionalCoordinates); - const shouldBeVisible = currentAdditionalCoordinatesAsString === newAdditionalCoordinates; - const recordsOfLayers = meshRecords[currentAdditionalCoordinatesAsString] || {}; + const currentAdditionalCoordKey = getAdditionalCoordinatesAsString(additionalCoordinates); + const shouldBeVisible = currentAdditionalCoordKey === newAdditionalCoordKey; + const recordsOfLayers = meshRecords[currentAdditionalCoordKey] || {}; for (const [layerName, recordsForOneLayer] of Object.entries(recordsOfLayers)) { const segmentIds = Object.keys(recordsForOneLayer); for (const segmentIdAsString of segmentIds) { From 85c06a7f2108c0299a658dc4b4242800960edecf Mon Sep 17 00:00:00 2001 From: Philipp Otto Date: Wed, 6 Dec 2023 16:35:21 +0100 Subject: [PATCH 36/43] reduce cyclic dependencies --- .../oblique_bucket_picker.ts | 2 +- tools/check-cyclic-dependencies.js | 87 ++----------------- 2 files changed, 8 insertions(+), 81 deletions(-) diff --git a/frontend/javascripts/oxalis/model/bucket_data_handling/bucket_picker_strategies/oblique_bucket_picker.ts b/frontend/javascripts/oxalis/model/bucket_data_handling/bucket_picker_strategies/oblique_bucket_picker.ts index 2a710b98c33..869ee9ae728 100644 --- a/frontend/javascripts/oxalis/model/bucket_data_handling/bucket_picker_strategies/oblique_bucket_picker.ts +++ b/frontend/javascripts/oxalis/model/bucket_data_handling/bucket_picker_strategies/oblique_bucket_picker.ts @@ -8,7 +8,7 @@ import ThreeDMap from "libs/ThreeDMap"; import { OrthoViewWithoutTD, Vector2, Vector3, Vector4, ViewMode } from "oxalis/constants"; import constants from "oxalis/constants"; import traverse from "oxalis/model/bucket_data_handling/bucket_traversals"; -import { LoadingStrategy, PlaneRects } from "oxalis/store"; +import type { LoadingStrategy, PlaneRects } from "oxalis/store"; import { MAX_ZOOM_STEP_DIFF, getPriorityWeightForZoomStepDiff } from "../loading_strategy_logic"; // Note that the fourth component of Vector4 (if passed) is ignored, as it's not needed diff --git a/tools/check-cyclic-dependencies.js b/tools/check-cyclic-dependencies.js index 946127e8133..6eaed58811c 100755 --- a/tools/check-cyclic-dependencies.js +++ b/tools/check-cyclic-dependencies.js @@ -1,83 +1,6 @@ const madge = require("madge"); const KNOWN_CYCLES = [ - ["types/api_flow_types.ts", "admin/organization/pricing_plan_utils.ts"], - [ - "oxalis/model/accessors/flycam_accessor.ts", - "oxalis/model/bucket_data_handling/bucket_picker_strategies/oblique_bucket_picker.ts", - "oxalis/store.ts", - "oxalis/model/reducers/annotation_reducer.ts", - "oxalis/model/reducers/reducer_helpers.ts", - "oxalis/model/accessors/tool_accessor.ts", - ], - [ - "oxalis/model/accessors/volumetracing_accessor.ts", - "oxalis/model/accessors/flycam_accessor.ts", - "oxalis/model/bucket_data_handling/bucket_picker_strategies/oblique_bucket_picker.ts", - "oxalis/store.ts", - "oxalis/model/reducers/annotation_reducer.ts", - "oxalis/model/reducers/reducer_helpers.ts", - "oxalis/model/accessors/tool_accessor.ts", - ], - [ - "oxalis/model/accessors/volumetracing_accessor.ts", - "oxalis/model/accessors/flycam_accessor.ts", - "oxalis/model/bucket_data_handling/bucket_picker_strategies/oblique_bucket_picker.ts", - "oxalis/store.ts", - "oxalis/model/reducers/annotation_reducer.ts", - "oxalis/model/reducers/reducer_helpers.ts", - ], - [ - "oxalis/model/accessors/flycam_accessor.ts", - "oxalis/model/bucket_data_handling/bucket_picker_strategies/oblique_bucket_picker.ts", - "oxalis/store.ts", - "oxalis/model/reducers/flycam_reducer.ts", - ], - [ - "oxalis/model/accessors/volumetracing_accessor.ts", - "oxalis/model/accessors/flycam_accessor.ts", - "oxalis/model/bucket_data_handling/bucket_picker_strategies/oblique_bucket_picker.ts", - "oxalis/store.ts", - "oxalis/model/reducers/save_reducer.ts", - "oxalis/model/reducers/volumetracing_reducer_helpers.ts", - ], - [ - "oxalis/model/accessors/volumetracing_accessor.ts", - "oxalis/model/accessors/flycam_accessor.ts", - "oxalis/model/bucket_data_handling/bucket_picker_strategies/oblique_bucket_picker.ts", - "oxalis/store.ts", - "oxalis/model/reducers/settings_reducer.ts", - ], - [ - "types/schemas/user_settings.schema.ts", - "oxalis/model/accessors/volumetracing_accessor.ts", - "oxalis/model/accessors/flycam_accessor.ts", - "oxalis/model/bucket_data_handling/bucket_picker_strategies/oblique_bucket_picker.ts", - "oxalis/store.ts", - "oxalis/model/reducers/settings_reducer.ts", - ], - [ - "types/schemas/user_settings.schema.ts", - "oxalis/model/accessors/volumetracing_accessor.ts", - "oxalis/model/accessors/flycam_accessor.ts", - "oxalis/model/bucket_data_handling/bucket_picker_strategies/oblique_bucket_picker.ts", - "oxalis/store.ts", - "oxalis/model/reducers/skeletontracing_reducer.ts", - ], - [ - "oxalis/model/accessors/volumetracing_accessor.ts", - "oxalis/model/accessors/flycam_accessor.ts", - "oxalis/model/bucket_data_handling/bucket_picker_strategies/oblique_bucket_picker.ts", - "oxalis/store.ts", - "oxalis/model/reducers/volumetracing_reducer.ts", - ], - ["admin/organization/upgrade_plan_modal.tsx", "admin/organization/organization_cards.tsx"], - ["admin/team/edit_team_modal_view.tsx", "admin/team/team_list_view.tsx"], - [ - "oxalis/geometries/materials/plane_material_factory.ts", - "oxalis/shaders/main_data_shaders.glsl.ts", - "oxalis/geometries/plane.ts", - ], [ "admin/admin_rest_api.ts", "admin/api/mesh_v0.ts", @@ -86,11 +9,15 @@ const KNOWN_CYCLES = [ "admin/datastore_health_check.ts", ], ["libs/request.ts", "admin/datastore_health_check.ts"], + ["types/api_flow_types.ts", "admin/organization/pricing_plan_utils.ts"], ["libs/mjs.ts"], - ["oxalis/controller/url_manager.ts", "oxalis/model_initialization.ts"], + ["oxalis/model/accessors/flycam_accessor.ts", "oxalis/model/reducers/flycam_reducer.ts"], + ["admin/organization/upgrade_plan_modal.tsx", "admin/organization/organization_cards.tsx"], + ["admin/team/edit_team_modal_view.tsx", "admin/team/team_list_view.tsx"], [ - "oxalis/view/action-bar/tracing_actions_view.tsx", - "oxalis/view/action-bar/view_dataset_actions_view.tsx", + "oxalis/geometries/materials/plane_material_factory.ts", + "oxalis/shaders/main_data_shaders.glsl.ts", + "oxalis/geometries/plane.ts", ], ]; From c619c76f6c48c7d486b2e99beda5231eb59ac694 Mon Sep 17 00:00:00 2001 From: Philipp Otto Date: Wed, 6 Dec 2023 16:58:09 +0100 Subject: [PATCH 37/43] fix tests by setting scene controller in test setup --- frontend/javascripts/test/helpers/apiHelpers.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frontend/javascripts/test/helpers/apiHelpers.ts b/frontend/javascripts/test/helpers/apiHelpers.ts index 13918692fdb..615305da6bc 100644 --- a/frontend/javascripts/test/helpers/apiHelpers.ts +++ b/frontend/javascripts/test/helpers/apiHelpers.ts @@ -11,6 +11,7 @@ import sinon from "sinon"; import window from "libs/window"; import dummyUser from "test/fixtures/dummy_user"; import dummyOrga from "test/fixtures/dummy_organization"; +import { setSceneController } from "oxalis/controller/scene_controller_provider"; import { tracing as SKELETON_TRACING, annotation as SKELETON_ANNOTATION, @@ -220,6 +221,11 @@ export function __setupOxalis( .withArgs(sinon.match((arg) => arg === `/api/users/${dummyUser.id}/taskTypeId`)) .returns(Promise.resolve(dummyUser)); + setSceneController({ + name: "This is a dummy scene controller so that getSceneController works in the tests.", + segmentMeshController: { meshesGroupsPerSegmentationId: {} }, + }); + return Model.fetch( ANNOTATION_TYPE, { From cb1e3313c3f9ed578dc2c5857e9e65aaf034bbf7 Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Wed, 6 Dec 2023 18:42:26 +0100 Subject: [PATCH 38/43] improve typing --- CHANGELOG.unreleased.md | 6 --- frontend/javascripts/oxalis/api/api_latest.ts | 4 +- .../controller/segment_mesh_controller.ts | 47 +++++++------------ .../model/actions/annotation_actions.ts | 4 +- .../model/actions/segmentation_actions.ts | 4 +- .../model/actions/volumetracing_actions.ts | 4 +- .../model/reducers/annotation_reducer.ts | 33 ++++++------- .../model/reducers/volumetracing_reducer.ts | 2 +- .../oxalis/model/sagas/mesh_saga.ts | 14 ++---- .../oxalis/model/sagas/update_actions.ts | 2 +- frontend/javascripts/oxalis/store.ts | 4 +- .../segments_tab/segment_list_item.tsx | 20 ++++---- .../segments_tab/segments_view.tsx | 17 +++---- 13 files changed, 63 insertions(+), 98 deletions(-) diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index 6fac8b8548d..8693fe6e145 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -11,12 +11,6 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released [Commits](https://github.com/scalableminds/webknossos/compare/23.12.0...HEAD) ### Added -- Zarr datasets can now be directly uploaded to WEBKNOSSOS. [#7397](https://github.com/scalableminds/webknossos/pull/7397) -- Added support for reading uint24 rgb layers in datasets with zarr2/zarr3/n5/neuroglancerPrecomputed format, as used for voxelytics predictions. [#7413](https://github.com/scalableminds/webknossos/pull/7413) -- Adding a remote dataset can now be done by providing a Neuroglancer URI. [#7416](https://github.com/scalableminds/webknossos/pull/7416) -- Added a filter to the Task List->Stats column to quickly filter for tasks with "Prending", "In-Progress" or "Finished" instances. [#7430](https://github.com/scalableminds/webknossos/pull/7430) -- Added support for S3-compliant object storage services (e.g. MinIO) as a storage backend for remote datasets. [#7453](https://github.com/scalableminds/webknossos/pull/7453) -- Added support for blosc compressed N5 datasets. [#7465](https://github.com/scalableminds/webknossos/pull/7465) - Added support for S3-compliant object storage services (e.g. MinIO) as a storage backend for remote datasets. [#7453](https://github.com/scalableminds/webknossos/pull/7453) - Added support for blosc compressed N5 datasets. [#7465](https://github.com/scalableminds/webknossos/pull/7465) - Added route for triggering the compute segment index worker job. [#7471](https://github.com/scalableminds/webknossos/pull/7471) diff --git a/frontend/javascripts/oxalis/api/api_latest.ts b/frontend/javascripts/oxalis/api/api_latest.ts index a5d5fde7c9e..60cae648093 100644 --- a/frontend/javascripts/oxalis/api/api_latest.ts +++ b/frontend/javascripts/oxalis/api/api_latest.ts @@ -2265,7 +2265,7 @@ class DataApi { Store.dispatch(updateMeshVisibilityAction(effectiveLayerName, segmentId, isVisible)); } else { throw new Error( - `Error while setting mesh visibility. Mesh for segment ${segmentId} was not found in OxalisState.localSegmentationData.`, + `Mesh for segment ${segmentId} was not found in OxalisState.localSegmentationData.`, ); } } @@ -2290,7 +2290,7 @@ class DataApi { Store.dispatch(removeMeshAction(effectiveLayerName, segmentId)); } else { throw new Error( - `Error while removing mesh. Mesh for segment ${segmentId} was not found in OxalisState.localSegmentationData.`, + `Mesh for segment ${segmentId} was not found in OxalisState.localSegmentationData.`, ); } } diff --git a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts index 664f9d71a61..7d210057599 100644 --- a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts +++ b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts @@ -131,37 +131,24 @@ export default class SegmentMeshController { this.addMeshToMeshGroups(additionalCoordinatesString, layerName, segmentationId, lod, mesh); } - removeMeshById( - segmentationId: number, - layerName: string, - additionalCoordinates: AdditionalCoordinate[] | null | undefined, - ): void { - // either remove a mesh for specific additional coordinates, or remove all meshes for a segment per default by passing null as additionalCoordinates. - let additionalCoordinatesToRemoveMeshes; - if (additionalCoordinates == null) { - additionalCoordinatesToRemoveMeshes = Object.keys(this.meshesGroupsPerSegmentationId); - } else { - additionalCoordinatesToRemoveMeshes = [ - getAdditionalCoordinatesAsString(additionalCoordinates), - ]; - } - for (const additionalCoordinatesString of additionalCoordinatesToRemoveMeshes) { - if (this.getMeshGroups(additionalCoordinatesString, layerName, segmentationId) == null) { - return; - } - _.forEach( - this.getMeshGroups(additionalCoordinatesString, layerName, segmentationId), - (meshGroup, lod) => { - const lodNumber = parseInt(lod); - if (lodNumber !== NO_LOD_MESH_INDEX) { - this.meshesLODRootGroup.removeLODMesh(meshGroup, lodNumber); - } else { - this.meshesLODRootGroup.removeNoLODSupportedMesh(meshGroup); - } - }, - ); - this.removeMeshFromMeshGroups(additionalCoordinatesString, layerName, segmentationId); + removeMeshById(segmentationId: number, layerName: string): void { + const additionalCoordinates = Store.getState().flycam.additionalCoordinates; + const additionalCoordKey = getAdditionalCoordinatesAsString(additionalCoordinates); + if (this.getMeshGroups(additionalCoordKey, layerName, segmentationId) == null) { + return; } + _.forEach( + this.getMeshGroups(additionalCoordKey, layerName, segmentationId), + (meshGroup, lod) => { + const lodNumber = parseInt(lod); + if (lodNumber !== NO_LOD_MESH_INDEX) { + this.meshesLODRootGroup.removeLODMesh(meshGroup, lodNumber); + } else { + this.meshesLODRootGroup.removeNoLODSupportedMesh(meshGroup); + } + }, + ); + this.removeMeshFromMeshGroups(additionalCoordKey, layerName, segmentationId); } getMeshGeometryInBestLOD( diff --git a/frontend/javascripts/oxalis/model/actions/annotation_actions.ts b/frontend/javascripts/oxalis/model/actions/annotation_actions.ts index c2f72b2a05e..077efe00d31 100644 --- a/frontend/javascripts/oxalis/model/actions/annotation_actions.ts +++ b/frontend/javascripts/oxalis/model/actions/annotation_actions.ts @@ -287,7 +287,7 @@ export const addAdHocMeshAction = ( layerName: string, segmentId: number, seedPosition: Vector3, - seedAdditionalCoordinates: AdditionalCoordinate[] | undefined, + seedAdditionalCoordinates: AdditionalCoordinate[] | undefined | null, mappingName: string | null | undefined, mappingType: MappingType | null | undefined, ) => @@ -305,7 +305,7 @@ export const addPrecomputedMeshAction = ( layerName: string, segmentId: number, seedPosition: Vector3, - seedAdditionalCoordinates: AdditionalCoordinate[] | undefined, + seedAdditionalCoordinates: AdditionalCoordinate[] | undefined | null, meshFileName: string, ) => ({ diff --git a/frontend/javascripts/oxalis/model/actions/segmentation_actions.ts b/frontend/javascripts/oxalis/model/actions/segmentation_actions.ts index 965071c80d2..b5f0936f151 100644 --- a/frontend/javascripts/oxalis/model/actions/segmentation_actions.ts +++ b/frontend/javascripts/oxalis/model/actions/segmentation_actions.ts @@ -16,7 +16,7 @@ export type SegmentationAction = LoadAdHocMeshAction | LoadPrecomputedMeshAction export const loadAdHocMeshAction = ( segmentId: number, seedPosition: Vector3, - seedAdditionalCoordinates: AdditionalCoordinate[] | undefined, + seedAdditionalCoordinates: AdditionalCoordinate[] | undefined | null, extraInfo?: AdHocMeshInfo, layerName?: string, ) => @@ -32,7 +32,7 @@ export const loadAdHocMeshAction = ( export const loadPrecomputedMeshAction = ( segmentId: number, seedPosition: Vector3, - seedAdditionalCoordinates: AdditionalCoordinate[] | undefined, + seedAdditionalCoordinates: AdditionalCoordinate[] | undefined | null, meshFileName: string, layerName?: string, ) => diff --git a/frontend/javascripts/oxalis/model/actions/volumetracing_actions.ts b/frontend/javascripts/oxalis/model/actions/volumetracing_actions.ts index da21c096b74..f6ab0c2aac8 100644 --- a/frontend/javascripts/oxalis/model/actions/volumetracing_actions.ts +++ b/frontend/javascripts/oxalis/model/actions/volumetracing_actions.ts @@ -174,7 +174,7 @@ export const finishEditingAction = () => export const setActiveCellAction = ( segmentId: number, somePosition?: Vector3, - someAdditionalCoordinates?: AdditionalCoordinate[], + someAdditionalCoordinates?: AdditionalCoordinate[] | null, ) => ({ type: "SET_ACTIVE_CELL", @@ -186,7 +186,7 @@ export const setActiveCellAction = ( export const clickSegmentAction = ( segmentId: number, somePosition: Vector3, - someAdditionalCoordinates: AdditionalCoordinate[] | undefined, + someAdditionalCoordinates: AdditionalCoordinate[] | undefined | null, layerName?: string, ) => ({ diff --git a/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts b/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts index ed02443d184..a85e9520501 100644 --- a/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts +++ b/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts @@ -9,6 +9,7 @@ import { getDisplayedDataExtentInPlaneMode } from "oxalis/model/accessors/view_m import { convertServerAnnotationToFrontendAnnotation } from "oxalis/model/reducers/reducer_helpers"; import _ from "lodash"; import { getAdditionalCoordinatesAsString } from "../accessors/flycam_accessor"; +import { getMeshesForAdditionalCoordinates } from "../accessors/volumetracing_accessor"; const updateTracing = (state: OxalisState, shape: Partial): OxalisState => updateKey(state, "tracing", shape); @@ -229,18 +230,17 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { case "REMOVE_MESH": { const { layerName, segmentId } = action; const newMeshes: Record> = {}; - const additionalCoordKey = getAdditionalCoordinatesAsString( - state.flycam.additionalCoordinates, + const additionalCoordinates = state.flycam.additionalCoordinates; + const additionalCoordKey = getAdditionalCoordinatesAsString(additionalCoordinates); + const maybeMeshes = getMeshesForAdditionalCoordinates( + state, + additionalCoordinates, + layerName, ); - if ( - state.localSegmentationData[layerName].meshes == null || - state.localSegmentationData[layerName].meshes![additionalCoordKey] == null - ) { + if (maybeMeshes == null) { throw Error("No mesh data found in state.localSegmentationData."); } - const { [segmentId]: _, ...remainingMeshes } = state.localSegmentationData[layerName].meshes![ - additionalCoordKey - ] as Record; + const { [segmentId]: _, ...remainingMeshes } = maybeMeshes as Record; newMeshes[additionalCoordKey] = remainingMeshes; return update(state, { localSegmentationData: { @@ -266,25 +266,20 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { const meshInfo: MeshInformation = { segmentId: segmentId, seedPosition, - seedAdditionalCoordinates, + seedAdditionalCoordinates: seedAdditionalCoordinates, isLoading: false, isVisible: true, isPrecomputed: false, mappingName, mappingType, }; - const additionalCoordKey = getAdditionalCoordinatesAsString( - state.flycam.additionalCoordinates, - ); + const additionalCoordinates = state.flycam.additionalCoordinates; + const additionalCoordKey = getAdditionalCoordinatesAsString(additionalCoordinates); let stateWithCurrentAddCoords = state; - // maybe add current add. coord. as key in meshes records - if ( - state.localSegmentationData[layerName] == null || - state.localSegmentationData[layerName].meshes == null || - state.localSegmentationData[layerName].meshes![additionalCoordKey] == null - ) { + // maybe add current add. coord. as key for meshes in state + if (getMeshesForAdditionalCoordinates(state, additionalCoordinates, layerName) == null) { stateWithCurrentAddCoords = update(state, { localSegmentationData: { [layerName]: { diff --git a/frontend/javascripts/oxalis/model/reducers/volumetracing_reducer.ts b/frontend/javascripts/oxalis/model/reducers/volumetracing_reducer.ts index ac8c20fecc0..bc0e9944913 100644 --- a/frontend/javascripts/oxalis/model/reducers/volumetracing_reducer.ts +++ b/frontend/javascripts/oxalis/model/reducers/volumetracing_reducer.ts @@ -161,7 +161,7 @@ function handleUpdateSegment(state: OxalisState, action: UpdateSegmentAction) { const oldSegment = segments.getNullable(segmentId); let somePosition; - let someAdditionalCoordinates: AdditionalCoordinate[] | undefined; + let someAdditionalCoordinates: AdditionalCoordinate[] | undefined | null; if (segment.somePosition) { somePosition = Utils.floor3(segment.somePosition); someAdditionalCoordinates = segment.someAdditionalCoordinates; diff --git a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts index 83d2c721e25..7d6e5045b65 100644 --- a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts @@ -246,7 +246,7 @@ function* getInfoForMeshLoading( function* loadAdHocMesh( seedPosition: Vector3, - seedAdditionalCoordinates: AdditionalCoordinate[] | undefined, + seedAdditionalCoordinates: AdditionalCoordinate[] | undefined | null, segmentId: number, removeExistingMesh: boolean = false, layerName?: string | null | undefined, @@ -309,7 +309,7 @@ function* loadFullAdHocMesh( layer: DataLayer, segmentId: number, position: Vector3, - additionalCoordinates: AdditionalCoordinate[] | undefined, + additionalCoordinates: AdditionalCoordinate[] | undefined | null, zoomStep: number, meshExtraInfo: AdHocMeshInfo, resolutionInfo: ResolutionInfo, @@ -502,7 +502,7 @@ function* maybeLoadMeshChunk( const vertices = new Float32Array(responseBuffer); if (removeExistingMesh) { - segmentMeshController.removeMeshById(segmentId, layer.name, additionalCoordinates); + segmentMeshController.removeMeshById(segmentId, layer.name); } segmentMeshController.addMeshFromVertices( @@ -747,7 +747,7 @@ type ChunksMap = Record { @@ -1174,11 +1174,7 @@ function* removeMesh(action: RemoveMeshAction, removeFromScene: boolean = true): const segmentId = action.segmentId; if (removeFromScene) { - getSceneController().segmentMeshController.removeMeshById( - segmentId, - layerName, - additionalCoordinates, - ); + getSceneController().segmentMeshController.removeMeshById(segmentId, layerName); } removeMapForSegment(layerName, segmentId, additionalCoordKey); } diff --git a/frontend/javascripts/oxalis/model/sagas/update_actions.ts b/frontend/javascripts/oxalis/model/sagas/update_actions.ts index 69cffebd1fb..3588e497e27 100644 --- a/frontend/javascripts/oxalis/model/sagas/update_actions.ts +++ b/frontend/javascripts/oxalis/model/sagas/update_actions.ts @@ -319,7 +319,7 @@ export function createSegmentVolumeAction( export function updateSegmentVolumeAction( id: number, anchorPosition: Vector3 | null | undefined, - additionalCoordinates: AdditionalCoordinate[] | undefined, + additionalCoordinates: AdditionalCoordinate[] | undefined | null, name: string | null | undefined, color: Vector3 | null, groupId: number | null | undefined, diff --git a/frontend/javascripts/oxalis/store.ts b/frontend/javascripts/oxalis/store.ts index c5126a51b0f..0674c283207 100644 --- a/frontend/javascripts/oxalis/store.ts +++ b/frontend/javascripts/oxalis/store.ts @@ -226,7 +226,7 @@ export type Segment = { readonly id: number; readonly name: string | null | undefined; readonly somePosition: Vector3 | undefined; - readonly someAdditionalCoordinates: AdditionalCoordinate[] | undefined; + readonly someAdditionalCoordinates: AdditionalCoordinate[] | undefined | null; readonly creationTime: number | null | undefined; readonly color: Vector3 | null; readonly groupId: number | null | undefined; @@ -533,7 +533,7 @@ type UiInformation = { type BaseMeshInformation = { readonly segmentId: number; readonly seedPosition: Vector3; - readonly seedAdditionalCoordinates?: AdditionalCoordinate[]; + readonly seedAdditionalCoordinates?: AdditionalCoordinate[] | null; readonly isLoading: boolean; readonly isVisible: boolean; }; diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx index c5eab639667..388fa790677 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx @@ -53,7 +53,7 @@ const getLoadPrecomputedMeshMenuItem = ( loadPrecomputedMesh: ( segmentId: number, seedPosition: Vector3, - seedAdditionalCoordinates: AdditionalCoordinate[] | undefined, + seedAdditionalCoordinates: AdditionalCoordinate[] | undefined | null, meshFileName: string, ) => void, andCloseContextMenu: (_ignore?: any) => void, @@ -148,7 +148,7 @@ const getMakeSegmentActiveMenuItem = ( setActiveCell: ( arg0: number, somePosition?: Vector3, - someAdditionalCoordinates?: AdditionalCoordinate[], + someAdditionalCoordinates?: AdditionalCoordinate[] | null, ) => void, activeCellId: number | null | undefined, isEditingDisabled: boolean, @@ -162,11 +162,7 @@ const getMakeSegmentActiveMenuItem = ( key: "setActiveCell", onClick: () => andCloseContextMenu( - setActiveCell( - segment.id, - segment.somePosition, - segment.someAdditionalCoordinates || undefined, - ), + setActiveCell(segment.id, segment.somePosition, segment.someAdditionalCoordinates), ), disabled: isActiveSegment || isEditingDisabled, label: ( @@ -201,22 +197,24 @@ type Props = { loadAdHocMesh: ( segmentId: number, somePosition: Vector3, - someAdditionalCoordinates: AdditionalCoordinate[] | undefined, + someAdditionalCoordinates: AdditionalCoordinate[] | undefined | null, ) => void; loadPrecomputedMesh: ( segmentId: number, seedPosition: Vector3, - seedAdditionalCoordinates: AdditionalCoordinate[] | undefined, + seedAdditionalCoordinates: AdditionalCoordinate[] | undefined | null, meshFileName: string, ) => void; setActiveCell: ( arg0: number, somePosition?: Vector3, - someAdditionalCoordinates?: AdditionalCoordinate[], + someAdditionalCoordinates?: AdditionalCoordinate[] | null, ) => void; mesh: MeshInformation | null | undefined; setPosition: (arg0: Vector3) => void; - setAdditionalCoordinates: (additionalCoordinates: AdditionalCoordinate[] | undefined) => void; + setAdditionalCoordinates: ( + additionalCoordinates: AdditionalCoordinate[] | undefined | null, + ) => void; currentMeshFile: APIMeshFile | null | undefined; onRenameStart: () => void; onRenameEnd: () => void; diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx index 219860fd192..c0da354d170 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx @@ -214,7 +214,7 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ loadAdHocMesh( segmentId: number, seedPosition: Vector3, - additionalCoordinates: AdditionalCoordinate[] | undefined, + additionalCoordinates: AdditionalCoordinate[] | undefined | null, ) { dispatch(loadAdHocMeshAction(segmentId, seedPosition, additionalCoordinates)); }, @@ -222,7 +222,7 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ loadPrecomputedMesh( segmentId: number, seedPosition: Vector3, - seedAdditionalCoordinates: AdditionalCoordinate[] | undefined, + seedAdditionalCoordinates: AdditionalCoordinate[] | undefined | null, meshFileName: string, ) { dispatch( @@ -233,7 +233,7 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ setActiveCell( segmentId: number, somePosition?: Vector3, - someAdditionalCoordinates?: AdditionalCoordinate[], + someAdditionalCoordinates?: AdditionalCoordinate[] | null, ) { dispatch(setActiveCellAction(segmentId, somePosition, someAdditionalCoordinates)); }, @@ -246,7 +246,7 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ dispatch(setPositionAction(position)); }, - setAdditionalCoordinates(additionalCoordinates: AdditionalCoordinate[] | undefined) { + setAdditionalCoordinates(additionalCoordinates: AdditionalCoordinate[] | undefined | null) { if ( getAdditionalCoordinatesAsString(Store.getState().flycam.additionalCoordinates) !== getAdditionalCoordinatesAsString(additionalCoordinates) @@ -1310,12 +1310,7 @@ class SegmentsView extends React.Component { this.handlePerSegment(groupId, (segment) => { if (meshes[segment.id] != null) { Store.dispatch( - updateMeshVisibilityAction( - layerName, - segment.id, - isVisible, - additionalCoordinates || undefined, - ), + updateMeshVisibilityAction(layerName, segment.id, isVisible, additionalCoordinates), ); } }); @@ -1327,7 +1322,7 @@ class SegmentsView extends React.Component { this.props.loadAdHocMesh( segment.id, segment.somePosition, - this.props.flycam.additionalCoordinates || undefined, + this.props.flycam.additionalCoordinates, ); }); }; From eb072a97fc73597b30ba50c6fccc543193a143f4 Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Wed, 6 Dec 2023 19:30:29 +0100 Subject: [PATCH 39/43] improve naming and types --- frontend/javascripts/oxalis/api/api_latest.ts | 10 ++-------- .../oxalis/model/sagas/mesh_saga.ts | 20 +++++++++---------- .../segments_tab/segment_list_item.tsx | 4 ++-- 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/frontend/javascripts/oxalis/api/api_latest.ts b/frontend/javascripts/oxalis/api/api_latest.ts index 60cae648093..ecb07cc92ca 100644 --- a/frontend/javascripts/oxalis/api/api_latest.ts +++ b/frontend/javascripts/oxalis/api/api_latest.ts @@ -2258,10 +2258,7 @@ class DataApi { layerName, ).name; - if ( - Store.getState().localSegmentationData[effectiveLayerName].meshes != null && - Store.getState().localSegmentationData[effectiveLayerName].meshes![segmentId] != null - ) { + if (Store.getState().localSegmentationData[effectiveLayerName].meshes?.[segmentId] != null) { Store.dispatch(updateMeshVisibilityAction(effectiveLayerName, segmentId, isVisible)); } else { throw new Error( @@ -2283,10 +2280,7 @@ class DataApi { layerName, ).name; - if ( - Store.getState().localSegmentationData[effectiveLayerName].meshes != null && - Store.getState().localSegmentationData[effectiveLayerName].meshes![segmentId] != null - ) { + if (Store.getState().localSegmentationData[effectiveLayerName].meshes?.[segmentId] != null) { Store.dispatch(removeMeshAction(effectiveLayerName, segmentId)); } else { throw new Error( diff --git a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts index 7d6e5045b65..8ffff8ae011 100644 --- a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts @@ -289,10 +289,10 @@ function* loadAdHocMesh( action.layerName === layer.name, ), }); - removeMeshWithoutVoxelsFromStore(segmentId, layer.name, seedAdditionalCoordinates); + removeMeshWithoutVoxels(segmentId, layer.name, seedAdditionalCoordinates); } -function removeMeshWithoutVoxelsFromStore( +function removeMeshWithoutVoxels( segmentId: number, layerName: string, additionalCoordinates: AdditionalCoordinate[] | undefined | null, @@ -367,7 +367,6 @@ function* loadFullAdHocMesh( if (positionsToRequest.length === 0) { //if no positions are requested, remove the mesh, //so that the old one isn't displayed anymore - console.log("371"); yield* put(removeMeshAction(layer.name, segmentId)); } while (positionsToRequest.length > 0) { @@ -568,7 +567,7 @@ function* refreshMeshes(): Saga { function* refreshMesh(action: RefreshMeshAction): Saga { const additionalCoordinates = yield* select((state) => state.flycam.additionalCoordinates); - let additionalCoordKey = getAdditionalCoordinatesAsString(additionalCoordinates); + const additionalCoordKey = getAdditionalCoordinatesAsString(additionalCoordinates); const { segmentId, layerName } = action; @@ -577,7 +576,9 @@ function* refreshMesh(action: RefreshMeshAction): Saga { ); if (meshInfo == null) { - throw new Error("Mesh refreshing failed due to lack of mesh data in store."); + throw new Error( + `Mesh refreshing failed due to lack of mesh info for segment ${segmentId} in store.`, + ); } if (meshInfo.isPrecomputed) { @@ -611,7 +612,9 @@ function* _refreshMeshWithMap( getMeshInfoForSegment(state, additionalCoordinates, layerName, segmentId), ); if (meshInfo == null) { - throw new Error("Mesh refreshing failed due to lack of mesh data in store."); + throw new Error( + `Mesh refreshing failed due to lack of mesh info for segment ${segmentId} in store.`, + ); } yield* call( [ErrorHandling, ErrorHandling.assert], @@ -626,7 +629,6 @@ function* _refreshMeshWithMap( return; } - //yield* put(startedLoadingMeshAction(layerName, segmentId)); TODO can i leave this out? // Remove mesh from cache. yield* call(removeMesh, removeMeshAction(layerName, segmentId), false); // The mesh should only be removed once after re-fetching the mesh first position. @@ -638,7 +640,7 @@ function* _refreshMeshWithMap( yield* call( loadAdHocMesh, position, - additionalCoordinates || undefined, + additionalCoordinates, segmentId, shouldBeRemoved, layerName, @@ -649,8 +651,6 @@ function* _refreshMeshWithMap( ); shouldBeRemoved = false; } - //yield* put(finishedLoadingMeshAction(layerName, segmentId)); TODO can I leave this out? - removeMeshWithoutVoxelsFromStore(segmentId, layerName, additionalCoordinates); } /* diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx index 388fa790677..96e32f3e6e9 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_list_item.tsx @@ -112,7 +112,7 @@ const getComputeMeshAdHocMenuItem = ( loadAdHocMesh: ( segmentId: number, seedPosition: Vector3, - seedAdditionalCoordinates: AdditionalCoordinate[] | undefined, + seedAdditionalCoordinates: AdditionalCoordinate[] | undefined | null, ) => void, isSegmentationLayerVisible: boolean, andCloseContextMenu: (_ignore?: any) => void, @@ -134,7 +134,7 @@ const getComputeMeshAdHocMenuItem = ( loadAdHocMesh( segment.id, segment.somePosition, - Store.getState().flycam.additionalCoordinates || undefined, + Store.getState().flycam.additionalCoordinates, ), ); }, From 4f2642a25d3985eed5cc01881e9d5f6427b7acd4 Mon Sep 17 00:00:00 2001 From: Philipp Otto Date: Thu, 7 Dec 2023 10:05:10 +0100 Subject: [PATCH 40/43] adapt check-cyclic-dependencies.js so that it ensures that the amount of known cycles is always correct --- tools/check-cyclic-dependencies.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/check-cyclic-dependencies.js b/tools/check-cyclic-dependencies.js index 6eaed58811c..b89433f47a3 100755 --- a/tools/check-cyclic-dependencies.js +++ b/tools/check-cyclic-dependencies.js @@ -44,6 +44,12 @@ madge("frontend/javascripts/main.tsx", { .map((cycle) => cycle.join(" -> ")) .join("\n")}\n`, ); + } else if (cyclicDependencies.length < knownCycleStrings.length) { + throw new Error(`Congratulations! Your admirable work removed at least one cyclic dependency from the TypeScript modules. To ensure + that this improvement is not undone accidentally in the future, please adapt the KNOWN_CYCLES variable in the check-cyclic-dependies.js + script. Please set the variable to the following and commit it: + ${JSON.stringify(cyclicDependencies, null, " ")} + `); } console.log("Success."); }); From c81cf378155202740bcc8114860d7fea9c8fd72c Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Mon, 11 Dec 2023 12:47:54 +0100 Subject: [PATCH 41/43] address review: fix bug when adding new segments --- frontend/javascripts/oxalis/api/api_latest.ts | 4 +- frontend/javascripts/oxalis/constants.ts | 2 +- .../controller/segment_mesh_controller.ts | 37 ++++++------ .../model/accessors/volumetracing_accessor.ts | 2 +- .../model/reducers/annotation_reducer.ts | 60 +++++++++++++------ .../oxalis/model/sagas/mesh_saga.ts | 30 ++++------ frontend/javascripts/oxalis/store.ts | 4 +- 7 files changed, 81 insertions(+), 58 deletions(-) diff --git a/frontend/javascripts/oxalis/api/api_latest.ts b/frontend/javascripts/oxalis/api/api_latest.ts index ecb07cc92ca..934fef2c616 100644 --- a/frontend/javascripts/oxalis/api/api_latest.ts +++ b/frontend/javascripts/oxalis/api/api_latest.ts @@ -2262,7 +2262,7 @@ class DataApi { Store.dispatch(updateMeshVisibilityAction(effectiveLayerName, segmentId, isVisible)); } else { throw new Error( - `Mesh for segment ${segmentId} was not found in OxalisState.localSegmentationData.`, + `Mesh for segment ${segmentId} was not found in State.localSegmentationData.`, ); } } @@ -2284,7 +2284,7 @@ class DataApi { Store.dispatch(removeMeshAction(effectiveLayerName, segmentId)); } else { throw new Error( - `Mesh for segment ${segmentId} was not found in OxalisState.localSegmentationData.`, + `Mesh for segment ${segmentId} was not found in State.localSegmentationData.`, ); } } diff --git a/frontend/javascripts/oxalis/constants.ts b/frontend/javascripts/oxalis/constants.ts index 462dccd8902..0ad265e7820 100644 --- a/frontend/javascripts/oxalis/constants.ts +++ b/frontend/javascripts/oxalis/constants.ts @@ -381,4 +381,4 @@ export enum BLEND_MODES { export const Identity4x4 = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]); export const IdentityTransform = { type: "affine", affineMatrix: Identity4x4 } as const; -export const EMPTY_OBJECT = {}; +export const EMPTY_OBJECT = {} as const; diff --git a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts index 7d210057599..437a2ef7922 100644 --- a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts +++ b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts @@ -105,22 +105,27 @@ export default class SegmentMeshController { additionalCoordinates: AdditionalCoordinate[] | null | undefined, ): void { const additionalCoordinatesString = getAdditionalCoordinatesAsString(additionalCoordinates); - const newGroup = new THREE.Group(); const keys = [additionalCoordinatesString, layerName, segmentationId, lod]; + const isNewlyAddedMesh = + this.meshesGroupsPerSegmentationId[additionalCoordinatesString]?.[layerName]?.[ + segmentationId + ] == null; + const targetGroup = _.get(this.meshesGroupsPerSegmentationId, keys, new THREE.Group()); _.set( this.meshesGroupsPerSegmentationId, keys, - _.get(this.meshesGroupsPerSegmentationId, keys, newGroup), + _.get(this.meshesGroupsPerSegmentationId, keys, targetGroup), ); - if (lod === NO_LOD_MESH_INDEX) { - this.meshesLODRootGroup.addNoLODSupportedMesh(newGroup); - } else { - this.meshesLODRootGroup.addLODMesh(newGroup, lod); - } - // @ts-ignore - newGroup.cellId = segmentationId; - if (scale != null) { - newGroup.scale.copy(new THREE.Vector3(...scale)); + if (isNewlyAddedMesh) { + if (lod === NO_LOD_MESH_INDEX) { + this.meshesLODRootGroup.addNoLODSupportedMesh(targetGroup); + } else { + this.meshesLODRootGroup.addLODMesh(targetGroup, lod); + } + targetGroup.cellId = segmentationId; + if (scale != null) { + targetGroup.scale.copy(new THREE.Vector3(...scale)); + } } const mesh = this.constructMesh(segmentationId, geometry); if (offset) { @@ -155,13 +160,11 @@ export default class SegmentMeshController { segmentId: number, layerName: string, additionalCoordinates?: AdditionalCoordinate[] | null, - ): THREE.Group { + ): THREE.Group | null { const additionalCoordKey = getAdditionalCoordinatesAsString(additionalCoordinates); - const bestLod = Math.min( - ...Object.keys(this.getMeshGroups(additionalCoordKey, layerName, segmentId)).map((lodVal) => - parseInt(lodVal), - ), - ); + const meshGroups = this.getMeshGroups(additionalCoordKey, layerName, segmentId); + if (meshGroups == null) return null; + const bestLod = Math.min(...Object.keys(meshGroups).map((lodVal) => parseInt(lodVal))); return this.getMeshGroupsByLOD(additionalCoordinates, layerName, segmentId, bestLod); } diff --git a/frontend/javascripts/oxalis/model/accessors/volumetracing_accessor.ts b/frontend/javascripts/oxalis/model/accessors/volumetracing_accessor.ts index c046bd04750..2505c54b2d7 100644 --- a/frontend/javascripts/oxalis/model/accessors/volumetracing_accessor.ts +++ b/frontend/javascripts/oxalis/model/accessors/volumetracing_accessor.ts @@ -669,7 +669,7 @@ export function hasAgglomerateMapping(state: OxalisState) { export function getMeshesForAdditionalCoordinates( state: OxalisState, - additionalCoordinates: AdditionalCoordinate[] | null, + additionalCoordinates: AdditionalCoordinate[] | null | undefined, layerName: string, ) { const addCoordKey = getAdditionalCoordinatesAsString(additionalCoordinates); diff --git a/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts b/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts index a85e9520501..2d6011f7e07 100644 --- a/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts +++ b/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts @@ -10,6 +10,7 @@ import { convertServerAnnotationToFrontendAnnotation } from "oxalis/model/reduce import _ from "lodash"; import { getAdditionalCoordinatesAsString } from "../accessors/flycam_accessor"; import { getMeshesForAdditionalCoordinates } from "../accessors/volumetracing_accessor"; +import { AdditionalCoordinate } from "types/api_flow_types"; const updateTracing = (state: OxalisState, shape: Partial): OxalisState => updateKey(state, "tracing", shape); @@ -49,6 +50,26 @@ const updateUserBoundingBoxes = (state: OxalisState, userBoundingBoxes: Array { + if (getMeshesForAdditionalCoordinates(state, additionalCoordinates, layerName) == null) { + const additionalCoordKey = getAdditionalCoordinatesAsString(additionalCoordinates); + return update(state, { + localSegmentationData: { + [layerName]: { + meshes: { + [additionalCoordKey]: { $set: [] }, + }, + }, + }, + }); + } + return state; +}; + function AnnotationReducer(state: OxalisState, action: Action): OxalisState { switch (action.type) { case "INITIALIZE_ANNOTATION": { @@ -240,6 +261,9 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { if (maybeMeshes == null) { throw Error("No mesh data found in state.localSegmentationData."); } + if (maybeMeshes[segmentId] == null) { + return state; + } const { [segmentId]: _, ...remainingMeshes } = maybeMeshes as Record; newMeshes[additionalCoordKey] = remainingMeshes; return update(state, { @@ -266,7 +290,7 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { const meshInfo: MeshInformation = { segmentId: segmentId, seedPosition, - seedAdditionalCoordinates: seedAdditionalCoordinates, + seedAdditionalCoordinates, isLoading: false, isVisible: true, isPrecomputed: false, @@ -276,20 +300,11 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { const additionalCoordinates = state.flycam.additionalCoordinates; const additionalCoordKey = getAdditionalCoordinatesAsString(additionalCoordinates); - let stateWithCurrentAddCoords = state; - - // maybe add current add. coord. as key for meshes in state - if (getMeshesForAdditionalCoordinates(state, additionalCoordinates, layerName) == null) { - stateWithCurrentAddCoords = update(state, { - localSegmentationData: { - [layerName]: { - meshes: { - [additionalCoordKey]: { $set: [] }, - }, - }, - }, - }); - } + const stateWithCurrentAddCoords = maybeAddAdditionalCoordinatesToMeshState( + state, + additionalCoordinates, + layerName, + ); const updatedKey = update(stateWithCurrentAddCoords, { localSegmentationData: { @@ -308,19 +323,26 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { } case "ADD_PRECOMPUTED_MESH": { - const { layerName, segmentId, seedPosition, meshFileName } = action; + const { layerName, segmentId, seedPosition, seedAdditionalCoordinates, meshFileName } = + action; const meshInfo: MeshInformation = { segmentId: segmentId, seedPosition, + seedAdditionalCoordinates, isLoading: false, isVisible: true, isPrecomputed: true, meshFileName, }; - const additionalCoordKey = getAdditionalCoordinatesAsString( - state.flycam.additionalCoordinates, + const additionalCoordinates = state.flycam.additionalCoordinates; + const additionalCoordKey = getAdditionalCoordinatesAsString(additionalCoordinates); + + const stateWithCurrentAddCoords = maybeAddAdditionalCoordinatesToMeshState( + state, + additionalCoordinates, + layerName, ); - const updatedKey = update(state, { + const updatedKey = update(stateWithCurrentAddCoords, { localSegmentationData: { [layerName]: { meshes: { diff --git a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts index 8ffff8ae011..24e7900d2fc 100644 --- a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts @@ -1106,25 +1106,21 @@ function* downloadMeshCellsAsZIP( const additionalCoordinates = yield* select((state) => state.flycam.additionalCoordinates); try { const addFileToZipWriterPromises = segments.map((element) => { - if ( - segmentMeshController.hasMesh(element.segmentId, element.layerName, additionalCoordinates) - ) { - const geometry = segmentMeshController.getMeshGeometryInBestLOD( - element.segmentId, - element.layerName, - additionalCoordinates, - ); + const geometry = segmentMeshController.getMeshGeometryInBestLOD( + element.segmentId, + element.layerName, + additionalCoordinates, + ); - if (geometry == null) { - const errorMessage = messages["tracing.not_mesh_available_to_download"]; - Toast.error(errorMessage, { - sticky: false, - }); - return; - } - const stlDataReader = new Zip.BlobReader(getSTLBlob(geometry, element.segmentId)); - return zipWriter.add(`${element.segmentName}-${element.segmentId}.stl`, stlDataReader); + if (geometry == null) { + const errorMessage = messages["tracing.not_mesh_available_to_download"]; + Toast.error(errorMessage, { + sticky: false, + }); + return; } + const stlDataReader = new Zip.BlobReader(getSTLBlob(geometry, element.segmentId)); + return zipWriter.add(`${element.segmentName}-${element.segmentId}.stl`, stlDataReader); }); yield all(addFileToZipWriterPromises); const result = yield* call([zipWriter, zipWriter.close]); diff --git a/frontend/javascripts/oxalis/store.ts b/frontend/javascripts/oxalis/store.ts index 0674c283207..54c6644a31f 100644 --- a/frontend/javascripts/oxalis/store.ts +++ b/frontend/javascripts/oxalis/store.ts @@ -570,7 +570,9 @@ export type OxalisState = { readonly localSegmentationData: Record< string, //layerName { - readonly meshes: Record | undefined> | undefined; //string represents additional coordinates, number is the segment ID + //for meshes, the string represents additional coordinates, number is the segment ID. + // The undefined types were added to enforce null checks when using this structure. + readonly meshes: Record | undefined> | undefined; readonly availableMeshFiles: Array | null | undefined; readonly currentMeshFile: APIMeshFile | null | undefined; // Note that for a volume tracing, this information should be stored From f0c34a15bb7a26cb4ee4295254dda5dd18f5e0e6 Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Wed, 13 Dec 2023 14:16:53 +0100 Subject: [PATCH 42/43] fix bug concerning precomputed meshes --- .../controller/segment_mesh_controller.ts | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts index 437a2ef7922..896c1a64a38 100644 --- a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts +++ b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts @@ -109,7 +109,7 @@ export default class SegmentMeshController { const isNewlyAddedMesh = this.meshesGroupsPerSegmentationId[additionalCoordinatesString]?.[layerName]?.[ segmentationId - ] == null; + ]?.[lod] == null; const targetGroup = _.get(this.meshesGroupsPerSegmentationId, keys, new THREE.Group()); _.set( this.meshesGroupsPerSegmentationId, @@ -139,20 +139,18 @@ export default class SegmentMeshController { removeMeshById(segmentationId: number, layerName: string): void { const additionalCoordinates = Store.getState().flycam.additionalCoordinates; const additionalCoordKey = getAdditionalCoordinatesAsString(additionalCoordinates); - if (this.getMeshGroups(additionalCoordKey, layerName, segmentationId) == null) { + const meshGroups = this.getMeshGroups(additionalCoordKey, layerName, segmentationId); + if (meshGroups == null) { return; } - _.forEach( - this.getMeshGroups(additionalCoordKey, layerName, segmentationId), - (meshGroup, lod) => { - const lodNumber = parseInt(lod); - if (lodNumber !== NO_LOD_MESH_INDEX) { - this.meshesLODRootGroup.removeLODMesh(meshGroup, lodNumber); - } else { - this.meshesLODRootGroup.removeNoLODSupportedMesh(meshGroup); - } - }, - ); + _.forEach(meshGroups, (meshGroup, lod) => { + const lodNumber = parseInt(lod); + if (lodNumber !== NO_LOD_MESH_INDEX) { + this.meshesLODRootGroup.removeLODMesh(meshGroup, lodNumber); + } else { + this.meshesLODRootGroup.removeNoLODSupportedMesh(meshGroup); + } + }); this.removeMeshFromMeshGroups(additionalCoordKey, layerName, segmentationId); } From 534ef4465f0783d7297d4cb5deff762d6f03c2af Mon Sep 17 00:00:00 2001 From: Philipp Otto Date: Wed, 13 Dec 2023 14:50:58 +0100 Subject: [PATCH 43/43] avoid implicit any types --- .../oxalis/controller/segment_mesh_controller.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts index 896c1a64a38..d761ebc87f2 100644 --- a/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts +++ b/frontend/javascripts/oxalis/controller/segment_mesh_controller.ts @@ -235,13 +235,17 @@ export default class SegmentMeshController { layerName: string, segmentationId: number, lod: number, - ) { + ): THREE.Group | null { const additionalCoordKey = getAdditionalCoordinatesAsString(additionalCoordinates); const keys = [additionalCoordKey, layerName, segmentationId, lod]; return _.get(this.meshesGroupsPerSegmentationId, keys, null); } - getMeshGroups(additionalCoordKey: string, layerName: string, segmentationId: number) { + getMeshGroups( + additionalCoordKey: string, + layerName: string, + segmentationId: number, + ): Record | null { const keys = [additionalCoordKey, layerName, segmentationId]; return _.get(this.meshesGroupsPerSegmentationId, keys, null); }