Skip to content

Commit

Permalink
fix rendering transforms for volume layers without fallback & fix ini…
Browse files Browse the repository at this point in the history
…t datasetconfig when nativelyRenderedLayerName is not present in current view / dataset
  • Loading branch information
Michael Büßemeyer authored and Michael Büßemeyer committed Dec 5, 2024
1 parent 86d0b42 commit 5f9a301
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -150,15 +150,24 @@ export const getOriginalTransformsForLayerOrNull = memoizeWithTwoKeys(
_getOriginalTransformsForLayerOrNull,
);

export function isLayerWithoutTransformConfigSupport(layer: APIDataLayer | APISkeletonLayer) {
return (
layer.category === "skeleton" || (layer.category === "segmentation" && !layer.fallbackLayer)
);
}

function _getTransformsForLayerOrNull(
dataset: APIDataset,
layer: APIDataLayer | APISkeletonLayer,
nativelyRenderedLayerName: string | null,
): Transform | null {
if (layer.category === "skeleton") {
return getTransformsForSkeletonLayerOrNull(dataset, nativelyRenderedLayerName);
if (isLayerWithoutTransformConfigSupport(layer)) {
return getTransformsForLayerWithoutTransformationConfigOrNull(
dataset,
nativelyRenderedLayerName,
);
}
const layerTransforms = getOriginalTransformsForLayerOrNull(dataset, layer);
const layerTransforms = getOriginalTransformsForLayerOrNull(dataset, layer as APIDataLayer);

if (layer.name === nativelyRenderedLayerName) {
// This layer should be rendered without any transforms.
Expand Down Expand Up @@ -223,7 +232,7 @@ export function isIdentityTransform(transform: Transform) {
return transform.type === "affine" && _.isEqual(transform.affineMatrix, Identity4x4);
}

function _getTransformsForSkeletonLayerOrNull(
function _getTransformsForLayerWithoutTransformationConfigOrNull(
dataset: APIDataset,
nativelyRenderedLayerName: string | null,
): Transform | null {
Expand All @@ -233,42 +242,50 @@ function _getTransformsForSkeletonLayerOrNull(
// No layer is requested to be rendered natively. -> We can use each layer's transforms as is.
if (!doAllLayersHaveTheSameRotation) {
// 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 skeleton layer.
// 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 needs transformed just like the other layers. Thus, we simply duplicate the first layers transforms.
const someLayersTransforms = getTransformsForLayerOrNull(
dataset,
layers[0],
nativelyRenderedLayerName,
// The skeleton layer needs transformed just like the other layers. Thus, we simply use the first usable layer.
// Filtering for a layer that might actually have transforms prevents an infinite loop
// between cyclic calls of _getTransformsForLayerWithoutTransformationConfigOrNull and getTransformsForLayerOrNull.
const usableReferenceLayer = layers.find(
(layer) =>
layer.category === "color" || (layer.category === "segmentation" && layer.fallbackLayer),
);
return someLayersTransforms;
const someLayersTransformsMaybe = usableReferenceLayer
? getTransformsForLayerOrNull(dataset, usableReferenceLayer, nativelyRenderedLayerName)
: null;
return someLayersTransformsMaybe;
} else if (nativelyRenderedLayerName != null && doAllLayersHaveTheSameRotation) {
// 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
// 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
// The inverse of no transforms, are no transforms.
return null;
}

return invertTransform(transformsOfNativeLayer);
}

export const getTransformsForSkeletonLayerOrNull = memoizeOne(_getTransformsForSkeletonLayerOrNull);
export const getTransformsForLayerWithoutTransformationConfigOrNull = memoizeOne(
_getTransformsForLayerWithoutTransformationConfigOrNull,
);

export function getTransformsForSkeletonLayer(
dataset: APIDataset,
nativelyRenderedLayerName: string | null,
): Transform {
return (
getTransformsForSkeletonLayerOrNull(dataset, nativelyRenderedLayerName) || IdentityTransform
getTransformsForLayerWithoutTransformationConfigOrNull(dataset, nativelyRenderedLayerName) ||
IdentityTransform
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
import type { TreeType, Vector3 } from "oxalis/constants";
import { invertTransform, transformPointUnscaled } from "../helpers/transformation_helpers";
import {
getTransformsForSkeletonLayerOrNull,
getTransformsForLayerWithoutTransformationConfigOrNull,
getTransformsForSkeletonLayer,
} from "./dataset_layer_rotation_accessor";

Expand Down Expand Up @@ -218,7 +218,7 @@ export function getNodeAndTreeOrNull(

export function isSkeletonLayerTransformed(state: OxalisState) {
return (
getTransformsForSkeletonLayerOrNull(
getTransformsForLayerWithoutTransformationConfigOrNull(
state.dataset,
state.datasetConfiguration.nativelyRenderedLayerName,
) != null
Expand Down
26 changes: 23 additions & 3 deletions frontend/javascripts/oxalis/model_initialization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ import {
} from "admin/organization/pricing_plan_utils";
import { convertServerAdditionalAxesToFrontEnd } from "./model/reducers/reducer_helpers";
import { haveAllLayersSameRotation } from "./model/accessors/dataset_layer_rotation_accessor";
import type { Mutable } from "types/globals";

export const HANDLED_ERROR = "error_was_handled";
type DataLayerCollection = Record<string, DataLayer>;
Expand Down Expand Up @@ -852,13 +853,32 @@ 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.
*/

if (!annotation) {
return originalDatasetSettings;
const initialDatasetSettings: Mutable<DatasetConfiguration> =
_.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;
}
}

const initialDatasetSettings: DatasetConfiguration = _.cloneDeep(originalDatasetSettings);
if (!annotation) {
return initialDatasetSettings;
}

if (annotation.viewConfiguration) {
// The annotation already contains a viewConfiguration. Merge that into the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ import {
haveAllLayersSameRotation,
getTransformsForLayer,
isIdentityTransform,
isLayerWithoutTransformConfigSupport,
} from "oxalis/model/accessors/dataset_layer_rotation_accessor";
import {
invertTransform,
Expand Down Expand Up @@ -243,18 +244,17 @@ function TransformationIcon({ layer }: { layer: APIDataLayer | APISkeletonLayer
affine: "icon-affine-transformation.svg",
};

const isDisabled =
(isRenderedNatively && !doAllLayersHaveTheSameTransform) ||
// Cannot toggle transforms on a layer into whose coordinate system other layer transform.
isLayerWithoutTransformConfigSupport(layer);
// Cannot toggle transformations on a skeleton layer as a skeleton layer cannot have transformations.
// Therefore, it cannot be used as a reference for other layers.
// The same goes for segmentation layers without fallback.
// Such layers can only transform according to transformations of other layers.

const toggleLayerTransforms = () => {
const state = Store.getState();
//TODO: fix for skeleton layer
if (isRenderedNatively && !doAllLayersHaveTheSameTransform) {
// Cannot toggle transforms on a layer into whose coordinate system other layer transform.
return;
}
if (layer.category === "skeleton" && isRenderedNatively) {
// Cannot turn on transformations on a skeleton layer as a skeleton layer cannot have transformations.
// A skeleton layer can only transform accordingly to other layers.
return;
}
// Get transform of layer. null is passed as nativelyRenderedLayerName to
// get the layers transform even in case it is currently rendered natively.
const currentTransform = getTransformsForLayer(state.dataset, layer, null);
Expand Down Expand Up @@ -288,6 +288,15 @@ function TransformationIcon({ layer }: { layer: APIDataLayer | APISkeletonLayer
dispatch(setZoomStepAction(state.flycam.zoomStep * scaleChange));
};

const style = {
cursor: transform != null ? "pointer" : "default",
width: 14,
height: 14,
marginBottom: 4,
marginRight: 5,
...(isDisabled ? { cursor: "not-allowed", opacity: "0.5" } : {}),
};

return (
<div className="flex-item">
<FastTooltip
Expand All @@ -296,20 +305,14 @@ function TransformationIcon({ layer }: { layer: APIDataLayer | APISkeletonLayer
? "This layer is shown natively (i.e., without any transformations)."
: `This layer is rendered with ${
typeToLabel[transform.type]
} transformation. Click to render this layer without any transforms.`
} transformation.${isDisabled ? "" : " Click to render this layer without any transforms."}`
}
>
<img
src={`/assets/images/${typeToImage[isRenderedNatively ? "none" : transform.type]}`}
alt="Transformed Layer Icon"
style={{
cursor: transform != null ? "pointer" : "default",
width: 14,
height: 14,
marginBottom: 4,
marginRight: 5,
}}
onClick={toggleLayerTransforms}
style={style}
onClick={isDisabled ? () => {} : toggleLayerTransforms}
/>
</FastTooltip>
</div>
Expand Down
3 changes: 3 additions & 0 deletions frontend/javascripts/types/globals.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@ export type ArbitraryObject = Record<string, any>;
export type ArbitraryFunction = (...args: Array<any>) => any;
export type Comparator<T> = (arg0: T, arg1: T) => -1 | 0 | 1;
export type ArrayElement<A> = A extends readonly (infer T)[] ? T : never;
export type Mutable<T> = {
-readonly [K in keyof T]: T[K];
};

0 comments on commit 5f9a301

Please sign in to comment.