From 5af5856ce5d4d06b1ecbc1b99eb330741dfb2507 Mon Sep 17 00:00:00 2001 From: Tom Herold Date: Thu, 14 Mar 2024 15:24:16 +0100 Subject: [PATCH 01/12] updated animation job to use proxy mesh infromation for new mesh api --- app/controllers/JobsController.scala | 6 ++--- .../action-bar/create_animation_modal.tsx | 23 ++++++++----------- frontend/javascripts/types/api_flow_types.ts | 4 ++-- 3 files changed, 13 insertions(+), 20 deletions(-) diff --git a/app/controllers/JobsController.scala b/app/controllers/JobsController.scala index 4431bd7dc97..ae9089ee51d 100644 --- a/app/controllers/JobsController.scala +++ b/app/controllers/JobsController.scala @@ -35,8 +35,7 @@ case class AnimationJobOptions( boundingBox: BoundingBox, includeWatermark: Boolean, segmentationLayerName: Option[String], - meshFileName: Option[String], - meshSegmentIds: Array[Int], + meshes: JsValue, movieResolution: MovieResolutionSetting.Value, cameraPosition: CameraPositionSetting.Value, intensityMin: Double, @@ -394,8 +393,7 @@ class JobsController @Inject()( "segmentation_layer_name" -> animationJobOptions.segmentationLayerName, "bounding_box" -> animationJobOptions.boundingBox.toLiteral, "include_watermark" -> animationJobOptions.includeWatermark, - "mesh_segment_ids" -> animationJobOptions.meshSegmentIds, - "meshfile_name" -> animationJobOptions.meshFileName, + "meshes" -> animationJobOptions.meshes, "movie_resolution" -> animationJobOptions.movieResolution, "camera_position" -> animationJobOptions.cameraPosition, "intensity_min" -> animationJobOptions.intensityMin, 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 ba8c8f93395..77980ba0dd7 100644 --- a/frontend/javascripts/oxalis/view/action-bar/create_animation_modal.tsx +++ b/frontend/javascripts/oxalis/view/action-bar/create_animation_modal.tsx @@ -128,7 +128,7 @@ function CreateAnimationModal(props: Props) { const validateAnimationOptions = ( colorLayer: APIDataLayer, selectedBoundingBox: BoundingBoxType, - meshSegmentIds: number[], + meshes: MeshInformation[], ) => { // Validate the select parameters and dataset to make sure it actually works and does not overload the server @@ -151,7 +151,7 @@ function CreateAnimationModal(props: Props) { !is2dDataset(state.dataset) && (colorLayer.additionalAxes?.length || 0) === 0; if (isDataset3D) errorMessages.push("Sorry, animations are only supported for 3D datasets."); - const isTooManyMeshes = meshSegmentIds.length > MAX_MESHES_PER_ANIMATION; + const isTooManyMeshes = meshes.length > MAX_MESHES_PER_ANIMATION; if (isTooManyMeshes) errorMessages.push( `You selected too many meshes for the animation. Please keep the number of meshes below ${MAX_MESHES_PER_ANIMATION} to create an animation.`, @@ -172,8 +172,7 @@ function CreateAnimationModal(props: Props) { )!.boundingBox; // Submit currently visible pre-computed meshes - let meshSegmentIds: number[] = []; - let meshFileName: string | undefined; + let meshes: MeshInformation[] = []; let segmentationLayerName: string | undefined; const visibleSegmentationLayer = Model.getVisibleSegmentationLayer(); @@ -183,13 +182,10 @@ function CreateAnimationModal(props: Props) { if (availableMeshes == null) { throw new Error("There is no mesh data in localSegmentationData."); } - meshSegmentIds = Object.values(availableMeshes as Record) - .filter((mesh) => mesh.isVisible && mesh.isPrecomputed) - .map((mesh) => mesh.segmentId); - - const currentMeshFile = - state.localSegmentationData[visibleSegmentationLayer.name].currentMeshFile; - meshFileName = currentMeshFile?.meshFileName; + const axis = ""; + meshes = Object.values(availableMeshes[axis] as Record).filter( + (mesh) => mesh.isVisible, + ); if (visibleSegmentationLayer.fallbackLayerInfo) { segmentationLayerName = visibleSegmentationLayer.fallbackLayerInfo.name; @@ -210,8 +206,7 @@ function CreateAnimationModal(props: Props) { const animationOptions: RenderAnimationOptions = { layerName: selectedColorLayerName, segmentationLayerName, - meshFileName, - meshSegmentIds, + meshes, intensityMin, intensityMax, magForTextures, @@ -221,7 +216,7 @@ function CreateAnimationModal(props: Props) { cameraPosition: selectedCameraPosition, }; - if (!validateAnimationOptions(colorLayer, boundingBox, meshSegmentIds)) return; + if (!validateAnimationOptions(colorLayer, boundingBox, meshes)) return; startRenderAnimationJob(state.dataset.owningOrganization, state.dataset.name, animationOptions); diff --git a/frontend/javascripts/types/api_flow_types.ts b/frontend/javascripts/types/api_flow_types.ts index 09fc30c9ffa..5cdeda082b8 100644 --- a/frontend/javascripts/types/api_flow_types.ts +++ b/frontend/javascripts/types/api_flow_types.ts @@ -6,6 +6,7 @@ import type { TreeGroup, RecommendedConfiguration, SegmentGroup, + MeshInformation, } from "oxalis/store"; import type { ServerUpdateAction } from "oxalis/model/sagas/update_actions"; import type { @@ -1086,8 +1087,7 @@ export enum MOVIE_RESOLUTIONS { export type RenderAnimationOptions = { layerName: string; segmentationLayerName?: string; - meshFileName?: string; - meshSegmentIds: number[]; + meshes: MeshInformation[]; boundingBox: BoundingBoxObject; includeWatermark: boolean; intensityMin: number; From f796435cb6e256e358998e576f3fa45834045d02 Mon Sep 17 00:00:00 2001 From: Tom Herold Date: Fri, 15 Mar 2024 18:48:31 +0100 Subject: [PATCH 02/12] added ad-hoc meshes to animation job --- app/controllers/JobsController.scala | 15 +++--- .../action-bar/create_animation_modal.tsx | 50 +++++++++---------- frontend/javascripts/types/api_flow_types.ts | 6 +-- 3 files changed, 34 insertions(+), 37 deletions(-) diff --git a/app/controllers/JobsController.scala b/app/controllers/JobsController.scala index ae9089ee51d..6e12d9e42bd 100644 --- a/app/controllers/JobsController.scala +++ b/app/controllers/JobsController.scala @@ -31,10 +31,10 @@ object CameraPositionSetting extends ExtendedEnumeration { } case class AnimationJobOptions( - layerName: String, + colorLayerName: String, boundingBox: BoundingBox, includeWatermark: Boolean, - segmentationLayerName: Option[String], + isViewMode: Boolean, meshes: JsValue, movieResolution: MovieResolutionSetting.Value, cameraPosition: CameraPositionSetting.Value, @@ -379,18 +379,17 @@ class JobsController @Inject()( _ <- Fox.runIf(userOrganization.pricingPlan == PricingPlan.Basic) { bool2Fox(animationJobOptions.movieResolution == MovieResolutionSetting.SD) ?~> "job.renderAnimation.resolutionMustBeSD" } - layerName = animationJobOptions.layerName - _ <- datasetService.assertValidLayerName(layerName) - _ <- Fox.runOptional(animationJobOptions.segmentationLayerName)(datasetService.assertValidLayerName) - exportFileName = s"webknossos_animation_${formatDateForFilename(new Date())}__${datasetName}__$layerName.mp4" + colorLayerName = animationJobOptions.colorLayerName + _ <- datasetService.assertValidLayerName(colorLayerName) + exportFileName = s"webknossos_animation_${formatDateForFilename(new Date())}__${datasetName}__$colorLayerName.mp4" command = JobCommand.render_animation commandArgs = Json.obj( "organization_name" -> organizationName, "dataset_name" -> datasetName, "export_file_name" -> exportFileName, "user_auth_token" -> userAuthToken.id, - "layer_name" -> animationJobOptions.layerName, - "segmentation_layer_name" -> animationJobOptions.segmentationLayerName, + "is_view_mode" -> animationJobOptions.isViewMode, + "color_layer_name" -> animationJobOptions.colorLayerName, "bounding_box" -> animationJobOptions.boundingBox.toLiteral, "include_watermark" -> animationJobOptions.includeWatermark, "meshes" -> animationJobOptions.meshes, 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 77980ba0dd7..532af6020d0 100644 --- a/frontend/javascripts/oxalis/view/action-bar/create_animation_modal.tsx +++ b/frontend/javascripts/oxalis/view/action-bar/create_animation_modal.tsx @@ -31,7 +31,7 @@ import { PricingPlanEnum, isFeatureAllowedByPricingPlan, } from "admin/organization/pricing_plan_utils"; -import { BoundingBoxType, Vector3 } from "oxalis/constants"; +import { BoundingBoxType, ControlModeEnum, Vector3 } from "oxalis/constants"; import BoundingBox from "oxalis/model/bucket_data_handling/bounding_box"; import { Model } from "oxalis/singletons"; import { BoundingBoxSelection, LayerSelection } from "./starting_job_modals"; @@ -128,7 +128,7 @@ function CreateAnimationModal(props: Props) { const validateAnimationOptions = ( colorLayer: APIDataLayer, selectedBoundingBox: BoundingBoxType, - meshes: MeshInformation[], + meshes: Partial[], ) => { // Validate the select parameters and dataset to make sure it actually works and does not overload the server @@ -171,28 +171,25 @@ function CreateAnimationModal(props: Props) { (bb) => bb.id === selectedBoundingBoxId, )!.boundingBox; - // Submit currently visible pre-computed meshes - let meshes: MeshInformation[] = []; - let segmentationLayerName: string | undefined; - - const visibleSegmentationLayer = Model.getVisibleSegmentationLayer(); - - if (visibleSegmentationLayer) { - const availableMeshes = state.localSegmentationData[visibleSegmentationLayer.name].meshes; - if (availableMeshes == null) { - throw new Error("There is no mesh data in localSegmentationData."); - } - const axis = ""; - meshes = Object.values(availableMeshes[axis] as Record).filter( - (mesh) => mesh.isVisible, - ); - - if (visibleSegmentationLayer.fallbackLayerInfo) { - segmentationLayerName = visibleSegmentationLayer.fallbackLayerInfo.name; - } else { - segmentationLayerName = visibleSegmentationLayer.name; - } - } + // Submit currently visible pre-computed & ad-hoc meshes + const axis = ""; + const layerNames = Object.keys(state.localSegmentationData); + + const meshes: RenderAnimationOptions["meshes"] = layerNames.flatMap((layerName) => { + const meshInfos = state.localSegmentationData[layerName]?.meshes?.[axis] || {}; + + return Object.values(meshInfos) + .filter((meshInfo: MeshInformation) => meshInfo.isVisible) + .flatMap((meshInfo: MeshInformation) => { + const layer = Model.getLayerByName(layerName); + const fullLayerName = layer.fallbackLayerInfo?.name || layerName; + return { + layerName: fullLayerName, + isSegmentationLayer: !!layer?.isSegmentation, + ...meshInfo, + }; + }); + }); // Submit the configured min/max intensity info to support float datasets const [intensityMin, intensityMax] = getEffectiveIntensityRange( @@ -202,14 +199,15 @@ function CreateAnimationModal(props: Props) { ); const [magForTextures, _] = selectMagForTextureCreation(colorLayer, boundingBox); + const isViewMode = state.temporaryConfiguration.controlMode === ControlModeEnum.VIEW; const animationOptions: RenderAnimationOptions = { - layerName: selectedColorLayerName, - segmentationLayerName, + colorLayerName: selectedColorLayerName, meshes, intensityMin, intensityMax, magForTextures, + isViewMode, boundingBox: computeBoundingBoxObjectFromBoundingBox(boundingBox), includeWatermark: isWatermarkEnabled, movieResolution: selectedMovieResolution, diff --git a/frontend/javascripts/types/api_flow_types.ts b/frontend/javascripts/types/api_flow_types.ts index 5cdeda082b8..5dcb6e3430c 100644 --- a/frontend/javascripts/types/api_flow_types.ts +++ b/frontend/javascripts/types/api_flow_types.ts @@ -1085,10 +1085,10 @@ export enum MOVIE_RESOLUTIONS { } export type RenderAnimationOptions = { - layerName: string; - segmentationLayerName?: string; - meshes: MeshInformation[]; + colorLayerName: string; + meshes: ({ layerName: string; isSegmentationLayer: boolean } & MeshInformation)[]; boundingBox: BoundingBoxObject; + isViewMode: boolean; includeWatermark: boolean; intensityMin: number; intensityMax: number; From 3434c53d0002e371fa3f0dff42c10aa50b5b805f Mon Sep 17 00:00:00 2001 From: Tom Herold Date: Mon, 18 Mar 2024 13:21:37 +0100 Subject: [PATCH 03/12] pass fallback layer and adhoc meshing options to worker --- .../view/action-bar/create_animation_modal.tsx | 17 ++++++++++++++--- frontend/javascripts/types/api_flow_types.ts | 6 +++++- 2 files changed, 19 insertions(+), 4 deletions(-) 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 532af6020d0..2a335f463a8 100644 --- a/frontend/javascripts/oxalis/view/action-bar/create_animation_modal.tsx +++ b/frontend/javascripts/oxalis/view/action-bar/create_animation_modal.tsx @@ -11,6 +11,8 @@ import { getColorLayers, getEffectiveIntensityRange, getLayerByName, + getResolutionInfo, + hasFallbackLayer, is2dDataset, } from "oxalis/model/accessors/dataset_accessor"; import { @@ -24,6 +26,7 @@ import { MOVIE_RESOLUTIONS, APIDataLayer, APIJobType, + APISegmentationLayer, } from "types/api_flow_types"; import { InfoCircleOutlined } from "@ant-design/icons"; import { PricingEnforcedSpan } from "components/pricing_enforcers"; @@ -33,7 +36,6 @@ import { } from "admin/organization/pricing_plan_utils"; import { BoundingBoxType, ControlModeEnum, Vector3 } from "oxalis/constants"; import BoundingBox from "oxalis/model/bucket_data_handling/bounding_box"; -import { Model } from "oxalis/singletons"; import { BoundingBoxSelection, LayerSelection } from "./starting_job_modals"; type Props = { @@ -174,6 +176,7 @@ function CreateAnimationModal(props: Props) { // Submit currently visible pre-computed & ad-hoc meshes const axis = ""; const layerNames = Object.keys(state.localSegmentationData); + const { preferredQualityForMeshAdHocComputation } = state.temporaryConfiguration; const meshes: RenderAnimationOptions["meshes"] = layerNames.flatMap((layerName) => { const meshInfos = state.localSegmentationData[layerName]?.meshes?.[axis] || {}; @@ -181,11 +184,19 @@ function CreateAnimationModal(props: Props) { return Object.values(meshInfos) .filter((meshInfo: MeshInformation) => meshInfo.isVisible) .flatMap((meshInfo: MeshInformation) => { - const layer = Model.getLayerByName(layerName); + const layer = getLayerByName(state.dataset, layerName) as APISegmentationLayer; + const hasAFallbackLayer = hasFallbackLayer(layer); const fullLayerName = layer.fallbackLayerInfo?.name || layerName; + + const adhoc_mag_index = getResolutionInfo(layer.resolutions).getClosestExistingIndex( + preferredQualityForMeshAdHocComputation, + ); + const adhoc_mag = layer.resolutions[adhoc_mag_index]; + return { layerName: fullLayerName, - isSegmentationLayer: !!layer?.isSegmentation, + hasFallbackLayer: hasAFallbackLayer, + adhoc_mag, ...meshInfo, }; }); diff --git a/frontend/javascripts/types/api_flow_types.ts b/frontend/javascripts/types/api_flow_types.ts index 5dcb6e3430c..9fc6ae95779 100644 --- a/frontend/javascripts/types/api_flow_types.ts +++ b/frontend/javascripts/types/api_flow_types.ts @@ -1086,7 +1086,11 @@ export enum MOVIE_RESOLUTIONS { export type RenderAnimationOptions = { colorLayerName: string; - meshes: ({ layerName: string; isSegmentationLayer: boolean } & MeshInformation)[]; + meshes: ({ + layerName: string; + hasFallbackLayer: boolean; + adhoc_mag: Vector3; + } & MeshInformation)[]; boundingBox: BoundingBoxObject; isViewMode: boolean; includeWatermark: boolean; From 8bf5e6a22570c696c4040d3b6d8f9a6d2869ff34 Mon Sep 17 00:00:00 2001 From: Tom Herold Date: Mon, 18 Mar 2024 14:01:40 +0100 Subject: [PATCH 04/12] upadted changelog --- CHANGELOG.unreleased.md | 1 + .../oxalis/view/action-bar/create_animation_modal.tsx | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index 049bd6986f2..859b9d5554b 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -31,6 +31,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released - Added the config key `webKnossos.user.timeTrackingOnlyWithSignificantChanges`, which when set to `true` will only track time if the user has made significant changes to the annotation. [#7627](https://github.com/scalableminds/webknossos/pull/7627) - Only display UI elements to launch background jobs if the (worker) backend actually supports them. [#7591](https://github.com/scalableminds/webknossos/pull/7591) - If the current dataset folder in the dashboard cannot be found (e.g., because somebody else deleted it), the page navigates to the root folder automatically. [#7669](https://github.com/scalableminds/webknossos/pull/7669) +- Updated dataset animations to use the new meshing API. Animitation now support ad-hoc meshes and mappings. [#7692](https://github.com/scalableminds/webknossos/pull/7692) ### Fixed - Fixed rare SIGBUS crashes of the datastore module that were caused by memory mapping on unstable file systems. [#7528](https://github.com/scalableminds/webknossos/pull/7528) 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 2a335f463a8..9d7877c318d 100644 --- a/frontend/javascripts/oxalis/view/action-bar/create_animation_modal.tsx +++ b/frontend/javascripts/oxalis/view/action-bar/create_animation_modal.tsx @@ -82,7 +82,10 @@ export function CreateAnimationModalWrapper(props: Props) { // early stop if no color layer exists const colorLayers = getColorLayers(dataset); - if (colorLayers.length === 0) return null; + if (colorLayers.length === 0) { + console.warn("WK can not create animations for datasets without color layers."); + return null; + } return ; } @@ -342,7 +345,7 @@ function CreateAnimationModal(props: Props) { > Include the currently selected 3D meshes From a4180eb97a41039d60af6b49478798f2a324870c Mon Sep 17 00:00:00 2001 From: Tom Herold Date: Mon, 18 Mar 2024 14:50:05 +0100 Subject: [PATCH 05/12] updated wk docs --- docs/animations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/animations.md b/docs/animations.md index d6df7209828..9efa42dc19e 100644 --- a/docs/animations.md +++ b/docs/animations.md @@ -9,7 +9,7 @@ A picture is worth a thousand words. In this spirit, you can use WEBKNOSSOS to c Creating an animation is easy: 1. Open any dataset or annotation that you want to use for your animation. -2. Optionally, load any [pre-computed 3D meshes](./mesh_visualization.md#pre-computed-mesh-generation) for any segments that you wish to highlight. +2. Optionally, load some [3D meshes](./mesh_visualization.md#pre-computed-mesh-generation) for any segments that you wish to highlight. 3. For larger datasets, use the bounding box tool to create a bounding box around your area of interest. Smaller datasets can be used in their entirety. 4. From the `Menu` dropdown in navbar at the top of the screen, select "Create Animation". 5. Configure the animation options as desired, i.e. camera movement or resolution. From 0566b887e7e2bad131577d6c97b6dee688c55dc3 Mon Sep 17 00:00:00 2001 From: Tom Herold Date: Wed, 20 Mar 2024 09:17:12 +0100 Subject: [PATCH 06/12] apply PR feedback --- docs/animations.md | 2 +- .../action-bar/create_animation_modal.tsx | 23 ++++++++++--------- frontend/javascripts/types/api_flow_types.ts | 2 +- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/docs/animations.md b/docs/animations.md index 9efa42dc19e..d339793dfd2 100644 --- a/docs/animations.md +++ b/docs/animations.md @@ -9,7 +9,7 @@ A picture is worth a thousand words. In this spirit, you can use WEBKNOSSOS to c Creating an animation is easy: 1. Open any dataset or annotation that you want to use for your animation. -2. Optionally, load some [3D meshes](./mesh_visualization.md#pre-computed-mesh-generation) for any segments that you wish to highlight. +2. Optionally, load some [3D meshes](./mesh_visualization.md) for any segments that you wish to highlight. 3. For larger datasets, use the bounding box tool to create a bounding box around your area of interest. Smaller datasets can be used in their entirety. 4. From the `Menu` dropdown in navbar at the top of the screen, select "Create Animation". 5. Configure the animation options as desired, i.e. camera movement or resolution. 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 9d7877c318d..fbed41ad024 100644 --- a/frontend/javascripts/oxalis/view/action-bar/create_animation_modal.tsx +++ b/frontend/javascripts/oxalis/view/action-bar/create_animation_modal.tsx @@ -37,6 +37,7 @@ import { import { BoundingBoxType, ControlModeEnum, Vector3 } from "oxalis/constants"; import BoundingBox from "oxalis/model/bucket_data_handling/bounding_box"; import { BoundingBoxSelection, LayerSelection } from "./starting_job_modals"; +import { getAdditionalCoordinatesAsString } from "oxalis/model/accessors/flycam_accessor"; type Props = { isOpen: boolean; @@ -177,29 +178,29 @@ function CreateAnimationModal(props: Props) { )!.boundingBox; // Submit currently visible pre-computed & ad-hoc meshes - const axis = ""; + const axis = getAdditionalCoordinatesAsString([]); const layerNames = Object.keys(state.localSegmentationData); const { preferredQualityForMeshAdHocComputation } = state.temporaryConfiguration; const meshes: RenderAnimationOptions["meshes"] = layerNames.flatMap((layerName) => { const meshInfos = state.localSegmentationData[layerName]?.meshes?.[axis] || {}; + const layer = getLayerByName(state.dataset, layerName) as APISegmentationLayer; + const hasAFallbackLayer = hasFallbackLayer(layer); + const fullLayerName = layer.fallbackLayerInfo?.name || layerName; + + const adhocMagIndex = getResolutionInfo(layer.resolutions).getClosestExistingIndex( + preferredQualityForMeshAdHocComputation, + ); + const adhocMag = layer.resolutions[adhocMagIndex]; + return Object.values(meshInfos) .filter((meshInfo: MeshInformation) => meshInfo.isVisible) .flatMap((meshInfo: MeshInformation) => { - const layer = getLayerByName(state.dataset, layerName) as APISegmentationLayer; - const hasAFallbackLayer = hasFallbackLayer(layer); - const fullLayerName = layer.fallbackLayerInfo?.name || layerName; - - const adhoc_mag_index = getResolutionInfo(layer.resolutions).getClosestExistingIndex( - preferredQualityForMeshAdHocComputation, - ); - const adhoc_mag = layer.resolutions[adhoc_mag_index]; - return { layerName: fullLayerName, hasFallbackLayer: hasAFallbackLayer, - adhoc_mag, + adhocMag, ...meshInfo, }; }); diff --git a/frontend/javascripts/types/api_flow_types.ts b/frontend/javascripts/types/api_flow_types.ts index a4521ae110c..3a5b25bb0bf 100644 --- a/frontend/javascripts/types/api_flow_types.ts +++ b/frontend/javascripts/types/api_flow_types.ts @@ -1090,7 +1090,7 @@ export type RenderAnimationOptions = { meshes: ({ layerName: string; hasFallbackLayer: boolean; - adhoc_mag: Vector3; + adhocMag: Vector3; } & MeshInformation)[]; boundingBox: BoundingBoxObject; isViewMode: boolean; From ba2a6fb89515849007a3d7e90219e6b1c9c584b9 Mon Sep 17 00:00:00 2001 From: Tom Herold Date: Wed, 20 Mar 2024 09:21:10 +0100 Subject: [PATCH 07/12] apply PR feedback 2 --- .../oxalis/view/action-bar/create_animation_modal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 fbed41ad024..b05396893ab 100644 --- a/frontend/javascripts/oxalis/view/action-bar/create_animation_modal.tsx +++ b/frontend/javascripts/oxalis/view/action-bar/create_animation_modal.tsx @@ -84,7 +84,7 @@ export function CreateAnimationModalWrapper(props: Props) { // early stop if no color layer exists const colorLayers = getColorLayers(dataset); if (colorLayers.length === 0) { - console.warn("WK can not create animations for datasets without color layers."); + Toast.warning("WK can not create animations for datasets without color layers."); return null; } From 0e8ea9bff7084f8c3d93a7d96569a963a2c55fe0 Mon Sep 17 00:00:00 2001 From: Tom Herold Date: Thu, 21 Mar 2024 13:02:07 +0100 Subject: [PATCH 08/12] reverted colorLayerName to layerName --- app/controllers/JobsController.scala | 6 +++--- .../oxalis/view/action-bar/create_animation_modal.tsx | 2 +- frontend/javascripts/types/api_flow_types.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/controllers/JobsController.scala b/app/controllers/JobsController.scala index 6e12d9e42bd..c3b01f0baae 100644 --- a/app/controllers/JobsController.scala +++ b/app/controllers/JobsController.scala @@ -31,7 +31,7 @@ object CameraPositionSetting extends ExtendedEnumeration { } case class AnimationJobOptions( - colorLayerName: String, + layerName: String, boundingBox: BoundingBox, includeWatermark: Boolean, isViewMode: Boolean, @@ -379,7 +379,7 @@ class JobsController @Inject()( _ <- Fox.runIf(userOrganization.pricingPlan == PricingPlan.Basic) { bool2Fox(animationJobOptions.movieResolution == MovieResolutionSetting.SD) ?~> "job.renderAnimation.resolutionMustBeSD" } - colorLayerName = animationJobOptions.colorLayerName + colorLayerName = animationJobOptions.layerName _ <- datasetService.assertValidLayerName(colorLayerName) exportFileName = s"webknossos_animation_${formatDateForFilename(new Date())}__${datasetName}__$colorLayerName.mp4" command = JobCommand.render_animation @@ -389,7 +389,7 @@ class JobsController @Inject()( "export_file_name" -> exportFileName, "user_auth_token" -> userAuthToken.id, "is_view_mode" -> animationJobOptions.isViewMode, - "color_layer_name" -> animationJobOptions.colorLayerName, + "layer_name" -> animationJobOptions.layerName, "bounding_box" -> animationJobOptions.boundingBox.toLiteral, "include_watermark" -> animationJobOptions.includeWatermark, "meshes" -> animationJobOptions.meshes, 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 b05396893ab..919c149489f 100644 --- a/frontend/javascripts/oxalis/view/action-bar/create_animation_modal.tsx +++ b/frontend/javascripts/oxalis/view/action-bar/create_animation_modal.tsx @@ -217,7 +217,7 @@ function CreateAnimationModal(props: Props) { const isViewMode = state.temporaryConfiguration.controlMode === ControlModeEnum.VIEW; const animationOptions: RenderAnimationOptions = { - colorLayerName: selectedColorLayerName, + layerName: selectedColorLayerName, meshes, intensityMin, intensityMax, diff --git a/frontend/javascripts/types/api_flow_types.ts b/frontend/javascripts/types/api_flow_types.ts index d2e89470d2f..2630110a3a4 100644 --- a/frontend/javascripts/types/api_flow_types.ts +++ b/frontend/javascripts/types/api_flow_types.ts @@ -1090,7 +1090,7 @@ export enum MOVIE_RESOLUTIONS { } export type RenderAnimationOptions = { - colorLayerName: string; + layerName: string; meshes: ({ layerName: string; hasFallbackLayer: boolean; From ba0e7e6193f5f0bd88ad85ec183b92ee3128ed08 Mon Sep 17 00:00:00 2001 From: Tom Herold Date: Wed, 3 Apr 2024 17:31:43 +0200 Subject: [PATCH 09/12] use tracing id for animations --- .../oxalis/view/action-bar/create_animation_modal.tsx | 4 +--- frontend/javascripts/types/api_flow_types.ts | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) 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 919c149489f..87356a57dd6 100644 --- a/frontend/javascripts/oxalis/view/action-bar/create_animation_modal.tsx +++ b/frontend/javascripts/oxalis/view/action-bar/create_animation_modal.tsx @@ -12,7 +12,6 @@ import { getEffectiveIntensityRange, getLayerByName, getResolutionInfo, - hasFallbackLayer, is2dDataset, } from "oxalis/model/accessors/dataset_accessor"; import { @@ -186,7 +185,6 @@ function CreateAnimationModal(props: Props) { const meshInfos = state.localSegmentationData[layerName]?.meshes?.[axis] || {}; const layer = getLayerByName(state.dataset, layerName) as APISegmentationLayer; - const hasAFallbackLayer = hasFallbackLayer(layer); const fullLayerName = layer.fallbackLayerInfo?.name || layerName; const adhocMagIndex = getResolutionInfo(layer.resolutions).getClosestExistingIndex( @@ -199,7 +197,7 @@ function CreateAnimationModal(props: Props) { .flatMap((meshInfo: MeshInformation) => { return { layerName: fullLayerName, - hasFallbackLayer: hasAFallbackLayer, + tracingId: layer.tracingId || null, adhocMag, ...meshInfo, }; diff --git a/frontend/javascripts/types/api_flow_types.ts b/frontend/javascripts/types/api_flow_types.ts index 9d7404368ce..f3806f25994 100644 --- a/frontend/javascripts/types/api_flow_types.ts +++ b/frontend/javascripts/types/api_flow_types.ts @@ -1095,7 +1095,7 @@ export type RenderAnimationOptions = { layerName: string; meshes: ({ layerName: string; - hasFallbackLayer: boolean; + tracingId: string | null; adhocMag: Vector3; } & MeshInformation)[]; boundingBox: BoundingBoxObject; From 7bc9f905837d36d623662b9fb6b0eb89e7130f56 Mon Sep 17 00:00:00 2001 From: Tom Herold Date: Fri, 5 Apr 2024 10:07:56 +0200 Subject: [PATCH 10/12] remove view mode parameter from animations --- app/controllers/JobsController.scala | 2 -- .../oxalis/view/action-bar/create_animation_modal.tsx | 4 +--- frontend/javascripts/types/api_flow_types.ts | 1 - 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/app/controllers/JobsController.scala b/app/controllers/JobsController.scala index 8672930d176..c2beb396c7e 100644 --- a/app/controllers/JobsController.scala +++ b/app/controllers/JobsController.scala @@ -34,7 +34,6 @@ case class AnimationJobOptions( layerName: String, boundingBox: BoundingBox, includeWatermark: Boolean, - isViewMode: Boolean, meshes: JsValue, movieResolution: MovieResolutionSetting.Value, cameraPosition: CameraPositionSetting.Value, @@ -388,7 +387,6 @@ class JobsController @Inject()( "dataset_name" -> datasetName, "export_file_name" -> exportFileName, "user_auth_token" -> userAuthToken.id, - "is_view_mode" -> animationJobOptions.isViewMode, "layer_name" -> animationJobOptions.layerName, "bounding_box" -> animationJobOptions.boundingBox.toLiteral, "include_watermark" -> animationJobOptions.includeWatermark, 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 87356a57dd6..01dc5785ee6 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, ControlModeEnum, Vector3 } from "oxalis/constants"; +import { BoundingBoxType, Vector3 } from "oxalis/constants"; import BoundingBox from "oxalis/model/bucket_data_handling/bounding_box"; import { BoundingBoxSelection, LayerSelection } from "./starting_job_modals"; import { getAdditionalCoordinatesAsString } from "oxalis/model/accessors/flycam_accessor"; @@ -212,7 +212,6 @@ function CreateAnimationModal(props: Props) { ); const [magForTextures, _] = selectMagForTextureCreation(colorLayer, boundingBox); - const isViewMode = state.temporaryConfiguration.controlMode === ControlModeEnum.VIEW; const animationOptions: RenderAnimationOptions = { layerName: selectedColorLayerName, @@ -220,7 +219,6 @@ function CreateAnimationModal(props: Props) { intensityMin, intensityMax, magForTextures, - isViewMode, boundingBox: computeBoundingBoxObjectFromBoundingBox(boundingBox), includeWatermark: isWatermarkEnabled, movieResolution: selectedMovieResolution, diff --git a/frontend/javascripts/types/api_flow_types.ts b/frontend/javascripts/types/api_flow_types.ts index f3806f25994..e8e9a884751 100644 --- a/frontend/javascripts/types/api_flow_types.ts +++ b/frontend/javascripts/types/api_flow_types.ts @@ -1099,7 +1099,6 @@ export type RenderAnimationOptions = { adhocMag: Vector3; } & MeshInformation)[]; boundingBox: BoundingBoxObject; - isViewMode: boolean; includeWatermark: boolean; intensityMin: number; intensityMax: number; From d74b2c97aea76d1c920d54143d76707beae22bee Mon Sep 17 00:00:00 2001 From: Philipp Otto Date: Fri, 12 Apr 2024 13:59:31 +0200 Subject: [PATCH 11/12] also store mappingName for precomputed mesh info objects --- .../model/actions/annotation_actions.ts | 2 ++ .../model/reducers/annotation_reducer.ts | 11 +++++-- .../oxalis/model/sagas/mesh_saga.ts | 29 ++++++++++++++----- frontend/javascripts/oxalis/store.ts | 2 +- 4 files changed, 34 insertions(+), 10 deletions(-) diff --git a/frontend/javascripts/oxalis/model/actions/annotation_actions.ts b/frontend/javascripts/oxalis/model/actions/annotation_actions.ts index 42e40667b68..0ed6269d75b 100644 --- a/frontend/javascripts/oxalis/model/actions/annotation_actions.ts +++ b/frontend/javascripts/oxalis/model/actions/annotation_actions.ts @@ -307,6 +307,7 @@ export const addPrecomputedMeshAction = ( seedPosition: Vector3, seedAdditionalCoordinates: AdditionalCoordinate[] | undefined | null, meshFileName: string, + mappingName: string | null | undefined, ) => ({ type: "ADD_PRECOMPUTED_MESH", @@ -315,6 +316,7 @@ export const addPrecomputedMeshAction = ( seedPosition, seedAdditionalCoordinates, meshFileName, + mappingName, }) as const; export const setOthersMayEditForAnnotationAction = (othersMayEdit: boolean) => diff --git a/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts b/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts index 3b13fee8142..cb84bb6e8c2 100644 --- a/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts +++ b/frontend/javascripts/oxalis/model/reducers/annotation_reducer.ts @@ -321,8 +321,14 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { } case "ADD_PRECOMPUTED_MESH": { - const { layerName, segmentId, seedPosition, seedAdditionalCoordinates, meshFileName } = - action; + const { + layerName, + segmentId, + seedPosition, + seedAdditionalCoordinates, + meshFileName, + mappingName, + } = action; const meshInfo: MeshInformation = { segmentId: segmentId, seedPosition, @@ -331,6 +337,7 @@ function AnnotationReducer(state: OxalisState, action: Action): OxalisState { isVisible: true, isPrecomputed: true, meshFileName, + mappingName, }; const additionalCoordinates = state.flycam.additionalCoordinates; const additionalCoordKey = getAdditionalCoordinatesAsString(additionalCoordinates); diff --git a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts index 6617dbb6333..f8c483fe6d6 100644 --- a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts @@ -748,8 +748,16 @@ function* loadPrecomputedMeshForSegmentId( segmentationLayer: APISegmentationLayer, ): Saga { const layerName = segmentationLayer.name; + const mappingName = yield* call(getMappingName, segmentationLayer); yield* put( - addPrecomputedMeshAction(layerName, id, seedPosition, seedAdditionalCoordinates, meshFileName), + addPrecomputedMeshAction( + layerName, + id, + seedPosition, + seedAdditionalCoordinates, + meshFileName, + mappingName, + ), ); yield* put(startedLoadingMeshAction(layerName, id)); const dataset = yield* select((state) => state.dataset); @@ -815,6 +823,18 @@ function* loadPrecomputedMeshForSegmentId( yield* put(finishedLoadingMeshAction(layerName, id)); } +function* getMappingName(segmentationLayer: APISegmentationLayer) { + const meshExtraInfo = yield* call(getMeshExtraInfo, segmentationLayer.name, null); + const editableMapping = yield* select((state) => + getEditableMappingForVolumeTracingId(state, segmentationLayer.tracingId), + ); + + // meshExtraInfo.mappingName contains the currently active mapping + // (can be the id of an editable mapping). However, we always need to + // use the mapping name of the on-disk mapping. + return editableMapping != null ? editableMapping.baseMappingName : meshExtraInfo.mappingName; +} + function* _getChunkLoadingDescriptors( id: number, dataset: APIDataset, @@ -832,7 +852,6 @@ function* _getChunkLoadingDescriptors( }); const version = meshFile.formatVersion; const { meshFileName } = meshFile; - const meshExtraInfo = yield* call(getMeshExtraInfo, segmentationLayer.name, null); const editableMapping = yield* select((state) => getEditableMappingForVolumeTracingId(state, segmentationLayer.tracingId), @@ -840,11 +859,7 @@ function* _getChunkLoadingDescriptors( const tracing = yield* select((state) => getTracingForSegmentationLayer(state, segmentationLayer), ); - const mappingName = - // meshExtraInfo.mappingName contains the currently active mapping - // (can be the id of an editable mapping). However, we always need to - // use the mapping name of the on-disk mapping. - editableMapping != null ? editableMapping.baseMappingName : meshExtraInfo.mappingName; + const mappingName = yield* call(getMappingName, segmentationLayer); if (version < 3) { console.warn( diff --git a/frontend/javascripts/oxalis/store.ts b/frontend/javascripts/oxalis/store.ts index 1375eaec3dd..fac20ffd884 100644 --- a/frontend/javascripts/oxalis/store.ts +++ b/frontend/javascripts/oxalis/store.ts @@ -544,10 +544,10 @@ type BaseMeshInformation = { readonly seedAdditionalCoordinates?: AdditionalCoordinate[] | null; readonly isLoading: boolean; readonly isVisible: boolean; + readonly mappingName: string | null | undefined; }; export type AdHocMeshInformation = BaseMeshInformation & { readonly isPrecomputed: false; - readonly mappingName: string | null | undefined; readonly mappingType: MappingType | null | undefined; }; export type PrecomputedMeshInformation = BaseMeshInformation & { From 95e5dd3365f8307396957287a7705d059f712d2a Mon Sep 17 00:00:00 2001 From: Tom Herold Date: Fri, 19 Apr 2024 15:28:22 +0200 Subject: [PATCH 12/12] fix animation modal error message for DS without color layers --- .../view/action-bar/create_animation_modal.tsx | 12 +++++++----- .../oxalis/view/action-bar/tracing_actions_view.tsx | 2 +- .../view/action-bar/view_dataset_actions_view.tsx | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) 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 01dc5785ee6..ccda594706e 100644 --- a/frontend/javascripts/oxalis/view/action-bar/create_animation_modal.tsx +++ b/frontend/javascripts/oxalis/view/action-bar/create_animation_modal.tsx @@ -77,14 +77,18 @@ function selectMagForTextureCreation( return [bestMag, bestDifference]; } -export function CreateAnimationModalWrapper(props: Props) { +export default function CreateAnimationModalWrapper(props: Props) { const dataset = useSelector((state: OxalisState) => state.dataset); // early stop if no color layer exists const colorLayers = getColorLayers(dataset); if (colorLayers.length === 0) { - Toast.warning("WK can not create animations for datasets without color layers."); - return null; + const { isOpen, onClose } = props; + return ( + + WEBKNOSSOS cannot create animations for datasets without color layers. + + ); } return ; @@ -405,5 +409,3 @@ function CreateAnimationModal(props: Props) { ); } - -export default CreateAnimationModal; diff --git a/frontend/javascripts/oxalis/view/action-bar/tracing_actions_view.tsx b/frontend/javascripts/oxalis/view/action-bar/tracing_actions_view.tsx index 72e67e51ec6..da04547bdba 100644 --- a/frontend/javascripts/oxalis/view/action-bar/tracing_actions_view.tsx +++ b/frontend/javascripts/oxalis/view/action-bar/tracing_actions_view.tsx @@ -74,7 +74,7 @@ import UrlManager from "oxalis/controller/url_manager"; import { withAuthentication } from "admin/auth/authentication_modal"; import { PrivateLinksModal } from "./private_links_view"; import { ItemType, SubMenuType } from "antd/lib/menu/hooks/useItems"; -import { CreateAnimationModalWrapper as CreateAnimationModal } from "./create_animation_modal"; +import CreateAnimationModal from "./create_animation_modal"; const AsyncButtonWithAuthentication = withAuthentication( AsyncButton, diff --git a/frontend/javascripts/oxalis/view/action-bar/view_dataset_actions_view.tsx b/frontend/javascripts/oxalis/view/action-bar/view_dataset_actions_view.tsx index ff8826826b0..c5641d805f5 100644 --- a/frontend/javascripts/oxalis/view/action-bar/view_dataset_actions_view.tsx +++ b/frontend/javascripts/oxalis/view/action-bar/view_dataset_actions_view.tsx @@ -19,7 +19,7 @@ import { import Store, { OxalisState } from "oxalis/store"; import { MenuItemType, SubMenuType } from "antd/lib/menu/hooks/useItems"; import DownloadModalView from "./download_modal_view"; -import { CreateAnimationModalWrapper as CreateAnimationModal } from "./create_animation_modal"; +import CreateAnimationModal from "./create_animation_modal"; type Props = { layoutMenu: SubMenuType;