From 9b8d792934b536bd4d2546ed79e541f0747d93e0 Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Fri, 6 Jun 2025 14:29:39 +0200 Subject: [PATCH 01/10] add some bbox and skeleton checks for neuron segmentation --- .../accessors/skeletontracing_accessor.ts | 4 ++++ .../view/action-bar/starting_job_modals.tsx | 20 +++++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/frontend/javascripts/viewer/model/accessors/skeletontracing_accessor.ts b/frontend/javascripts/viewer/model/accessors/skeletontracing_accessor.ts index b4a5eb2149e..54403e4644f 100644 --- a/frontend/javascripts/viewer/model/accessors/skeletontracing_accessor.ts +++ b/frontend/javascripts/viewer/model/accessors/skeletontracing_accessor.ts @@ -117,6 +117,10 @@ export function findTreeByNodeId(trees: TreeMap, nodeId: number): Tree | undefin return trees.values().find((tree) => tree.nodes.has(nodeId)); } +export function hasEmptyTrees(trees: TreeMap): boolean { + return trees.values().some((tree: Tree) => tree.nodes.size() === 0); +} + export function findTreeByName(trees: TreeMap, treeName: string): Tree | undefined { return trees.values().find((tree: Tree) => tree.name === treeName); } diff --git a/frontend/javascripts/viewer/view/action-bar/starting_job_modals.tsx b/frontend/javascripts/viewer/view/action-bar/starting_job_modals.tsx index 264860f9929..497300bf1e1 100644 --- a/frontend/javascripts/viewer/view/action-bar/starting_job_modals.tsx +++ b/frontend/javascripts/viewer/view/action-bar/starting_job_modals.tsx @@ -56,6 +56,7 @@ import { getMagInfo, getSegmentationLayers, } from "viewer/model/accessors/dataset_accessor"; +import { hasEmptyTrees } from "viewer/model/accessors/skeletontracing_accessor"; import { getUserBoundingBoxesFromState } from "viewer/model/accessors/tracing_accessor"; import { getActiveSegmentationTracingLayer, @@ -1054,9 +1055,10 @@ export function NucleiDetectionForm() { export function NeuronSegmentationForm() { const dataset = useWkSelector((state) => state.dataset); const { neuronInferralCostPerGVx } = features(); - const hasSkeletonAnnotation = useWkSelector((state) => state.annotation.skeleton != null); + const skeletonAnnotation = useWkSelector((state) => state.annotation.skeleton); const dispatch = useDispatch(); const [doSplitMergerEvaluation, setDoSplitMergerEvaluation] = React.useState(false); + const userBoundingBoxes = useWkSelector((state) => getUserBoundingBoxesFromState(state)); return ( dispatch(setAIJobModalStateAction("invisible"))} @@ -1099,6 +1101,20 @@ export function NeuronSegmentationForm() { doSplitMergerEvaluation, ); } + if (userBoundingBoxes.find((bbox) => bbox.id === selectedBoundingBox.id) == null) { + Toast.error( + "To use the split/merger evaluation, please select a bounding box that is not the full layer bounding box.", + ); + return; + } + if (skeletonAnnotation == null || skeletonAnnotation?.trees.size() === 0) { + Toast.error("Please ensure that the skeleton annotation has at least one tree."); + return; + } + if (hasEmptyTrees(skeletonAnnotation.trees)) { + Toast.error("Please ensure that all skeleton trees in this annotation have some nodes."); + return; + } return startNeuronInferralJob( dataset.id, colorLayer.name, @@ -1126,7 +1142,7 @@ export function NeuronSegmentationForm() { } jobSpecificInputFields={ - hasSkeletonAnnotation && ( + skeletonAnnotation != null && ( Date: Fri, 6 Jun 2025 14:50:13 +0200 Subject: [PATCH 02/10] add more info for ground truth --- .../viewer/view/action-bar/starting_job_modals.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/frontend/javascripts/viewer/view/action-bar/starting_job_modals.tsx b/frontend/javascripts/viewer/view/action-bar/starting_job_modals.tsx index 497300bf1e1..e30547046cf 100644 --- a/frontend/javascripts/viewer/view/action-bar/starting_job_modals.tsx +++ b/frontend/javascripts/viewer/view/action-bar/starting_job_modals.tsx @@ -637,6 +637,14 @@ function CollapsibleSplitMergerEvaluationSettings({ children: ( +
+ To use it as the ground truth, your annotation should contain +
    +
  • a user-defined bounding box,
  • +
  • at least one tree,
  • +
  • and every tree should have at least one node.
  • +
+
Date: Wed, 11 Jun 2025 14:27:20 +0200 Subject: [PATCH 03/10] improve modal text --- .../view/action-bar/starting_job_modals.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/frontend/javascripts/viewer/view/action-bar/starting_job_modals.tsx b/frontend/javascripts/viewer/view/action-bar/starting_job_modals.tsx index e30547046cf..95973bab872 100644 --- a/frontend/javascripts/viewer/view/action-bar/starting_job_modals.tsx +++ b/frontend/javascripts/viewer/view/action-bar/starting_job_modals.tsx @@ -638,11 +638,15 @@ function CollapsibleSplitMergerEvaluationSettings({
- To use it as the ground truth, your annotation should contain + You can use the selected bounding box to evaluate the splits/mergers. +
+ The selected bounding box should
    -
  • a user-defined bounding box,
  • -
  • at least one tree,
  • -
  • and every tree should have at least one node.
  • +
  • be either user-defined or the bounding box of a task
  • +
  • + contain at least one neuron (sparse) or all neurons (dense) annotated as + skeletons. +
state.annotation.skeleton); const dispatch = useDispatch(); const [doSplitMergerEvaluation, setDoSplitMergerEvaluation] = React.useState(false); - const userBoundingBoxes = useWkSelector((state) => getUserBoundingBoxesFromState(state)); + const userAndTaskBoundingBoxes = useWkSelector((state) => getUserBoundingBoxesFromState(state)); return ( dispatch(setAIJobModalStateAction("invisible"))} @@ -1109,7 +1113,7 @@ export function NeuronSegmentationForm() { doSplitMergerEvaluation, ); } - if (userBoundingBoxes.find((bbox) => bbox.id === selectedBoundingBox.id) == null) { + if (userAndTaskBoundingBoxes.find((bbox) => bbox.id === selectedBoundingBox.id) == null) { Toast.error( "To use the split/merger evaluation, please select a bounding box that is not the full layer bounding box.", ); From e9177ace66cff5ad55f05ea10bc8c135c0bf5b96 Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Wed, 11 Jun 2025 14:53:18 +0200 Subject: [PATCH 04/10] improve toasts --- .../viewer/view/action-bar/starting_job_modals.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/javascripts/viewer/view/action-bar/starting_job_modals.tsx b/frontend/javascripts/viewer/view/action-bar/starting_job_modals.tsx index 95973bab872..6fd0ac61b2e 100644 --- a/frontend/javascripts/viewer/view/action-bar/starting_job_modals.tsx +++ b/frontend/javascripts/viewer/view/action-bar/starting_job_modals.tsx @@ -1115,12 +1115,14 @@ export function NeuronSegmentationForm() { } if (userAndTaskBoundingBoxes.find((bbox) => bbox.id === selectedBoundingBox.id) == null) { Toast.error( - "To use the split/merger evaluation, please select a bounding box that is not the full layer bounding box.", + "To use the split/merger evaluation, please select either a user-defined bounding box or a task bounding box.", ); return; } if (skeletonAnnotation == null || skeletonAnnotation?.trees.size() === 0) { - Toast.error("Please ensure that the skeleton annotation has at least one tree."); + Toast.error( + "Please ensure that a skeleton tree exists within the selected bounding box.", + ); return; } if (hasEmptyTrees(skeletonAnnotation.trees)) { From b1290cef1a8863c8fd71c27877bed9812846780e Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Wed, 11 Jun 2025 15:08:46 +0200 Subject: [PATCH 05/10] changelog --- CHANGELOG.unreleased.md | 3 ++- conf/application.conf | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index 61c76046a41..45d8be3c6ba 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -15,7 +15,8 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released - Meshes are now reloaded using their previous opacity value. [#8622](https://github.com/scalableminds/webknossos/pull/8622) ### Changed - +- Before starting a neuron segmentation with `Evaluation Settings` enabled, it is checked that a useful bounding box was selected and that some skeletons exist within the annotation, preventing the job from failing. [#8678](https://github.com/scalableminds/webknossos/pull/8678) +- Before starting a neuron segmentation with `Evaluation Settings` enabled, it is checked that a useful bounding box was selected and that some skeletons exist within the annotation, preventing the job ### Fixed - Improved efficiency of saving bounding box related changes. [#8492](https://github.com/scalableminds/webknossos/pull/8492) - When deleting a dataset, its caches are cleared, so that if a new dataset by the same name is uploaded afterwards, only new data is loaded. [#8638](https://github.com/scalableminds/webknossos/pull/8638) diff --git a/conf/application.conf b/conf/application.conf index aac6419d24e..eb75c00f487 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -151,8 +151,8 @@ features { taskReopenAllowedInSeconds = 30 allowDeleteDatasets = true # to enable jobs for local development, use "yarn enable-jobs" to also activate it in the database - jobsEnabled = false - voxelyticsEnabled = false + jobsEnabled = true + voxelyticsEnabled = true neuronInferralCostPerGVx = 1 mitochondriaInferralCostPerGVx = 0.5 alignmentCostPerGVx = 0.5 From 97e03d05b9cea6879b50aca5e1dae5bddb1261bf Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Wed, 11 Jun 2025 15:12:39 +0200 Subject: [PATCH 06/10] add comment --- conf/application.conf | 4 ++-- .../viewer/view/action-bar/starting_job_modals.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/conf/application.conf b/conf/application.conf index eb75c00f487..aac6419d24e 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -151,8 +151,8 @@ features { taskReopenAllowedInSeconds = 30 allowDeleteDatasets = true # to enable jobs for local development, use "yarn enable-jobs" to also activate it in the database - jobsEnabled = true - voxelyticsEnabled = true + jobsEnabled = false + voxelyticsEnabled = false neuronInferralCostPerGVx = 1 mitochondriaInferralCostPerGVx = 0.5 alignmentCostPerGVx = 0.5 diff --git a/frontend/javascripts/viewer/view/action-bar/starting_job_modals.tsx b/frontend/javascripts/viewer/view/action-bar/starting_job_modals.tsx index 6fd0ac61b2e..ecae749af12 100644 --- a/frontend/javascripts/viewer/view/action-bar/starting_job_modals.tsx +++ b/frontend/javascripts/viewer/view/action-bar/starting_job_modals.tsx @@ -1070,7 +1070,7 @@ export function NeuronSegmentationForm() { const skeletonAnnotation = useWkSelector((state) => state.annotation.skeleton); const dispatch = useDispatch(); const [doSplitMergerEvaluation, setDoSplitMergerEvaluation] = React.useState(false); - const userAndTaskBoundingBoxes = useWkSelector((state) => getUserBoundingBoxesFromState(state)); + const userAndTaskBoundingBoxes = useWkSelector((state) => getUserBoundingBoxesFromState(state)); // Includes task bounding boxes return ( dispatch(setAIJobModalStateAction("invisible"))} From c458eb623d02ac5e8f6336867dabfedaed9ae6f5 Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Fri, 13 Jun 2025 09:54:50 +0200 Subject: [PATCH 07/10] remove duplicate changelog entry --- CHANGELOG.unreleased.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index 45d8be3c6ba..220271df87b 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -16,7 +16,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released ### Changed - Before starting a neuron segmentation with `Evaluation Settings` enabled, it is checked that a useful bounding box was selected and that some skeletons exist within the annotation, preventing the job from failing. [#8678](https://github.com/scalableminds/webknossos/pull/8678) -- Before starting a neuron segmentation with `Evaluation Settings` enabled, it is checked that a useful bounding box was selected and that some skeletons exist within the annotation, preventing the job + ### Fixed - Improved efficiency of saving bounding box related changes. [#8492](https://github.com/scalableminds/webknossos/pull/8492) - When deleting a dataset, its caches are cleared, so that if a new dataset by the same name is uploaded afterwards, only new data is loaded. [#8638](https://github.com/scalableminds/webknossos/pull/8638) From dc7b6c90ac6a6117d19bd27dc18766e2798cfea6 Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Fri, 13 Jun 2025 15:18:13 +0200 Subject: [PATCH 08/10] check that there is exactly one bbox --- frontend/javascripts/messages.tsx | 2 ++ .../view/action-bar/starting_job_modals.tsx | 28 ++++++++++++++----- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/frontend/javascripts/messages.tsx b/frontend/javascripts/messages.tsx index e9e7dc996c0..855d6c22cab 100644 --- a/frontend/javascripts/messages.tsx +++ b/frontend/javascripts/messages.tsx @@ -503,4 +503,6 @@ instead. Only enable this option if you understand its effect. All layers will n `This feature is not available in your organization's plan. Ask the owner of your organization ${organizationOwnerName} to upgrade to a ${requiredPlan} plan or higher.`, "organization.plan.feature_not_available.owner": (requiredPlan: string) => `This feature is not available in your organization's plan. Consider upgrading to a ${requiredPlan} plan or higher.`, + "jobs.wrongNumberOfBoundingBoxes": + "To use the split/merger evaluation, make sure to have exactly one bounding box, either user-defined or from a task.", }; diff --git a/frontend/javascripts/viewer/view/action-bar/starting_job_modals.tsx b/frontend/javascripts/viewer/view/action-bar/starting_job_modals.tsx index ecae749af12..6bcd760a910 100644 --- a/frontend/javascripts/viewer/view/action-bar/starting_job_modals.tsx +++ b/frontend/javascripts/viewer/view/action-bar/starting_job_modals.tsx @@ -47,6 +47,7 @@ import { rgbToHex, } from "libs/utils"; import _ from "lodash"; +import messages from "messages"; import React, { useEffect, useState, useMemo } from "react"; import { useDispatch } from "react-redux"; import { type APIDataLayer, type APIJob, APIJobType, type VoxelSize } from "types/api_types"; @@ -57,7 +58,10 @@ import { getSegmentationLayers, } from "viewer/model/accessors/dataset_accessor"; import { hasEmptyTrees } from "viewer/model/accessors/skeletontracing_accessor"; -import { getUserBoundingBoxesFromState } from "viewer/model/accessors/tracing_accessor"; +import { + getTaskBoundingBoxes, + getUserBoundingBoxesFromState, +} from "viewer/model/accessors/tracing_accessor"; import { getActiveSegmentationTracingLayer, getReadableNameOfVolumeLayer, @@ -1070,7 +1074,7 @@ export function NeuronSegmentationForm() { const skeletonAnnotation = useWkSelector((state) => state.annotation.skeleton); const dispatch = useDispatch(); const [doSplitMergerEvaluation, setDoSplitMergerEvaluation] = React.useState(false); - const userAndTaskBoundingBoxes = useWkSelector((state) => getUserBoundingBoxesFromState(state)); // Includes task bounding boxes + return ( dispatch(setAIJobModalStateAction("invisible"))} @@ -1113,13 +1117,23 @@ export function NeuronSegmentationForm() { doSplitMergerEvaluation, ); } - if (userAndTaskBoundingBoxes.find((bbox) => bbox.id === selectedBoundingBox.id) == null) { - Toast.error( - "To use the split/merger evaluation, please select either a user-defined bounding box or a task bounding box.", - ); + + const state = Store.getState(); + const userBoundingBoxCount = getUserBoundingBoxesFromState(state).length; + + if (userBoundingBoxCount > 1) { + Toast.error(messages["jobs.wrongNumberOfBoundingBoxes"]); return; } - if (skeletonAnnotation == null || skeletonAnnotation?.trees.size() === 0) { + + const taskBoundingBox = getTaskBoundingBoxes(state.annotation); + const taskBoundingBoxCount = state.task != null ? Object.values(taskBoundingBox).length : 0; + if (taskBoundingBoxCount + userBoundingBoxCount !== 1) { + Toast.error(messages["jobs.wrongNumberOfBoundingBoxes"]); + return; + } + + if (skeletonAnnotation == null || skeletonAnnotation.trees.size() === 0) { Toast.error( "Please ensure that a skeleton tree exists within the selected bounding box.", ); From 2510fcd26941e32bdcf796660a8a0f0a60022480 Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Fri, 13 Jun 2025 15:52:33 +0200 Subject: [PATCH 09/10] update getTracingBoundingBoxes method to return empty without tracing --- .../javascripts/viewer/controller/scene_controller.ts | 11 ++++++++--- .../viewer/model/accessors/tracing_accessor.ts | 7 ++++--- .../viewer/view/action-bar/starting_job_modals.tsx | 5 ++--- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/frontend/javascripts/viewer/controller/scene_controller.ts b/frontend/javascripts/viewer/controller/scene_controller.ts index 2bbad2759be..ecd9d887239 100644 --- a/frontend/javascripts/viewer/controller/scene_controller.ts +++ b/frontend/javascripts/viewer/controller/scene_controller.ts @@ -334,13 +334,18 @@ class SceneController { adding a new one. Since this function is executed very rarely, this is not a performance problem. */ - for (const [tracingId, boundingBox] of Object.entries(taskCubeByTracingId)) { + + // Clean up old entries + for (const [tracingId, _boundingBox] of Object.entries(this.taskCubeByTracingId)) { let taskCube = this.taskCubeByTracingId[tracingId]; - // Remove the old box if it exists if (taskCube != null) { taskCube.getMeshes().forEach((mesh) => this.rootNode.remove(mesh)); } this.taskCubeByTracingId[tracingId] = null; + } + // Add new entries + for (const [tracingId, boundingBox] of Object.entries(taskCubeByTracingId)) { + let taskCube = this.taskCubeByTracingId[tracingId]; if (boundingBox == null || Store.getState().task == null) { continue; } @@ -733,7 +738,7 @@ class SceneController { this.updateMeshesAccordingToLayerVisibility(), ), listenToStoreProperty( - (storeState) => getTaskBoundingBoxes(storeState.annotation), + (storeState) => getTaskBoundingBoxes(storeState), (boundingBoxesByTracingId) => this.updateTaskBoundingBoxes(boundingBoxesByTracingId), true, ), diff --git a/frontend/javascripts/viewer/model/accessors/tracing_accessor.ts b/frontend/javascripts/viewer/model/accessors/tracing_accessor.ts index 68ff504b40f..008debb1a37 100644 --- a/frontend/javascripts/viewer/model/accessors/tracing_accessor.ts +++ b/frontend/javascripts/viewer/model/accessors/tracing_accessor.ts @@ -91,9 +91,10 @@ export function selectTracing( return tracing; } -function _getTaskBoundingBoxes(annotation: StoreAnnotation) { - const layers = _.compact([annotation.skeleton, ...annotation.volumes, annotation.readOnly]); - +function _getTaskBoundingBoxes(state: WebknossosState) { + const { annotation, task } = state; + if (task == null) return {}; + const layers = _.compact([annotation.skeleton, ...annotation.volumes]); return Object.fromEntries(layers.map((l) => [l.tracingId, l.boundingBox])); } diff --git a/frontend/javascripts/viewer/view/action-bar/starting_job_modals.tsx b/frontend/javascripts/viewer/view/action-bar/starting_job_modals.tsx index 6bcd760a910..a155492d436 100644 --- a/frontend/javascripts/viewer/view/action-bar/starting_job_modals.tsx +++ b/frontend/javascripts/viewer/view/action-bar/starting_job_modals.tsx @@ -1126,9 +1126,8 @@ export function NeuronSegmentationForm() { return; } - const taskBoundingBox = getTaskBoundingBoxes(state.annotation); - const taskBoundingBoxCount = state.task != null ? Object.values(taskBoundingBox).length : 0; - if (taskBoundingBoxCount + userBoundingBoxCount !== 1) { + const taskBoundingBoxes = getTaskBoundingBoxes(state); + if (Object.values(taskBoundingBoxes).length + userBoundingBoxCount !== 1) { Toast.error(messages["jobs.wrongNumberOfBoundingBoxes"]); return; } From 6fc7dc6113664730fcd9e84fbbd30c4c5de56b3d Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Fri, 13 Jun 2025 16:04:43 +0200 Subject: [PATCH 10/10] adjust text in modal --- .../viewer/view/action-bar/starting_job_modals.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/frontend/javascripts/viewer/view/action-bar/starting_job_modals.tsx b/frontend/javascripts/viewer/view/action-bar/starting_job_modals.tsx index a155492d436..c494ebd6fec 100644 --- a/frontend/javascripts/viewer/view/action-bar/starting_job_modals.tsx +++ b/frontend/javascripts/viewer/view/action-bar/starting_job_modals.tsx @@ -642,13 +642,14 @@ function CollapsibleSplitMergerEvaluationSettings({
- You can use the selected bounding box to evaluate the splits/mergers. + You can evaluate splits/mergers on a given bounding box.
+ By default this is the user defined bounding box or the bounding box of a task.{" "}
- The selected bounding box should + Thus your annotation should contain
    -
  • be either user-defined or the bounding box of a task
  • +
  • either one user defined bounding box or the bounding box of a task
  • - contain at least one neuron (sparse) or all neurons (dense) annotated as + with at least one neuron (sparse) or all neurons (dense) annotated as skeletons.