diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index a6c37f2e20..e3b5b58a71 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -11,7 +11,6 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released [Commits](https://github.com/scalableminds/webknossos/compare/25.01.0...HEAD) ### Added -- Added the possibility to configure a rotation for a dataset, which can be toggled off and on when viewing and annotating data. [#8159](https://github.com/scalableminds/webknossos/pull/8159) ### Changed 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 2c8d4c0891..75a75f551b 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,8 +28,7 @@ import _ from "lodash"; import messages from "messages"; import { WkDevFlags } from "oxalis/api/wk_dev"; import type { Vector3 } from "oxalis/constants"; -import { getReadableURLPart } from "oxalis/model/accessors/dataset_accessor"; -import { flatToNestedMatrix } from "oxalis/model/accessors/dataset_layer_transformation_accessor"; +import { flatToNestedMatrix, getReadableURLPart } from "oxalis/model/accessors/dataset_accessor"; import { checkLandmarksForThinPlateSpline } from "oxalis/model/helpers/transformation_helpers"; import type { OxalisState } from "oxalis/store"; import React, { useState } from "react"; diff --git a/frontend/javascripts/dashboard/dataset/dataset_rotation_form_item.tsx b/frontend/javascripts/dashboard/dataset/dataset_rotation_form_item.tsx deleted file mode 100644 index 0db527adda..0000000000 --- a/frontend/javascripts/dashboard/dataset/dataset_rotation_form_item.tsx +++ /dev/null @@ -1,191 +0,0 @@ -import { InfoCircleOutlined } from "@ant-design/icons"; -import { Col, Form, type FormInstance, InputNumber, Row, Slider, Tooltip, Typography } from "antd"; -import FormItem from "antd/es/form/FormItem"; -import { - AXIS_TO_TRANSFORM_INDEX, - EXPECTED_TRANSFORMATION_LENGTH, - IDENTITY_TRANSFORM, - doAllLayersHaveTheSameRotation, - fromCenterToOrigin, - fromOriginToCenter, - getRotationMatrixAroundAxis, -} from "oxalis/model/accessors/dataset_layer_transformation_accessor"; -import BoundingBox from "oxalis/model/bucket_data_handling/bounding_box"; -import { useCallback, useEffect, useMemo } from "react"; -import type { APIDataLayer } from "types/api_flow_types"; -import { FormItemWithInfo } from "./helper_components"; - -const { Text } = Typography; - -type AxisRotationFormItemProps = { - form: FormInstance | undefined; - axis: "x" | "y" | "z"; -}; - -function getDatasetBoundingBoxFromLayers(layers: APIDataLayer[]): BoundingBox | undefined { - if (!layers || layers.length === 0) { - return undefined; - } - let datasetBoundingBox = BoundingBox.fromBoundBoxObject(layers[0].boundingBox); - for (let i = 1; i < layers.length; i++) { - datasetBoundingBox = datasetBoundingBox.extend( - BoundingBox.fromBoundBoxObject(layers[i].boundingBox), - ); - } - return datasetBoundingBox; -} - -export const AxisRotationFormItem: React.FC = ({ - form, - axis, -}: AxisRotationFormItemProps) => { - const dataLayers: APIDataLayer[] = Form.useWatch(["dataSource", "dataLayers"], form); - const datasetBoundingBox = useMemo( - () => getDatasetBoundingBoxFromLayers(dataLayers), - [dataLayers], - ); - // Update the transformations in case the user changes the dataset bounding box. - useEffect(() => { - if ( - datasetBoundingBox == null || - dataLayers[0].coordinateTransformations?.length !== EXPECTED_TRANSFORMATION_LENGTH || - !form - ) { - return; - } - const rotationValues = form.getFieldValue(["datasetRotation"]); - const transformations = [ - fromCenterToOrigin(datasetBoundingBox), - getRotationMatrixAroundAxis("x", rotationValues["x"]), - getRotationMatrixAroundAxis("y", rotationValues["y"]), - getRotationMatrixAroundAxis("z", rotationValues["z"]), - fromOriginToCenter(datasetBoundingBox), - ]; - const dataLayersWithUpdatedTransforms = dataLayers.map((layer) => { - return { - ...layer, - coordinateTransformations: transformations, - }; - }); - form.setFieldValue(["dataSource", "dataLayers"], dataLayersWithUpdatedTransforms); - }, [datasetBoundingBox, dataLayers, form]); - - const setMatrixRotationsForAllLayer = useCallback( - (rotationInDegrees: number): void => { - if (!form) { - return; - } - const dataLayers: APIDataLayer[] = form.getFieldValue(["dataSource", "dataLayers"]); - const datasetBoundingBox = getDatasetBoundingBoxFromLayers(dataLayers); - if (datasetBoundingBox == null) { - return; - } - - const rotationInRadians = rotationInDegrees * (Math.PI / 180); - const rotationMatrix = getRotationMatrixAroundAxis(axis, rotationInRadians); - const dataLayersWithUpdatedTransforms: APIDataLayer[] = dataLayers.map((layer) => { - let transformations = layer.coordinateTransformations; - if (transformations == null || transformations.length !== EXPECTED_TRANSFORMATION_LENGTH) { - transformations = [ - fromCenterToOrigin(datasetBoundingBox), - IDENTITY_TRANSFORM, - IDENTITY_TRANSFORM, - IDENTITY_TRANSFORM, - fromOriginToCenter(datasetBoundingBox), - ]; - } - transformations[AXIS_TO_TRANSFORM_INDEX[axis]] = rotationMatrix; - return { - ...layer, - coordinateTransformations: transformations, - }; - }); - form.setFieldValue(["dataSource", "dataLayers"], dataLayersWithUpdatedTransforms); - }, - [axis, form], - ); - return ( - - - - - - - - - - // InputNumber might be called with null, so we need to check for that. - value != null && setMatrixRotationsForAllLayer(value) - } - /> - - - - ); -}; - -type AxisRotationSettingForDatasetProps = { - form: FormInstance | undefined; -}; - -export type DatasetRotation = { - x: number; - y: number; - z: number; -}; - -export const AxisRotationSettingForDataset: React.FC = ({ - form, -}: AxisRotationSettingForDatasetProps) => { - const dataLayers: APIDataLayer[] = form?.getFieldValue(["dataSource", "dataLayers"]); - const isRotationOnly = useMemo(() => doAllLayersHaveTheSameRotation(dataLayers), [dataLayers]); - - if (!isRotationOnly) { - return ( - - Each layers transformations must be equal and each layer needs exactly 5 affine - transformation with the following schema: -
    -
  • Translation to the origin
  • -
  • Rotation around the x-axis
  • -
  • Rotation around the y-axis
  • -
  • Rotation around the z-axis
  • -
  • Translation back to the original position
  • -
- To easily enable this setting, delete all coordinateTransformations of all layers in the - advanced tab, save and reload the dataset settings. - - } - > - - Setting a dataset's rotation is only supported when all layers have the same rotation - transformation. - -
- ); - } - - return ( -
- - - -
- ); -}; diff --git a/frontend/javascripts/dashboard/dataset/dataset_settings_data_tab.tsx b/frontend/javascripts/dashboard/dataset/dataset_settings_data_tab.tsx index 1de2617722..36e6bc08ba 100644 --- a/frontend/javascripts/dashboard/dataset/dataset_settings_data_tab.tsx +++ b/frontend/javascripts/dashboard/dataset/dataset_settings_data_tab.tsx @@ -34,7 +34,6 @@ import { type APIDataLayer, type APIDataset, APIJobType } from "types/api_flow_t import type { ArbitraryObject } from "types/globals"; import type { DataLayer } from "types/schemas/datasource.types"; import { isValidJSON, syncValidator, validateDatasourceJSON } from "types/validation"; -import { AxisRotationSettingForDataset } from "./dataset_rotation_form_item"; const FormItem = Form.Item; @@ -268,12 +267,6 @@ function SimpleDatasetForm({ - - - - - - diff --git a/frontend/javascripts/dashboard/dataset/dataset_settings_view.tsx b/frontend/javascripts/dashboard/dataset/dataset_settings_view.tsx index 52dc630e0a..890aeae8ca 100644 --- a/frontend/javascripts/dashboard/dataset/dataset_settings_view.tsx +++ b/frontend/javascripts/dashboard/dataset/dataset_settings_view.tsx @@ -25,11 +25,6 @@ import _ from "lodash"; import messages from "messages"; import { Unicode } from "oxalis/constants"; import { getReadableURLPart } from "oxalis/model/accessors/dataset_accessor"; -import { - EXPECTED_TRANSFORMATION_LENGTH, - doAllLayersHaveTheSameRotation, - getRotationFromTransformationIn90DegreeSteps, -} from "oxalis/model/accessors/dataset_layer_transformation_accessor"; import type { DatasetConfiguration, OxalisState } from "oxalis/store"; import * as React from "react"; import { connect } from "react-redux"; @@ -42,7 +37,6 @@ import type { MutableAPIDataset, } from "types/api_flow_types"; import { enforceValidatedDatasetViewConfiguration } from "types/schemas/dataset_view_configuration_defaults"; -import type { DatasetRotation } from "./dataset_rotation_form_item"; import DatasetSettingsDataTab, { syncDataSourceFields } from "./dataset_settings_data_tab"; import DatasetSettingsDeleteTab from "./dataset_settings_delete_tab"; import DatasetSettingsMetadataTab from "./dataset_settings_metadata_tab"; @@ -82,7 +76,6 @@ export type FormData = { dataset: APIDataset; defaultConfiguration: DatasetConfiguration; defaultConfigurationLayersJson: string; - datasetRotation?: DatasetRotation; }; class DatasetSettingsView extends React.PureComponent { @@ -201,32 +194,6 @@ class DatasetSettingsView extends React.PureComponent diff --git a/frontend/javascripts/libs/mjs.ts b/frontend/javascripts/libs/mjs.ts index 679d5fbe9b..4588fc10f6 100644 --- a/frontend/javascripts/libs/mjs.ts +++ b/frontend/javascripts/libs/mjs.ts @@ -220,10 +220,6 @@ const M4x4 = { r[2] = m[14]; return r; }, - - identity(): Matrix4x4 { - return BareM4x4.identity; - }, }; const V2 = { diff --git a/frontend/javascripts/oxalis/api/api_latest.ts b/frontend/javascripts/oxalis/api/api_latest.ts index f4cf84ddea..d5a685be74 100644 --- a/frontend/javascripts/oxalis/api/api_latest.ts +++ b/frontend/javascripts/oxalis/api/api_latest.ts @@ -45,13 +45,13 @@ import { import UrlManager from "oxalis/controller/url_manager"; import type { OxalisModel } from "oxalis/model"; import { + flatToNestedMatrix, getLayerBoundingBox, getLayerByName, getMagInfo, getMappingInfo, getVisibleSegmentationLayer, } from "oxalis/model/accessors/dataset_accessor"; -import { flatToNestedMatrix } from "oxalis/model/accessors/dataset_layer_transformation_accessor"; import { getActiveMagIndexForLayer, getPosition, diff --git a/frontend/javascripts/oxalis/constants.ts b/frontend/javascripts/oxalis/constants.ts index 1630557c41..14c1715ac5 100644 --- a/frontend/javascripts/oxalis/constants.ts +++ b/frontend/javascripts/oxalis/constants.ts @@ -15,8 +15,6 @@ export type Vector4 = [number, number, number, number]; export type Vector5 = [number, number, number, number, number]; export type Vector6 = [number, number, number, number, number, number]; -export type NestedMatrix4 = [Vector4, Vector4, Vector4, Vector4]; // Represents a row major matrix. - // For 3D data BucketAddress = x, y, z, mag // For higher dimensional data, BucketAddress = x, y, z, mag, [{name: "t", value: t}, ...] export type BucketAddress = diff --git a/frontend/javascripts/oxalis/controller/combinations/volume_handlers.ts b/frontend/javascripts/oxalis/controller/combinations/volume_handlers.ts index fd28b7d2a9..7abd4d4aac 100644 --- a/frontend/javascripts/oxalis/controller/combinations/volume_handlers.ts +++ b/frontend/javascripts/oxalis/controller/combinations/volume_handlers.ts @@ -2,7 +2,6 @@ import { V3 } from "libs/mjs"; import memoizeOne from "memoize-one"; import type { OrthoView, Point2, Vector3 } from "oxalis/constants"; import { ContourModeEnum } from "oxalis/constants"; -import { globalToLayerTransformedPosition } from "oxalis/model/accessors/dataset_layer_transformation_accessor"; import { calculateGlobalPos } from "oxalis/model/accessors/view_mode_accessor"; import { updateUserSettingAction } from "oxalis/model/actions/settings_actions"; import { @@ -52,21 +51,15 @@ export const getSegmentIdForPosition = memoizeOne( if (!layer) { return 0; } - const posInLayerSpace = globalToLayerTransformedPosition( - globalPos, - layer.name, - "segmentation", - Store.getState(), - ); const segmentationCube = layer.cube; const segmentationLayerName = layer.name; const renderedZoomStepForCameraPosition = api.data.getRenderedZoomStepAtPosition( segmentationLayerName, - posInLayerSpace, + globalPos, ); return segmentationCube.getMappedDataValue( - posInLayerSpace, + globalPos, additionalCoordinates, renderedZoomStepForCameraPosition, ); @@ -83,28 +76,22 @@ export async function getSegmentIdForPositionAsync(globalPos: Vector3) { if (!layer) { return 0; } - const posInLayerSpace = globalToLayerTransformedPosition( - globalPos, - layer.name, - "segmentation", - Store.getState(), - ); const segmentationCube = layer.cube; const segmentationLayerName = layer.name; const renderedZoomStepForCameraPosition = await api.data.getUltimatelyRenderedZoomStepAtPosition( segmentationLayerName, - posInLayerSpace, + globalPos, ); // Make sure the corresponding bucket is loaded await api.data.getDataValue( segmentationLayerName, - posInLayerSpace, + globalPos, renderedZoomStepForCameraPosition, additionalCoordinates, ); return segmentationCube.getMappedDataValue( - posInLayerSpace, + globalPos, additionalCoordinates, renderedZoomStepForCameraPosition, ); diff --git a/frontend/javascripts/oxalis/controller/scene_controller.ts b/frontend/javascripts/oxalis/controller/scene_controller.ts index c0c4fada66..2bbdc04bad 100644 --- a/frontend/javascripts/oxalis/controller/scene_controller.ts +++ b/frontend/javascripts/oxalis/controller/scene_controller.ts @@ -32,8 +32,8 @@ import { getDatasetBoundingBox, getLayerBoundingBox, getLayerNameToIsDisabled, + getTransformsForLayerOrNull, } from "oxalis/model/accessors/dataset_accessor"; -import { getTransformsForLayerOrNull } from "oxalis/model/accessors/dataset_layer_transformation_accessor"; import { getActiveMagIndicesForLayers, getPosition } from "oxalis/model/accessors/flycam_accessor"; import { getSkeletonTracing } from "oxalis/model/accessors/skeletontracing_accessor"; import { getSomeTracing } from "oxalis/model/accessors/tracing_accessor"; diff --git a/frontend/javascripts/oxalis/geometries/materials/edge_shader.ts b/frontend/javascripts/oxalis/geometries/materials/edge_shader.ts index c880418650..ae6b471d83 100644 --- a/frontend/javascripts/oxalis/geometries/materials/edge_shader.ts +++ b/frontend/javascripts/oxalis/geometries/materials/edge_shader.ts @@ -3,7 +3,7 @@ import type TPS3D from "libs/thin_plate_spline"; import _ from "lodash"; import { COLOR_TEXTURE_WIDTH_FIXED } from "oxalis/geometries/materials/node_shader"; import type { Uniforms } from "oxalis/geometries/materials/plane_material_factory"; -import { getTransformsForSkeletonLayer } from "oxalis/model/accessors/dataset_layer_transformation_accessor"; +import { getTransformsForSkeletonLayer } from "oxalis/model/accessors/dataset_accessor"; import { listenToStoreProperty } from "oxalis/model/helpers/listener_helpers"; import shaderEditor from "oxalis/model/helpers/shader_editor"; import { diff --git a/frontend/javascripts/oxalis/geometries/materials/node_shader.ts b/frontend/javascripts/oxalis/geometries/materials/node_shader.ts index 2ed9af62d1..8364bdcf1f 100644 --- a/frontend/javascripts/oxalis/geometries/materials/node_shader.ts +++ b/frontend/javascripts/oxalis/geometries/materials/node_shader.ts @@ -3,7 +3,7 @@ import type TPS3D from "libs/thin_plate_spline"; import _ from "lodash"; import { ViewModeValues, ViewModeValuesIndices } from "oxalis/constants"; import type { Uniforms } from "oxalis/geometries/materials/plane_material_factory"; -import { getTransformsForSkeletonLayer } from "oxalis/model/accessors/dataset_layer_transformation_accessor"; +import { getTransformsForSkeletonLayer } from "oxalis/model/accessors/dataset_accessor"; import { getZoomValue } from "oxalis/model/accessors/flycam_accessor"; import { listenToStoreProperty } from "oxalis/model/helpers/listener_helpers"; import shaderEditor from "oxalis/model/helpers/shader_editor"; diff --git a/frontend/javascripts/oxalis/geometries/materials/plane_material_factory.ts b/frontend/javascripts/oxalis/geometries/materials/plane_material_factory.ts index cb4f551646..5d79b46e7e 100644 --- a/frontend/javascripts/oxalis/geometries/materials/plane_material_factory.ts +++ b/frontend/javascripts/oxalis/geometries/materials/plane_material_factory.ts @@ -24,13 +24,11 @@ import { getMagInfoByLayer, getMappingInfoForSupportedLayer, getSegmentationLayerWithMappingSupport, - getVisibleSegmentationLayer, -} from "oxalis/model/accessors/dataset_accessor"; -import { getTransformsForLayer, getTransformsPerLayer, + getVisibleSegmentationLayer, invertAndTranspose, -} from "oxalis/model/accessors/dataset_layer_transformation_accessor"; +} from "oxalis/model/accessors/dataset_accessor"; import { getActiveMagIndicesForLayers, getUnrenderableLayerInfosForCurrentZoom, @@ -247,7 +245,8 @@ class PlaneMaterialFactory { this.uniforms.activeMagIndices = { value: Object.values(activeMagIndices), }; - const { nativelyRenderedLayerName } = Store.getState().datasetConfiguration; + const nativelyRenderedLayerName = + Store.getState().datasetConfiguration.nativelyRenderedLayerName; const dataset = Store.getState().dataset; for (const dataLayer of Model.getAllLayers()) { const layerName = sanitizeName(dataLayer.name); diff --git a/frontend/javascripts/oxalis/merger_mode.ts b/frontend/javascripts/oxalis/merger_mode.ts index 0761e4ce31..164a103378 100644 --- a/frontend/javascripts/oxalis/merger_mode.ts +++ b/frontend/javascripts/oxalis/merger_mode.ts @@ -2,8 +2,10 @@ import _ from "lodash"; import messages from "messages"; import type { UnregisterHandler } from "oxalis/api/api_latest"; import type { Vector3 } from "oxalis/constants"; -import { getVisibleSegmentationLayer } from "oxalis/model/accessors/dataset_accessor"; -import { getInverseSegmentationTransformer } from "oxalis/model/accessors/dataset_layer_transformation_accessor"; +import { + getInverseSegmentationTransformer, + getVisibleSegmentationLayer, +} from "oxalis/model/accessors/dataset_accessor"; import { getNodePosition, getSkeletonTracing, diff --git a/frontend/javascripts/oxalis/model.ts b/frontend/javascripts/oxalis/model.ts index a04e1b7db7..e014317803 100644 --- a/frontend/javascripts/oxalis/model.ts +++ b/frontend/javascripts/oxalis/model.ts @@ -21,8 +21,6 @@ import Store from "oxalis/store"; import type { Versions } from "oxalis/view/version_view"; import type { APICompoundType } from "types/api_flow_types"; -import { globalToLayerTransformedPosition } from "./model/accessors/dataset_layer_transformation_accessor"; -import { invertTransform, transformPointUnscaled } from "./model/helpers/transformation_helpers"; import { initialize } from "./model_initialization"; // TODO: Non-reactive @@ -226,15 +224,8 @@ export class OxalisModel { ); const getIdForPos = (pos: Vector3, usableZoomStep: number) => { - const state = Store.getState(); - const additionalCoordinates = state.flycam.additionalCoordinates; - const posInLayerSpace = globalToLayerTransformedPosition( - pos, - segmentationLayer.name, - "segmentation", - state, - ); - const id = cube.getDataValue(posInLayerSpace, additionalCoordinates, null, usableZoomStep); + const additionalCoordinates = Store.getState().flycam.additionalCoordinates; + const id = cube.getDataValue(pos, additionalCoordinates, null, usableZoomStep); return { // Note that this id can be an unmapped id even when // a mapping is active, if it is a HDF5 mapping that is partially loaded diff --git a/frontend/javascripts/oxalis/model/accessors/dataset_accessor.ts b/frontend/javascripts/oxalis/model/accessors/dataset_accessor.ts index 802e2b3e38..d5fc3a89b8 100644 --- a/frontend/javascripts/oxalis/model/accessors/dataset_accessor.ts +++ b/frontend/javascripts/oxalis/model/accessors/dataset_accessor.ts @@ -1,11 +1,18 @@ import ErrorHandling from "libs/error_handling"; import { formatExtentInUnitWithLength, formatNumberToLength } from "libs/format_utils"; -import { V3 } from "libs/mjs"; +import { M4x4, type Matrix4x4, V3 } from "libs/mjs"; +import MultiKeyMap from "libs/multi_key_map"; import { aggregateBoundingBox, maxValue } from "libs/utils"; import _ from "lodash"; import memoizeOne from "memoize-one"; import messages from "messages"; -import { LongUnitToShortUnitMap, type Vector3, type ViewMode } from "oxalis/constants"; +import { + IdentityTransform, + LongUnitToShortUnitMap, + type Vector3, + type Vector4, + type ViewMode, +} from "oxalis/constants"; import constants, { ViewModeValues, Vector3Indicies, MappingStatusEnum } from "oxalis/constants"; import type { ActiveMappingInfo, @@ -22,12 +29,21 @@ import type { APIDatasetCompact, APIMaybeUnimportedDataset, APISegmentationLayer, + APISkeletonLayer, AdditionalAxis, ElementClass, } from "types/api_flow_types"; import type { DataLayer } from "types/schemas/datasource.types"; import BoundingBox from "../bucket_data_handling/bounding_box"; import { MagInfo, convertToDenseMag } from "../helpers/mag_info"; +import { + type Transform, + chainTransforms, + createAffineTransformFromMatrix, + createThinPlateSplineTransform, + invertTransform, + transformPointUnscaled, +} from "../helpers/transformation_helpers"; function _getMagInfo(magnifications: Array): MagInfo { return new MagInfo(magnifications); @@ -699,6 +715,188 @@ export function getMappingInfoForSupportedLayer(state: OxalisState): ActiveMappi ); } +// Returns the transforms (if they exist) for a layer as +// they are defined in the dataset properties. +function _getOriginalTransformsForLayerOrNull( + dataset: APIDataset, + layer: APIDataLayer, +): Transform | null { + const coordinateTransformations = layer.coordinateTransformations; + if (!coordinateTransformations || coordinateTransformations.length === 0) { + return null; + } + if (coordinateTransformations.length > 1) { + console.error( + "Data layer has defined multiple coordinate transforms. This is currently not supported and ignored", + ); + return null; + } + const transformation = coordinateTransformations[0]; + const { type } = transformation; + + if (type === "affine") { + const nestedMatrix = transformation.matrix; + return createAffineTransformFromMatrix(nestedMatrix); + } else if (type === "thin_plate_spline") { + const { source, target } = transformation.correspondences; + + return createThinPlateSplineTransform(source, target, dataset.dataSource.scale.factor); + } + + console.error( + "Data layer has defined a coordinate transform that is not affine or thin_plate_spline. This is currently not supported and ignored", + ); + return null; +} + +function _getTransformsForLayerOrNull( + dataset: APIDataset, + layer: APIDataLayer | APISkeletonLayer, + nativelyRenderedLayerName: string | null, +): Transform | null { + if (layer.category === "skeleton") { + return getTransformsForSkeletonLayerOrNull(dataset, nativelyRenderedLayerName); + } + const layerTransforms = _getOriginalTransformsForLayerOrNull(dataset, layer); + + if (nativelyRenderedLayerName == null) { + // No layer is requested to be rendered natively. Just use the transforms + // as they are in the dataset. + return layerTransforms; + } + + if (nativelyRenderedLayerName === layer.name) { + // This layer should be rendered without any transforms. + return null; + } + + // Apply the inverse of the layer that should be rendered natively + // to the current layers transforms + const nativeLayer = getLayerByName(dataset, nativelyRenderedLayerName, true); + + const transformsOfNativeLayer = _getOriginalTransformsForLayerOrNull(dataset, nativeLayer); + + if (transformsOfNativeLayer == null) { + // The inverse of no transforms, are no transforms. Leave the layer + // transforms untouched. + return layerTransforms; + } + + const inverseNativeTransforms = invertTransform(transformsOfNativeLayer); + return chainTransforms(layerTransforms, inverseNativeTransforms); +} + +function memoizeWithThreeKeys(fn: (a: A, b: B, c: C) => T) { + const map = new MultiKeyMap(); + return (a: A, b: B, c: C): T => { + let res = map.get([a, b, c]); + if (res === undefined) { + res = fn(a, b, c); + map.set([a, b, c], res); + } + return res; + }; +} + +export const getTransformsForLayerOrNull = memoizeWithThreeKeys(_getTransformsForLayerOrNull); +export function getTransformsForLayer( + dataset: APIDataset, + layer: APIDataLayer | APISkeletonLayer, + nativelyRenderedLayerName: string | null, +): Transform { + return ( + getTransformsForLayerOrNull(dataset, layer, nativelyRenderedLayerName || null) || + IdentityTransform + ); +} + +function _getTransformsForSkeletonLayerOrNull( + dataset: APIDataset, + nativelyRenderedLayerName: string | null, +): Transform | null { + if (nativelyRenderedLayerName == null) { + // No layer is requested to be rendered natively. We can use + // each layer's transforms as is. The skeleton layer doesn't have + // a transforms property currently, which is why we return null. + return null; + } + + // Compute the inverse of the layer that should be rendered natively + const nativeLayer = getLayerByName(dataset, nativelyRenderedLayerName, true); + const transformsOfNativeLayer = _getOriginalTransformsForLayerOrNull(dataset, nativeLayer); + + if (transformsOfNativeLayer == null) { + // The inverse of no transforms, are no transforms + return null; + } + + return invertTransform(transformsOfNativeLayer); +} + +export const getTransformsForSkeletonLayerOrNull = memoizeOne(_getTransformsForSkeletonLayerOrNull); + +export function getTransformsForSkeletonLayer( + dataset: APIDataset, + nativelyRenderedLayerName: string | null, +): Transform { + return ( + getTransformsForSkeletonLayerOrNull(dataset, nativelyRenderedLayerName || null) || + IdentityTransform + ); +} + +function _getTransformsPerLayer( + dataset: APIDataset, + nativelyRenderedLayerName: string | null, +): Record { + const transformsPerLayer: Record = {}; + const layers = dataset.dataSource.dataLayers; + for (const layer of layers) { + const transforms = getTransformsForLayer(dataset, layer, nativelyRenderedLayerName); + transformsPerLayer[layer.name] = transforms; + } + + return transformsPerLayer; +} + +export const getTransformsPerLayer = memoizeOne(_getTransformsPerLayer); + +export function getInverseSegmentationTransformer( + state: OxalisState, + segmentationLayerName: string, +) { + const { dataset } = state; + const { nativelyRenderedLayerName } = state.datasetConfiguration; + const layer = getLayerByName(dataset, segmentationLayerName); + const segmentationTransforms = getTransformsForLayer(dataset, layer, nativelyRenderedLayerName); + return transformPointUnscaled(invertTransform(segmentationTransforms)); +} + +export const hasDatasetTransforms = memoizeOne((dataset: APIDataset) => { + const layers = dataset.dataSource.dataLayers; + return layers.some((layer) => _getOriginalTransformsForLayerOrNull(dataset, layer) != null); +}); + +export function flatToNestedMatrix(matrix: Matrix4x4): [Vector4, Vector4, Vector4, Vector4] { + return [ + matrix.slice(0, 4) as Vector4, + matrix.slice(4, 8) as Vector4, + matrix.slice(8, 12) as Vector4, + matrix.slice(12, 16) as Vector4, + ]; +} + +// Transposition is often needed so that the matrix has the right format +// for matrix operations (e.g., on the GPU; but not for ThreeJS). +// Inversion is needed when the position of an "output voxel" (e.g., during +// rendering in the fragment shader) needs to be mapped to its original +// data position (i.e., how it's stored without the transformation). +// Without the inversion, the matrix maps from stored position to the position +// where it should be rendered. +export const invertAndTranspose = _.memoize((mat: Matrix4x4) => { + return M4x4.transpose(M4x4.inverse(mat)); +}); + export function getEffectiveIntensityRange( dataset: APIDataset, layerName: string, diff --git a/frontend/javascripts/oxalis/model/accessors/dataset_layer_transformation_accessor.ts b/frontend/javascripts/oxalis/model/accessors/dataset_layer_transformation_accessor.ts deleted file mode 100644 index 7890515a47..0000000000 --- a/frontend/javascripts/oxalis/model/accessors/dataset_layer_transformation_accessor.ts +++ /dev/null @@ -1,458 +0,0 @@ -import { M4x4, type Matrix4x4 } from "libs/mjs"; -import MultiKeyMap from "libs/multi_key_map"; -import { mod } from "libs/utils"; -import _ from "lodash"; -import memoizeOne from "memoize-one"; -import { - Identity4x4, - IdentityTransform, - type NestedMatrix4, - type Vector3, - type Vector4, -} from "oxalis/constants"; -import type { OxalisState } from "oxalis/store"; -import * as THREE from "three"; -import type { - APIDataLayer, - APIDataset, - APISkeletonLayer, - AffineTransformation, - CoordinateTransformation, -} from "types/api_flow_types"; -import type BoundingBox from "../bucket_data_handling/bounding_box"; -import { - type Transform, - chainTransforms, - createAffineTransformFromMatrix, - createThinPlateSplineTransform, - invertTransform, - nestedToFlatMatrix, - transformPointUnscaled, -} from "../helpers/transformation_helpers"; -import { getLayerByName } from "./dataset_accessor"; - -const IDENTITY_MATRIX = [ - [1, 0, 0, 0], - [0, 1, 0, 0], - [0, 0, 1, 0], - [0, 0, 0, 1], -] as NestedMatrix4; - -export const IDENTITY_TRANSFORM: CoordinateTransformation = { - type: "affine", - matrix: IDENTITY_MATRIX, -}; - -// cf. https://en.wikipedia.org/wiki/Rotation_matrix#In_three_dimensions -const sinusLocationOfRotationInMatrix = { - x: [2, 1], - y: [0, 2], - z: [1, 0], -}; - -const cosineLocationOfRotationInMatrix = { - x: [1, 1], - y: [0, 0], - z: [0, 0], -}; - -export const AXIS_TO_TRANSFORM_INDEX = { - x: 1, - y: 2, - z: 3, -}; - -export function flatToNestedMatrix(matrix: Matrix4x4): NestedMatrix4 { - return [ - matrix.slice(0, 4) as Vector4, - matrix.slice(4, 8) as Vector4, - matrix.slice(8, 12) as Vector4, - matrix.slice(12, 16) as Vector4, - ]; -} - -// This function extracts the rotation in 90 degree steps a the transformation matrix. -// The transformation matrix must only include a rotation around one of the main axis. -export function getRotationFromTransformationIn90DegreeSteps( - transformation: CoordinateTransformation | undefined, - axis: "x" | "y" | "z", -) { - if (transformation && transformation.type !== "affine") { - return 0; - } - const matrix = transformation ? transformation.matrix : IDENTITY_MATRIX; - const cosineLocation = cosineLocationOfRotationInMatrix[axis]; - const sinusLocation = sinusLocationOfRotationInMatrix[axis]; - const sinOfAngle = matrix[sinusLocation[0]][sinusLocation[1]]; - const cosOfAngle = matrix[cosineLocation[0]][cosineLocation[1]]; - const rotation = - Math.abs(cosOfAngle) > 1e-6 // Avoid division by zero - ? Math.atan2(sinOfAngle, cosOfAngle) - : sinOfAngle > 0 - ? Math.PI / 2 - : -Math.PI / 2; - const rotationInDegrees = rotation * (180 / Math.PI); - // Round to multiple of 90 degrees and keep the result positive. - const roundedRotation = mod(Math.round((rotationInDegrees + 360) / 90) * 90, 360); - return roundedRotation; -} - -export function fromCenterToOrigin(bbox: BoundingBox): AffineTransformation { - const center = bbox.getCenter(); - const translationMatrix = new THREE.Matrix4() - .makeTranslation(-center[0], -center[1], -center[2]) - .transpose(); // Column-major to row-major - return { type: "affine", matrix: flatToNestedMatrix(translationMatrix.toArray()) }; -} - -export function fromOriginToCenter(bbox: BoundingBox): AffineTransformation { - const center = bbox.getCenter(); - const translationMatrix = new THREE.Matrix4() - .makeTranslation(center[0], center[1], center[2]) - .transpose(); // Column-major to row-major - return { type: "affine", matrix: flatToNestedMatrix(translationMatrix.toArray()) }; -} -export function getRotationMatrixAroundAxis( - axis: "x" | "y" | "z", - angleInRadians: number, -): AffineTransformation { - const euler = new THREE.Euler(); - euler[axis] = angleInRadians; - const rotationMatrix = new THREE.Matrix4().makeRotationFromEuler(euler).transpose(); // Column-major to row-major - const matrixWithoutNearlyZeroValues = rotationMatrix - .toArray() - // Avoid nearly zero values due to floating point arithmetic inaccuracies. - .map((value) => (Math.abs(value) < Number.EPSILON ? 0 : value)) as Matrix4x4; - return { type: "affine", matrix: flatToNestedMatrix(matrixWithoutNearlyZeroValues) }; -} - -function memoizeWithThreeKeys(fn: (a: A, b: B, c: C) => T) { - const map = new MultiKeyMap(); - return (a: A, b: B, c: C): T => { - let res = map.get([a, b, c]); - if (res === undefined) { - res = fn(a, b, c); - map.set([a, b, c], res); - } - return res; - }; -} - -function memoizeWithTwoKeys(fn: (a: A, b: B) => T) { - const map = new MultiKeyMap(); - return (a: A, b: B): T => { - let res = map.get([a, b]); - if (res === undefined) { - res = fn(a, b); - map.set([a, b], res); - } - return res; - }; -} - -// Returns the transforms (if they exist) for a layer as -// they are defined in the dataset properties. -function _getOriginalTransformsForLayerOrNull( - dataset: APIDataset, - layer: APIDataLayer, -): Transform | null { - const coordinateTransformations = layer.coordinateTransformations; - if (!coordinateTransformations || coordinateTransformations.length === 0) { - return null; - } - - const transforms = coordinateTransformations.map((coordTransformation) => { - const { type } = coordTransformation; - if (type === "affine") { - const nestedMatrix = coordTransformation.matrix; - return createAffineTransformFromMatrix(nestedMatrix); - } else if (type === "thin_plate_spline") { - const { source, target } = coordTransformation.correspondences; - - return createThinPlateSplineTransform(source, target, dataset.dataSource.scale.factor); - } - - console.error( - "Data layer has defined a coordinate transform that is not affine or thin_plate_spline. This is currently not supported and ignored", - ); - return IdentityTransform; - }); - return transforms.reduce(chainTransforms, IdentityTransform); -} - -export const getOriginalTransformsForLayerOrNull = memoizeWithTwoKeys( - _getOriginalTransformsForLayerOrNull, -); - -export function isLayerWithoutTransformationConfigSupport(layer: APIDataLayer | APISkeletonLayer) { - return ( - layer.category === "skeleton" || - (layer.category === "segmentation" && "tracingId" in layer && !layer.fallbackLayer) - ); -} - -function _getTransformsForLayerOrNull( - dataset: APIDataset, - layer: APIDataLayer | APISkeletonLayer, - nativelyRenderedLayerName: string | null, -): Transform | null { - if (isLayerWithoutTransformationConfigSupport(layer)) { - return getTransformsForLayerThatDoesNotSupportTransformationConfigOrNull( - dataset, - nativelyRenderedLayerName, - ); - } - - if (layer.name === nativelyRenderedLayerName) { - // This layer should be rendered without any transforms. - return null; - } - const layerTransforms = getOriginalTransformsForLayerOrNull(dataset, layer as APIDataLayer); - if (nativelyRenderedLayerName == null) { - // No layer is requested to be rendered natively. -> We can use the layer's transforms as is. - return layerTransforms; - } - - // Apply the inverse of the layer that should be rendered natively - // to the current layer's transforms. - const nativeLayer = getLayerByName(dataset, nativelyRenderedLayerName, true); - const transformsOfNativeLayer = getOriginalTransformsForLayerOrNull(dataset, nativeLayer); - - if (transformsOfNativeLayer == null) { - // The inverse of no transforms, are no transforms. Leave the layer - // transforms untouched. - return layerTransforms; - } - - const inverseNativeTransforms = invertTransform(transformsOfNativeLayer); - return chainTransforms(layerTransforms, inverseNativeTransforms); -} - -export const getTransformsForLayerOrNull = memoizeWithThreeKeys(_getTransformsForLayerOrNull); -export function getTransformsForLayer( - dataset: APIDataset, - layer: APIDataLayer | APISkeletonLayer, - nativelyRenderedLayerName: string | null, -): Transform { - return ( - getTransformsForLayerOrNull(dataset, layer, nativelyRenderedLayerName) || IdentityTransform - ); -} - -export function isIdentityTransform(transform: Transform) { - return transform.type === "affine" && _.isEqual(transform.affineMatrix, Identity4x4); -} - -function _getTransformsForLayerThatDoesNotSupportTransformationConfigOrNull( - dataset: APIDataset, - nativelyRenderedLayerName: string | null, -): Transform | null { - const layers = dataset.dataSource.dataLayers; - const allLayersSameRotation = doAllLayersHaveTheSameRotation(layers); - if (nativelyRenderedLayerName == null) { - // No layer is requested to be rendered natively. -> We can use each layer's transforms as is. - if (!allLayersSameRotation) { - // If the dataset's layers do not have a consistent transformation (which only rotates the dataset), - // we cannot guess what transformation should be applied to the layer. - // As skeleton layer and volume layer without fallback don't have a transforms property currently. - return null; - } - - // The skeleton layer / volume layer without fallback needs transformed just like the other layers. - // Thus, we simply use the first usable layer which supports transforms. - const usableReferenceLayer = layers.find( - (layer) => !isLayerWithoutTransformationConfigSupport(layer), - ); - const someLayersTransformsMaybe = usableReferenceLayer - ? getTransformsForLayerOrNull(dataset, usableReferenceLayer, nativelyRenderedLayerName) - : null; - return someLayersTransformsMaybe; - } else if (nativelyRenderedLayerName != null && allLayersSameRotation) { - // If all layers have the same transformations and at least one is rendered natively, this means that all layer should be rendered natively. - return null; - } - - // Compute the inverse of the layer that should be rendered natively. - const nativeLayer = getLayerByName(dataset, nativelyRenderedLayerName, true); - const transformsOfNativeLayer = getOriginalTransformsForLayerOrNull(dataset, nativeLayer); - - if (transformsOfNativeLayer == null) { - // The inverse of no transforms, are no transforms. - return null; - } - - return invertTransform(transformsOfNativeLayer); -} - -export const getTransformsForLayerThatDoesNotSupportTransformationConfigOrNull = memoizeOne( - _getTransformsForLayerThatDoesNotSupportTransformationConfigOrNull, -); - -export function getTransformsForSkeletonLayer( - dataset: APIDataset, - nativelyRenderedLayerName: string | null, -): Transform { - return ( - getTransformsForLayerThatDoesNotSupportTransformationConfigOrNull( - dataset, - nativelyRenderedLayerName, - ) || IdentityTransform - ); -} - -function _getTransformsPerLayer( - dataset: APIDataset, - nativelyRenderedLayerName: string | null, -): Record { - const transformsPerLayer: Record = {}; - const layers = dataset.dataSource.dataLayers; - for (const layer of layers) { - const transforms = getTransformsForLayer(dataset, layer, nativelyRenderedLayerName); - transformsPerLayer[layer.name] = transforms; - } - - return transformsPerLayer; -} - -export const getTransformsPerLayer = memoizeWithTwoKeys(_getTransformsPerLayer); - -export function getInverseSegmentationTransformer( - state: OxalisState, - segmentationLayerName: string, -) { - const { dataset } = state; - const { nativelyRenderedLayerName } = state.datasetConfiguration; - const layer = getLayerByName(dataset, segmentationLayerName); - const segmentationTransforms = getTransformsForLayer(dataset, layer, nativelyRenderedLayerName); - return transformPointUnscaled(invertTransform(segmentationTransforms)); -} - -export const hasDatasetTransforms = memoizeOne((dataset: APIDataset) => { - const layers = dataset.dataSource.dataLayers; - return layers.some((layer) => getOriginalTransformsForLayerOrNull(dataset, layer) != null); -}); - -// Transposition is often needed so that the matrix has the right format -// for matrix operations (e.g., on the GPU; but not for ThreeJS). -// Inversion is needed when the position of an "output voxel" (e.g., during -// rendering in the fragment shader) needs to be mapped to its original -// data position (i.e., how it's stored without the transformation). -// Without the inversion, the matrix maps from stored position to the position -// where it should be rendered. -export const invertAndTranspose = _.memoize((mat: Matrix4x4) => { - return M4x4.transpose(M4x4.inverse(mat)); -}); - -const translation = new THREE.Vector3(); -const scale = new THREE.Vector3(); -const quaternion = new THREE.Quaternion(); -const IDENTITY_QUATERNION = new THREE.Quaternion(); - -const NON_SCALED_VECTOR = new THREE.Vector3(1, 1, 1); - -function isTranslationOnly(transformation?: AffineTransformation) { - if (!transformation) { - return false; - } - const threeMatrix = new THREE.Matrix4() - .fromArray(nestedToFlatMatrix(transformation.matrix)) - .transpose(); - threeMatrix.decompose(translation, quaternion, scale); - return scale.equals(NON_SCALED_VECTOR) && quaternion.equals(IDENTITY_QUATERNION); -} - -function isRotationOnly(transformation?: AffineTransformation) { - if (!transformation) { - return false; - } - const threeMatrix = new THREE.Matrix4() - .fromArray(nestedToFlatMatrix(transformation.matrix)) - .transpose(); - threeMatrix.decompose(translation, quaternion, scale); - return translation.length() === 0 && scale.equals(NON_SCALED_VECTOR); -} - -function hasValidTransformationCount(dataLayers: Array): boolean { - return dataLayers.every((layer) => layer.coordinateTransformations?.length === 5); -} - -function hasOnlyAffineTransformations(dataLayers: Array): boolean { - return dataLayers.every((layer) => - layer.coordinateTransformations?.every((transformation) => transformation.type === "affine"), - ); -} - -// The transformation array consists of 5 matrices: -// 1. Translation to coordinate system origin -// 2. Rotation around x-axis -// 3. Rotation around y-axis -// 4. Rotation around z-axis -// 5. Translation back to original position -export const EXPECTED_TRANSFORMATION_LENGTH = 5; - -function hasValidTransformationPattern(transformations: CoordinateTransformation[]): boolean { - return ( - transformations.length === EXPECTED_TRANSFORMATION_LENGTH && - isTranslationOnly(transformations[0] as AffineTransformation) && - isRotationOnly(transformations[1] as AffineTransformation) && - isRotationOnly(transformations[2] as AffineTransformation) && - isRotationOnly(transformations[3] as AffineTransformation) && - isTranslationOnly(transformations[4] as AffineTransformation) - ); -} - -function _doAllLayersHaveTheSameRotation(dataLayers: Array): boolean { - const firstDataLayerTransformations = dataLayers[0]?.coordinateTransformations; - if (firstDataLayerTransformations == null || firstDataLayerTransformations.length === 0) { - // No transformations in all layers compatible with setting a rotation for the whole dataset. - return dataLayers.every( - (layer) => - layer.coordinateTransformations == null || layer.coordinateTransformations.length === 0, - ); - } - // There should be a translation to the origin, one transformation for each axis and one translation back. => A total of 5 affine transformations. - if (!hasValidTransformationCount(dataLayers) || !hasOnlyAffineTransformations(dataLayers)) { - return false; - } - - if (!hasValidTransformationPattern(firstDataLayerTransformations)) { - return false; - } - for (let i = 1; i < dataLayers.length; i++) { - const transformations = dataLayers[i].coordinateTransformations; - if ( - transformations == null || - !_.isEqual(transformations[0], firstDataLayerTransformations[0]) || - !_.isEqual(transformations[1], firstDataLayerTransformations[1]) || - !_.isEqual(transformations[2], firstDataLayerTransformations[2]) || - !_.isEqual(transformations[3], firstDataLayerTransformations[3]) || - !_.isEqual(transformations[4], firstDataLayerTransformations[4]) - ) { - return false; - } - } - return true; -} - -export const doAllLayersHaveTheSameRotation = _.memoize(_doAllLayersHaveTheSameRotation); - -export function globalToLayerTransformedPosition( - globalPos: Vector3, - layerName: string, - layerCategory: APIDataLayer["category"] | "skeleton", - state: OxalisState, -): Vector3 { - const layerDescriptor = - layerCategory !== "skeleton" - ? getLayerByName(state.dataset, layerName, true) - : ({ name: "skeleton", category: "skeleton" } as APISkeletonLayer); - const layerTransforms = getTransformsForLayerOrNull( - state.dataset, - layerDescriptor, - state.datasetConfiguration.nativelyRenderedLayerName, - ); - if (layerTransforms) { - return transformPointUnscaled(invertTransform(layerTransforms))(globalPos); - } - return globalPos; -} diff --git a/frontend/javascripts/oxalis/model/accessors/flycam_accessor.ts b/frontend/javascripts/oxalis/model/accessors/flycam_accessor.ts index 77ad4b822c..d3c1ecd0e9 100644 --- a/frontend/javascripts/oxalis/model/accessors/flycam_accessor.ts +++ b/frontend/javascripts/oxalis/model/accessors/flycam_accessor.ts @@ -1,5 +1,5 @@ import type { Matrix4x4 } from "libs/mjs"; -import { M4x4, V3 } from "libs/mjs"; +import { M4x4 } from "libs/mjs"; import { map3, mod } from "libs/utils"; import _ from "lodash"; import memoizeOne from "memoize-one"; @@ -20,6 +20,8 @@ import { getLayerByName, getMagInfo, getMaxZoomStep, + getTransformsForLayer, + invertAndTranspose, } from "oxalis/model/accessors/dataset_accessor"; import { getViewportRects } from "oxalis/model/accessors/view_mode_accessor"; import determineBucketsForFlight from "oxalis/model/bucket_data_handling/bucket_picker_strategies/flight_bucket_picker"; @@ -33,15 +35,8 @@ import * as THREE from "three"; import type { AdditionalCoordinate, VoxelSize } from "types/api_flow_types"; import { baseDatasetViewConfiguration } from "types/schemas/dataset_view_configuration.schema"; import type { SmallerOrHigherInfo } from "../helpers/mag_info"; -import { - type Transform, - chainTransforms, - invertTransform, - transformPointUnscaled, -} from "../helpers/transformation_helpers"; import { getMatrixScale, rotateOnAxis } from "../reducers/flycam_reducer"; import { reuseInstanceOnEquality } from "./accessor_helpers"; -import { getTransformsForLayer, invertAndTranspose } from "./dataset_layer_transformation_accessor"; export const ZOOM_STEP_INTERVAL = 1.1; @@ -199,7 +194,7 @@ const perLayerFnCache: Map = new Map() // Only exported for testing. export const _getDummyFlycamMatrix = memoizeOne((scale: Vector3) => { const scaleMatrix = getMatrixScale(scale); - return rotateOnAxis(M4x4.scale(scaleMatrix, M4x4.identity(), []), Math.PI, [0, 0, 1]); + return rotateOnAxis(M4x4.scale(scaleMatrix, M4x4.identity, []), Math.PI, [0, 0, 1]); }); export function getMoveOffset(state: OxalisState, timeFactor: number) { @@ -258,36 +253,6 @@ function getMaximumZoomForAllMagsFromStore(state: OxalisState, layerName: string ); } -// This function depends on functionality from this and the dataset_layer_transformation_accessor module. -// To avoid cyclic dependencies and as the result of the function is a position and scale change, -// this function is arguably semantically closer to this flycam module. -export function getNewPositionAndZoomChangeFromTransformationChange( - activeTransformation: Transform, - nextTransform: Transform, - state: OxalisState, -) { - // Calculate the difference between the current and the next transformation. - const currentTransformInverted = invertTransform(activeTransformation); - const changeInAppliedTransformation = chainTransforms(currentTransformInverted, nextTransform); - - const currentPosition = getPosition(state.flycam); - const newPosition = transformPointUnscaled(changeInAppliedTransformation)(currentPosition); - - // Also transform a reference coordinate to determine how the scaling - // changed. Then, adapt the zoom accordingly. - - const referenceOffset: Vector3 = [10, 10, 10]; - const secondPosition = V3.add(currentPosition, referenceOffset, [0, 0, 0]); - const newSecondPosition = transformPointUnscaled(changeInAppliedTransformation)(secondPosition); - - const scaleChange = _.mean( - // Only consider XY for now to determine the zoom change (by slicing from 0 to 2) - V3.abs(V3.divide3(V3.sub(newPosition, newSecondPosition), referenceOffset)).slice(0, 2), - ); - - return { newPosition, scaleChange }; -} - function _getUp(flycam: Flycam): Vector3 { const matrix = flycam.currentMatrix; return [matrix[4], matrix[5], matrix[6]]; diff --git a/frontend/javascripts/oxalis/model/accessors/skeletontracing_accessor.ts b/frontend/javascripts/oxalis/model/accessors/skeletontracing_accessor.ts index 21ffe94fcc..4d4d8ab7f3 100644 --- a/frontend/javascripts/oxalis/model/accessors/skeletontracing_accessor.ts +++ b/frontend/javascripts/oxalis/model/accessors/skeletontracing_accessor.ts @@ -25,9 +25,9 @@ import type { } from "types/api_flow_types"; import { invertTransform, transformPointUnscaled } from "../helpers/transformation_helpers"; import { - getTransformsForLayerThatDoesNotSupportTransformationConfigOrNull, getTransformsForSkeletonLayer, -} from "./dataset_layer_transformation_accessor"; + getTransformsForSkeletonLayerOrNull, +} from "./dataset_accessor"; export function getSkeletonTracing(tracing: Tracing): Maybe { if (tracing.skeleton != null) { @@ -218,7 +218,7 @@ export function getNodeAndTreeOrNull( export function isSkeletonLayerTransformed(state: OxalisState) { return ( - getTransformsForLayerThatDoesNotSupportTransformationConfigOrNull( + getTransformsForSkeletonLayerOrNull( state.dataset, state.datasetConfiguration.nativelyRenderedLayerName, ) != null @@ -231,14 +231,16 @@ export function getNodePosition(node: Node, state: OxalisState): Vector3 { export function transformNodePosition(position: Vector3, state: OxalisState): Vector3 { const dataset = state.dataset; - const { nativelyRenderedLayerName } = state.datasetConfiguration; + const nativelyRenderedLayerName = state.datasetConfiguration.nativelyRenderedLayerName; + const currentTransforms = getTransformsForSkeletonLayer(dataset, nativelyRenderedLayerName); return transformPointUnscaled(currentTransforms)(position); } export function untransformNodePosition(position: Vector3, state: OxalisState): Vector3 { const dataset = state.dataset; - const { nativelyRenderedLayerName } = state.datasetConfiguration; + const nativelyRenderedLayerName = state.datasetConfiguration.nativelyRenderedLayerName; + const currentTransforms = getTransformsForSkeletonLayer(dataset, nativelyRenderedLayerName); return transformPointUnscaled(invertTransform(currentTransforms))(position); } diff --git a/frontend/javascripts/oxalis/model/accessors/tool_accessor.ts b/frontend/javascripts/oxalis/model/accessors/tool_accessor.ts index 5d17a6911b..366d0a9116 100644 --- a/frontend/javascripts/oxalis/model/accessors/tool_accessor.ts +++ b/frontend/javascripts/oxalis/model/accessors/tool_accessor.ts @@ -6,7 +6,10 @@ import { import memoizeOne from "memoize-one"; import { type AnnotationTool, IdentityTransform } from "oxalis/constants"; import { AnnotationToolEnum } from "oxalis/constants"; -import { getVisibleSegmentationLayer } from "oxalis/model/accessors/dataset_accessor"; +import { + getTransformsPerLayer, + getVisibleSegmentationLayer, +} from "oxalis/model/accessors/dataset_accessor"; import { isMagRestrictionViolated } from "oxalis/model/accessors/flycam_accessor"; import { type AgglomerateState, @@ -18,7 +21,6 @@ import { import type { OxalisState } from "oxalis/store"; import type { APIOrganization, APIUser } from "types/api_flow_types"; import { reuseInstanceOnEquality } from "./accessor_helpers"; -import { getTransformsPerLayer } from "./dataset_layer_transformation_accessor"; import { isSkeletonLayerTransformed } from "./skeletontracing_accessor"; const zoomInToUseToolMessage = diff --git a/frontend/javascripts/oxalis/model/bucket_data_handling/bounding_box.ts b/frontend/javascripts/oxalis/model/bucket_data_handling/bounding_box.ts index 7a0bd3a0d0..50d5ac1936 100644 --- a/frontend/javascripts/oxalis/model/bucket_data_handling/bounding_box.ts +++ b/frontend/javascripts/oxalis/model/bucket_data_handling/bounding_box.ts @@ -3,7 +3,6 @@ import { map3, mod } from "libs/utils"; import _ from "lodash"; import type { BoundingBoxType, OrthoView, Vector2, Vector3, Vector4 } from "oxalis/constants"; import constants, { Vector3Indicies } from "oxalis/constants"; -import type { BoundingBoxObject } from "oxalis/store"; import Dimensions from "../dimensions"; import type { MagInfo } from "../helpers/mag_info"; @@ -25,13 +24,6 @@ class BoundingBox { } } - static fromBoundBoxObject(boundingBox: BoundingBoxObject): BoundingBox { - return new BoundingBox({ - min: boundingBox.topLeft, - max: V3.add(boundingBox.topLeft, [boundingBox.width, boundingBox.height, boundingBox.depth]), - }); - } - getMinUV(activeViewport: OrthoView): Vector2 { const [u, v, _w] = Dimensions.transDim(this.min, activeViewport); return [u, v]; diff --git a/frontend/javascripts/oxalis/model/bucket_data_handling/layer_rendering_manager.ts b/frontend/javascripts/oxalis/model/bucket_data_handling/layer_rendering_manager.ts index 0dbb278094..41412d87e9 100644 --- a/frontend/javascripts/oxalis/model/bucket_data_handling/layer_rendering_manager.ts +++ b/frontend/javascripts/oxalis/model/bucket_data_handling/layer_rendering_manager.ts @@ -14,6 +14,8 @@ import { getElementClass, getLayerByName, getMagInfo, + getTransformsForLayer, + invertAndTranspose, isLayerVisible, } from "oxalis/model/accessors/dataset_accessor"; import type { DataBucket } from "oxalis/model/bucket_data_handling/bucket"; @@ -26,10 +28,6 @@ import AsyncBucketPickerWorker from "oxalis/workers/async_bucket_picker.worker"; import { createWorker } from "oxalis/workers/comlink_wrapper"; import type * as THREE from "three"; import type { AdditionalCoordinate } from "types/api_flow_types"; -import { - getTransformsForLayer, - invertAndTranspose, -} from "../accessors/dataset_layer_transformation_accessor"; import { getViewportRects } from "../accessors/view_mode_accessor"; import { getSegmentsForLayer } from "../accessors/volumetracing_accessor"; import { listenToStoreProperty } from "../helpers/listener_helpers"; diff --git a/frontend/javascripts/oxalis/model/helpers/nml_helpers.ts b/frontend/javascripts/oxalis/model/helpers/nml_helpers.ts index 5e2c775a68..c873a0e06f 100644 --- a/frontend/javascripts/oxalis/model/helpers/nml_helpers.ts +++ b/frontend/javascripts/oxalis/model/helpers/nml_helpers.ts @@ -37,7 +37,7 @@ import { findGroup } from "oxalis/view/right-border-tabs/trees_tab/tree_hierarch import Saxophone from "saxophone"; import type { APIBuildInfo, MetadataEntryProto } from "types/api_flow_types"; import type { AdditionalCoordinate } from "types/api_flow_types"; -import { getTransformsForSkeletonLayer } from "../accessors/dataset_layer_transformation_accessor"; +import { getTransformsForSkeletonLayer } from "../accessors/dataset_accessor"; import { getNodePosition } from "../accessors/skeletontracing_accessor"; // NML Defaults diff --git a/frontend/javascripts/oxalis/model/helpers/transformation_helpers.ts b/frontend/javascripts/oxalis/model/helpers/transformation_helpers.ts index 2918fd7a66..01e910b9cf 100644 --- a/frontend/javascripts/oxalis/model/helpers/transformation_helpers.ts +++ b/frontend/javascripts/oxalis/model/helpers/transformation_helpers.ts @@ -2,9 +2,9 @@ import { estimateAffineMatrix4x4 } from "libs/estimate_affine"; import { M4x4 } from "libs/mjs"; import TPS3D from "libs/thin_plate_spline"; import type { Matrix4x4 } from "mjs"; -import type { NestedMatrix4, Vector3 } from "oxalis/constants"; +import type { Vector3, Vector4 } from "oxalis/constants"; -export function nestedToFlatMatrix(matrix: NestedMatrix4): Matrix4x4 { +export function nestedToFlatMatrix(matrix: [Vector4, Vector4, Vector4, Vector4]): Matrix4x4 { return [...matrix[0], ...matrix[1], ...matrix[2], ...matrix[3]]; } @@ -26,7 +26,9 @@ export type Transform = scaledTps: TPS3D; }; -export function createAffineTransformFromMatrix(nestedMatrix: NestedMatrix4): Transform { +export function createAffineTransformFromMatrix( + nestedMatrix: [Vector4, Vector4, Vector4, Vector4], +): Transform { const affineMatrix = nestedToFlatMatrix(nestedMatrix); return { type: "affine", affineMatrix, affineMatrixInv: M4x4.inverse(affineMatrix) }; } diff --git a/frontend/javascripts/oxalis/model/reducers/flycam_reducer.ts b/frontend/javascripts/oxalis/model/reducers/flycam_reducer.ts index c6f50d5659..54c5ac9599 100644 --- a/frontend/javascripts/oxalis/model/reducers/flycam_reducer.ts +++ b/frontend/javascripts/oxalis/model/reducers/flycam_reducer.ts @@ -102,7 +102,7 @@ function resetMatrix(matrix: Matrix4x4, voxelSize: Vector3) { // Save position const position = [matrix[12], matrix[13], matrix[14]]; // Reset rotation - const newMatrix = rotateOnAxis(M4x4.scale(scale, M4x4.identity(), []), Math.PI, [0, 0, 1]); + const newMatrix = rotateOnAxis(M4x4.scale(scale, M4x4.identity, []), Math.PI, [0, 0, 1]); // Restore position newMatrix[12] = position[0]; newMatrix[13] = position[1]; diff --git a/frontend/javascripts/oxalis/model/sagas/dataset_saga.ts b/frontend/javascripts/oxalis/model/sagas/dataset_saga.ts index d5a43f62b7..b6b367aadd 100644 --- a/frontend/javascripts/oxalis/model/sagas/dataset_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/dataset_saga.ts @@ -13,12 +13,10 @@ import { getLayerByName, getMagInfo, getMaybeSegmentIndexAvailability, - isLayerVisible, -} from "../accessors/dataset_accessor"; -import { getTransformsForLayer, invertAndTranspose, -} from "../accessors/dataset_layer_transformation_accessor"; + isLayerVisible, +} from "../accessors/dataset_accessor"; import { getCurrentMag } from "../accessors/flycam_accessor"; import { getViewportExtents } from "../accessors/view_mode_accessor"; import { diff --git a/frontend/javascripts/oxalis/model/sagas/quick_select_heuristic_saga.ts b/frontend/javascripts/oxalis/model/sagas/quick_select_heuristic_saga.ts index 68ae2210a2..b8bbddc521 100644 --- a/frontend/javascripts/oxalis/model/sagas/quick_select_heuristic_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/quick_select_heuristic_saga.ts @@ -50,8 +50,8 @@ import { getEnabledColorLayers, getLayerBoundingBox, getMagInfo, + getTransformsForLayer, } from "../accessors/dataset_accessor"; -import { getTransformsForLayer } from "../accessors/dataset_layer_transformation_accessor"; import { getActiveMagIndexForLayer } from "../accessors/flycam_accessor"; import { updateUserSettingAction } from "../actions/settings_actions"; import { diff --git a/frontend/javascripts/oxalis/model_initialization.ts b/frontend/javascripts/oxalis/model_initialization.ts index 402753714a..b09c17e4f4 100644 --- a/frontend/javascripts/oxalis/model_initialization.ts +++ b/frontend/javascripts/oxalis/model_initialization.ts @@ -104,8 +104,6 @@ import type { ServerTracing, ServerVolumeTracing, } from "types/api_flow_types"; -import type { Mutable } from "types/globals"; -import { doAllLayersHaveTheSameRotation } from "./model/accessors/dataset_layer_transformation_accessor"; import { convertServerAdditionalAxesToFrontEnd } from "./model/reducers/reducer_helpers"; export const HANDLED_ERROR = "error_was_handled"; @@ -478,7 +476,6 @@ function getMergedDataLayersFromDatasetAndVolumeTracings( const originalLayers = dataset.dataSource.dataLayers; const newLayers = originalLayers.slice(); - const allLayersSameRotation = doAllLayersHaveTheSameRotation(originalLayers); for (const tracing of tracings) { // The tracing always contains the layer information for the user segmentation. @@ -496,16 +493,6 @@ function getMergedDataLayersFromDatasetAndVolumeTracings( const boundingBox = getDatasetBoundingBox(dataset).asServerBoundingBox(); const mags = tracing.mags || []; const tracingHasMagList = mags.length > 0; - let coordinateTransformsMaybe = {}; - if (allLayersSameRotation) { - coordinateTransformsMaybe = { - coordinateTransformations: originalLayers?.[0].coordinateTransformations, - }; - } else if (fallbackLayer?.coordinateTransformations) { - coordinateTransformsMaybe = { - coordinateTransformations: fallbackLayer.coordinateTransformations, - }; - } // Legacy tracings don't have the `tracing.mags` property // since they were created before WK started to maintain multiple magnifications // in volume annotations. Therefore, this code falls back to mag (1, 1, 1) for @@ -527,7 +514,6 @@ function getMergedDataLayersFromDatasetAndVolumeTracings( fallbackLayer: tracing.fallbackLayer, fallbackLayerInfo: fallbackLayer, additionalAxes: convertServerAdditionalAxesToFrontEnd(tracing.additionalAxes), - ...coordinateTransformsMaybe, }; if (fallbackLayerIndex > -1) { newLayers[fallbackLayerIndex] = tracingLayer; @@ -853,33 +839,14 @@ function applyAnnotationSpecificViewConfiguration( * Apply annotation-specific view configurations to the dataset settings which are persisted * per user per dataset. The AnnotationViewConfiguration currently only holds the "isDisabled" * information per layer which should override the isDisabled information in DatasetConfiguration. - * Moreover, due to another annotation nativelyRenderedLayerName might be set to a layer which does - * not exist in this view / annotation. In this case, the nativelyRenderedLayerName should be set to null. */ - const initialDatasetSettings: Mutable = - _.cloneDeep(originalDatasetSettings); - - if (originalDatasetSettings.nativelyRenderedLayerName) { - const isNativelyRenderedNamePresent = - dataset.dataSource.dataLayers.some( - (layer) => - layer.name === originalDatasetSettings.nativelyRenderedLayerName || - (layer.category === "segmentation" && - layer.fallbackLayer === originalDatasetSettings.nativelyRenderedLayerName), - ) || - annotation?.annotationLayers.some( - (layer) => layer.name === originalDatasetSettings.nativelyRenderedLayerName, - ); - if (!isNativelyRenderedNamePresent) { - initialDatasetSettings.nativelyRenderedLayerName = null; - } - } - if (!annotation) { - return initialDatasetSettings; + return originalDatasetSettings; } + const initialDatasetSettings: DatasetConfiguration = _.cloneDeep(originalDatasetSettings); + if (annotation.viewConfiguration) { // The annotation already contains a viewConfiguration. Merge that into the // dataset settings. diff --git a/frontend/javascripts/oxalis/store.ts b/frontend/javascripts/oxalis/store.ts index 5cc4d3d4ca..4d074326f6 100644 --- a/frontend/javascripts/oxalis/store.ts +++ b/frontend/javascripts/oxalis/store.ts @@ -336,14 +336,11 @@ export type DatasetConfiguration = { // that name (or id) should be rendered without any transforms. // This means, that all other layers should be transformed so that // they still correlated with each other. - // If other layers have the same transformation they will also be rendered - // natively as their transform and the inverse transform of the nativelyRenderedLayer - // layer cancel each other out. // If nativelyRenderedLayerName is null, all layers are rendered // as their transforms property signal it. - // Currently, skeleton layers and volume layers without fallback do not have transforms - // as a stored property. So, to render the skeleton layer natively, - // nativelyRenderedLayerName can be set to null. + // Currently, the skeleton layer does not have transforms as a stored + // property. So, to render the skeleton layer natively, nativelyRenderedLayerName + // can be set to null. readonly nativelyRenderedLayerName: string | null; }; diff --git a/frontend/javascripts/oxalis/view/context_menu.tsx b/frontend/javascripts/oxalis/view/context_menu.tsx index f82444e7cb..1abc5a7ba7 100644 --- a/frontend/javascripts/oxalis/view/context_menu.tsx +++ b/frontend/javascripts/oxalis/view/context_menu.tsx @@ -1047,7 +1047,6 @@ function getNoNodeContextMenuOptions(props: NoNodeContextMenuProps): ItemType[] : null} ), - disabled: isSkeletonLayerTransformed(state), }, { key: "load-agglomerate-skeleton", diff --git a/frontend/javascripts/oxalis/view/left-border-tabs/layer_settings_tab.tsx b/frontend/javascripts/oxalis/view/left-border-tabs/layer_settings_tab.tsx index e21cc3ac3c..3f05a53907 100644 --- a/frontend/javascripts/oxalis/view/left-border-tabs/layer_settings_tab.tsx +++ b/frontend/javascripts/oxalis/view/left-border-tabs/layer_settings_tab.tsx @@ -52,20 +52,12 @@ import { getLayerBoundingBox, getLayerByName, getMagInfo, - getWidestMags, -} from "oxalis/model/accessors/dataset_accessor"; -import { getTransformsForLayer, getTransformsForLayerOrNull, + getWidestMags, hasDatasetTransforms, - isIdentityTransform, - isLayerWithoutTransformationConfigSupport, -} from "oxalis/model/accessors/dataset_layer_transformation_accessor"; -import { - getMaxZoomValueForMag, - getNewPositionAndZoomChangeFromTransformationChange, - getPosition, -} from "oxalis/model/accessors/flycam_accessor"; +} from "oxalis/model/accessors/dataset_accessor"; +import { getMaxZoomValueForMag, getPosition } from "oxalis/model/accessors/flycam_accessor"; import { enforceSkeletonTracing, getActiveNode, @@ -89,6 +81,10 @@ import { setNodeRadiusAction, setShowSkeletonsAction, } from "oxalis/model/actions/skeletontracing_actions"; +import { + invertTransform, + transformPointUnscaled, +} from "oxalis/model/helpers/transformation_helpers"; import { Model } from "oxalis/singletons"; import { api } from "oxalis/singletons"; import type { @@ -195,16 +191,10 @@ function TransformationIcon({ layer }: { layer: APIDataLayer | APISkeletonLayer state.datasetConfiguration.nativelyRenderedLayerName, ), ); - const canLayerHaveTransforms = !isLayerWithoutTransformationConfigSupport(layer); - const hasLayerTransformsConfigured = useSelector( - (state: OxalisState) => getTransformsForLayerOrNull(state.dataset, layer, null) != null, - ); - const showIcon = useSelector((state: OxalisState) => hasDatasetTransforms(state.dataset)); if (!showIcon) { return null; } - const isRenderedNatively = transform == null || isIdentityTransform(transform); const typeToLabel = { affine: "an affine", @@ -217,64 +207,69 @@ function TransformationIcon({ layer }: { layer: APIDataLayer | APISkeletonLayer affine: "icon-affine-transformation.svg", }; - // Cannot toggle transforms for a layer that cannot have no transforms or turn them on in case the layer has no transforms. - // Layers that cannot have transformations like skeleton layer and volume tracing layers without fallback - // automatically copy to the dataset transformation if all other layers have the same transformation. - const isDisabled = - !canLayerHaveTransforms || (isRenderedNatively && !hasLayerTransformsConfigured); - const toggleLayerTransforms = () => { const state = Store.getState(); - // Set nativelyRenderedLayerName to null in case the current layer is already natively rendered or does not have its own transformations configured (e.g. a skeleton layer) . - const nextNativelyRenderedLayerName = isRenderedNatively ? null : layer.name; - const activeTransformation = getTransformsForLayer( + const { nativelyRenderedLayerName } = state.datasetConfiguration; + if ( + layer.category === "skeleton" + ? nativelyRenderedLayerName == null + : nativelyRenderedLayerName === layer.name + ) { + return; + } + // Transform current position using the inverse transform + // so that the user will still look at the same data location. + const currentPosition = getPosition(state.flycam); + const currentTransforms = getTransformsForLayer( state.dataset, layer, state.datasetConfiguration.nativelyRenderedLayerName, ); - const nextTransform = getTransformsForLayer( - state.dataset, - layer, - nextNativelyRenderedLayerName, - ); - const { scaleChange, newPosition } = getNewPositionAndZoomChangeFromTransformationChange( - activeTransformation, - nextTransform, - state, + const invertedTransform = invertTransform(currentTransforms); + const newPosition = transformPointUnscaled(invertedTransform)(currentPosition); + + // Also transform a reference coordinate to determine how the scaling + // changed. Then, adapt the zoom accordingly. + const referenceOffset: Vector3 = [10, 10, 10]; + const secondPosition = V3.add(currentPosition, referenceOffset, [0, 0, 0]); + const newSecondPosition = transformPointUnscaled(invertedTransform)(secondPosition); + + const scaleChange = _.mean( + // Only consider XY for now to determine the zoom change (by slicing from 0 to 2) + V3.abs(V3.divide3(V3.sub(newPosition, newSecondPosition), referenceOffset)).slice(0, 2), ); dispatch( - updateDatasetSettingAction("nativelyRenderedLayerName", nextNativelyRenderedLayerName), + updateDatasetSettingAction( + "nativelyRenderedLayerName", + layer.category === "skeleton" ? null : layer.name, + ), ); dispatch(setPositionAction(newPosition)); dispatch(setZoomStepAction(state.flycam.zoomStep * scaleChange)); }; - const style = { - width: 14, - height: 14, - marginBottom: 4, - marginRight: 5, - ...(isDisabled - ? { cursor: "not-allowed", opacity: "0.5" } - : { cursor: "pointer", opacity: "1.0" }), - }; - return (
Transformed Layer Icon {} : toggleLayerTransforms} + style={{ + cursor: transform != null ? "pointer" : "default", + width: 14, + height: 14, + marginBottom: 4, + marginRight: 5, + }} + onClick={toggleLayerTransforms} />
@@ -311,8 +306,8 @@ function LayerInfoIconWithTooltip({ Min - {layer.boundingBox.topLeft[0]} - {layer.boundingBox.topLeft[1]} + {layer.boundingBox.topLeft[0]} + {layer.boundingBox.topLeft[1]} {layer.boundingBox.topLeft[2]} @@ -768,8 +763,8 @@ class DatasetSettings extends React.PureComponent { placement="left" > } - hoveredIcon={} + icon={} + hoveredIcon={} onClick={() => { this.setState({ isAddVolumeLayerModalVisible: true, @@ -1188,7 +1183,7 @@ class DatasetSettings extends React.PureComponent { const readableName = "Skeleton"; const skeletonTracing = enforceSkeletonTracing(tracing); const isOnlyAnnotationLayer = tracing.annotationLayers.length === 1; - const { showSkeletons, tracingId } = skeletonTracing; + const { showSkeletons } = skeletonTracing; const activeNodeRadius = getActiveNode(skeletonTracing)?.radius ?? 0; return ( @@ -1238,7 +1233,7 @@ class DatasetSettings extends React.PureComponent { paddingRight: 1, }} > - + {!isOnlyAnnotationLayer ? this.getDeleteAnnotationLayerButton(readableName) : null} diff --git a/frontend/javascripts/test/reducers/flycam_reducer.spec.ts b/frontend/javascripts/test/reducers/flycam_reducer.spec.ts index 6b71ed3f84..546537b97b 100644 --- a/frontend/javascripts/test/reducers/flycam_reducer.spec.ts +++ b/frontend/javascripts/test/reducers/flycam_reducer.spec.ts @@ -36,7 +36,7 @@ const initialState = { flycam: { zoomStep: 2, additionalCoordinates: [], - currentMatrix: M4x4.identity(), + currentMatrix: M4x4.identity, spaceDirectionOrtho: [1, 1, 1], }, temporaryConfiguration: { diff --git a/frontend/javascripts/types/api_flow_types.ts b/frontend/javascripts/types/api_flow_types.ts index 9f77a1d745..df4e68bda6 100644 --- a/frontend/javascripts/types/api_flow_types.ts +++ b/frontend/javascripts/types/api_flow_types.ts @@ -3,11 +3,11 @@ import _ from "lodash"; import type { ColorObject, LOG_LEVELS, - NestedMatrix4, Point3, TreeType, UnitLong, Vector3, + Vector4, Vector6, } from "oxalis/constants"; import type { @@ -60,17 +60,15 @@ export type ServerAdditionalAxis = { name: string; }; -export type AffineTransformation = { - type: "affine"; - matrix: NestedMatrix4; // Stored in row major order. -}; - -export type ThinPlateSplineTransformation = { - type: "thin_plate_spline"; - correspondences: { source: Vector3[]; target: Vector3[] }; -}; - -export type CoordinateTransformation = AffineTransformation | ThinPlateSplineTransformation; +export type CoordinateTransformation = + | { + type: "affine"; + matrix: [Vector4, Vector4, Vector4, Vector4]; + } + | { + type: "thin_plate_spline"; + correspondences: { source: Vector3[]; target: Vector3[] }; + }; type APIDataLayerBase = { readonly name: string; readonly boundingBox: BoundingBoxObject; @@ -96,8 +94,8 @@ export type APISegmentationLayer = APIDataLayerBase & { export type APIDataLayer = APIColorLayer | APISegmentationLayer; // Only used in rare cases to generalize over actual data layers and -// a skeleton layer. The name should be the skeleton tracing id to very likely ensure it is unique. -export type APISkeletonLayer = { category: "skeleton"; name: string }; +// a skeleton layer. +export type APISkeletonLayer = { category: "skeleton" }; export type LayerLink = { datasetId: string; diff --git a/frontend/javascripts/types/globals.d.ts b/frontend/javascripts/types/globals.d.ts index 0cec5eb30a..c19f5fcd35 100644 --- a/frontend/javascripts/types/globals.d.ts +++ b/frontend/javascripts/types/globals.d.ts @@ -16,6 +16,3 @@ export type ArbitraryObject = Record; export type ArbitraryFunction = (...args: Array) => any; export type Comparator = (arg0: T, arg1: T) => -1 | 0 | 1; export type ArrayElement
= A extends readonly (infer T)[] ? T : never; -export type Mutable = { - -readonly [K in keyof T]: T[K]; -}; diff --git a/frontend/javascripts/types/schemas/dataset_view_configuration.schema.ts b/frontend/javascripts/types/schemas/dataset_view_configuration.schema.ts index 94ee101037..758414ee74 100644 --- a/frontend/javascripts/types/schemas/dataset_view_configuration.schema.ts +++ b/frontend/javascripts/types/schemas/dataset_view_configuration.schema.ts @@ -150,9 +150,6 @@ export const datasetViewConfiguration = { type: "string", }, }, - nativelyRenderedLayerName: { - type: "string", - }, }; export default { $schema: "http://json-schema.org/draft-06/schema#",