Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released

### Changed
- 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)
- Increase the flood fill maximum bounding box size limits for segmentation layers restricted to coarser mags. [#8382](https://github.com/scalableminds/webknossos/pull/8382)

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

### Removed
- 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)
Expand Down
1 change: 1 addition & 0 deletions biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
},
"correctness": {
"noUnusedVariables": "error",
"noUnusedImports": "error",
"noRenderReturnValue": "off",
"useHookAtTopLevel": "error"
},
Expand Down
3 changes: 1 addition & 2 deletions frontend/javascripts/components/slider.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Slider as AntdSlider, type SliderSingleProps } from "antd";
import type { SliderRangeProps } from "antd/lib/slider";
import { clamp } from "libs/utils";
import _ from "lodash";
import { useCallback, useEffect, useRef, useState } from "react";
import { useCallback, useEffect, useRef } from "react";

const DEFAULT_WHEEL_FACTOR = 0.02;
const DEFAULT_STEP = 1;
Expand Down
1 change: 0 additions & 1 deletion frontend/javascripts/oxalis/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import Store from "oxalis/store";
import type { APICompoundType } from "types/api_flow_types";

import { globalToLayerTransformedPosition } from "./model/accessors/dataset_layer_transformation_accessor";
import { invertTransform, transformPointUnscaled } from "./model/helpers/transformation_helpers";
import { initialize } from "./model_initialization";

// TODO: Non-reactive
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -696,22 +696,27 @@ class DataCube {
currentGlobalBucketPosition,
V3.scale3(adjustedNeighbourVoxelXyz, currentMag),
);
// When flood filling in a coarser mag, a voxel in the coarse mag is more than one voxel in mag 1
const voxelBoundingBoxInMag1 = new BoundingBox({
min: currentGlobalPosition,
max: V3.add(currentGlobalPosition, currentMag),
});

if (bucketData[neighbourVoxelIndex] === sourceSegmentId) {
if (floodfillBoundingBox.containsPoint(currentGlobalPosition)) {
if (floodfillBoundingBox.intersectedWith(voxelBoundingBoxInMag1).getVolume() > 0) {
bucketData[neighbourVoxelIndex] = segmentId;
markUvwInSliceAsLabeled(neighbourVoxelUvw);
neighbourVoxelStackUvw.pushVoxel(neighbourVoxelUvw);
labeledVoxelCount++;

coveredBBoxMin[0] = Math.min(coveredBBoxMin[0], currentGlobalPosition[0]);
coveredBBoxMin[1] = Math.min(coveredBBoxMin[1], currentGlobalPosition[1]);
coveredBBoxMin[2] = Math.min(coveredBBoxMin[2], currentGlobalPosition[2]);
coveredBBoxMin[0] = Math.min(coveredBBoxMin[0], voxelBoundingBoxInMag1.min[0]);
coveredBBoxMin[1] = Math.min(coveredBBoxMin[1], voxelBoundingBoxInMag1.min[1]);
coveredBBoxMin[2] = Math.min(coveredBBoxMin[2], voxelBoundingBoxInMag1.min[2]);

// The maximum is exclusive which is why we add 1 to the position
coveredBBoxMax[0] = Math.max(coveredBBoxMax[0], currentGlobalPosition[0] + 1);
coveredBBoxMax[1] = Math.max(coveredBBoxMax[1], currentGlobalPosition[1] + 1);
coveredBBoxMax[2] = Math.max(coveredBBoxMax[2], currentGlobalPosition[2] + 1);
coveredBBoxMax[0] = Math.max(coveredBBoxMax[0], voxelBoundingBoxInMag1.max[0] + 1);
coveredBBoxMax[1] = Math.max(coveredBBoxMax[1], voxelBoundingBoxInMag1.max[1] + 1);
coveredBBoxMax[2] = Math.max(coveredBBoxMax[2], voxelBoundingBoxInMag1.max[2] + 1);

if (labeledVoxelCount % 1000000 === 0) {
console.log(`Labeled ${labeledVoxelCount} Vx. Continuing...`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
getNodeAndTree,
getSkeletonTracing,
getTree,
getTreesWithType,
isSkeletonLayerTransformed,
} from "oxalis/model/accessors/skeletontracing_accessor";
import type { Action } from "oxalis/model/actions/actions";
Expand Down
2 changes: 1 addition & 1 deletion frontend/javascripts/oxalis/model/reducers/ui_reducer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AnnotationToolEnum, AvailableToolsInViewMode, ControlModeEnum } from "oxalis/constants";
import { AnnotationToolEnum, AvailableToolsInViewMode } from "oxalis/constants";
import type { Action } from "oxalis/model/actions/actions";
import { updateKey, updateKey2 } from "oxalis/model/helpers/deep_update";
import {
Expand Down
2 changes: 1 addition & 1 deletion frontend/javascripts/oxalis/model/sagas/proofread_saga.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ import {
updateSegmentAction,
} from "oxalis/model/actions/volumetracing_actions";
import type { Saga } from "oxalis/model/sagas/effect-generators";
import { select, take } from "oxalis/model/sagas/effect-generators";
import { select } from "oxalis/model/sagas/effect-generators";
import {
type UpdateActionWithoutIsolationRequirement,
mergeAgglomerate,
Expand Down
2 changes: 1 addition & 1 deletion frontend/javascripts/oxalis/model/sagas/saga_helpers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ActionPattern, Predicate } from "@redux-saga/types";
import type { ActionPattern } from "@redux-saga/types";
import { Modal } from "antd";
import Toast from "libs/toast";
import messages from "messages";
Expand Down
71 changes: 57 additions & 14 deletions frontend/javascripts/oxalis/model/sagas/volume/floodfill_saga.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
import BoundingBox from "oxalis/model/bucket_data_handling/bounding_box";
import Dimensions from "oxalis/model/dimensions";
import type { Saga } from "oxalis/model/sagas/effect-generators";
import { select, take } from "oxalis/model/sagas/effect-generators";
import { select } from "oxalis/model/sagas/effect-generators";
import { requestBucketModificationInVolumeTracing } from "oxalis/model/sagas/saga_helpers";
import { Model } from "oxalis/singletons";
import { call, put, takeEvery } from "typed-redux-saga";
Expand All @@ -41,7 +41,24 @@ export function* floodFill(): Saga<void> {
yield* takeEvery("FLOOD_FILL", handleFloodFill);
}

function* getBoundingBoxForFloodFillWhenRestricted(position: Vector3, currentViewport: OrthoView) {
function getMaximumBoundingBoxSizeForFloodfill(
fillMode: FillMode,
finestSegmentationLayerMag: Vector3,
): Vector3 {
const maximumBoundingBoxMag1 = Constants.FLOOD_FILL_EXTENTS[fillMode];
// If the finest mag of the segmentation layer is not Mag(1), the maximum bounding box needs to be scaled accordingly
const maximumBoundingBoxInFinestMag = V3.scale3(
maximumBoundingBoxMag1,
finestSegmentationLayerMag,
);
return maximumBoundingBoxInFinestMag;
}

function* getBoundingBoxForFloodFillWhenRestricted(
position: Vector3,
currentViewport: OrthoView,
finestSegmentationLayerMag: Vector3,
) {
const fillMode = yield* select((state) => state.userConfiguration.fillMode);
const bboxes = yield* select((state) => getUserBoundingBoxesThatContainPosition(state, position));
if (bboxes.length === 0) {
Expand All @@ -52,9 +69,12 @@ function* getBoundingBoxForFloodFillWhenRestricted(position: Vector3, currentVie
}
const smallestBbox = _.sortBy(bboxes, (bbox) => new BoundingBox(bbox.boundingBox).getVolume())[0];

const maximumVoxelSize =
Constants.FLOOD_FILL_MULTIPLIER_FOR_BBOX_RESTRICTION *
V3.prod(Constants.FLOOD_FILL_EXTENTS[fillMode]);
const maxBboxSize = yield* call(
getMaximumBoundingBoxSizeForFloodfill,
fillMode,
finestSegmentationLayerMag,
);
const maxBboxVolume = Constants.FLOOD_FILL_MULTIPLIER_FOR_BBOX_RESTRICTION * V3.prod(maxBboxSize);
const bboxObj = new BoundingBox(smallestBbox.boundingBox);

const bboxVolume =
Expand All @@ -64,9 +84,9 @@ function* getBoundingBoxForFloodFillWhenRestricted(position: Vector3, currentVie
V2.prod(
Dimensions.getIndices(currentViewport).map((idx) => bboxObj.getSize()[idx]) as Vector2,
);
if (bboxVolume > maximumVoxelSize) {
if (bboxVolume > maxBboxVolume) {
return {
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.`,
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.`,
};
}
return smallestBbox.boundingBox;
Expand All @@ -75,18 +95,24 @@ function* getBoundingBoxForFloodFillWhenRestricted(position: Vector3, currentVie
function* getBoundingBoxForFloodFillWhenUnrestricted(
position: Vector3,
currentViewport: OrthoView,
finestSegmentationLayerMag: Vector3,
) {
const fillMode = yield* select((state) => state.userConfiguration.fillMode);
const halfBoundingBoxSizeUVW = V3.scale(Constants.FLOOD_FILL_EXTENTS[fillMode], 0.5);
const maxBoundingBoxSize = yield* call(
getMaximumBoundingBoxSizeForFloodfill,
fillMode,
finestSegmentationLayerMag,
);
const halfBoundingBoxSize = V3.scale(maxBoundingBoxSize, 0.5);
const currentViewportBounding = {
min: V3.sub(position, halfBoundingBoxSizeUVW),
max: V3.add(position, halfBoundingBoxSizeUVW),
min: V3.sub(position, halfBoundingBoxSize),
max: V3.add(position, halfBoundingBoxSize),
};

if (fillMode === FillModeEnum._2D) {
// Only use current plane
const thirdDimension = Dimensions.thirdDimensionForPlane(currentViewport);
const numberOfSlices = 1;
const numberOfSlices = finestSegmentationLayerMag[thirdDimension];
currentViewportBounding.min[thirdDimension] = position[thirdDimension];
currentViewportBounding.max[thirdDimension] = position[thirdDimension] + numberOfSlices;
}
Expand All @@ -104,14 +130,25 @@ function* getBoundingBoxForFloodFillWhenUnrestricted(
function* getBoundingBoxForFloodFill(
position: Vector3,
currentViewport: OrthoView,
finestSegmentationLayerMag: Vector3,
): Saga<BoundingBoxType | { failureReason: string }> {
const isRestrictedToBoundingBox = yield* select(
(state) => state.userConfiguration.isFloodfillRestrictedToBoundingBox,
);
if (isRestrictedToBoundingBox) {
return yield* call(getBoundingBoxForFloodFillWhenRestricted, position, currentViewport);
return yield* call(
getBoundingBoxForFloodFillWhenRestricted,
position,
currentViewport,
finestSegmentationLayerMag,
);
} else {
return yield* call(getBoundingBoxForFloodFillWhenUnrestricted, position, currentViewport);
return yield* call(
getBoundingBoxForFloodFillWhenUnrestricted,
position,
currentViewport,
finestSegmentationLayerMag,
);
}
}

Expand Down Expand Up @@ -172,7 +209,13 @@ function* handleFloodFill(floodFillAction: FloodFillAction): Saga<void> {
if (!isModificationAllowed) {
return;
}
const boundingBoxForFloodFill = yield* call(getBoundingBoxForFloodFill, seedPosition, planeId);
const finestSegmentationLayerMag = magInfo.getFinestMag();
const boundingBoxForFloodFill = yield* call(
getBoundingBoxForFloodFill,
seedPosition,
planeId,
finestSegmentationLayerMag,
);
if ("failureReason" in boundingBoxForFloodFill) {
Toast.warning(boundingBoxForFloodFill.failureReason, {
key: NO_FLOODFILL_BBOX_TOAST_KEY,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import { document } from "libs/window";
import {
type AnnotationTool,
AnnotationToolEnum,
ControlModeEnum,
FillModeEnum,
type InterpolationMode,
InterpolationModeEnum,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import _ from "lodash";
import { getDefaultValueRangeOfLayer, isColorLayer } from "oxalis/model/accessors/dataset_accessor";
import type { APIDataLayer, APIDataset, APIMaybeUnimportedDataset } from "types/api_flow_types";
import type { APIDataset, APIMaybeUnimportedDataset } from "types/api_flow_types";
import {
defaultDatasetViewConfiguration,
getDefaultLayerViewConfiguration,
Expand Down