Skip to content

Commit 49d285d

Browse files
knollengewaechsfm3philippotto
authored
Check bounding boxes and trees before starting neuron segmentation (#8678)
### Steps to test: - enable long-running jobs - create an annotation and open the `AI jobs` modal. select neuron segmentation. enable `Evaluation settings`. Either of the following conditions should yield a corresponding error toasts and prevent you from starting the job: - having no skeleton trees - having an empty skeleton (any trees without nodes) - having no user bounding boxes - having multiple user bounding boxes - having exactly one user bounding box and a task bounding box. - all this should be independent of the selected bounding box. - open the annotation of a finished task. if a bounding box appears in the bounding box tab, this bounding box should be selectable in the bounding box dropdown of the modal. after selecting it (given that there are skeletons with nodes), you should be able to start the job. - the scene controller was adjusted in the place where the task bounding boxes are updated. thus you should create some tasks with different bounding boxes and check after clicking `Finish and get next task` the lime green task bounding box is updated within the view port. ### context questions/feedback: https://scm.slack.com/archives/C5AKLAV0B/p1749201952606739 ### TODOs: - [x] maybe incorporate feedback from slack ### Issues: - fixes #8537 ------ (Please delete unneeded items, merge only when none are left open) - [x] Updated [changelog](../blob/master/CHANGELOG.unreleased.md#unreleased) - [ ] Updated [migration guide](../blob/master/MIGRATIONS.unreleased.md#unreleased) if applicable - [x] ❓ Updated [documentation](../blob/master/docs) if applicable [not much in the docs anyway, description was added to modal] - [ ] Adapted [wk-libs python client](https://github.com/scalableminds/webknossos-libs/tree/master/webknossos/webknossos/client) if relevant API parts change - [ ] Removed dev-only changes like prints and application.conf edits - [x] Considered [common edge cases](../blob/master/.github/common_edge_cases.md) - [ ] Needs datastore update after deployment --------- Co-authored-by: Florian M <fm3@users.noreply.github.com> Co-authored-by: Philipp Otto <philipp.4096@gmail.com>
1 parent d93f70e commit 49d285d

File tree

6 files changed

+67
-9
lines changed

6 files changed

+67
-9
lines changed

frontend/javascripts/messages.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,4 +503,6 @@ instead. Only enable this option if you understand its effect. All layers will n
503503
`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.`,
504504
"organization.plan.feature_not_available.owner": (requiredPlan: string) =>
505505
`This feature is not available in your organization's plan. Consider upgrading to a ${requiredPlan} plan or higher.`,
506+
"jobs.wrongNumberOfBoundingBoxes":
507+
"To use the split/merger evaluation, make sure to have exactly one bounding box, either user-defined or from a task.",
506508
};

frontend/javascripts/viewer/controller/scene_controller.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -329,13 +329,18 @@ class SceneController {
329329
adding a new one. Since this function is executed very rarely,
330330
this is not a performance problem.
331331
*/
332-
for (const [tracingId, boundingBox] of Object.entries(taskCubeByTracingId)) {
332+
333+
// Clean up old entries
334+
for (const [tracingId, _boundingBox] of Object.entries(this.taskCubeByTracingId)) {
333335
let taskCube = this.taskCubeByTracingId[tracingId];
334-
// Remove the old box if it exists
335336
if (taskCube != null) {
336337
taskCube.getMeshes().forEach((mesh) => this.rootNode.remove(mesh));
337338
}
338339
this.taskCubeByTracingId[tracingId] = null;
340+
}
341+
// Add new entries
342+
for (const [tracingId, boundingBox] of Object.entries(taskCubeByTracingId)) {
343+
let taskCube = this.taskCubeByTracingId[tracingId];
339344
if (boundingBox == null || Store.getState().task == null) {
340345
continue;
341346
}
@@ -728,7 +733,7 @@ class SceneController {
728733
this.updateMeshesAccordingToLayerVisibility(),
729734
),
730735
listenToStoreProperty(
731-
(storeState) => getTaskBoundingBoxes(storeState.annotation),
736+
(storeState) => getTaskBoundingBoxes(storeState),
732737
(boundingBoxesByTracingId) => this.updateTaskBoundingBoxes(boundingBoxesByTracingId),
733738
true,
734739
),

frontend/javascripts/viewer/model/accessors/skeletontracing_accessor.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,10 @@ export function findTreeByNodeId(trees: TreeMap, nodeId: number): Tree | undefin
117117
return trees.values().find((tree) => tree.nodes.has(nodeId));
118118
}
119119

120+
export function hasEmptyTrees(trees: TreeMap): boolean {
121+
return trees.values().some((tree: Tree) => tree.nodes.size() === 0);
122+
}
123+
120124
export function findTreeByName(trees: TreeMap, treeName: string): Tree | undefined {
121125
return trees.values().find((tree: Tree) => tree.name === treeName);
122126
}

frontend/javascripts/viewer/model/accessors/tracing_accessor.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,10 @@ export function selectTracing(
8686
return tracing;
8787
}
8888

89-
function _getTaskBoundingBoxes(annotation: StoreAnnotation) {
90-
const layers = _.compact([annotation.skeleton, ...annotation.volumes, annotation.readOnly]);
91-
89+
function _getTaskBoundingBoxes(state: WebknossosState) {
90+
const { annotation, task } = state;
91+
if (task == null) return {};
92+
const layers = _.compact([annotation.skeleton, ...annotation.volumes]);
9293
return Object.fromEntries(layers.map((l) => [l.tracingId, l.boundingBox]));
9394
}
9495

frontend/javascripts/viewer/view/action-bar/starting_job_modals.tsx

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import {
4747
rgbToHex,
4848
} from "libs/utils";
4949
import _ from "lodash";
50+
import messages from "messages";
5051
import React, { useEffect, useState, useMemo } from "react";
5152
import { useDispatch } from "react-redux";
5253
import { type APIDataLayer, type APIJob, APIJobType, type VoxelSize } from "types/api_types";
@@ -56,7 +57,11 @@ import {
5657
getMagInfo,
5758
getSegmentationLayers,
5859
} from "viewer/model/accessors/dataset_accessor";
59-
import { getUserBoundingBoxesFromState } from "viewer/model/accessors/tracing_accessor";
60+
import { hasEmptyTrees } from "viewer/model/accessors/skeletontracing_accessor";
61+
import {
62+
getTaskBoundingBoxes,
63+
getUserBoundingBoxesFromState,
64+
} from "viewer/model/accessors/tracing_accessor";
6065
import {
6166
getActiveSegmentationTracingLayer,
6267
getReadableNameOfVolumeLayer,
@@ -636,6 +641,19 @@ function CollapsibleSplitMergerEvaluationSettings({
636641
children: (
637642
<Row>
638643
<Col style={{ width: "100%" }}>
644+
<div style={{ marginBottom: 24 }}>
645+
You can evaluate splits/mergers on a given bounding box. <br />
646+
By default this is the user defined bounding box or the bounding box of a task.{" "}
647+
<br />
648+
Thus your annotation should contain
649+
<ul>
650+
<li> either one user defined bounding box or the bounding box of a task</li>
651+
<li>
652+
with at least one neuron (sparse) or all neurons (dense) annotated as
653+
skeletons.
654+
</li>
655+
</ul>
656+
</div>
639657
<Form.Item
640658
layout="horizontal"
641659
label="Use sparse ground truth tracing"
@@ -1054,9 +1072,10 @@ export function NucleiDetectionForm() {
10541072
export function NeuronSegmentationForm() {
10551073
const dataset = useWkSelector((state) => state.dataset);
10561074
const { neuronInferralCostPerGVx } = features();
1057-
const hasSkeletonAnnotation = useWkSelector((state) => state.annotation.skeleton != null);
1075+
const skeletonAnnotation = useWkSelector((state) => state.annotation.skeleton);
10581076
const dispatch = useDispatch();
10591077
const [doSplitMergerEvaluation, setDoSplitMergerEvaluation] = React.useState(false);
1078+
10601079
return (
10611080
<StartJobForm
10621081
handleClose={() => dispatch(setAIJobModalStateAction("invisible"))}
@@ -1099,6 +1118,31 @@ export function NeuronSegmentationForm() {
10991118
doSplitMergerEvaluation,
11001119
);
11011120
}
1121+
1122+
const state = Store.getState();
1123+
const userBoundingBoxCount = getUserBoundingBoxesFromState(state).length;
1124+
1125+
if (userBoundingBoxCount > 1) {
1126+
Toast.error(messages["jobs.wrongNumberOfBoundingBoxes"]);
1127+
return;
1128+
}
1129+
1130+
const taskBoundingBoxes = getTaskBoundingBoxes(state);
1131+
if (Object.values(taskBoundingBoxes).length + userBoundingBoxCount !== 1) {
1132+
Toast.error(messages["jobs.wrongNumberOfBoundingBoxes"]);
1133+
return;
1134+
}
1135+
1136+
if (skeletonAnnotation == null || skeletonAnnotation.trees.size() === 0) {
1137+
Toast.error(
1138+
"Please ensure that a skeleton tree exists within the selected bounding box.",
1139+
);
1140+
return;
1141+
}
1142+
if (hasEmptyTrees(skeletonAnnotation.trees)) {
1143+
Toast.error("Please ensure that all skeleton trees in this annotation have some nodes.");
1144+
return;
1145+
}
11021146
return startNeuronInferralJob(
11031147
dataset.id,
11041148
colorLayer.name,
@@ -1126,7 +1170,7 @@ export function NeuronSegmentationForm() {
11261170
</>
11271171
}
11281172
jobSpecificInputFields={
1129-
hasSkeletonAnnotation && (
1173+
skeletonAnnotation != null && (
11301174
<CollapsibleSplitMergerEvaluationSettings
11311175
isActive={doSplitMergerEvaluation}
11321176
setActive={setDoSplitMergerEvaluation}

unreleased_changes/8678.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
### Changed
2+
- 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.

0 commit comments

Comments
 (0)