From bd4385ca27dd4fae1f8f19d5567dcb4218d21f8a Mon Sep 17 00:00:00 2001 From: Tom Herold Date: Mon, 17 Jun 2024 10:10:35 +0200 Subject: [PATCH] Validate animation job bounding box (#7883) * validate BB size to be larger then 0 * changelog * appy PR feedback --- CHANGELOG.unreleased.md | 1 + .../action-bar/create_animation_modal.tsx | 56 ++++++++++++++----- 2 files changed, 44 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index c3700da12f..b3afa96d01 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -17,6 +17,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released - When downloading + reuploading an annotation that is based on a segmentation layer with active mapping, that mapping is now still be selected after the reupload. [#7822](https://github.com/scalableminds/webknossos/pull/7822) - In the Voxelytics workflow list, the name of the WEBKNOSSOS user who started the job is displayed. [#7794](https://github.com/scalableminds/webknossos/pull/7795) - Start an alignment job (aligns the section in a dataset) via the "AI Analysis" button. [#7820](https://github.com/scalableminds/webknossos/pull/7820) +- Added additional validation for the animation job modal. Bounding boxes must be larger then zero. [#7883](https://github.com/scalableminds/webknossos/pull/7883) ### Changed - The "WEBKNOSSOS Changelog" modal now lazily loads its content potentially speeding up the initial loading time of WEBKNOSSOS and thus improving the UX. [#7843](https://github.com/scalableminds/webknossos/pull/7843) 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 29bc390785..f671f80645 100644 --- a/frontend/javascripts/oxalis/view/action-bar/create_animation_modal.tsx +++ b/frontend/javascripts/oxalis/view/action-bar/create_animation_modal.tsx @@ -33,7 +33,7 @@ import { PricingPlanEnum, isFeatureAllowedByPricingPlan, } from "admin/organization/pricing_plan_utils"; -import { BoundingBoxType, Vector3 } from "oxalis/constants"; +import { Vector3 } from "oxalis/constants"; import BoundingBox from "oxalis/model/bucket_data_handling/bounding_box"; import { BoundingBoxSelection } from "./starting_job_modals"; import { LayerSelection } from "components/layer_selection"; @@ -50,17 +50,13 @@ const MAX_MESHES_PER_ANIMATION = 40; // arbitrary limit to not overload the serv function selectMagForTextureCreation( colorLayer: APIDataLayer, - boundingBox: BoundingBoxType, + boundingBox: BoundingBox, ): [Vector3, number] { // Utility method to determine the best mag in relation to the dataset size to create the textures in the worker job // We aim to create textures with a rough length/height of 2000px (aka target_video_frame_size) - const colorLayerBB = new BoundingBox( - computeBoundingBoxFromBoundingBoxObject(colorLayer.boundingBox), - ); - const bb = new BoundingBox(boundingBox).intersectedWith(colorLayerBB); - const longestSide = Math.max(...bb.getSize()); - const dimensionLongestSide = bb.getSize().indexOf(longestSide); + const longestSide = Math.max(...boundingBox.getSize()); + const dimensionLongestSide = boundingBox.getSize().indexOf(longestSide); let bestMag = colorLayer.resolutions[0]; let bestDifference = Infinity; @@ -137,7 +133,8 @@ function CreateAnimationModal(props: Props) { const validateAnimationOptions = ( colorLayer: APIDataLayer, - selectedBoundingBox: BoundingBoxType, + selectedBoundingBox: BoundingBox, + renderingBoundingBox: BoundingBox, meshes: Partial[], ) => { // Validate the select parameters and dataset to make sure it actually works and does not overload the server @@ -145,7 +142,7 @@ function CreateAnimationModal(props: Props) { const state = Store.getState(); const errorMessages: string[] = []; - const [_, estimatedTextureSize] = selectMagForTextureCreation(colorLayer, selectedBoundingBox); + const [_, estimatedTextureSize] = selectMagForTextureCreation(colorLayer, renderingBoundingBox); const hasEnoughMags = estimatedTextureSize < 1.5 * TARGET_TEXTURE_SIZE; if (!hasEnoughMags) @@ -167,7 +164,25 @@ function CreateAnimationModal(props: Props) { `You selected too many meshes for the animation. Please keep the number of meshes below ${MAX_MESHES_PER_ANIMATION} to create an animation.`, ); - const validationStatus = hasEnoughMags && isDtypeSupported && isDataset3D && !isTooManyMeshes; + const isBoundingBoxEmpty = selectedBoundingBox.getVolume() === 0; + if (isBoundingBoxEmpty) + errorMessages.push( + "Please select a bounding box that is not empty. Width, height, and depth of the bounding box must be larger than zero.", + ); + + const isRenderingBoundingBoxEmpty = renderingBoundingBox.getVolume() === 0; + if (isRenderingBoundingBoxEmpty && !isBoundingBoxEmpty) + errorMessages.push( + "Your selected bounding box is located outside the dataset's volume. Please select a bounding box contained within the dataset's outer bounds.", + ); + + const validationStatus = + hasEnoughMags && + isDtypeSupported && + isDataset3D && + !isTooManyMeshes && + !isBoundingBoxEmpty && + !isRenderingBoundingBoxEmpty; setValidationErrors(errorMessages); setIsValid(validationStatus); @@ -216,7 +231,14 @@ function CreateAnimationModal(props: Props) { state.datasetConfiguration, ); - const [magForTextures, _] = selectMagForTextureCreation(colorLayer, boundingBox); + // the actual rendering bounding box in Blender is the intersection of the selected user bounding box and the color layer's outer bounds + const colorLayerBB = new BoundingBox( + computeBoundingBoxFromBoundingBoxObject(selectedColorLayer.boundingBox), + ); + const selectedBoundingBox = new BoundingBox(boundingBox); + const renderingBoundingBox = selectedBoundingBox.intersectedWith(colorLayerBB); + + const [magForTextures, _] = selectMagForTextureCreation(colorLayer, renderingBoundingBox); const animationOptions: RenderAnimationOptions = { layerName: selectedColorLayerName, @@ -230,7 +252,15 @@ function CreateAnimationModal(props: Props) { cameraPosition: selectedCameraPosition, }; - if (!validateAnimationOptions(selectedColorLayer, boundingBox, meshes)) return; + if ( + !validateAnimationOptions( + selectedColorLayer, + selectedBoundingBox, + renderingBoundingBox, + meshes, + ) + ) + return; startRenderAnimationJob(state.dataset.owningOrganization, state.dataset.name, animationOptions);