From 10fb4e4798d4de5b6e7ff8178d30469b6571e706 Mon Sep 17 00:00:00 2001 From: Philipp Otto Date: Wed, 14 Aug 2024 14:32:42 +0200 Subject: [PATCH 01/19] use tps instead of affine in compose --- .../composition_wizard/04_configure_new_dataset.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx b/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx index 98e7c471cad..6586f93a746 100644 --- a/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx +++ b/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx @@ -55,9 +55,13 @@ export function ConfigureNewDataset(props: WizardComponentProps) { const transformationArr = sourcePoints.length > 0 && targetPoints.length > 0 ? [ + // { + // type: "affine" as const, + // matrix: flatToNestedMatrix(estimateAffineMatrix4x4(sourcePoints, targetPoints)), + // }, { - type: "affine" as const, - matrix: flatToNestedMatrix(estimateAffineMatrix4x4(sourcePoints, targetPoints)), + type: "thin_plate_spline" as const, + correspondences: { source: sourcePoints, target: targetPoints }, }, ] : []; From d571e52970ac4dcb74b57b9a93f4aa6140da3f12 Mon Sep 17 00:00:00 2001 From: Philipp Otto Date: Wed, 14 Aug 2024 14:33:04 +0200 Subject: [PATCH 02/19] temporarily disable some CI checks --- .circleci/not-on-master.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.circleci/not-on-master.sh b/.circleci/not-on-master.sh index 581393ebead..e3078cdb9ce 100755 --- a/.circleci/not-on-master.sh +++ b/.circleci/not-on-master.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash set -Eeuo pipefail -if [ "${CIRCLE_BRANCH}" == "master" ]; then +# if [ "${CIRCLE_BRANCH}" == "master" ]; then echo "Skipping this step on master..." -else - exec "$@" -fi +# else +# exec "$@" +# fi From 0f7eebb9ff73a056ef0dc8e90d22f5920b8a9647 Mon Sep 17 00:00:00 2001 From: Philipp Otto Date: Wed, 14 Aug 2024 17:14:15 +0200 Subject: [PATCH 03/19] always sort trees when composing datasets with landmarks --- .../admin/dataset/composition_wizard/02_upload_files.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/javascripts/admin/dataset/composition_wizard/02_upload_files.tsx b/frontend/javascripts/admin/dataset/composition_wizard/02_upload_files.tsx index 6bd0ac87973..aee0b57194c 100644 --- a/frontend/javascripts/admin/dataset/composition_wizard/02_upload_files.tsx +++ b/frontend/javascripts/admin/dataset/composition_wizard/02_upload_files.tsx @@ -173,7 +173,10 @@ async function parseNmlFiles(fileList: FileList): Promise throw new SoftError("The two NML files should have the same tree count."); } - for (const [tree1, tree2] of _.zip(Utils.values(trees1), Utils.values(trees2))) { + for (const [tree1, tree2] of _.zip( + Utils.values(trees1).sort((a, b) => a.treeId - b.treeId), + Utils.values(trees2).sort((a, b) => a.treeId - b.treeId), + )) { if (tree1 == null || tree2 == null) { // Satisfy TS. This should not happen, as we checked before that both tree collections // have the same size. From 509b7db13645cb7171e428ba8332953946c97d2c Mon Sep 17 00:00:00 2001 From: Philipp Otto Date: Wed, 14 Aug 2024 17:14:46 +0200 Subject: [PATCH 04/19] add UI checkbox for affine vs tps --- .../04_configure_new_dataset.tsx | 67 ++++++++++++------- frontend/javascripts/types/api_flow_types.ts | 3 + 2 files changed, 46 insertions(+), 24 deletions(-) diff --git a/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx b/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx index 6586f93a746..1899507cf6b 100644 --- a/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx +++ b/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx @@ -5,7 +5,7 @@ import { DatasetNameFormItem, layerNameRules, } from "admin/dataset/dataset_components"; -import { Button, Col, Form, FormInstance, Input, List, Row, Tooltip } from "antd"; +import { Button, Checkbox, Col, Form, FormInstance, Input, List, Row, Tooltip } from "antd"; import { FormItemWithInfo } from "dashboard/dataset/helper_components"; import FolderSelection from "dashboard/folders/folder_selection"; import { estimateAffineMatrix4x4 } from "libs/estimate_affine"; @@ -13,12 +13,18 @@ import Toast from "libs/toast"; import * as Utils from "libs/utils"; import _ from "lodash"; import messages from "messages"; -import { Vector3 } from "oxalis/constants"; import { flatToNestedMatrix } from "oxalis/model/accessors/dataset_accessor"; import { OxalisState } from "oxalis/store"; import React, { useState } from "react"; import { useSelector } from "react-redux"; -import { APIDataLayer, APIDataset, APIDatasetId, APITeam, LayerLink } from "types/api_flow_types"; +import { + APIDataLayer, + APIDataset, + APIDatasetId, + APITeam, + areDatasetsIdentical, + LayerLink, +} from "types/api_flow_types"; import { syncValidator } from "types/validation"; import { WizardComponentProps } from "./common"; import { useEffectOnlyOnce } from "libs/react_hooks"; @@ -50,24 +56,9 @@ export function ConfigureNewDataset(props: WizardComponentProps) { form.setFieldsValue({ layers: newLayers }); }; - const handleTransformImport = async (sourcePoints: Vector3[], targetPoints: Vector3[]) => { - const datasets = linkedDatasets; - const transformationArr = - sourcePoints.length > 0 && targetPoints.length > 0 - ? [ - // { - // type: "affine" as const, - // matrix: flatToNestedMatrix(estimateAffineMatrix4x4(sourcePoints, targetPoints)), - // }, - { - type: "thin_plate_spline" as const, - correspondences: { source: sourcePoints, target: targetPoints }, - }, - ] - : []; - + const handleTransformImport = async () => { const newLinks: LayerLink[] = ( - _.flatMap(datasets, (dataset) => + _.flatMap(linkedDatasets, (dataset) => dataset.dataSource.dataLayers.map((layer) => [dataset, layer]), ) as [APIDataset, APIDataLayer][] ).map( @@ -78,21 +69,46 @@ export function ConfigureNewDataset(props: WizardComponentProps) { }, sourceName: dataLayer.name, newName: dataLayer.name, - transformations: dataset === datasets[0] ? transformationArr : [], + transformations: [], }), ); form.setFieldsValue({ layers: newLinks }); }; useEffectOnlyOnce(() => { - handleTransformImport(wizardContext.sourcePoints, wizardContext.targetPoints); + handleTransformImport(); }); const handleSubmit = async () => { if (activeUser == null) { throw new Error("Cannot create dataset without being logged in."); } - const layers = form.getFieldValue(["layers"]); + const layersWithoutTransforms = form.getFieldValue(["layers"]); + const useThinPlateSplines = form.getFieldValue("useThinPlateSplines"); + + function withTransforms(layers: LayerLink[]) { + const { sourcePoints, targetPoints } = wizardContext; + const transformationArr = + sourcePoints.length > 0 && targetPoints.length > 0 + ? [ + useThinPlateSplines + ? { + type: "thin_plate_spline" as const, + correspondences: { source: sourcePoints, target: targetPoints }, + } + : { + type: "affine" as const, + matrix: flatToNestedMatrix(estimateAffineMatrix4x4(sourcePoints, targetPoints)), + }, + ] + : []; + return layers.map((layer) => ({ + ...layer, + transformations: areDatasetsIdentical(layer.datasetId, linkedDatasets[0]) + ? transformationArr + : [], + })); + } const uploadableDatastores = props.datastores.filter((datastore) => datastore.allowsUpload); const datastoreToUse = uploadableDatastores[0]; @@ -109,7 +125,7 @@ export function ConfigureNewDataset(props: WizardComponentProps) { targetFolderId: form.getFieldValue(["targetFolderId"]), organizationName: activeUser.organization, voxelSize: linkedDatasets.slice(-1)[0].dataSource.scale, - layers, + layers: withTransforms(layersWithoutTransforms), }); } finally { setIsLoading(false); @@ -184,6 +200,9 @@ export function ConfigureNewDataset(props: WizardComponentProps) { ); }} + + Use Thin-Plate-Splines (Experimental) + ; export type APIDatasetDetails = { readonly species?: string; From b624b5823dfc4987da42691a7ebe5420aa829385 Mon Sep 17 00:00:00 2001 From: Philipp Otto Date: Wed, 14 Aug 2024 17:21:31 +0200 Subject: [PATCH 05/19] fix that p tag cannot contain ul --- frontend/javascripts/admin/dataset/dataset_upload_view.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/javascripts/admin/dataset/dataset_upload_view.tsx b/frontend/javascripts/admin/dataset/dataset_upload_view.tsx index 2b842421d81..765b542184a 100644 --- a/frontend/javascripts/admin/dataset/dataset_upload_view.tsx +++ b/frontend/javascripts/admin/dataset/dataset_upload_view.tsx @@ -1008,7 +1008,7 @@ function FileUploadArea({ color: "var(--ant-color-primary)", }} /> -

) : null} -

+ {files.length > 0 ? ( From ddf1d2642389f06ad916f536d184ec29c9fab959 Mon Sep 17 00:00:00 2001 From: Philipp Otto Date: Fri, 16 Aug 2024 09:19:57 +0200 Subject: [PATCH 06/19] also sort nodes by id --- .../admin/dataset/composition_wizard/02_upload_files.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/javascripts/admin/dataset/composition_wizard/02_upload_files.tsx b/frontend/javascripts/admin/dataset/composition_wizard/02_upload_files.tsx index aee0b57194c..a49513e6cbf 100644 --- a/frontend/javascripts/admin/dataset/composition_wizard/02_upload_files.tsx +++ b/frontend/javascripts/admin/dataset/composition_wizard/02_upload_files.tsx @@ -182,8 +182,8 @@ async function parseNmlFiles(fileList: FileList): Promise // have the same size. throw new SoftError("A tree was unexpectedly parsed as null. Please try again"); } - const nodes1 = Array.from(tree1.nodes.values()); - const nodes2 = Array.from(tree2.nodes.values()); + const nodes1 = Array.from(tree1.nodes.values()).sort((a, b) => a.id - b.id); + const nodes2 = Array.from(tree2.nodes.values()).sort((a, b) => a.id - b.id); for (const [node1, node2] of _.zip(nodes1, nodes2)) { if ((node1 == null) !== (node2 == null)) { throw new SoftError( From 5aae4d95abcf67089fb2070df9a69903c80f7469 Mon Sep 17 00:00:00 2001 From: Philipp Otto Date: Fri, 16 Aug 2024 14:08:16 +0200 Subject: [PATCH 07/19] fix inverted TPS transform creation when loading dataset; add more comments --- .../admin/dataset/composition_wizard/02_upload_files.tsx | 4 ++-- .../dataset/composition_wizard/04_configure_new_dataset.tsx | 1 + frontend/javascripts/libs/estimate_affine.ts | 3 ++- .../javascripts/oxalis/model/accessors/dataset_accessor.ts | 2 +- .../oxalis/model/helpers/transformation_helpers.ts | 2 ++ 5 files changed, 8 insertions(+), 4 deletions(-) diff --git a/frontend/javascripts/admin/dataset/composition_wizard/02_upload_files.tsx b/frontend/javascripts/admin/dataset/composition_wizard/02_upload_files.tsx index a49513e6cbf..e3d30151f49 100644 --- a/frontend/javascripts/admin/dataset/composition_wizard/02_upload_files.tsx +++ b/frontend/javascripts/admin/dataset/composition_wizard/02_upload_files.tsx @@ -204,8 +204,8 @@ async function parseNmlFiles(fileList: FileList): Promise return { datasets: datasets || [], - sourcePoints, - targetPoints, + sourcePoints, // The first dataset (will be transformed to match the second later) + targetPoints, // The second dataset (won't be transformed by default) currentWizardStep: "SelectDatasets", }; } diff --git a/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx b/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx index 1899507cf6b..49886101871 100644 --- a/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx +++ b/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx @@ -104,6 +104,7 @@ export function ConfigureNewDataset(props: WizardComponentProps) { : []; return layers.map((layer) => ({ ...layer, + // The first dataset will be transformed to match the second. transformations: areDatasetsIdentical(layer.datasetId, linkedDatasets[0]) ? transformationArr : [], diff --git a/frontend/javascripts/libs/estimate_affine.ts b/frontend/javascripts/libs/estimate_affine.ts index 715a87d1385..12bdd608aca 100644 --- a/frontend/javascripts/libs/estimate_affine.ts +++ b/frontend/javascripts/libs/estimate_affine.ts @@ -3,8 +3,8 @@ import { Matrix4x4 } from "mjs"; import { Matrix, solve } from "ml-matrix"; import { Vector3 } from "oxalis/constants"; -// Estimates an affine matrix that transforms from source points to target points. export default function estimateAffine(sourcePoints: Vector3[], targetPoints: Vector3[]) { + /* Estimates an affine matrix that transforms from source points to target points. */ // Number of correspondences const N = sourcePoints.length; @@ -52,5 +52,6 @@ export function estimateAffineMatrix4x4( sourcePoints: Vector3[], targetPoints: Vector3[], ): Matrix4x4 { + /* Estimates an affine matrix that transforms from source points to target points. */ return estimateAffine(sourcePoints, targetPoints).to1DArray() as any as Matrix4x4; } diff --git a/frontend/javascripts/oxalis/model/accessors/dataset_accessor.ts b/frontend/javascripts/oxalis/model/accessors/dataset_accessor.ts index 46f0df76b90..a65ea04a6e4 100644 --- a/frontend/javascripts/oxalis/model/accessors/dataset_accessor.ts +++ b/frontend/javascripts/oxalis/model/accessors/dataset_accessor.ts @@ -722,7 +722,7 @@ function _getOriginalTransformsForLayerOrNull( } else if (type === "thin_plate_spline") { const { source, target } = transformation.correspondences; - return createThinPlateSplineTransform(target, source, dataset.dataSource.scale.factor); + return createThinPlateSplineTransform(source, target, dataset.dataSource.scale.factor); } console.error( diff --git a/frontend/javascripts/oxalis/model/helpers/transformation_helpers.ts b/frontend/javascripts/oxalis/model/helpers/transformation_helpers.ts index 4f0ac826a27..08b63097c89 100644 --- a/frontend/javascripts/oxalis/model/helpers/transformation_helpers.ts +++ b/frontend/javascripts/oxalis/model/helpers/transformation_helpers.ts @@ -34,6 +34,7 @@ export function createAffineTransformFromMatrix( } export function createAffineTransform(source: Vector3[], target: Vector3[]): Transform { + /* Creates an affine transform that transforms from source points to target points. */ const affineMatrix = estimateAffineMatrix4x4(source, target); return { @@ -48,6 +49,7 @@ export function createThinPlateSplineTransform( target: Vector3[], scale: Vector3, ): Transform { + /* Creates a TPS that transforms from source points to target points. */ const affineMatrix = estimateAffineMatrix4x4(source, target); const affineMatrixInv = estimateAffineMatrix4x4(target, source); From cef63e841c2dcfae35f6b770de2f7c09942d589f Mon Sep 17 00:00:00 2001 From: Philipp Otto Date: Fri, 16 Aug 2024 14:40:50 +0200 Subject: [PATCH 08/19] render dataset description as markdown in dashboard; update dataset description after composing --- .../04_configure_new_dataset.tsx | 50 ++++++++++++++++--- .../dashboard/folders/details_sidebar.tsx | 3 +- frontend/javascripts/libs/estimate_affine.ts | 19 ++++--- 3 files changed, 57 insertions(+), 15 deletions(-) diff --git a/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx b/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx index 49886101871..80074a22fce 100644 --- a/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx +++ b/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx @@ -1,5 +1,5 @@ import { DeleteOutlined } from "@ant-design/icons"; -import { createDatasetComposition } from "admin/admin_rest_api"; +import { createDatasetComposition, updateDatasetPartial } from "admin/admin_rest_api"; import { AllowedTeamsFormItem, DatasetNameFormItem, @@ -83,8 +83,11 @@ export function ConfigureNewDataset(props: WizardComponentProps) { if (activeUser == null) { throw new Error("Cannot create dataset without being logged in."); } - const layersWithoutTransforms = form.getFieldValue(["layers"]); - const useThinPlateSplines = form.getFieldValue("useThinPlateSplines"); + const layersWithoutTransforms = form.getFieldValue(["layers"]) as LayerLink[]; + // todop: does this throw when the ui was not rendered? + const useThinPlateSplines = form.getFieldValue("useThinPlateSplines") as boolean; + + const affineMeanError = { meanError: 0 }; function withTransforms(layers: LayerLink[]) { const { sourcePoints, targetPoints } = wizardContext; @@ -98,7 +101,9 @@ export function ConfigureNewDataset(props: WizardComponentProps) { } : { type: "affine" as const, - matrix: flatToNestedMatrix(estimateAffineMatrix4x4(sourcePoints, targetPoints)), + matrix: flatToNestedMatrix( + estimateAffineMatrix4x4(sourcePoints, targetPoints, affineMeanError), + ), }, ] : []; @@ -128,6 +133,35 @@ export function ConfigureNewDataset(props: WizardComponentProps) { voxelSize: linkedDatasets.slice(-1)[0].dataSource.scale, layers: withTransforms(layersWithoutTransforms), }); + + const uniqueDatasets = _.uniqBy( + layersWithoutTransforms.map((layer) => layer.datasetId), + (id) => id.owningOrganization + "-" + id.name, + ); + const datasetMarkdownLinks = uniqueDatasets + .map((el) => `- [${el.name}](/datasets/${el.owningOrganization}/${el.name})`) + .join("\n"); + + await updateDatasetPartial( + { owningOrganization: activeUser.organization, name: newDatasetName }, + { + description: [ + "This dataset was composed from:", + datasetMarkdownLinks, + "", + "The layers were combined " + + (wizardContext.sourcePoints.length === 0 + ? "without any transforms" + : `with ${ + useThinPlateSplines + ? `Thin-Plate-Splines (${wizardContext.sourcePoints.length} correspondences)` + : `an affine transformation (mean error: ${affineMeanError})` + }`) + + ".", + ].join("\n"), + }, + // todop test + ); } finally { setIsLoading(false); } @@ -201,9 +235,11 @@ export function ConfigureNewDataset(props: WizardComponentProps) { ); }} - - Use Thin-Plate-Splines (Experimental) - + {wizardContext.sourcePoints.length > 0 && ( + + Use Thin-Plate-Splines (Experimental) + + )}
Description
-
{fullDataset?.description}
+ {fullDataset?.description}
diff --git a/frontend/javascripts/libs/estimate_affine.ts b/frontend/javascripts/libs/estimate_affine.ts index 12bdd608aca..dc077ee675a 100644 --- a/frontend/javascripts/libs/estimate_affine.ts +++ b/frontend/javascripts/libs/estimate_affine.ts @@ -3,7 +3,11 @@ import { Matrix4x4 } from "mjs"; import { Matrix, solve } from "ml-matrix"; import { Vector3 } from "oxalis/constants"; -export default function estimateAffine(sourcePoints: Vector3[], targetPoints: Vector3[]) { +export default function estimateAffine( + sourcePoints: Vector3[], + targetPoints: Vector3[], + optInfoOut?: { meanError: number }, +) { /* Estimates an affine matrix that transforms from source points to target points. */ // Number of correspondences const N = sourcePoints.length; @@ -31,11 +35,11 @@ export default function estimateAffine(sourcePoints: Vector3[], targetPoints: Ve const x = xMatrix.to1DArray(); const error = Matrix.sub(b, new Matrix(A).mmul(xMatrix)).to1DArray(); if (!process.env.IS_TESTING) { - console.log( - "Affine estimation error: ", - error, - `(mean=${_.mean(error.map((el) => Math.abs(el)))})`, - ); + const meanError = _.mean(error.map((el) => Math.abs(el))); + console.log("Affine estimation error: ", error, `(mean=${meanError})`); + if (optInfoOut) { + optInfoOut.meanError = meanError; + } } const affineMatrix = new Matrix([ @@ -51,7 +55,8 @@ export default function estimateAffine(sourcePoints: Vector3[], targetPoints: Ve export function estimateAffineMatrix4x4( sourcePoints: Vector3[], targetPoints: Vector3[], + optInfoOut?: { meanError: number }, ): Matrix4x4 { /* Estimates an affine matrix that transforms from source points to target points. */ - return estimateAffine(sourcePoints, targetPoints).to1DArray() as any as Matrix4x4; + return estimateAffine(sourcePoints, targetPoints, optInfoOut).to1DArray() as any as Matrix4x4; } From 5a690e10ebbb50c013a1d35526c90bdbae4dd909 Mon Sep 17 00:00:00 2001 From: Philipp Otto Date: Fri, 16 Aug 2024 16:13:20 +0200 Subject: [PATCH 09/19] fix mean error formatting in description --- .../dataset/composition_wizard/04_configure_new_dataset.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx b/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx index 80074a22fce..e73d9c3e0ad 100644 --- a/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx +++ b/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx @@ -28,6 +28,7 @@ import { import { syncValidator } from "types/validation"; import { WizardComponentProps } from "./common"; import { useEffectOnlyOnce } from "libs/react_hooks"; +import { formatNumber } from "libs/format_utils"; const FormItem = Form.Item; @@ -155,7 +156,9 @@ export function ConfigureNewDataset(props: WizardComponentProps) { : `with ${ useThinPlateSplines ? `Thin-Plate-Splines (${wizardContext.sourcePoints.length} correspondences)` - : `an affine transformation (mean error: ${affineMeanError})` + : `an affine transformation (mean error: ${formatNumber( + affineMeanError.meanError, + )} vx)` }`) + ".", ].join("\n"), From 8fcc8eeee480f0d10512138e837b116870bb8017 Mon Sep 17 00:00:00 2001 From: Philipp Otto Date: Mon, 19 Aug 2024 13:39:13 +0200 Subject: [PATCH 10/19] disable TPS checkbox for now --- .../dataset/composition_wizard/03_select_datasets.tsx | 6 +++++- .../composition_wizard/04_configure_new_dataset.tsx | 7 +++---- .../javascripts/dashboard/explorative_annotations_view.tsx | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/frontend/javascripts/admin/dataset/composition_wizard/03_select_datasets.tsx b/frontend/javascripts/admin/dataset/composition_wizard/03_select_datasets.tsx index 5dc73e6294d..8ad912cb6d7 100644 --- a/frontend/javascripts/admin/dataset/composition_wizard/03_select_datasets.tsx +++ b/frontend/javascripts/admin/dataset/composition_wizard/03_select_datasets.tsx @@ -48,7 +48,11 @@ export default function SelectDatasets({ wizardContext, setWizardContext }: Wiza return (
-

Select the datasets that you want to combine or doublecheck the pre-selected datasets.

+

+ Select the datasets that you want to combine or doublecheck the pre-selected datasets. Note + that the order of the datasets is important and needs to be equal to the order of the files + from the upload. +

>(null); @@ -85,8 +86,7 @@ export function ConfigureNewDataset(props: WizardComponentProps) { throw new Error("Cannot create dataset without being logged in."); } const layersWithoutTransforms = form.getFieldValue(["layers"]) as LayerLink[]; - // todop: does this throw when the ui was not rendered? - const useThinPlateSplines = form.getFieldValue("useThinPlateSplines") as boolean; + const useThinPlateSplines = (form.getFieldValue("useThinPlateSplines") ?? false) as boolean; const affineMeanError = { meanError: 0 }; @@ -163,7 +163,6 @@ export function ConfigureNewDataset(props: WizardComponentProps) { ".", ].join("\n"), }, - // todop test ); } finally { setIsLoading(false); @@ -238,7 +237,7 @@ export function ConfigureNewDataset(props: WizardComponentProps) { ); }} - {wizardContext.sourcePoints.length > 0 && ( + {ALLOW_TPS_IN_COMPOSITION && wizardContext.sourcePoints.length > 0 && ( Use Thin-Plate-Splines (Experimental) diff --git a/frontend/javascripts/dashboard/explorative_annotations_view.tsx b/frontend/javascripts/dashboard/explorative_annotations_view.tsx index 357a2e98ad2..0e18a0a4b51 100644 --- a/frontend/javascripts/dashboard/explorative_annotations_view.tsx +++ b/frontend/javascripts/dashboard/explorative_annotations_view.tsx @@ -682,7 +682,7 @@ class ExplorativeAnnotationsView extends React.PureComponent { return ( <>
- + {ownerName}
From c6227b1520bd79d69316e4343aea22ed435349e4 Mon Sep 17 00:00:00 2001 From: Philipp Otto Date: Mon, 19 Aug 2024 13:39:38 +0200 Subject: [PATCH 11/19] restore ci --- .circleci/not-on-master.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.circleci/not-on-master.sh b/.circleci/not-on-master.sh index e3078cdb9ce..581393ebead 100755 --- a/.circleci/not-on-master.sh +++ b/.circleci/not-on-master.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash set -Eeuo pipefail -# if [ "${CIRCLE_BRANCH}" == "master" ]; then +if [ "${CIRCLE_BRANCH}" == "master" ]; then echo "Skipping this step on master..." -# else -# exec "$@" -# fi +else + exec "$@" +fi From 7ccf57cb633f6c9f228f0d84975dbadecb6f2fca Mon Sep 17 00:00:00 2001 From: Philipp Otto Date: Mon, 19 Aug 2024 13:47:05 +0200 Subject: [PATCH 12/19] update changelog --- CHANGELOG.unreleased.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index cbe39f2b42f..1158f910a5c 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -49,6 +49,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released - Fixed that activating the skeleton tab would always change the active position to the active node. [#7958](https://github.com/scalableminds/webknossos/pull/7958) - Fixed that skeleton groups couldn't be collapsed or expanded in locked annotations. [#7988](https://github.com/scalableminds/webknossos/pull/7988) - Fixed uploading datasets in neuroglancer precomputed and n5 data format. [#8008](https://github.com/scalableminds/webknossos/pull/8008) +- Various fixes for composing datasets with landmarks. Note that the interpretation of correspondence points was inverted for thin plate splines. [#7992](https://github.com/scalableminds/webknossos/pull/7992) ### Removed From e74a0690d2ccefe932f986b9ca9af429b769ec45 Mon Sep 17 00:00:00 2001 From: Philipp Otto Date: Tue, 20 Aug 2024 09:23:49 +0200 Subject: [PATCH 13/19] make optInfoOut writing independent from IS_TESTING --- frontend/javascripts/libs/estimate_affine.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/javascripts/libs/estimate_affine.ts b/frontend/javascripts/libs/estimate_affine.ts index dc077ee675a..7413361dbc9 100644 --- a/frontend/javascripts/libs/estimate_affine.ts +++ b/frontend/javascripts/libs/estimate_affine.ts @@ -34,12 +34,12 @@ export default function estimateAffine( const xMatrix = solve(A, b); const x = xMatrix.to1DArray(); const error = Matrix.sub(b, new Matrix(A).mmul(xMatrix)).to1DArray(); + const meanError = _.mean(error.map((el) => Math.abs(el))); + if (optInfoOut) { + optInfoOut.meanError = meanError; + } if (!process.env.IS_TESTING) { - const meanError = _.mean(error.map((el) => Math.abs(el))); console.log("Affine estimation error: ", error, `(mean=${meanError})`); - if (optInfoOut) { - optInfoOut.meanError = meanError; - } } const affineMatrix = new Matrix([ From 734d4264fd1f6b6f69ba440aa40b646185ca2744 Mon Sep 17 00:00:00 2001 From: Philipp Otto Date: Tue, 20 Aug 2024 14:10:43 +0200 Subject: [PATCH 14/19] ask user for landmark augmentation when affine estimation fails; assert >= landmarks; test tps creation eagerly etc --- .../composition_wizard/02_upload_files.tsx | 4 + .../04_configure_new_dataset.tsx | 91 ++++++++++++++----- .../model/helpers/transformation_helpers.ts | 8 ++ .../oxalis/model/sagas/saga_helpers.ts | 2 +- 4 files changed, 81 insertions(+), 24 deletions(-) diff --git a/frontend/javascripts/admin/dataset/composition_wizard/02_upload_files.tsx b/frontend/javascripts/admin/dataset/composition_wizard/02_upload_files.tsx index e3d30151f49..352442e0661 100644 --- a/frontend/javascripts/admin/dataset/composition_wizard/02_upload_files.tsx +++ b/frontend/javascripts/admin/dataset/composition_wizard/02_upload_files.tsx @@ -197,6 +197,10 @@ async function parseNmlFiles(fileList: FileList): Promise } } + if (sourcePoints.length < 3) { + throw new SoftError("Each file should contain at least 3 nodes."); + } + const datasets = await tryToFetchDatasetsByName( [datasetName1, datasetName2], "Could not derive datasets from NML. Please specify these manually.", diff --git a/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx b/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx index 314c1ff95a4..5bc025f35d3 100644 --- a/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx +++ b/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx @@ -5,7 +5,7 @@ import { DatasetNameFormItem, layerNameRules, } from "admin/dataset/dataset_components"; -import { Button, Checkbox, Col, Form, FormInstance, Input, List, Row, Tooltip } from "antd"; +import { Button, Checkbox, Col, Form, FormInstance, Input, List, Modal, Row, Tooltip } from "antd"; import { FormItemWithInfo } from "dashboard/dataset/helper_components"; import FolderSelection from "dashboard/folders/folder_selection"; import { estimateAffineMatrix4x4 } from "libs/estimate_affine"; @@ -25,14 +25,27 @@ import { areDatasetsIdentical, LayerLink, } from "types/api_flow_types"; +import ErrorHandling from "libs/error_handling"; import { syncValidator } from "types/validation"; import { WizardComponentProps } from "./common"; import { useEffectOnlyOnce } from "libs/react_hooks"; import { formatNumber } from "libs/format_utils"; +import { checkLandmarksForThinPlateSpline } from "oxalis/model/helpers/transformation_helpers"; +import { Vector3 } from "oxalis/constants"; const FormItem = Form.Item; const ALLOW_TPS_IN_COMPOSITION = false; +async function guardedWithErrorToast(fn: () => Promise) { + try { + await fn(); + } catch (error) { + Toast.error("An error unexpected occurred. Please check the console for details"); + console.error(error); + ErrorHandling.notify(error as Error); + } +} + export function ConfigureNewDataset(props: WizardComponentProps) { const formRef = React.useRef>(null); @@ -90,24 +103,27 @@ export function ConfigureNewDataset(props: WizardComponentProps) { const affineMeanError = { meanError: 0 }; - function withTransforms(layers: LayerLink[]) { - const { sourcePoints, targetPoints } = wizardContext; - const transformationArr = - sourcePoints.length > 0 && targetPoints.length > 0 - ? [ - useThinPlateSplines - ? { - type: "thin_plate_spline" as const, - correspondences: { source: sourcePoints, target: targetPoints }, - } - : { - type: "affine" as const, - matrix: flatToNestedMatrix( - estimateAffineMatrix4x4(sourcePoints, targetPoints, affineMeanError), - ), - }, - ] - : []; + function withTransforms(layers: LayerLink[], sourcePoints: Vector3[], targetPoints: Vector3[]) { + if (sourcePoints.length + targetPoints.length === 0) { + return layers; + } + + const transformationArr = [ + useThinPlateSplines + ? { + type: "thin_plate_spline" as const, + correspondences: { source: sourcePoints, target: targetPoints }, + } + : { + type: "affine" as const, + matrix: flatToNestedMatrix( + estimateAffineMatrix4x4(sourcePoints, targetPoints, affineMeanError), + ), + }, + ]; + if (useThinPlateSplines) { + checkLandmarksForThinPlateSpline(sourcePoints, targetPoints); + } return layers.map((layer) => ({ ...layer, // The first dataset will be transformed to match the second. @@ -124,6 +140,35 @@ export function ConfigureNewDataset(props: WizardComponentProps) { return; } + let layersWithTransforms; + const { sourcePoints, targetPoints } = wizardContext; + try { + layersWithTransforms = withTransforms(layersWithoutTransforms, sourcePoints, targetPoints); + } catch (exception) { + const tryAugmentation = await new Promise((resolve) => { + Modal.confirm({ + title: "Augment landmarks?", + content: + "The provided landmarks can't be used for affine estimation, possibly " + + "due to their planar nature. Should a constant translation in the Z " + + "direction be assumed, and the landmarks adjusted accordingly?", + onOk: () => resolve(true), + onCancel: () => resolve(false), + }); + }); + const augmentLandmarks = (points: Vector3[]) => + points.concat(points.map((p) => [p[0], p[1], p[2] + 1])); + if (tryAugmentation) { + layersWithTransforms = withTransforms( + layersWithoutTransforms, + augmentLandmarks(sourcePoints), + augmentLandmarks(targetPoints), + ); + } else { + throw exception; + } + } + const newDatasetName = form.getFieldValue(["name"]); setIsLoading(true); try { @@ -132,7 +177,7 @@ export function ConfigureNewDataset(props: WizardComponentProps) { targetFolderId: form.getFieldValue(["targetFolderId"]), organizationName: activeUser.organization, voxelSize: linkedDatasets.slice(-1)[0].dataSource.scale, - layers: withTransforms(layersWithoutTransforms), + layers: layersWithTransforms, }); const uniqueDatasets = _.uniqBy( @@ -151,11 +196,11 @@ export function ConfigureNewDataset(props: WizardComponentProps) { datasetMarkdownLinks, "", "The layers were combined " + - (wizardContext.sourcePoints.length === 0 + (sourcePoints.length === 0 ? "without any transforms" : `with ${ useThinPlateSplines - ? `Thin-Plate-Splines (${wizardContext.sourcePoints.length} correspondences)` + ? `Thin-Plate-Splines (${sourcePoints.length} correspondences)` : `an affine transformation (mean error: ${formatNumber( affineMeanError.meanError, )} vx)` @@ -175,7 +220,7 @@ export function ConfigureNewDataset(props: WizardComponentProps) { // Using Forms here only to validate fields and for easy layout

Please configure the dataset that is about to be created.

-
+ guardedWithErrorToast(handleSubmit)}> diff --git a/frontend/javascripts/oxalis/model/helpers/transformation_helpers.ts b/frontend/javascripts/oxalis/model/helpers/transformation_helpers.ts index 08b63097c89..8b9b081f83e 100644 --- a/frontend/javascripts/oxalis/model/helpers/transformation_helpers.ts +++ b/frontend/javascripts/oxalis/model/helpers/transformation_helpers.ts @@ -44,6 +44,14 @@ export function createAffineTransform(source: Vector3[], target: Vector3[]): Tra }; } +export function checkLandmarksForThinPlateSpline(source: Vector3[], target: Vector3[]) { + // Strictly speaking, the TPS transform is not needed here, because it will + // be created when the actual dataset is opened. However, if the landmarks + // cannot be loaded into a TPS (e.g., because the landmarks are planar and + // affine estimation will crash), we want to detect this here automatically. + createThinPlateSplineTransform(source, target, [1, 1, 1]); +} + export function createThinPlateSplineTransform( source: Vector3[], target: Vector3[], diff --git a/frontend/javascripts/oxalis/model/sagas/saga_helpers.ts b/frontend/javascripts/oxalis/model/sagas/saga_helpers.ts index bc5572b4d74..7586b3ecc93 100644 --- a/frontend/javascripts/oxalis/model/sagas/saga_helpers.ts +++ b/frontend/javascripts/oxalis/model/sagas/saga_helpers.ts @@ -78,7 +78,7 @@ export function askUserForLockingActiveMapping( cancelText: "Abort Annotation Action", width: 600, onOk: lockMapping, - onCancel: async () => { + onCancel: () => { reject({ isMappingLockedIfNeeded: false, reason: "User aborted." }); }, }); From 173e0935ee298cb1685fbcd36232bb57c035a17f Mon Sep 17 00:00:00 2001 From: Philipp Otto Date: Wed, 21 Aug 2024 08:56:43 +0200 Subject: [PATCH 15/19] move allowThinPlateSplines into wk dev flags --- .../composition_wizard/04_configure_new_dataset.tsx | 13 +++++++------ frontend/javascripts/oxalis/api/wk_dev.ts | 3 +++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx b/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx index 5bc025f35d3..0a97c87b237 100644 --- a/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx +++ b/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx @@ -32,9 +32,9 @@ import { useEffectOnlyOnce } from "libs/react_hooks"; import { formatNumber } from "libs/format_utils"; import { checkLandmarksForThinPlateSpline } from "oxalis/model/helpers/transformation_helpers"; import { Vector3 } from "oxalis/constants"; +import { WkDevFlags } from "oxalis/api/wk_dev"; const FormItem = Form.Item; -const ALLOW_TPS_IN_COMPOSITION = false; async function guardedWithErrorToast(fn: () => Promise) { try { @@ -282,11 +282,12 @@ export function ConfigureNewDataset(props: WizardComponentProps) { ); }} - {ALLOW_TPS_IN_COMPOSITION && wizardContext.sourcePoints.length > 0 && ( - - Use Thin-Plate-Splines (Experimental) - - )} + {WkDevFlags.datasetComposition.allowThinPlateSplines && + wizardContext.sourcePoints.length > 0 && ( + + Use Thin-Plate-Splines (Experimental) + + )} Date: Wed, 21 Aug 2024 17:04:03 +0200 Subject: [PATCH 16/19] Update frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx Co-authored-by: MichaelBuessemeyer <39529669+MichaelBuessemeyer@users.noreply.github.com> --- .../dataset/composition_wizard/04_configure_new_dataset.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx b/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx index 0a97c87b237..b609bfba78b 100644 --- a/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx +++ b/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx @@ -40,7 +40,7 @@ async function guardedWithErrorToast(fn: () => Promise) { try { await fn(); } catch (error) { - Toast.error("An error unexpected occurred. Please check the console for details"); + Toast.error("An unexpected error occurred. Please check the console for details"); console.error(error); ErrorHandling.notify(error as Error); } From ff5415b20deff2ba5803b4f01b5eb044dab5ee08 Mon Sep 17 00:00:00 2001 From: Philipp Otto Date: Wed, 21 Aug 2024 17:05:33 +0200 Subject: [PATCH 17/19] move guardedWithErrorToast fn --- .../04_configure_new_dataset.tsx | 17 +++++------------ frontend/javascripts/libs/utils.ts | 12 ++++++++++++ 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx b/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx index 0a97c87b237..760387ecb3a 100644 --- a/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx +++ b/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx @@ -25,7 +25,6 @@ import { areDatasetsIdentical, LayerLink, } from "types/api_flow_types"; -import ErrorHandling from "libs/error_handling"; import { syncValidator } from "types/validation"; import { WizardComponentProps } from "./common"; import { useEffectOnlyOnce } from "libs/react_hooks"; @@ -36,16 +35,6 @@ import { WkDevFlags } from "oxalis/api/wk_dev"; const FormItem = Form.Item; -async function guardedWithErrorToast(fn: () => Promise) { - try { - await fn(); - } catch (error) { - Toast.error("An error unexpected occurred. Please check the console for details"); - console.error(error); - ErrorHandling.notify(error as Error); - } -} - export function ConfigureNewDataset(props: WizardComponentProps) { const formRef = React.useRef>(null); @@ -220,7 +209,11 @@ export function ConfigureNewDataset(props: WizardComponentProps) { // Using Forms here only to validate fields and for easy layout

Please configure the dataset that is about to be created.

- guardedWithErrorToast(handleSubmit)}> + Utils.guardedWithErrorToast(handleSubmit)} + > diff --git a/frontend/javascripts/libs/utils.ts b/frontend/javascripts/libs/utils.ts index dd146d2277e..bee78ba2633 100644 --- a/frontend/javascripts/libs/utils.ts +++ b/frontend/javascripts/libs/utils.ts @@ -16,6 +16,8 @@ import type { import window, { document, location } from "libs/window"; import { ArbitraryObject, Comparator } from "types/globals"; import dayjs from "dayjs"; +import Toast from "./toast"; +import ErrorHandling from "libs/error_handling"; type UrlParams = Record; // Fix JS modulo bug @@ -1300,3 +1302,13 @@ export function generateRandomId(length: number) { } return result; } + +export async function guardedWithErrorToast(fn: () => Promise) { + try { + await fn(); + } catch (error) { + Toast.error("An error unexpected occurred. Please check the console for details"); + console.error(error); + ErrorHandling.notify(error as Error); + } +} From 6bee6c5ffd84a5d3ff1b7dee252048bab4038550 Mon Sep 17 00:00:00 2001 From: Philipp Otto Date: Wed, 21 Aug 2024 19:30:07 +0200 Subject: [PATCH 18/19] format --- .../04_configure_new_dataset.tsx | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx b/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx index c9a32333e7c..760387ecb3a 100644 --- a/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx +++ b/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx @@ -100,15 +100,15 @@ export function ConfigureNewDataset(props: WizardComponentProps) { const transformationArr = [ useThinPlateSplines ? { - type: "thin_plate_spline" as const, - correspondences: { source: sourcePoints, target: targetPoints }, - } + type: "thin_plate_spline" as const, + correspondences: { source: sourcePoints, target: targetPoints }, + } : { - type: "affine" as const, - matrix: flatToNestedMatrix( - estimateAffineMatrix4x4(sourcePoints, targetPoints, affineMeanError), - ), - }, + type: "affine" as const, + matrix: flatToNestedMatrix( + estimateAffineMatrix4x4(sourcePoints, targetPoints, affineMeanError), + ), + }, ]; if (useThinPlateSplines) { checkLandmarksForThinPlateSpline(sourcePoints, targetPoints); @@ -185,15 +185,16 @@ export function ConfigureNewDataset(props: WizardComponentProps) { datasetMarkdownLinks, "", "The layers were combined " + - (sourcePoints.length === 0 - ? "without any transforms" - : `with ${useThinPlateSplines - ? `Thin-Plate-Splines (${sourcePoints.length} correspondences)` - : `an affine transformation (mean error: ${formatNumber( - affineMeanError.meanError, - )} vx)` - }`) + - ".", + (sourcePoints.length === 0 + ? "without any transforms" + : `with ${ + useThinPlateSplines + ? `Thin-Plate-Splines (${sourcePoints.length} correspondences)` + : `an affine transformation (mean error: ${formatNumber( + affineMeanError.meanError, + )} vx)` + }`) + + ".", ].join("\n"), }, ); From 4bab1a9fba755e62d25e12be69b8feac18bde9dc Mon Sep 17 00:00:00 2001 From: Philipp Otto Date: Thu, 22 Aug 2024 09:26:02 +0200 Subject: [PATCH 19/19] fix cyclic dependency --- .../composition_wizard/04_configure_new_dataset.tsx | 8 ++------ frontend/javascripts/libs/toast.tsx | 13 +++++++++++++ frontend/javascripts/libs/utils.ts | 12 ------------ 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx b/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx index 760387ecb3a..3f6689a6a74 100644 --- a/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx +++ b/frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx @@ -9,7 +9,7 @@ import { Button, Checkbox, Col, Form, FormInstance, Input, List, Modal, Row, Too import { FormItemWithInfo } from "dashboard/dataset/helper_components"; import FolderSelection from "dashboard/folders/folder_selection"; import { estimateAffineMatrix4x4 } from "libs/estimate_affine"; -import Toast from "libs/toast"; +import Toast, { guardedWithErrorToast } from "libs/toast"; import * as Utils from "libs/utils"; import _ from "lodash"; import messages from "messages"; @@ -209,11 +209,7 @@ export function ConfigureNewDataset(props: WizardComponentProps) { // Using Forms here only to validate fields and for easy layout

Please configure the dataset that is about to be created.

- Utils.guardedWithErrorToast(handleSubmit)} - > + guardedWithErrorToast(handleSubmit)}> diff --git a/frontend/javascripts/libs/toast.tsx b/frontend/javascripts/libs/toast.tsx index 4afbc5664c1..e8e210397a1 100644 --- a/frontend/javascripts/libs/toast.tsx +++ b/frontend/javascripts/libs/toast.tsx @@ -35,6 +35,19 @@ type ToastParams = { details?: string; }; +export async function guardedWithErrorToast(fn: () => Promise) { + try { + await fn(); + } catch (error) { + import("libs/error_handling").then((_ErrorHandling) => { + const ErrorHandling = _ErrorHandling.default; + Toast.error("An unexpected error occurred. Please check the console for details"); + console.error(error); + ErrorHandling.notify(error as Error); + }); + } +} + const Toast = { // The notificationAPI is designed to be a singleton spawned by the ToastContextMountRoot // mounted in the GlobalThemeProvider. diff --git a/frontend/javascripts/libs/utils.ts b/frontend/javascripts/libs/utils.ts index 0128578f401..dd146d2277e 100644 --- a/frontend/javascripts/libs/utils.ts +++ b/frontend/javascripts/libs/utils.ts @@ -16,8 +16,6 @@ import type { import window, { document, location } from "libs/window"; import { ArbitraryObject, Comparator } from "types/globals"; import dayjs from "dayjs"; -import Toast from "./toast"; -import ErrorHandling from "libs/error_handling"; type UrlParams = Record; // Fix JS modulo bug @@ -1302,13 +1300,3 @@ export function generateRandomId(length: number) { } return result; } - -export async function guardedWithErrorToast(fn: () => Promise) { - try { - await fn(); - } catch (error) { - Toast.error("An unexpected error occurred. Please check the console for details"); - console.error(error); - ErrorHandling.notify(error as Error); - } -}