Skip to content

Commit 08041b5

Browse files
Fix flood fill tool for coarse mags (#8382)
* Fix flood fill tool for coarse mags. The 2D flood fill tool did not work properly when flood filling in a mag other than the finest one if the "third-dimension" coordinate was not divisible by the respective mag. This resulted in early-out flood fills only filling a single voxel. Also, this commit adjusts the flood fill maximum bounding size limits if the segmentation layer's finest mag is not 1. This allows to use the flood fill tool in coarser mags if the segmentation layer is properly mag-restricted. * update changelog * Update frontend/javascripts/oxalis/model/sagas/volume/floodfill_saga.tsx Co-authored-by: Philipp Otto <[email protected]> * Apply naming feedback * Add linting rule for unused imports and fix 10 occurrences * Fix 3 new unused imports after master merge --------- Co-authored-by: Philipp Otto <[email protected]>
1 parent 7333c22 commit 08041b5

File tree

12 files changed

+77
-30
lines changed

12 files changed

+77
-30
lines changed

CHANGELOG.unreleased.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released
2020

2121
### Changed
2222
- Improved the scrolling behaviour of sliders: Sliders must be focused to scroll them. Additionally, parent element scrolling is prevented when using the slider. [#8321](https://github.com/scalableminds/webknossos/pull/8321) [#8321](https://github.com/scalableminds/webknossos/pull/8321)
23+
- Increase the flood fill maximum bounding box size limits for segmentation layers restricted to coarser mags. [#8382](https://github.com/scalableminds/webknossos/pull/8382)
2324

2425
### Fixed
2526
- Fixed a bug that lead to trees being dropped when merging to trees together. [#8359](https://github.com/scalableminds/webknossos/pull/8359)
2627
- Fixed that the onboarding screen incorrectly appeared when a certain request failed. [#8356](https://github.com/scalableminds/webknossos/pull/8356)
2728
- Fixed the segment registering in coarser mags for non-mag-aligned bounding boxes. [#8364](https://github.com/scalableminds/webknossos/pull/8364)
29+
- Fixed using the flood fill tool in 2D mode for mags other than the finest one. [#8382](https://github.com/scalableminds/webknossos/pull/8382)
2830

2931
### Removed
3032
- Removed the feature to downsample existing volume annotations. All new volume annotations had a whole mag stack since [#4755](https://github.com/scalableminds/webknossos/pull/4755) (four years ago). [#7917](https://github.com/scalableminds/webknossos/pull/7917)

biome.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
},
6565
"correctness": {
6666
"noUnusedVariables": "error",
67+
"noUnusedImports": "error",
6768
"noRenderReturnValue": "off",
6869
"useHookAtTopLevel": "error"
6970
},

frontend/javascripts/components/slider.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import { Slider as AntdSlider, type SliderSingleProps } from "antd";
22
import type { SliderRangeProps } from "antd/lib/slider";
33
import { clamp } from "libs/utils";
4-
import _ from "lodash";
5-
import { useCallback, useEffect, useRef, useState } from "react";
4+
import { useCallback, useEffect, useRef } from "react";
65

76
const DEFAULT_WHEEL_FACTOR = 0.02;
87
const DEFAULT_STEP = 1;

frontend/javascripts/oxalis/model.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import Store from "oxalis/store";
2020
import type { APICompoundType } from "types/api_flow_types";
2121

2222
import { globalToLayerTransformedPosition } from "./model/accessors/dataset_layer_transformation_accessor";
23-
import { invertTransform, transformPointUnscaled } from "./model/helpers/transformation_helpers";
2423
import { initialize } from "./model_initialization";
2524

2625
// TODO: Non-reactive

frontend/javascripts/oxalis/model/bucket_data_handling/data_cube.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -696,22 +696,27 @@ class DataCube {
696696
currentGlobalBucketPosition,
697697
V3.scale3(adjustedNeighbourVoxelXyz, currentMag),
698698
);
699+
// When flood filling in a coarser mag, a voxel in the coarse mag is more than one voxel in mag 1
700+
const voxelBoundingBoxInMag1 = new BoundingBox({
701+
min: currentGlobalPosition,
702+
max: V3.add(currentGlobalPosition, currentMag),
703+
});
699704

700705
if (bucketData[neighbourVoxelIndex] === sourceSegmentId) {
701-
if (floodfillBoundingBox.containsPoint(currentGlobalPosition)) {
706+
if (floodfillBoundingBox.intersectedWith(voxelBoundingBoxInMag1).getVolume() > 0) {
702707
bucketData[neighbourVoxelIndex] = segmentId;
703708
markUvwInSliceAsLabeled(neighbourVoxelUvw);
704709
neighbourVoxelStackUvw.pushVoxel(neighbourVoxelUvw);
705710
labeledVoxelCount++;
706711

707-
coveredBBoxMin[0] = Math.min(coveredBBoxMin[0], currentGlobalPosition[0]);
708-
coveredBBoxMin[1] = Math.min(coveredBBoxMin[1], currentGlobalPosition[1]);
709-
coveredBBoxMin[2] = Math.min(coveredBBoxMin[2], currentGlobalPosition[2]);
712+
coveredBBoxMin[0] = Math.min(coveredBBoxMin[0], voxelBoundingBoxInMag1.min[0]);
713+
coveredBBoxMin[1] = Math.min(coveredBBoxMin[1], voxelBoundingBoxInMag1.min[1]);
714+
coveredBBoxMin[2] = Math.min(coveredBBoxMin[2], voxelBoundingBoxInMag1.min[2]);
710715

711716
// The maximum is exclusive which is why we add 1 to the position
712-
coveredBBoxMax[0] = Math.max(coveredBBoxMax[0], currentGlobalPosition[0] + 1);
713-
coveredBBoxMax[1] = Math.max(coveredBBoxMax[1], currentGlobalPosition[1] + 1);
714-
coveredBBoxMax[2] = Math.max(coveredBBoxMax[2], currentGlobalPosition[2] + 1);
717+
coveredBBoxMax[0] = Math.max(coveredBBoxMax[0], voxelBoundingBoxInMag1.max[0] + 1);
718+
coveredBBoxMax[1] = Math.max(coveredBBoxMax[1], voxelBoundingBoxInMag1.max[1] + 1);
719+
coveredBBoxMax[2] = Math.max(coveredBBoxMax[2], voxelBoundingBoxInMag1.max[2] + 1);
715720

716721
if (labeledVoxelCount % 1000000 === 0) {
717722
console.log(`Labeled ${labeledVoxelCount} Vx. Continuing...`);

frontend/javascripts/oxalis/model/reducers/skeletontracing_reducer.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010
getNodeAndTree,
1111
getSkeletonTracing,
1212
getTree,
13-
getTreesWithType,
1413
isSkeletonLayerTransformed,
1514
} from "oxalis/model/accessors/skeletontracing_accessor";
1615
import type { Action } from "oxalis/model/actions/actions";

frontend/javascripts/oxalis/model/reducers/ui_reducer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { AnnotationToolEnum, AvailableToolsInViewMode, ControlModeEnum } from "oxalis/constants";
1+
import { AnnotationToolEnum, AvailableToolsInViewMode } from "oxalis/constants";
22
import type { Action } from "oxalis/model/actions/actions";
33
import { updateKey, updateKey2 } from "oxalis/model/helpers/deep_update";
44
import {

frontend/javascripts/oxalis/model/sagas/proofread_saga.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ import {
6666
updateSegmentAction,
6767
} from "oxalis/model/actions/volumetracing_actions";
6868
import type { Saga } from "oxalis/model/sagas/effect-generators";
69-
import { select, take } from "oxalis/model/sagas/effect-generators";
69+
import { select } from "oxalis/model/sagas/effect-generators";
7070
import {
7171
type UpdateActionWithoutIsolationRequirement,
7272
mergeAgglomerate,

frontend/javascripts/oxalis/model/sagas/saga_helpers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { ActionPattern, Predicate } from "@redux-saga/types";
1+
import type { ActionPattern } from "@redux-saga/types";
22
import { Modal } from "antd";
33
import Toast from "libs/toast";
44
import messages from "messages";

frontend/javascripts/oxalis/model/sagas/volume/floodfill_saga.tsx

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {
2727
import BoundingBox from "oxalis/model/bucket_data_handling/bounding_box";
2828
import Dimensions from "oxalis/model/dimensions";
2929
import type { Saga } from "oxalis/model/sagas/effect-generators";
30-
import { select, take } from "oxalis/model/sagas/effect-generators";
30+
import { select } from "oxalis/model/sagas/effect-generators";
3131
import { requestBucketModificationInVolumeTracing } from "oxalis/model/sagas/saga_helpers";
3232
import { Model } from "oxalis/singletons";
3333
import { call, put, takeEvery } from "typed-redux-saga";
@@ -41,7 +41,24 @@ export function* floodFill(): Saga<void> {
4141
yield* takeEvery("FLOOD_FILL", handleFloodFill);
4242
}
4343

44-
function* getBoundingBoxForFloodFillWhenRestricted(position: Vector3, currentViewport: OrthoView) {
44+
function getMaximumBoundingBoxSizeForFloodfill(
45+
fillMode: FillMode,
46+
finestSegmentationLayerMag: Vector3,
47+
): Vector3 {
48+
const maximumBoundingBoxMag1 = Constants.FLOOD_FILL_EXTENTS[fillMode];
49+
// If the finest mag of the segmentation layer is not Mag(1), the maximum bounding box needs to be scaled accordingly
50+
const maximumBoundingBoxInFinestMag = V3.scale3(
51+
maximumBoundingBoxMag1,
52+
finestSegmentationLayerMag,
53+
);
54+
return maximumBoundingBoxInFinestMag;
55+
}
56+
57+
function* getBoundingBoxForFloodFillWhenRestricted(
58+
position: Vector3,
59+
currentViewport: OrthoView,
60+
finestSegmentationLayerMag: Vector3,
61+
) {
4562
const fillMode = yield* select((state) => state.userConfiguration.fillMode);
4663
const bboxes = yield* select((state) => getUserBoundingBoxesThatContainPosition(state, position));
4764
if (bboxes.length === 0) {
@@ -52,9 +69,12 @@ function* getBoundingBoxForFloodFillWhenRestricted(position: Vector3, currentVie
5269
}
5370
const smallestBbox = _.sortBy(bboxes, (bbox) => new BoundingBox(bbox.boundingBox).getVolume())[0];
5471

55-
const maximumVoxelSize =
56-
Constants.FLOOD_FILL_MULTIPLIER_FOR_BBOX_RESTRICTION *
57-
V3.prod(Constants.FLOOD_FILL_EXTENTS[fillMode]);
72+
const maxBboxSize = yield* call(
73+
getMaximumBoundingBoxSizeForFloodfill,
74+
fillMode,
75+
finestSegmentationLayerMag,
76+
);
77+
const maxBboxVolume = Constants.FLOOD_FILL_MULTIPLIER_FOR_BBOX_RESTRICTION * V3.prod(maxBboxSize);
5878
const bboxObj = new BoundingBox(smallestBbox.boundingBox);
5979

6080
const bboxVolume =
@@ -64,9 +84,9 @@ function* getBoundingBoxForFloodFillWhenRestricted(position: Vector3, currentVie
6484
V2.prod(
6585
Dimensions.getIndices(currentViewport).map((idx) => bboxObj.getSize()[idx]) as Vector2,
6686
);
67-
if (bboxVolume > maximumVoxelSize) {
87+
if (bboxVolume > maxBboxVolume) {
6888
return {
69-
failureReason: `The bounding box that encloses the clicked position is too large. Shrink its size so that it does not contain more than ${maximumVoxelSize} voxels.`,
89+
failureReason: `The bounding box that encloses the clicked position is too large, containing ${bboxVolume} voxels. Shrink its size so that it does not contain more than ${maxBboxVolume} voxels.`,
7090
};
7191
}
7292
return smallestBbox.boundingBox;
@@ -75,18 +95,24 @@ function* getBoundingBoxForFloodFillWhenRestricted(position: Vector3, currentVie
7595
function* getBoundingBoxForFloodFillWhenUnrestricted(
7696
position: Vector3,
7797
currentViewport: OrthoView,
98+
finestSegmentationLayerMag: Vector3,
7899
) {
79100
const fillMode = yield* select((state) => state.userConfiguration.fillMode);
80-
const halfBoundingBoxSizeUVW = V3.scale(Constants.FLOOD_FILL_EXTENTS[fillMode], 0.5);
101+
const maxBoundingBoxSize = yield* call(
102+
getMaximumBoundingBoxSizeForFloodfill,
103+
fillMode,
104+
finestSegmentationLayerMag,
105+
);
106+
const halfBoundingBoxSize = V3.scale(maxBoundingBoxSize, 0.5);
81107
const currentViewportBounding = {
82-
min: V3.sub(position, halfBoundingBoxSizeUVW),
83-
max: V3.add(position, halfBoundingBoxSizeUVW),
108+
min: V3.sub(position, halfBoundingBoxSize),
109+
max: V3.add(position, halfBoundingBoxSize),
84110
};
85111

86112
if (fillMode === FillModeEnum._2D) {
87113
// Only use current plane
88114
const thirdDimension = Dimensions.thirdDimensionForPlane(currentViewport);
89-
const numberOfSlices = 1;
115+
const numberOfSlices = finestSegmentationLayerMag[thirdDimension];
90116
currentViewportBounding.min[thirdDimension] = position[thirdDimension];
91117
currentViewportBounding.max[thirdDimension] = position[thirdDimension] + numberOfSlices;
92118
}
@@ -104,14 +130,25 @@ function* getBoundingBoxForFloodFillWhenUnrestricted(
104130
function* getBoundingBoxForFloodFill(
105131
position: Vector3,
106132
currentViewport: OrthoView,
133+
finestSegmentationLayerMag: Vector3,
107134
): Saga<BoundingBoxType | { failureReason: string }> {
108135
const isRestrictedToBoundingBox = yield* select(
109136
(state) => state.userConfiguration.isFloodfillRestrictedToBoundingBox,
110137
);
111138
if (isRestrictedToBoundingBox) {
112-
return yield* call(getBoundingBoxForFloodFillWhenRestricted, position, currentViewport);
139+
return yield* call(
140+
getBoundingBoxForFloodFillWhenRestricted,
141+
position,
142+
currentViewport,
143+
finestSegmentationLayerMag,
144+
);
113145
} else {
114-
return yield* call(getBoundingBoxForFloodFillWhenUnrestricted, position, currentViewport);
146+
return yield* call(
147+
getBoundingBoxForFloodFillWhenUnrestricted,
148+
position,
149+
currentViewport,
150+
finestSegmentationLayerMag,
151+
);
115152
}
116153
}
117154

@@ -172,7 +209,13 @@ function* handleFloodFill(floodFillAction: FloodFillAction): Saga<void> {
172209
if (!isModificationAllowed) {
173210
return;
174211
}
175-
const boundingBoxForFloodFill = yield* call(getBoundingBoxForFloodFill, seedPosition, planeId);
212+
const finestSegmentationLayerMag = magInfo.getFinestMag();
213+
const boundingBoxForFloodFill = yield* call(
214+
getBoundingBoxForFloodFill,
215+
seedPosition,
216+
planeId,
217+
finestSegmentationLayerMag,
218+
);
176219
if ("failureReason" in boundingBoxForFloodFill) {
177220
Toast.warning(boundingBoxForFloodFill.failureReason, {
178221
key: NO_FLOODFILL_BBOX_TOAST_KEY,

0 commit comments

Comments
 (0)