Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Readd fixed version of "Allow permanent dataset layer rotation in dataset settings"" #8355

Merged

Conversation

MichaelBuessemeyer
Copy link
Contributor

Reverts #8353
And re adds: #8159

Related Slack canvas: https://scm.slack.com/docs/T02A8MN9K/F083DBRCFC6

--> This PR adds new settings to the dataset settings for each layer. An interface (still in design process) enables the user to set a affine transformation matrix by letting the user define angles for each axis around which the dataset should be rotated.

  • https://___.webknossos.xyz

Steps to test:

  • Basic testing:
    • View your favourite DS
    • open the DS settings in another tab. Use the rotation slider to configure some rotation. e.g. z 90 degrees and save
    • reload the view DS tab. The DS should now be fully rotated by 90 degree around the z axis -> xy viewport should be rotated by 90 degrees
    • Play around with toggeling the rotation off and on in the layer setting in the view mode
  • Testing annotation support
    • create a hybrid annotation of a rotated DS
    • add an empty volume layer
    • turn of the transforms
    • toggling the transforms should not work on the skeleton layer and the volume layer without fallback. These do not have an underlying layer that might have transforms configured. Therefore, nativelyRenderedLayerName cannot be set to one of these layers. Moreover, the transforms cannot be toggled on on such layers as they do not have transformations.
    • toggle the transforms on other layer and have fun
    • toggle off the transforms, do volume annotations on each layer and create some skeleton nodes
    • toggle on the transforms again. The nodes and volume annotation should rotate accordingly. This also applies to the volume layer without fallback data.
    • This should hopefully cover all wanted annotation scenarios. If you can think up more, please also test them 🙏
  • In general: When toggling transformations, the camera position should update accordingly.
  • Check conflicting nativelyRenderedLayerName settings.
    • Open a hybrid annotation on a dataset with a rotation setting that has at least one segmentation layer.
    • On the editable segmentation layer (volume layer with fallback) use the "transform button" to render the volume layer natively
    • In another tab open another such hybrid tracing. The DatasetViewConfigs should now have nativelyRenderedLayerName set to the tracingId of the volume layer of the first annotation. This does not exist in the newly opened other annotation. => The annotation should still open up (but with all layers transformed), as the nativelyRenderedLayerName setting should be automatically disregarded as the layer does not exist in this annotation.
  • !!! important !!!
    • Please also test whether a DS with multi modality still works as expected 🙏

TODOs / Problems:

  • [ ] Design a visualization of the result. For some first ideas look at the issue #7334 --> deferred / not planned
  • The rotation should be done around the center of the dataset. For this, a translation by the dataset center is needed
  • The interface works by defining Euler angles. The problem is that Euler angles are not deterministic / there are multiple ways to express a certain rotation with different Euler angle values. See: https://stackoverflow.com/questions/32235395/does-euler-angles-quaternion-euler-angles-always-result-in-an-equivalent-r
    • This makes it hard to update the settings correctly -> a 180 degree rotation around y is equivalent to 180 on x and 180 on z axis :/
    • A solution for this is needed as the interface currently is pretty quirky.
  • Reset nativelyRenderedLayerName in model init if this layer does not exist. (Might happen when switching annotations)

Problems:

  • Layer rotates out of the dataset bbox

Issues:


(Please delete unneeded items, merge only when none are left open)

Copy link
Contributor

coderabbitai bot commented Jan 27, 2025

📝 Walkthrough

Walkthrough

This pull request introduces a comprehensive feature for configuring dataset rotations. The changes span multiple frontend files, creating a new system that allows users to rotate dataset layers around x, y, and z axes. A new module dataset_layer_transformation_accessor.ts centralizes transformation logic, and components like AxisRotationFormItem and AxisRotationSettingForDataset provide the user interface for specifying rotations. The implementation leverages existing affine transformation mechanisms and ensures that rotations can be applied consistently across different layer types.

Changes

File Change Summary
CHANGELOG.unreleased.md Added entry for dataset rotation configuration
frontend/javascripts/dashboard/dataset/dataset_rotation_form_item.tsx New components for axis rotation settings and form handling
frontend/javascripts/dashboard/dataset/dataset_settings_data_tab.tsx Integrated axis rotation settings into dataset form
frontend/javascripts/dashboard/dataset/dataset_settings_view.tsx Updated form data type to include dataset rotation
frontend/javascripts/oxalis/model/accessors/dataset_layer_transformation_accessor.ts New module for comprehensive transformation logic
Multiple files Refactored imports and transformation-related functions

Assessment against linked issues

Objective Addressed Explanation
Rotate dataset layers Implemented rotation configuration for x, y, z axes
Persist rotation Rotation can be saved and applied across layers
Work with existing transformations Leverages existing affine transformation mechanisms
Handle skeleton layers Transforms work with nodes and layer rendering

Possibly related PRs

Suggested labels

usability, backend

Suggested reviewers

  • hotzenklotz

Poem

🐰 Rotating layers with grace and might,
Transforming data, a digital delight!
X, Y, and Z, they spin and twirl,
Code dances, matrices unfurl.
A rabbit's spin of pure design! 🌀

✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (7)
frontend/javascripts/types/globals.d.ts (1)

19-21: Add JSDoc documentation for the Mutable type utility.

The type implementation looks correct, but it would benefit from documentation explaining its purpose and usage guidelines.

+/**
+ * Makes all properties of type T mutable by removing readonly modifiers.
+ * Use with caution as it bypasses readonly protection.
+ * @template T - The type to make mutable
+ * @example
+ * type ReadonlyUser = { readonly name: string };
+ * type MutableUser = Mutable<ReadonlyUser>; // { name: string }
+ */
 export type Mutable<T> = {
   -readonly [K in keyof T]: T[K];
 };
frontend/javascripts/types/schemas/dataset_view_configuration.schema.ts (1)

153-155: New schema property for nativelyRenderedLayerName.
This addition provides a dedicated location in view configuration for specifying the layer to render natively.
However, note that the default value in defaultDatasetViewConfigurationWithoutNull is null, while the schema strictly requires a string. If you intend to allow null, consider adjusting the schema to:

-nativelyRenderedLayerName: {
-  type: "string",
-},
+nativelyRenderedLayerName: {
+  type: ["string", "null"],
+},
frontend/javascripts/dashboard/dataset/dataset_rotation_form_item.tsx (3)

25-36: Consider memoizing dataset bounding box computation.

The getDatasetBoundingBoxFromLayers function performs iterations and object creation. Since it's used in multiple places, consider memoizing its results to avoid unnecessary recomputation.

-function getDatasetBoundingBoxFromLayers(layers: APIDataLayer[]): BoundingBox | undefined {
+const getDatasetBoundingBoxFromLayers = memoizeOne((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;
-}
+});

116-116: Consider adding aria-label for accessibility.

The slider control should have an aria-label for better accessibility.

-          <Slider min={0} max={270} step={90} onChange={setMatrixRotationsForAllLayer} />
+          <Slider 
+            min={0} 
+            max={270} 
+            step={90} 
+            onChange={setMatrixRotationsForAllLayer}
+            aria-label={`Rotate dataset around ${axis} axis`}
+          />

48-71: Consider debouncing the transformation updates.

The effect that updates transformations runs on every bounding box change. Consider debouncing these updates to prevent excessive re-renders, especially when multiple changes occur in quick succession.

+import { debounce } from 'lodash';

+const debouncedUpdateTransformations = debounce((
+  datasetBoundingBox: BoundingBox,
+  dataLayers: APIDataLayer[],
+  form: FormInstance,
+) => {
+  if (
+    datasetBoundingBox == null ||
+    dataLayers[0].coordinateTransformations?.length !== EXPECTED_TRANSFORMATION_LENGTH ||
+    !form
+  ) {
+    return;
+  }
+  // ... rest of the transformation update logic
+}, 250);

 useEffect(() => {
-  if (
-    datasetBoundingBox == null ||
-    dataLayers[0].coordinateTransformations?.length !== EXPECTED_TRANSFORMATION_LENGTH ||
-    !form
-  ) {
-    return;
-  }
-  // ... rest of the effect
+  if (form) {
+    debouncedUpdateTransformations(datasetBoundingBox, dataLayers, form);
+  }
   return () => {
+    debouncedUpdateTransformations.cancel();
   };
 }, [datasetBoundingBox, dataLayers, form]);
frontend/javascripts/oxalis/model.ts (1)

229-237: LGTM! Improved coordinate transformation in getHoveredCellId.

The use of globalToLayerTransformedPosition correctly transforms the mouse position from global to layer space before retrieving the data value. The code is now more accurate in handling coordinate transformations.

Consider extracting the layer category "segmentation" as a constant since it's used as a magic string:

+const SEGMENTATION_LAYER_CATEGORY = "segmentation";
+
 const posInLayerSpace = globalToLayerTransformedPosition(
   pos,
   segmentationLayer.name,
-  "segmentation",
+  SEGMENTATION_LAYER_CATEGORY,
   state,
 );
frontend/javascripts/dashboard/dataset/dataset_settings_view.tsx (1)

204-229: Consider enhancing the rotation initialization logic.

While the current implementation handles basic cases, consider adding:

  1. Validation for rotation values to ensure they are multiples of 90 degrees
  2. Error handling for malformed transformation arrays
  3. Logging when falling back to default values
 if (doAllLayersHaveTheSameRotation(dataSource.dataLayers)) {
   const firstLayerTransformations = dataSource.dataLayers[0].coordinateTransformations;
   let initialDatasetRotationSettings: DatasetRotation;
   if (
-    !firstLayerTransformations ||
-    firstLayerTransformations.length !== EXPECTED_TRANSFORMATION_LENGTH
+    !Array.isArray(firstLayerTransformations) ||
+    firstLayerTransformations.length !== EXPECTED_TRANSFORMATION_LENGTH ||
+    !firstLayerTransformations.every(transform => transform != null)
   ) {
+    console.warn('Invalid transformation array, falling back to default rotation values');
     initialDatasetRotationSettings = {
       x: 0,
       y: 0,
       z: 0,
     };
   } else {
+    const validateRotation = (value: number) => Math.round(value / 90) * 90;
     initialDatasetRotationSettings = {
-      x: getRotationFromTransformationIn90DegreeSteps(firstLayerTransformations[1], "x"),
-      y: getRotationFromTransformationIn90DegreeSteps(firstLayerTransformations[2], "y"),
-      z: getRotationFromTransformationIn90DegreeSteps(firstLayerTransformations[3], "z"),
+      x: validateRotation(getRotationFromTransformationIn90DegreeSteps(firstLayerTransformations[1], "x")),
+      y: validateRotation(getRotationFromTransformationIn90DegreeSteps(firstLayerTransformations[2], "y")),
+      z: validateRotation(getRotationFromTransformationIn90DegreeSteps(firstLayerTransformations[3], "z")),
     };
   }
   form.setFieldsValue({
     datasetRotation: initialDatasetRotationSettings,
   });
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e20c218 and b6fdcf1.

📒 Files selected for processing (36)
  • CHANGELOG.unreleased.md (1 hunks)
  • frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx (1 hunks)
  • frontend/javascripts/dashboard/dataset/dataset_rotation_form_item.tsx (1 hunks)
  • frontend/javascripts/dashboard/dataset/dataset_settings_data_tab.tsx (2 hunks)
  • frontend/javascripts/dashboard/dataset/dataset_settings_view.tsx (4 hunks)
  • frontend/javascripts/dashboard/dataset/dataset_settings_viewconfig_tab.tsx (1 hunks)
  • frontend/javascripts/libs/mjs.ts (1 hunks)
  • frontend/javascripts/oxalis/api/api_latest.ts (1 hunks)
  • frontend/javascripts/oxalis/constants.ts (1 hunks)
  • frontend/javascripts/oxalis/controller/combinations/volume_handlers.ts (3 hunks)
  • frontend/javascripts/oxalis/controller/scene_controller.ts (1 hunks)
  • frontend/javascripts/oxalis/geometries/materials/edge_shader.ts (1 hunks)
  • frontend/javascripts/oxalis/geometries/materials/node_shader.ts (1 hunks)
  • frontend/javascripts/oxalis/geometries/materials/plane_material_factory.ts (2 hunks)
  • frontend/javascripts/oxalis/merger_mode.ts (1 hunks)
  • frontend/javascripts/oxalis/model.ts (2 hunks)
  • frontend/javascripts/oxalis/model/accessors/dataset_accessor.ts (1 hunks)
  • frontend/javascripts/oxalis/model/accessors/dataset_layer_transformation_accessor.ts (1 hunks)
  • frontend/javascripts/oxalis/model/accessors/flycam_accessor.ts (4 hunks)
  • frontend/javascripts/oxalis/model/accessors/skeletontracing_accessor.ts (3 hunks)
  • frontend/javascripts/oxalis/model/accessors/tool_accessor.ts (2 hunks)
  • frontend/javascripts/oxalis/model/bucket_data_handling/bounding_box.ts (2 hunks)
  • frontend/javascripts/oxalis/model/bucket_data_handling/layer_rendering_manager.ts (1 hunks)
  • frontend/javascripts/oxalis/model/helpers/nml_helpers.ts (1 hunks)
  • frontend/javascripts/oxalis/model/helpers/transformation_helpers.ts (2 hunks)
  • frontend/javascripts/oxalis/model/reducers/flycam_reducer.ts (1 hunks)
  • frontend/javascripts/oxalis/model/sagas/dataset_saga.ts (1 hunks)
  • frontend/javascripts/oxalis/model/sagas/quick_select_heuristic_saga.ts (1 hunks)
  • frontend/javascripts/oxalis/model_initialization.ts (5 hunks)
  • frontend/javascripts/oxalis/store.ts (1 hunks)
  • frontend/javascripts/oxalis/view/context_menu.tsx (1 hunks)
  • frontend/javascripts/oxalis/view/left-border-tabs/layer_settings_tab.tsx (7 hunks)
  • frontend/javascripts/test/reducers/flycam_reducer.spec.ts (1 hunks)
  • frontend/javascripts/types/api_flow_types.ts (3 hunks)
  • frontend/javascripts/types/globals.d.ts (1 hunks)
  • frontend/javascripts/types/schemas/dataset_view_configuration.schema.ts (1 hunks)
✅ Files skipped from review due to trivial changes (8)
  • frontend/javascripts/oxalis/controller/scene_controller.ts
  • frontend/javascripts/oxalis/model/sagas/quick_select_heuristic_saga.ts
  • frontend/javascripts/oxalis/model/helpers/nml_helpers.ts
  • frontend/javascripts/dashboard/dataset/dataset_settings_viewconfig_tab.tsx
  • frontend/javascripts/oxalis/merger_mode.ts
  • frontend/javascripts/oxalis/geometries/materials/node_shader.ts
  • frontend/javascripts/oxalis/model/sagas/dataset_saga.ts
  • frontend/javascripts/oxalis/api/api_latest.ts
👮 Files not reviewed due to content moderation or server errors (4)
  • frontend/javascripts/oxalis/geometries/materials/plane_material_factory.ts
  • frontend/javascripts/oxalis/view/left-border-tabs/layer_settings_tab.tsx
  • frontend/javascripts/oxalis/view/context_menu.tsx
  • CHANGELOG.unreleased.md
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: circleci_build
🔇 Additional comments (44)
frontend/javascripts/types/globals.d.ts (1)

19-21: Verify usage patterns of the Mutable type utility.

The type implementation is correct, but let's verify its usage patterns to ensure type safety is maintained.

✅ Verification successful

The Mutable type utility is being used correctly and safely.

The codebase follows a consistent pattern of using readonly types by default and explicitly marking mutable versions where needed. This approach helps prevent accidental mutations while allowing controlled mutability when required.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find usages of Mutable type to verify type safety

# Search for imports and usages
echo "Searching for Mutable type imports and usages..."
rg "Mutable.*" -A 5

# Search for readonly properties that might be affected
echo "Searching for readonly properties that might be affected..."
rg "readonly.*" -A 5

Length of output: 96329

frontend/javascripts/oxalis/model/accessors/dataset_layer_transformation_accessor.ts (16)

1-33: Imports appear well-organized.
All the imported modules (e.g., lodash, three, etc.) are referenced in subsequent code, and there are no obvious unused imports.


34-63: Constants and indexing mappings look correct.
The IDENTITY_MATRIX, IDENTITY_TRANSFORM, and the sin/cos index lookups for rotation axes are neatly defined. The approach is consistent across the codebase, and these constants are easy to maintain.


65-72: Good matrix reshaping utility.
flatToNestedMatrix correctly slices a 16-element array into a 4x4 nested matrix. Straightforward and efficient approach.


74-98: Rotation extraction in 90-degree steps is well-structured.
The function properly calculates angles, avoids division by zero, and neatly rounds to multiples of 90. Keep in mind that non-orthonormal matrices may produce unexpected angles, but this is likely intended.


100-114: Center-origin translations are consistent.
Using THREE.Matrix4 and transposing to adjust row-major vs. column-major is handled cleanly. This accurately shifts the bounding box center to (and from) the origin.


115-127: Euler rotation and zero-value normalization.
getRotationMatrixAroundAxis leverages THREE.Euler properly and clamps near-zero values. This helps avoid floating-point drift in rotation matrices.


129-151: Cache behavior with multi-key memoization.
The custom memoizeWithThreeKeys and memoizeWithTwoKeys are clear and concise. Watch memory usage in scenarios with a large key domain, but otherwise good for performance.


153-192: Layer transform creation and config checks.

  1. _getOriginalTransformsForLayerOrNull correctly distinguishes between affine and thin-plate-spline transformations, defaulting to identity on invalid.
  2. isLayerWithoutTransformationConfigSupport is a helpful convenience check.
    Implementation is straightforward.

194-240: Layer transform retrieval and identity checks.

  1. _getTransformsForLayerOrNull neatly handles the natively rendered layer logic and merges inverses when needed.
  2. getTransformsForLayer provides a consistent default (identity) if no transform is found.
  3. isIdentityTransform is a succinct utility.

246-301: Fallback logic for layers lacking config support.

  1. _getTransformsForLayerThatDoesNotSupportTransformationConfigOrNull carefully checks if all layers share a consistent rotation or a natively rendered layer.
  2. The final fallback of returning the inverse transformation of the native layer is logical.
  3. getTransformsForSkeletonLayer reuses the same approach.

303-317: Comprehensive per-layer transforms.
_getTransformsPerLayer iterates over layers and aggregates transforms in a single object, simplifying usage upstream. No issues.


319-333: Inverse segmentation transformer & dataset transform checks.
getInverseSegmentationTransformer and hasDatasetTransforms do exactly what their names imply, with clear logic.


342-373: Matrix inversion & decomposition checks.

  1. invertAndTranspose appropriately inverts and transposes a 4x4 matrix for GPU usage.
  2. isTranslationOnly and isRotationOnly rely on THREE.Matrix4.decompose, then compare the result. Straightforward approach to check transformation nature.

375-435: Dataset-level rotation consistency checks.

  1. hasValidTransformationCount, hasOnlyAffineTransformations, and hasValidTransformationPattern ensure the transformation array structure is as expected.
  2. _doAllLayersHaveTheSameRotation implements the logic for verifying that all layers follow this rotation pattern.

437-438: Memoized rotation check export.
Exporting doAllLayersHaveTheSameRotation with a memoized version ensures repeated calls remain performant.


439-458: Global-to-layer position transformation.
globalToLayerTransformedPosition ensures correct coordinate conversion by inverting the layer transform. Straightforward and readable.

frontend/javascripts/oxalis/model/accessors/dataset_accessor.ts (1)

3-8: Import updates look consistent.
The addition of V3 from "libs/mjs" and typed imports from "oxalis/constants" align with usage in the file. No immediate issues or redundancies noted.

frontend/javascripts/oxalis/controller/combinations/volume_handlers.ts (1)

55-60: LGTM! Consistent coordinate transformation handling.

The changes correctly transform global coordinates to layer space before accessing segment data. This ensures proper handling of rotated datasets.

Also applies to: 86-91

frontend/javascripts/oxalis/geometries/materials/edge_shader.ts (1)

6-6: LGTM! Import path updated.

The import path change aligns with the centralization of transformation-related functions.

frontend/javascripts/oxalis/model/helpers/transformation_helpers.ts (1)

5-5: LGTM! Enhanced type safety for matrix operations.

The switch to NestedMatrix4 type provides better type guarantees while maintaining the same functionality.

Also applies to: 7-7, 29-29

frontend/javascripts/dashboard/dataset/dataset_rotation_form_item.tsx (1)

73-106: Verify the rotation matrix computation.

The rotation matrix computation and application look correct, but please verify:

  1. The order of rotations (x, y, z) is consistent with the expected behavior
  2. The conversion from degrees to radians is accurate
  3. The matrix multiplication order preserves the intended transformation
✅ Verification successful

The rotation matrix computation and application are correctly implemented

The implementation is mathematically sound and follows best practices:

  • Proper transformation sequence: translate to origin → rotate → translate back
  • Correct rotation order (x:1, y:2, z:3) maintained through AXIS_TO_TRANSFORM_INDEX
  • Accurate degree-to-radian conversion using Math.PI/180
  • Robust matrix operations using THREE.js with proper handling of floating-point precision
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other rotation matrix implementations to verify consistency
rg -A 5 "getRotationMatrix|rotateMatrix|rotation.*matrix" --type ts

Length of output: 4747


Script:

#!/bin/bash
# Get the implementation of getRotationMatrixAroundAxis and related constants
ast-grep --pattern 'export function getRotationMatrixAroundAxis($_) { $$$ }'

# Find AXIS_TO_TRANSFORM_INDEX definition
rg "AXIS_TO_TRANSFORM_INDEX.*=.*{" -A 5 --type ts

# Look for any matrix multiplication or transformation order related code
rg "EXPECTED_TRANSFORMATION_LENGTH|matrix.*multiplication|transform.*order" --type ts

Length of output: 1302


Script:

#!/bin/bash
# Find AXIS_TO_TRANSFORM_INDEX definition with fixed regex
rg "AXIS_TO_TRANSFORM_INDEX" -B 2 -A 2 --type ts

# Get the implementation details from the transformation accessor
cat frontend/javascripts/oxalis/model/accessors/dataset_layer_transformation_accessor.ts

# Look for documentation about transformation sequence
rg -l "transformation.*sequence|matrix.*order" --type ts --type md

Length of output: 18404

frontend/javascripts/oxalis/model/bucket_data_handling/bounding_box.ts (1)

28-33: LGTM! The implementation is correct and efficient.

The function correctly creates a BoundingBox instance by:

  1. Using topLeft as the minimum coordinates
  2. Calculating maximum coordinates by adding dimensions to topLeft
frontend/javascripts/test/reducers/flycam_reducer.spec.ts (1)

39-39: LGTM! Correctly updated to invoke M4x4.identity as a function.

The change aligns with the codebase-wide update to invoke M4x4.identity as a function instead of accessing it as a property.

frontend/javascripts/oxalis/model/accessors/skeletontracing_accessor.ts (3)

28-30: LGTM! Imports are correctly updated.

The imports are correctly updated to use the new transformation accessor functions from the dataset_layer_transformation_accessor module.


221-224: LGTM! Function correctly uses the new transformation accessor.

The function maintains its behavior while using the new transformation accessor function.


234-235: LGTM! Code readability improved using object destructuring.

The changes improve code readability by using object destructuring to extract nativelyRenderedLayerName without altering functionality.

Also applies to: 241-242

frontend/javascripts/oxalis/model/reducers/flycam_reducer.ts (1)

105-105: LGTM! Correctly updated to invoke M4x4.identity as a function.

The change aligns with the codebase-wide update to invoke M4x4.identity as a function instead of accessing it as a property.

frontend/javascripts/libs/mjs.ts (1)

223-226: LGTM! Clean implementation of the identity matrix method.

The addition of the identity() method to M4x4 is well-implemented, providing a straightforward way to obtain the identity matrix for transformation operations.

frontend/javascripts/oxalis/model/bucket_data_handling/layer_rendering_manager.ts (1)

29-32: LGTM! Good refactoring of transformation-related imports.

The relocation of transformation-related functions to a dedicated accessor module improves code organization and maintainability.

frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx (1)

31-32: LGTM! Consistent refactoring of transformation-related imports.

The relocation of flatToNestedMatrix to the dedicated transformation accessor module maintains consistency with the codebase-wide refactoring.

frontend/javascripts/oxalis/store.ts (1)

339-346: LGTM! Documentation improvements enhance clarity.

The updated comments provide better clarity about:

  1. How layers with matching transformations are handled
  2. Special cases for skeleton layers and volume layers without fallback
frontend/javascripts/oxalis/model/accessors/flycam_accessor.ts (2)

261-289: LGTM! Well-documented transformation calculation.

The implementation correctly:

  1. Calculates transformation differences
  2. Determines position changes
  3. Computes scale changes using reference coordinates

Note: The scale change calculation only considers XY dimensions. Verify if Z-axis scaling should also be considered for your use case.


202-202: LGTM! Fixed method call syntax.

Corrected the M4x4.identity invocation by adding parentheses.

frontend/javascripts/oxalis/model_initialization.ts (2)

481-481: LGTM! Enhanced layer transformation handling.

The implementation correctly:

  1. Checks if all layers share the same rotation
  2. Assigns coordinate transformations based on rotation consistency
  3. Falls back to the fallback layer's transformations when rotations differ

Also applies to: 499-508


860-876: LGTM! Improved validation of nativelyRenderedLayerName.

The implementation now properly validates that the nativelyRenderedLayerName corresponds to an existing layer in the dataset or annotation layers. If not found, it's correctly set to null.

frontend/javascripts/types/api_flow_types.ts (2)

63-73: LGTM! Improved type safety for transformations.

The split into separate types for affine and thin plate spline transformations:

  1. Enhances type safety
  2. Improves code clarity
  3. Makes the transformation types more maintainable

99-100: LGTM! Enhanced APISkeletonLayer type.

Added name property to ensure uniqueness by using the skeleton tracing ID.

frontend/javascripts/oxalis/model/accessors/tool_accessor.ts (1)

9-9: LGTM! Import refactoring improves code organization.

The relocation of getTransformsPerLayer to a dedicated transformation accessor module enhances code organization by centralizing transformation-related functionality.

Also applies to: 21-21

frontend/javascripts/oxalis/constants.ts (1)

18-19: LGTM! Well-defined matrix type with clear documentation.

The NestedMatrix4 type is properly defined as a tuple of four Vector4 elements, and the comment clearly indicates it represents a row-major matrix.

frontend/javascripts/dashboard/dataset/dataset_settings_data_tab.tsx (2)

37-37: LGTM! Clean import of the rotation settings component.

The import statement is properly placed with other component imports.


271-276: LGTM! Well-structured layout for rotation settings.

The rotation settings are cleanly integrated using Ant Design's grid system, with appropriate column spans and spacing.

frontend/javascripts/dashboard/dataset/dataset_settings_view.tsx (2)

28-32: LGTM! Well-organized imports for transformation functionality.

The imports are properly grouped and specific to the transformation-related functionality being added.


85-85: LGTM! Clear type definition update.

The FormData type is properly extended with an optional datasetRotation field.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (11)
frontend/javascripts/oxalis/model/accessors/dataset_layer_transformation_accessor.ts (6)

74-127: Consider enhancing documentation for mathematical concepts.

While the code is well-implemented, the mathematical concepts (e.g., rotation matrices, Euler angles) could benefit from more detailed documentation explaining:

  • The choice of rotation order
  • The handling of gimbal lock
  • The rationale for 90-degree step rounding

129-151: Consider adding cache management.

The memoization implementation could benefit from:

  • A maximum cache size limit
  • A cache clearing mechanism
  • A least-recently-used (LRU) eviction policy

This would prevent potential memory leaks in long-running sessions.

 function memoizeWithThreeKeys<A, B, C, T>(fn: (a: A, b: B, c: C) => T) {
   const map = new MultiKeyMap<A | B | C, T, [A, B, C]>();
+  const MAX_CACHE_SIZE = 1000;
   return (a: A, b: B, c: C): T => {
     let res = map.get([a, b, c]);
     if (res === undefined) {
       res = fn(a, b, c);
+      if (map.size >= MAX_CACHE_SIZE) {
+        // Clear half of the cache when limit is reached
+        const keys = Array.from(map.keys()).slice(0, MAX_CACHE_SIZE / 2);
+        keys.forEach(key => map.delete([key]));
+      }
       map.set([a, b, c], res);
     }
     return res;
   };
 }

175-178: Enhance error handling with more descriptive messages.

The error message for unsupported transformation types could be more informative by including:

  • The actual type encountered
  • The layer name/ID
  • Possible resolution steps
     console.error(
-      "Data layer has defined a coordinate transform that is not affine or thin_plate_spline. This is currently not supported and ignored",
+      `Layer "${layer.name}" has unsupported coordinate transform type "${type}". Only "affine" and "thin_plate_spline" are supported. The transform will be ignored. Please update the layer configuration.`,
     );

194-229: Consider optimizing transform chain calculations.

The transform chain calculations in _getTransformsForLayerOrNull could be optimized by:

  • Pre-computing and caching inverse transforms
  • Avoiding unnecessary chain operations when possible
  • Adding early returns for common cases

303-315: Consider batch processing optimization for multiple layers.

The current implementation processes layers sequentially. For datasets with many layers, consider:

  • Processing layers in batches
  • Parallelizing transform calculations where possible
  • Adding progress tracking for large datasets

393-402: Consider extracting validation patterns into a configuration object.

The transformation pattern validation could be more maintainable by:

  • Extracting the validation rules into a configuration object
  • Making the validation order configurable
  • Adding support for custom validation patterns
+const TRANSFORMATION_PATTERN = {
+  steps: [
+    { index: 0, type: 'translation', validate: isTranslationOnly },
+    { index: 1, type: 'rotation', validate: isRotationOnly },
+    { index: 2, type: 'rotation', validate: isRotationOnly },
+    { index: 3, type: 'rotation', validate: isRotationOnly },
+    { index: 4, type: 'translation', validate: isTranslationOnly },
+  ],
+};

 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)
+    TRANSFORMATION_PATTERN.steps.every(step =>
+      step.validate(transformations[step.index] as AffineTransformation)
+    )
   );
 }
frontend/javascripts/dashboard/dataset/dataset_rotation_form_item.tsx (5)

25-36: Consider optimizing the bounding box calculation.

The function could be optimized to use a single pass through the layers array instead of checking length separately.

Here's an optimized version:

 function getDatasetBoundingBoxFromLayers(layers: APIDataLayer[]): BoundingBox | undefined {
-  if (!layers || layers.length === 0) {
+  if (!Array.isArray(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 layers.reduce((acc, layer) => {
+    const layerBox = BoundingBox.fromBoundBoxObject(layer.boundingBox);
+    return acc ? acc.extend(layerBox) : layerBox;
+  }, undefined as BoundingBox | undefined);
-  return datasetBoundingBox;
 }

48-71: Consider optimizing the useEffect dependencies.

The effect dependency on dataLayers could cause unnecessary rerenders since it's an array reference. Consider using a stable value derived from dataLayers.

 useEffect(() => {
   if (
     datasetBoundingBox == null ||
-    dataLayers[0].coordinateTransformations?.length !== EXPECTED_TRANSFORMATION_LENGTH ||
+    !dataLayers?.[0]?.coordinateTransformations?.length !== EXPECTED_TRANSFORMATION_LENGTH ||
     !form
   ) {
     return;
   }
   // ... rest of the effect
-}, [datasetBoundingBox, dataLayers, form]);
+}, [datasetBoundingBox, form]);

116-116: Extract magic numbers into named constants.

The Slider component uses magic numbers (0, 270, 90) which should be extracted into named constants for better maintainability.

+const ROTATION_CONFIG = {
+  MIN_DEGREES: 0,
+  MAX_DEGREES: 270,
+  STEP_DEGREES: 90,
+} as const;
+
-<Slider min={0} max={270} step={90} onChange={setMatrixRotationsForAllLayer} />
+<Slider
+  min={ROTATION_CONFIG.MIN_DEGREES}
+  max={ROTATION_CONFIG.MAX_DEGREES}
+  step={ROTATION_CONFIG.STEP_DEGREES}
+  onChange={setMatrixRotationsForAllLayer}
+/>

161-184: Enhance accessibility and user guidance.

The tooltip content could be more accessible and user-friendly.

 <Tooltip
+  role="tooltip"
+  aria-label="Rotation requirements"
   title={
     <div>
-      Each layers transformations must be equal and each layer needs exactly 5 affine
-      transformation with the following schema:
+      To enable dataset rotation, all layers must have identical transformations following this structure:
       <ul>
         <li>Translation to the origin</li>
         <li>Rotation around the x-axis</li>
         <li>Rotation around the y-axis</li>
         <li>Rotation around the z-axis</li>
         <li>Translation back to the original position</li>
       </ul>
-      To easily enable this setting, delete all coordinateTransformations of all layers in the
-      advanced tab, save and reload the dataset settings.
+      Quick Fix: To enable rotation,
+      1. Go to the advanced tab
+      2. Remove all coordinate transformations from each layer
+      3. Save and reload the dataset settings
     </div>
   }
 >

1-194: Consider extracting rotation logic into a custom hook.

The rotation logic is complex and could benefit from being extracted into a custom hook (e.g., useDatasetRotation). This would:

  • Improve testability
  • Make the component logic more reusable
  • Separate concerns between UI and rotation calculations

Example structure:

function useDatasetRotation(form: FormInstance | undefined) {
  // Extract rotation logic here
  return {
    setRotation,
    updateTransformations,
    isRotationSupported,
    // ... other rotation-related utilities
  };
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b6fdcf1 and 3e6a780.

📒 Files selected for processing (2)
  • frontend/javascripts/dashboard/dataset/dataset_rotation_form_item.tsx (1 hunks)
  • frontend/javascripts/oxalis/model/accessors/dataset_layer_transformation_accessor.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: circleci_build
🔇 Additional comments (4)
frontend/javascripts/oxalis/model/accessors/dataset_layer_transformation_accessor.ts (1)

1-72: LGTM! Well-structured constants and utility functions.

The code demonstrates good practices with proper type definitions, clear constants, and focused utility functions.

frontend/javascripts/dashboard/dataset/dataset_rotation_form_item.tsx (3)

1-24: LGTM! Well-structured imports and type definitions.

The imports are organized logically, and the type definitions are clear and properly typed.


145-149: LGTM! Well-defined type for dataset rotation.

The DatasetRotation type is clear and properly typed for x, y, z rotation values.


73-106: Add error handling for rotation calculations.

The setMatrixRotationsForAllLayer function should handle potential errors in rotation calculations and invalid form states.

Consider adding validation and error handling:

 const setMatrixRotationsForAllLayer = useCallback(
   (rotationInDegrees: number): void => {
     if (!form) {
       return;
     }
+    if (typeof rotationInDegrees !== 'number' || isNaN(rotationInDegrees)) {
+      console.error('Invalid rotation value:', rotationInDegrees);
+      return;
+    }
     const dataLayers: APIDataLayer[] = form.getFieldValue(["dataSource", "dataLayers"]);
+    if (!Array.isArray(dataLayers) || dataLayers.length === 0) {
+      console.error('No data layers found');
+      return;
+    }
     // ... rest of the function
   },
   [axis, form],
 );

Copy link
Contributor Author

@MichaelBuessemeyer MichaelBuessemeyer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR should readd and fix the dataset rotation feature.
Please only check commit 3e6a780. This should include the bug fix

Comment on lines +154 to +159
// form -> dataSource -> dataLayers can be undefined in case of the add remote dataset form which is initially empty.
const dataLayers: APIDataLayer[] | undefined = form?.getFieldValue(["dataSource", "dataLayers"]);
const isRotationOnly = useMemo(
() => (dataLayers ? doAllLayersHaveTheSameRotation(dataLayers) : false),
[dataLayers],
);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be the acutal fix

Comment on lines +405 to +409
if (dataLayers.length === 0) {
// The dataset does not have any layers. Therefore no layers can be rotated.
return false;
}
const firstDataLayerTransformations = dataLayers[0].coordinateTransformations;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And here I noticed while thinking about this bug, that the previous code would return true for _doAllLayersHaveTheSameRotation when it was called with an empty array of data layers. This seemed wrong to me on a conceptual level as one cannot rotate something that has no data. Feel free to disagree :)

Copy link
Member

@philippotto philippotto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great, thank you!

…ert-8353-revert-8159-allow-perma-dataset-rotation
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
frontend/javascripts/types/api_flow_types.ts (1)

64-74: Well-structured transformation types with room for documentation.

The transformation types are well-designed, supporting both affine and non-linear transformations. The row-major order specification for the matrix is crucial for correct usage.

Consider adding JSDoc comments to document:

  • The mathematical properties of each transformation type
  • Example usage scenarios
  • The expected format and constraints of the transformation matrices
  • The meaning of source/target correspondences in thin plate spline transformations
+/**
+ * Represents an affine transformation using a 4x4 matrix.
+ * Supports operations like rotation, scaling, translation, and shearing.
+ */
 export type AffineTransformation = {
   type: "affine";
   matrix: NestedMatrix4; // Stored in row major order.
 };

+/**
+ * Represents a non-linear transformation using thin plate spline.
+ * Useful for smooth interpolation between corresponding points.
+ */
 export type ThinPlateSplineTransformation = {
   type: "thin_plate_spline";
   correspondences: { source: Vector3[]; target: Vector3[] };
 };
frontend/javascripts/oxalis/model_initialization.ts (1)

924-945: Consider adding a null check for annotation layers.

The implementation correctly handles edge cases, but the optional chaining in the isNativelyRenderedNamePresent check might not be sufficient. If annotation is defined but annotationLayers is undefined, the check could fail silently.

Consider this safer implementation:

     const isNativelyRenderedNamePresent =
       dataset.dataSource.dataLayers.some(
         (layer) =>
           layer.name === originalDatasetSettings.nativelyRenderedLayerName ||
           (layer.category === "segmentation" &&
             layer.fallbackLayer === originalDatasetSettings.nativelyRenderedLayerName),
       ) ||
-      annotation?.annotationLayers.some(
+      (annotation?.annotationLayers ?? []).some(
         (layer) => layer.name === originalDatasetSettings.nativelyRenderedLayerName,
       );
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d500d1b and 78438e2.

📒 Files selected for processing (11)
  • CHANGELOG.unreleased.md (1 hunks)
  • frontend/javascripts/oxalis/api/api_latest.ts (1 hunks)
  • frontend/javascripts/oxalis/merger_mode.ts (1 hunks)
  • frontend/javascripts/oxalis/model.ts (2 hunks)
  • frontend/javascripts/oxalis/model/accessors/skeletontracing_accessor.ts (3 hunks)
  • frontend/javascripts/oxalis/model/bucket_data_handling/layer_rendering_manager.ts (1 hunks)
  • frontend/javascripts/oxalis/model/sagas/dataset_saga.ts (1 hunks)
  • frontend/javascripts/oxalis/model_initialization.ts (5 hunks)
  • frontend/javascripts/oxalis/store.ts (1 hunks)
  • frontend/javascripts/oxalis/view/left-border-tabs/layer_settings_tab.tsx (7 hunks)
  • frontend/javascripts/types/api_flow_types.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
  • CHANGELOG.unreleased.md
  • frontend/javascripts/oxalis/model/sagas/dataset_saga.ts
  • frontend/javascripts/oxalis/model/bucket_data_handling/layer_rendering_manager.ts
  • frontend/javascripts/oxalis/model/accessors/skeletontracing_accessor.ts
  • frontend/javascripts/oxalis/model.ts
  • frontend/javascripts/oxalis/store.ts
  • frontend/javascripts/oxalis/merger_mode.ts
  • frontend/javascripts/oxalis/api/api_latest.ts
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: circleci_build
🔇 Additional comments (9)
frontend/javascripts/types/api_flow_types.ts (2)

6-6: LGTM!

The NestedMatrix4 type import is correctly placed with related geometric types.


100-101: LGTM!

Good documentation clarifying that the name should be the skeleton tracing ID for uniqueness.

frontend/javascripts/oxalis/view/left-border-tabs/layer_settings_tab.tsx (5)

52-65: LGTM! Well-organized imports for transformation functionality.

The new imports are properly organized and clearly indicate the separation of concerns between dataset transformations and flycam (camera) operations.


202-206: LGTM! Clear state management for transformation support.

The new state variables provide a clear way to track transformation capabilities and configuration status.


224-229: LGTM! Clear documentation of transformation constraints.

The comments effectively explain the constraints around toggling transformations for different layer types.


256-264: LGTM! Well-structured styling with proper accessibility.

The style object is well-organized and properly handles disabled states with cursor and opacity changes.


318-319: LGTM! Clear bounding box coordinate display.

The bounding box coordinates are properly displayed in the tooltip.

frontend/javascripts/oxalis/model_initialization.ts (2)

109-110: LGTM: New imports are correctly added.

The imports for Mutable type and doAllLayersHaveTheSameRotation function are properly added and align with their usage in the code.


545-545: LGTM: Robust handling of coordinate transformations.

The implementation correctly handles coordinate transformations by:

  1. Checking rotation consistency across layers
  2. Using the first layer's transformations if all layers have the same rotation
  3. Falling back to the fallback layer's transformations if available

Let's verify the rotation consistency check:

Also applies to: 563-572, 594-594

✅ Verification successful

✓ Verified: Rotation consistency check is correctly implemented

The doAllLayersHaveTheSameRotation function performs thorough validation of layer rotations by:

  • Validating transformation existence and count
  • Ensuring only affine transformations are present
  • Verifying the transformation pattern across layers
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the implementation of doAllLayersHaveTheSameRotation

# Test: Search for the implementation to ensure it correctly compares rotations
ast-grep --pattern 'export function doAllLayersHaveTheSameRotation($$$) {
  $$$
}'

Length of output: 84


Script:

#!/bin/bash
# Search for any occurrence of the function name or rotation-related code
rg -l "doAllLayersHaveTheSameRotation|rotation" frontend/javascripts/oxalis/model_initialization.ts

# Show the context around any rotation-related code
rg -B 5 -A 10 "rotation" frontend/javascripts/oxalis/model_initialization.ts

Length of output: 2416


Script:

#!/bin/bash
# Search for the function in the entire codebase
rg -g '*.ts' -g '*.js' -B 2 -A 10 "function doAllLayersHaveTheSameRotation|function.*SameRotation|checkRotation"

# Search for files that might contain layer rotation comparisons
fd -e ts -e js | xargs rg -l "layer.*rotation|rotation.*layer"

Length of output: 2008


Script:

#!/bin/bash
# Get the complete function implementation
rg -B 2 -A 20 "function _doAllLayersHaveTheSameRotation" frontend/javascripts/oxalis/model/accessors/dataset_layer_transformation_accessor.ts

Length of output: 1153

Comment on lines +270 to +274
isRenderedNatively
? `This layer is shown natively (i.e., without any transformations).${isDisabled ? "" : " Click to render this layer with its configured transforms."}`
: `This layer is rendered with ${
typeToLabel[transform.type]
} transformation. Click to render this layer without any transforms.`
: "This layer is shown natively (i.e., without any transformations)."
} transformation.${isDisabled ? "" : " Click to render this layer without any transforms."}`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Improve tooltip text construction to avoid potential undefined access.

The tooltip text construction could throw an error if transform.type is undefined when accessing typeToLabel[transform.type].

Apply this diff to add a null check:

-            ? `This layer is shown natively (i.e., without any transformations).${isDisabled ? "" : " Click to render this layer with its configured transforms."}`
-            : `This layer is rendered with ${
-                typeToLabel[transform.type]
-              } transformation.${isDisabled ? "" : " Click to render this layer without any transforms."}`
+            ? `This layer is shown natively (i.e., without any transformations).${
+                isDisabled ? "" : " Click to render this layer with its configured transforms."
+              }`
+            : `This layer is rendered with ${
+                transform?.type ? typeToLabel[transform.type] : "an unknown"
+              } transformation.${
+                isDisabled ? "" : " Click to render this layer without any transforms."
+              }`
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
isRenderedNatively
? `This layer is shown natively (i.e., without any transformations).${isDisabled ? "" : " Click to render this layer with its configured transforms."}`
: `This layer is rendered with ${
typeToLabel[transform.type]
} transformation. Click to render this layer without any transforms.`
: "This layer is shown natively (i.e., without any transformations)."
} transformation.${isDisabled ? "" : " Click to render this layer without any transforms."}`
isRenderedNatively
? `This layer is shown natively (i.e., without any transformations).${
isDisabled ? "" : " Click to render this layer with its configured transforms."
}`
: `This layer is rendered with ${
transform?.type ? typeToLabel[transform.type] : "an unknown"
} transformation.${
isDisabled ? "" : " Click to render this layer without any transforms."
}`

@MichaelBuessemeyer MichaelBuessemeyer merged commit a46d76b into master Jan 29, 2025
3 checks passed
@MichaelBuessemeyer MichaelBuessemeyer deleted the revert-8353-revert-8159-allow-perma-dataset-rotation branch January 29, 2025 12:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add "Rotate" button to dataset layers
2 participants