-
Notifications
You must be signed in to change notification settings - Fork 26
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
Allow permanent dataset layer rotation in dataset settings #8159
Conversation
📝 WalkthroughWalkthroughThis pull request introduces comprehensive changes to dataset layer transformations across multiple frontend files. The modifications focus on enhancing dataset rotation capabilities, reorganizing transformation-related functions, and improving the user interface for layer transformations. Key changes include creating a new Changes
Assessment against linked issues
Possibly related PRs
Suggested labels
Poem
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? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
…ow-perma-dataset-rotation
…ow-perma-dataset-rotation
… according to affine matrix coordTransform
- and some code clean up
…ow-perma-dataset-rotation
- also always translate by dataset bounding box and not by layer bounding box for consistent rotation results
…ow-perma-dataset-rotation
…a single one. - in case layers have the same transformation, the automatic inverse of the natively rendered layer (applied to all other layers) will cancel out the layers transformation - and fixing the code according to the logic of only saving one native layer
…t datasetconfig when nativelyRenderedLayerName is not present in current view / dataset
…ow-perma-dataset-rotation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🧹 Outside diff range and nitpick comments (19)
frontend/javascripts/dashboard/dataset/dataset_rotation_form_item.tsx (1)
153-177
: Avoid unnecessary re-renders by memoizing Tooltip contentThe JSX returned inside the
Tooltip
component creates a new anonymous function on each render. To prevent unnecessary re-renders and optimize performance, consider memoizing the tooltip content or extracting it into a separate component.frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx (3)
27-28
: Remove unused import 'getReadableURLPart'The
getReadableURLPart
function is imported but not used in the file. Removing unused imports can improve code readability and prevent potential maintenance issues.Apply this diff to remove the unused import:
import _ from "lodash"; import messages from "messages"; -import { getReadableURLPart } from "oxalis/model/accessors/dataset_accessor"; import { flatToNestedMatrix } from "oxalis/model/accessors/dataset_layer_rotation_accessor";
Line range hint
119-123
: Use dependency array in useEffectOnlyOnceThe
handleTransformImport
function is defined outside theuseEffectOnlyOnce
, but it's not included in its dependency array. AlthoughuseEffectOnlyOnce
implies it runs only once, adding dependencies ensures correct behavior if the hook's implementation changes.
Line range hint
174-178
: Improve error message clarityThe error message in the exception
"Cannot create dataset without being logged in."
could be more user-friendly by rephrasing it.Consider rephrasing the error message:
-throw new Error("Cannot create dataset without being logged in."); +throw new Error("User must be logged in to create a dataset.");frontend/javascripts/oxalis/model/accessors/dataset_layer_rotation_accessor.ts (1)
366-412
: Simplify 'doAllLayersHaveTheSameRotation' functionThe function can be simplified for better readability and maintainability. Consider refactoring nested conditions and loops.
frontend/javascripts/oxalis/model/accessors/dataset_accessor.ts (3)
Line range hint
55-58
: Remove unused function 'doesSupportVolumeWithFallback'The function
doesSupportVolumeWithFallback
is defined but not used anywhere in the codebase. Removing unused code improves maintainability.
Line range hint
264-269
: Avoid directly modifying function argumentsIn
getDatasetExtentAsProduct
, the function takesextent
as an argument and might be modifying it if not careful. Ensure that the function does not have side effects.
Line range hint
379-384
: Handle potential null values in 'getMappingInfoOrNull'The function
getMappingInfoOrNull
accessesactiveMappingInfos[layerName]
without checking iflayerName
is inactiveMappingInfos
. This could lead to undefined behavior iflayerName
is not present.Apply this diff to add a check:
export function getMappingInfoOrNull( activeMappingInfos: Record<string, ActiveMappingInfo>, layerName: string | null | undefined, ): ActiveMappingInfo | null { - if (layerName != null && activeMappingInfos[layerName]) { + if (layerName != null && layerName in activeMappingInfos) { return activeMappingInfos[layerName]; } return null; }frontend/javascripts/oxalis/view/left-border-tabs/layer_settings_tab.tsx (2)
247-254
: Clarify condition logic in 'TransformationIcon'The complex condition in
isDisabled
could be difficult to read. Consider simplifying or adding comments to clarify the logic.
797-798
: Replace 'LockOutlined' and 'UnlockOutlined' iconsIn Ant Design v4, the
LockOutlined
andUnlockOutlined
icons are deprecated. Consider replacing them with the updated icons from@ant-design/icons
.frontend/javascripts/types/globals.d.ts (1)
19-21
: LGTM! Consider documenting type usageThe
Mutable<T>
type is well-defined using TypeScript's mapped type feature. This utility type will be helpful for working with dataset rotation settings where immutable types need to be temporarily mutable.Consider adding a brief JSDoc comment explaining when to use this type, as removing readonly modifiers should be done judiciously. Example:
/** * Removes readonly modifiers from all properties of type T. * Use sparingly, primarily for initialization of immutable data structures. */ export type Mutable<T> = { -readonly [K in keyof T]: T[K]; };frontend/javascripts/oxalis/geometries/materials/edge_shader.ts (2)
Line range hint
47-52
: Add error handling for invalid layer namesThe transformation matrix calculation assumes the layer exists but doesn't handle invalid
nativelyRenderedLayerName
values.Add error handling:
this.uniforms["transform"] = { value: M4x4.transpose( - getTransformsForSkeletonLayer(dataset, nativelyRenderedLayerName).affineMatrix, + (() => { + try { + return getTransformsForSkeletonLayer(dataset, nativelyRenderedLayerName).affineMatrix; + } catch (error) { + console.error("Failed to get transforms:", error); + return M4x4.identity(); // Fallback to identity matrix + } + })(), ), };
Line range hint
71-89
: Consider debouncing transform updatesThe store listener for transformation changes could trigger frequent material updates. Consider debouncing these updates for better performance.
listenToStoreProperty( (storeState) => getTransformsForSkeletonLayer( storeState.dataset, storeState.datasetConfiguration.nativelyRenderedLayerName, ), - (skeletonTransforms) => { + _.debounce((skeletonTransforms) => { const transforms = skeletonTransforms; const { affineMatrix } = transforms; // ... rest of the handler - }, + }, 16), // Debounce to ~60fps true, );frontend/javascripts/oxalis/model/bucket_data_handling/bounding_box.ts (1)
28-33
: Consider adding input validation for BoundingBoxObject dimensions.While the implementation is correct, it might be good to add validation to ensure that width, height, and depth are non-negative values to prevent invalid bounding boxes.
static fromBoundBoxObject(boundingBox: BoundingBoxObject): BoundingBox { + if (boundingBox.width < 0 || boundingBox.height < 0 || boundingBox.depth < 0) { + throw new Error('BoundingBoxObject dimensions must be non-negative'); + } return new BoundingBox({ min: boundingBox.topLeft, max: V3.add(boundingBox.topLeft, [boundingBox.width, boundingBox.height, boundingBox.depth]), }); }frontend/javascripts/libs/mjs.ts (1)
223-226
: Consider adding type safety to the identity matrix.The implementation is correct, but consider adding type checking to ensure the returned identity matrix matches the expected
Matrix4x4
type.identity(): Matrix4x4 { - return BareM4x4.identity; + const identityMatrix = BareM4x4.identity; + if (!(identityMatrix instanceof Float32Array) && !Array.isArray(identityMatrix)) { + throw new Error('Invalid identity matrix type'); + } + return identityMatrix; }frontend/javascripts/oxalis/constants.ts (1)
18-19
: LGTM! Consider enhancing the type documentation.The
NestedMatrix4
type addition is well-structured and appropriate for handling affine transformations.Consider enhancing the documentation to include:
- Usage examples
- Relationship with affine transformations
- Expected matrix structure (e.g., rotation, translation components)
-export type NestedMatrix4 = [Vector4, Vector4, Vector4, Vector4]; // Represents a row major matrix. +/** + * Represents a 4x4 row-major matrix used for affine transformations. + * Structure: + * [ + * [m11, m12, m13, m14], // First row: rotation + scale + * [m21, m22, m23, m24], // Second row: rotation + scale + * [m31, m32, m33, m34], // Third row: rotation + scale + * [m41, m42, m43, m44] // Fourth row: translation + perspective + * ] + */ +export type NestedMatrix4 = [Vector4, Vector4, Vector4, Vector4];frontend/javascripts/oxalis/model_initialization.ts (2)
Line range hint
481-508
: Consider adding error handling for the coordinate transformation logic.While the conditional logic for applying transformations is sound, consider adding error handling for edge cases:
- When
originalLayers
is empty- When the first layer's transformations are undefined
- When fallback layer's transformations are invalid
const allLayersSameRotation = doAllLayersHaveTheSameRotation(originalLayers); +if (originalLayers.length === 0) { + return {}; +} let coordinateTransformsMaybe = {}; if (allLayersSameRotation) { + if (!originalLayers[0]?.coordinateTransformations) { + console.warn('First layer transformations undefined, using identity matrix'); + return {}; + } coordinateTransformsMaybe = { coordinateTransformations: originalLayers?.[0].coordinateTransformations, }; } else if (fallbackLayer?.coordinateTransformations) { + try { coordinateTransformsMaybe = { coordinateTransformations: fallbackLayer.coordinateTransformations, }; + } catch (err) { + console.error('Failed to apply fallback layer transformations:', err); + return {}; + } }
856-881
: Simplify the nativelyRenderedLayerName validation logic.The current nested conditions can be simplified for better readability while maintaining the same functionality.
- 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 { nativelyRenderedLayerName } = originalDatasetSettings; + if (!nativelyRenderedLayerName) { + return initialDatasetSettings; + } + + const isLayerInDataset = dataset.dataSource.dataLayers.some( + (layer) => + layer.name === nativelyRenderedLayerName || + (layer.category === "segmentation" && layer.fallbackLayer === nativelyRenderedLayerName) + ); + + const isLayerInAnnotation = annotation?.annotationLayers.some( + (layer) => layer.name === nativelyRenderedLayerName + ); + + if (!isLayerInDataset && !isLayerInAnnotation) { + initialDatasetSettings.nativelyRenderedLayerName = null; + }frontend/javascripts/test/reducers/flycam_reducer.spec.ts (1)
39-39
: Consider adding tests for dataset layer rotation interactionsGiven that this PR introduces dataset layer rotation features, consider adding test cases that verify:
- Flycam behavior when interacting with rotated dataset layers
- Transformation matrix calculations with layer rotation applied
- Edge cases when switching between rotated and non-rotated views
Would you like me to help draft these additional test cases?
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (32)
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
(3 hunks)frontend/javascripts/dashboard/dataset/dataset_settings_view.tsx
(3 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/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/accessors/dataset_accessor.ts
(1 hunks)frontend/javascripts/oxalis/model/accessors/dataset_layer_rotation_accessor.ts
(1 hunks)frontend/javascripts/oxalis/model/accessors/flycam_accessor.ts
(2 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/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 (4)
- frontend/javascripts/oxalis/model/helpers/nml_helpers.ts
- frontend/javascripts/oxalis/store.ts
- frontend/javascripts/dashboard/dataset/dataset_settings_viewconfig_tab.tsx
- frontend/javascripts/oxalis/api/api_latest.ts
🔇 Additional comments (30)
frontend/javascripts/dashboard/dataset/dataset_rotation_form_item.tsx (2)
72-105
:
Add 'dataLayers' and 'datasetBoundingBox' to useCallback dependency array
The setMatrixRotationsForAllLayer
function uses dataLayers
and datasetBoundingBox
obtained from form.getFieldValue
and getDatasetBoundingBoxFromLayers(dataLayers)
. To ensure the callback updates correctly when these values change, include them in the dependency array.
Apply this diff to update the dependencies:
}, [
axis,
form,
+ dataLayers,
+ datasetBoundingBox,
]);
Likely invalid or redundant comment.
47-70
:
Include 'rotationValues' in useEffect dependency array
The rotationValues
variable is derived from form.getFieldValue(["datasetRotation"])
and is used within the useEffect
hook. To ensure that the effect runs whenever rotationValues
change, consider including rotationValues
in the dependency array.
Apply this diff to include rotationValues
in the dependency array:
}, [
datasetBoundingBox,
dataLayers,
form,
+ rotationValues,
]);
Likely invalid or redundant comment.
frontend/javascripts/admin/dataset/composition_wizard/04_configure_new_dataset.tsx (1)
Line range hint 223-225
: Check for empty 'layersWithTransforms' before proceeding
After the withTransforms
function, ensure that layersWithTransforms
is not empty before proceeding to prevent potential runtime errors.
frontend/javascripts/oxalis/view/left-border-tabs/layer_settings_tab.tsx (1)
Line range hint 1185-1235
: Ensure consistent rendering of 'TransformationIcon' in 'getSkeletonLayer'
When rendering the TransformationIcon
within the getSkeletonLayer
method, verify that the layer
prop is correctly passed and that the skeleton layer supports transformations if applicable.
frontend/javascripts/types/schemas/dataset_view_configuration.schema.ts (1)
152-154
:
Add validation constraints for nativelyRenderedLayerName
The schema allows any string value for nativelyRenderedLayerName
without validation. This could lead to runtime errors if invalid layer names are provided.
Consider adding these constraints:
nativelyRenderedLayerName: {
type: "string",
+ minLength: 1,
+ pattern: "^[a-zA-Z0-9_-]+$"
},
Let's verify the layer name usage:
frontend/javascripts/oxalis/geometries/materials/edge_shader.ts (1)
14-14
: Verify transformation consistency after module reorganization
The getTransformsForSkeletonLayer
import has been moved to a new module. While the change improves code organization, we need to ensure transformation calculations remain consistent.
Let's verify the transformation logic:
✅ Verification successful
Based on the search results, I can now generate the final response:
Import change for getTransformsForSkeletonLayer is safe and consistent
The function is used consistently across the codebase:
- In edge_shader.ts and node_shader.ts for shader transformations
- In nml_helpers.ts for NML transformations
- In skeletontracing_accessor.ts for node position transformations
All usages correctly handle the transformation matrix and follow the same pattern of accessing the dataset and natively rendered layer name from the state.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Find all usages of getTransformsForSkeletonLayer to ensure consistent behavior
rg "getTransformsForSkeletonLayer" -A 5
# Look for related transformation functions
ast-grep --pattern 'function getTransformsFor$_($_, $_) {
$$$
}'
Length of output: 7571
frontend/javascripts/oxalis/model/helpers/transformation_helpers.ts (1)
5-5
: LGTM: Type imports and function signature changes enhance type safety.
The changes improve type safety by introducing the NestedMatrix4
type and updating function signatures accordingly.
Also applies to: 7-9
frontend/javascripts/oxalis/model/sagas/dataset_saga.ts (1)
15-18
: LGTM: Import restructuring improves code organization.
Moving transformation-related functions to a dedicated accessor module enhances code organization and maintainability.
frontend/javascripts/oxalis/model/bucket_data_handling/bounding_box.ts (1)
8-8
: LGTM: New type import.
The import of BoundingBoxObject
type is appropriate for the new functionality.
frontend/javascripts/oxalis/model/accessors/skeletontracing_accessor.ts (2)
26-30
: LGTM: Import changes align with new transformation module
The imports have been updated to use the new transformation helpers and layer rotation accessor, improving code organization.
221-224
: Verify transformation changes across the codebase
The refactoring of transformation logic looks correct, but we should verify its usage across the codebase.
Also applies to: 234-236, 241-243
✅ Verification successful
Let me gather more information about the transformation functions to ensure completeness of the verification.
Transformation functions are correctly used across the codebase
The verification shows that:
getTransformsForLayerWithoutTransformationConfigOrNull
is used consistently inskeletontracing_accessor.ts
anddataset_layer_rotation_accessor.ts
getTransformsForSkeletonLayer
is used in shader-related files and NML helpers- All transformation function calls follow the same parameter pattern (dataset and nativelyRenderedLayerName)
- No references to deprecated transformation functions were found
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Check for any remaining references to old transformation functions
rg -l "getTransformsForSkeletonLayerOrNull"
# Verify all transformation function calls
ast-grep --pattern 'getTransformsForSkeletonLayer($_, $_)'
Length of output: 1980
Script:
#!/bin/bash
# Check for all transformation-related function calls
ast-grep --pattern 'getTransformsForLayerWithoutTransformationConfigOrNull($_, $_)'
# Search for any other transformation-related functions
rg -l "getTransformsFor.*Layer"
Length of output: 2028
frontend/javascripts/oxalis/model/reducers/flycam_reducer.ts (1)
105-105
: LGTM: Fixed matrix initialization
The change from M4x4.identity
to M4x4.identity()
ensures a new matrix instance is created, preventing potential shared reference issues.
frontend/javascripts/oxalis/model/bucket_data_handling/layer_rendering_manager.ts (1)
33-36
: LGTM! Clean refactoring of transformation functions.
The relocation of transformation-related functions to a dedicated accessor module improves code organization and maintainability.
frontend/javascripts/oxalis/geometries/materials/node_shader.ts (1)
17-17
: Verify skeleton layer transformations.
The import change aligns with the refactoring, but given the PR's mention of skeleton layer compatibility with transformations, we should verify that all skeleton layer transformations work correctly.
✅ Verification successful
Let me gather more information about the transformation usage to better understand the impact.
Based on the search results, I can now generate the final response:
Import change for skeleton layer transformations is correctly implemented
The getTransformsForSkeletonLayer
function is properly used across the codebase:
- Used in shader materials (node_shader.ts, edge_shader.ts) for rendering transformations
- Integrated with NML helpers and skeleton tracing accessors
- Proper error handling exists for transformed skeleton layers in proofreading functionality
The import change aligns with the existing usage patterns and maintains the expected transformation behavior.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check for skeleton layer transformation usage and potential issues.
# Search for skeleton layer transformation tests
rg -A 5 "test.*skeleton.*transform|test.*transform.*skeleton"
# Search for skeleton layer transformation error handling
rg -A 5 "catch.*skeleton.*transform|error.*skeleton.*transform"
# Search for skeleton layer transformation edge cases
ast-grep --pattern $'getTransformsForSkeletonLayer($_, $_) {
$$$
if ($cond) {
$$$
}
$$$
}'
Length of output: 1419
Script:
#!/bin/bash
# Search for usages of getTransformsForSkeletonLayer
rg -A 5 "getTransformsForSkeletonLayer"
# Search for skeleton layer transformation related code
ast-grep --pattern 'class $_ {
$$$
transform($_, $_) {
$$$
}
$$$
}'
# Check for any other transformation related imports
rg "import.*transform.*dataset_layer_rotation"
Length of output: 7633
frontend/javascripts/oxalis/model/accessors/tool_accessor.ts (1)
10-11
: LGTM! Verify refactoring impact.
The import changes appropriately reflect the reorganization of transformation-related functions into a dedicated accessor module, which aligns with the PR's objective of supporting dataset layer rotations.
✅ Verification successful
Import changes are correctly refactored across the codebase
The verification shows that the transformation-related functions have been consistently moved to dataset_layer_rotation_accessor
. There are no remaining imports from the old location in dataset_accessor
, and the new import pattern is used consistently across 11 different files in the codebase.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify that all transformation-related imports are consistently updated across the codebase
# Check for any remaining old imports
rg "import.*getTransformsPerLayer.*dataset_accessor"
# Verify the new import pattern is used consistently
rg "import.*dataset_layer_rotation_accessor"
Length of output: 1818
frontend/javascripts/oxalis/merger_mode.ts (1)
10-11
: LGTM! Verify transformation handling in merger mode.
The import changes ensure proper handling of layer transformations in merger mode, maintaining correct coordinate mapping through inverse transformations.
✅ Verification successful
LGTM! Transformation handling in merger mode is correctly implemented.
The code shows proper usage of the imported transformation functions:
getInverseSegmentationTransformer
is used in two places (lines 203 and 370) to correctly transform node positions before looking up segment IDs- The transformation flow maintains coordinate mapping integrity:
- Node positions are first transformed to rendered coordinates
- Inverse segmentation transform is applied to get the correct lookup position
- Segment IDs are retrieved using the transformed coordinates
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify that merger mode correctly handles transformed layers
# Check for proper transformation handling in merger mode
rg -A 5 "getInverseSegmentationTransformer.*merger_mode"
# Verify no direct access to transformation functions from dataset_accessor
rg "import.*getTransformsPerLayer.*dataset_accessor.*merger_mode"
Length of output: 126
Script:
#!/bin/bash
# Let's search for usage of the imported functions in merger_mode.ts
ast-grep --pattern 'getInverseSegmentationTransformer($$$)'
# Also check for any usage of getVisibleSegmentationLayer
ast-grep --pattern 'getVisibleSegmentationLayer($$$)'
# Let's see the full content of merger_mode.ts to understand the context better
cat frontend/javascripts/oxalis/merger_mode.ts
Length of output: 20557
frontend/javascripts/oxalis/controller/scene_controller.ts (3)
49-49
: LGTM: Clean import addition
The import of getTransformsForLayerOrNull
is well-placed and follows the module structure.
Line range hint 408-411
: LGTM: Improved null check for dataLayers
The null check for dataLayers
has been improved to prevent potential runtime errors when filtering layer names.
Line range hint 203-225
: LGTM: Well-implemented transformation logic
The transformation application logic is well-structured:
- Correctly retrieves transformations for each layer
- Properly applies the affine transformation matrix using Three.js Matrix4
- Maintains the layer visibility state
frontend/javascripts/dashboard/dataset/dataset_settings_data_tab.tsx (2)
36-36
: LGTM: Clean import addition
The import of AxisRotationSettingForDataset
is well-placed and follows the module structure.
271-276
: LGTM: Well-structured UI layout
The rotation settings UI is well-integrated:
- Follows Ant Design's Row/Col pattern for responsive layout
- Appropriate placement within the form structure
- Clear separation of concerns with dedicated column for rotation settings
frontend/javascripts/dashboard/dataset/dataset_settings_view.tsx (3)
46-50
: LGTM: Clean type imports and declarations
The imports and type declarations are well-organized and properly documented.
84-84
: LGTM: Clean type extension
The FormData type is appropriately extended with the optional datasetRotation field.
203-225
: LGTM: Well-implemented rotation settings initialization
The rotation settings initialization logic is robust:
- Properly checks for consistent rotation across layers
- Handles missing transformations gracefully with default values
- Correctly extracts rotation values from transformation matrices
- Clear comments explaining the transformation sequence
frontend/javascripts/oxalis/model/accessors/flycam_accessor.ts (1)
196-196
: LGTM! Proper initialization of identity matrix.
The change correctly ensures that M4x4.identity()
is called as a function rather than accessing it as a property.
frontend/javascripts/oxalis/model/sagas/quick_select_heuristic_saga.ts (1)
61-61
: LGTM! Improved module organization.
The import has been correctly updated to use the new dedicated dataset_layer_rotation_accessor
module, which better organizes transformation-related functions.
frontend/javascripts/types/api_flow_types.ts (2)
63-73
: Well-structured type definitions for transformations.
The separation of transformation types and explicit documentation of matrix storage order (row-major) improves type safety and prevents potential matrix operation errors.
99-100
: Clear documentation for APISkeletonLayer name property.
The comment effectively explains that the name should be the skeleton tracing ID to ensure uniqueness.
frontend/javascripts/oxalis/geometries/materials/plane_material_factory.ts (1)
53-57
: Good modularization of transformation-related functions.
Moving transformation functions to a dedicated accessor module improves code organization and maintainability.
frontend/javascripts/test/reducers/flycam_reducer.spec.ts (1)
39-39
: Good change: Ensures proper matrix initialization
The change from M4x4.identity
to M4x4.identity()
ensures each test gets a fresh identity matrix instance, preventing potential mutation issues between tests.
frontend/javascripts/dashboard/dataset/dataset_rotation_form_item.tsx
Outdated
Show resolved
Hide resolved
frontend/javascripts/oxalis/model/accessors/dataset_layer_rotation_accessor.ts
Outdated
Show resolved
Hide resolved
frontend/javascripts/oxalis/model/accessors/dataset_layer_rotation_accessor.ts
Outdated
Show resolved
Hide resolved
frontend/javascripts/oxalis/view/left-border-tabs/layer_settings_tab.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this 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
♻️ Duplicate comments (1)
frontend/javascripts/dashboard/dataset/dataset_rotation_form_item.tsx (1)
48-71
:⚠️ Potential issueAdd cleanup to useEffect and handle race conditions.
The useEffect hook needs cleanup and race condition handling.
useEffect(() => { if ( datasetBoundingBox == null || dataLayers[0].coordinateTransformations?.length !== EXPECTED_TRANSFORMATION_LENGTH || !form ) { return; } + let isSubscribed = true; 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) => ({ ...layer, coordinateTransformations: transformations, })); + if (isSubscribed) { form.setFieldValue(["dataSource", "dataLayers"], dataLayersWithUpdatedTransforms); + } + return () => { + isSubscribed = false; + }; }, [datasetBoundingBox, dataLayers, form]);
🧹 Nitpick comments (2)
frontend/javascripts/dashboard/dataset/dataset_rotation_form_item.tsx (2)
25-36
: Consider using reduce for better readability and performance.The function could be refactored to use
reduce
for a more functional and potentially more performant approach.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; + return layers.reduce((acc, layer) => { + const layerBox = BoundingBox.fromBoundBoxObject(layer.boundingBox); + return acc ? acc.extend(layerBox) : layerBox; + }, undefined as BoundingBox | undefined); }
157-182
: Extract tooltip content and improve error message.The tooltip content could be extracted for reusability and the error message could be more concise.
+const ROTATION_REQUIREMENTS = { + TITLE: "Setting a dataset's rotation requires consistent layer transformations", + STEPS: [ + "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", + ], + RESOLUTION: "To enable this setting, remove all coordinateTransformations from all layers in the advanced tab, save and reload the dataset settings.", +} as const; if (!isRotationOnly) { return ( <Tooltip title={ <div> - Each layers transformations must be equal and each layer needs exactly 5 affine - transformation with the following schema: + Each layer must have exactly 5 identical affine transformations in this order: <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> + {ROTATION_REQUIREMENTS.STEPS.map((step, index) => ( + <li key={index}>{step}</li> + ))} </ul> - To easily enable this setting, delete all coordinateTransformations of all layers in the - advanced tab, save and reload the dataset settings. + {ROTATION_REQUIREMENTS.RESOLUTION} </div> } > <Text type="secondary"> - Setting a dataset's rotation is only supported when all layers have the same rotation - transformation. <InfoCircleOutlined /> + {ROTATION_REQUIREMENTS.TITLE} <InfoCircleOutlined /> </Text> </Tooltip> ); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
frontend/javascripts/dashboard/dataset/dataset_rotation_form_item.tsx
(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: circleci_build
🔇 Additional comments (1)
frontend/javascripts/dashboard/dataset/dataset_rotation_form_item.tsx (1)
1-24
: LGTM! Well-organized imports and type definitions.The imports are properly organized, and the type definitions are clear and well-structured.
Hmm, still doesn't work for me? I tested it here: https://allowpermadatasetrotation.webknossos.xyz/datasets/l4_sample-677e47bc010000b800249ffc/view#3584,3584,1024,0,1.3 |
There was a problem hiding this 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 (2)
frontend/javascripts/oxalis/model.ts (2)
229-233
: Consider caching the state and layer lookup.The state retrieval and layer lookup are performed for every mouse position update, which could impact performance during rapid mouse movements. Consider caching these values and updating them only when the state changes.
+ // Cache these values and update them only when state changes const state = Store.getState(); const additionalCoordinates = state.flycam.additionalCoordinates; const segmentationDataLayer = state.dataset.dataSource.dataLayers.find( (dataLayer) => dataLayer.name === segmentationLayer.name, );
234-243
: Add error handling and type safety improvements.The transformation logic could benefit from:
- Error logging for transformation failures
- Explicit type annotation for the transformed position
if (segmentationDataLayer) { const segmentationLayerTransforms = getTransformsForLayerOrNull( state.dataset, segmentationDataLayer, state.datasetConfiguration.nativelyRenderedLayerName, ); if (segmentationLayerTransforms) { - pos = transformPointUnscaled(invertTransform(segmentationLayerTransforms))(pos); + try { + const transformedPos: Vector3 = transformPointUnscaled( + invertTransform(segmentationLayerTransforms) + )(pos); + pos = transformedPos; + } catch (error) { + console.error('Failed to transform position:', error); + // Continue with untransformed position + } } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
CHANGELOG.unreleased.md
(1 hunks)frontend/javascripts/oxalis/model.ts
(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- CHANGELOG.unreleased.md
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: circleci_build
🔇 Additional comments (1)
frontend/javascripts/oxalis/model.ts (1)
24-25
: LGTM! Clear and well-organized imports.The new transformation-related imports are appropriately placed and follow the project's module structure.
Oh well. Thanks for finding this. I still had a little logic bug (I applied the segmentation layer's transform instead of the inverse of the transform). The newest commit 3c06db5 should fix this. ™️ Testing for me worked because I only had a 180 degree rotation for x configured and in that case applying the same rotation again instead of the inverse seems to result in the correct behaviour. As your test case was more complex and it worked for me locally with these settings, this should hopefully be fixed now :) |
Great! Unfortunately, the context menu uses the wrong position apparently (e.g., load mesh or the segment id in the context menu footer is wrong). Can you have another look? 🙈 |
…ow-perma-dataset-rotation
While working on this I also noticed that bounding boxes are not rotated 🙈? Should this be included here as well or in a follow up pr? |
Same for meshes 🙈 |
Bummer! The layer bboxes are able to transform according to the active layer transform. user bounding boxes probably need to use the same transforms as the skeleton layer. meshes should use the same transforms as their segmentation layer. |
…ransform configured
…ow-perma-dataset-rotation
The issue is here: #8340 And I found a but that toggling the skeleton layer crashed wk. I fixed this by disabling toggling skeleton layer / letting the user configure them as the active native layer. The mini discussion about this can be found here: https://scm.slack.com/archives/C083DBRCFC6/p1737378159151899 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🔭 Outside diff range comments (2)
frontend/javascripts/oxalis/view/context_menu.tsx (2)
Line range hint
1447-1467
: Improve error handling in getContextMenuPositionFromEvent.The function returns [0, 0] when no valid reference div is found, which could lead to incorrect menu positioning and a confusing user experience. Consider throwing an error or logging a warning in this case.
- if (referenceDiv == null) { - return [0, 0]; - } + if (referenceDiv == null) { + console.warn('No valid reference div found for context menu positioning'); + // Return a position that ensures the menu is visible within the viewport + return [event.clientX, event.clientY]; + }
Line range hint
186-205
: Add input sanitization for clipboard operations.The function writes values directly to clipboard without sanitization, which could be a security risk if the values contain malicious code.
+function sanitizeClipboardText(value: string | number): string { + // Convert to string and remove any potentially harmful characters + return String(value).replace(/[<>]/g, ''); +} function copyIconWithTooltip(value: string | number, title: string) { return ( <FastTooltip title={title}> <CopyOutlined style={{ margin: "0 0 0 5px", }} onClick={async () => { - await navigator.clipboard.writeText(value.toString()); + const sanitizedValue = sanitizeClipboardText(value); + await navigator.clipboard.writeText(sanitizedValue); Toast.success(`"${value}" copied to clipboard`); }} /> </FastTooltip> ); }
🧹 Nitpick comments (10)
frontend/javascripts/oxalis/view/context_menu.tsx (1)
Line range hint
1284-1288
: Optimize performance by memoizing the refresh handler.The
handleRefreshSegmentVolume
function is recreated on every render. Consider usinguseCallback
to memoize it.- const handleRefreshSegmentVolume = async () => { - await api.tracing.save(); - setLastTimeSegmentInfoShouldBeFetched(new Date()); - }; + const handleRefreshSegmentVolume = useCallback(async () => { + await api.tracing.save(); + setLastTimeSegmentInfoShouldBeFetched(new Date()); + }, []);frontend/javascripts/oxalis/controller/combinations/volume_handlers.ts (2)
86-91
: Consider caching transformed positions for async operations.While the position transformation is correctly implemented, consider caching the transformed position if it's used frequently in async operations to avoid redundant calculations.
+ // Cache the transformed position if used frequently const posInLayerSpace = memoizeOne( (globalPos: Vector3, layerName: string) => globalToLayerTransformedPosition( globalPos, layerName, "segmentation", Store.getState(), ), ([pos1, name1], [pos2, name2]) => V3.isEqual(pos1, pos2) && name1 === name2, );
Also applies to: 97-97, 102-102, 107-107
Line range hint
55-107
: Architecture feedback: Position transformation implementation aligns with rotation feature.The implementation of position transformation for rotated layers is well-structured and maintains consistency across both synchronous and asynchronous operations. The approach:
- Cleanly separates transformation logic into a dedicated accessor
- Consistently applies transformations before data access
- Preserves existing functionality while adding rotation support
This aligns well with the PR's objective of supporting dataset layer rotation.
frontend/javascripts/oxalis/model/accessors/dataset_layer_transformation_accessor.ts (3)
34-44
: Add JSDoc comments for constants.Consider adding JSDoc comments to document the purpose and structure of
IDENTITY_MATRIX
andIDENTITY_TRANSFORM
constants. This would improve code maintainability and help other developers understand their usage.+/** + * 4x4 identity matrix in nested format. + * Used as a default transformation matrix. + */ const IDENTITY_MATRIX = [ [1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1], ] as NestedMatrix4; +/** + * Default coordinate transformation using identity matrix. + * Represents no transformation (identity transformation). + */ export const IDENTITY_TRANSFORM: CoordinateTransformation = { type: "affine", matrix: IDENTITY_MATRIX, };
115-127
: Optimize matrix rotation performance.The current implementation creates new THREE.js objects for each rotation. Consider caching these objects to improve performance, especially when called frequently.
+// Cache THREE.js objects for better performance +const cachedEuler = new THREE.Euler(); +const cachedRotationMatrix = new THREE.Matrix4(); + 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 + cachedEuler[axis] = angleInRadians; + cachedRotationMatrix.makeRotationFromEuler(cachedEuler).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) }; }
404-435
: Optimize rotation validation performance.The
_doAllLayersHaveTheSameRotation
function performs multiple deep comparisons and matrix operations, which could be optimized:
- Cache intermediate results
- Early exit on validation failures
- Reduce redundant checks
function _doAllLayersHaveTheSameRotation(dataLayers: Array<APIDataLayer>): boolean { + if (dataLayers.length <= 1) { + return true; + } + 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, ); } + + // Early validation to avoid unnecessary processing + if (!hasValidTransformationCount(dataLayers) || !hasOnlyAffineTransformations(dataLayers)) { + return false; + } + + if (!hasValidTransformationPattern(firstDataLayerTransformations)) { + return false; + } + + // Cache the first layer's transformations for comparison + const referenceTransforms = firstDataLayerTransformations.map(t => JSON.stringify(t)); + 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]) - ) { + if (!transformations) { return false; } + // Compare stringified versions to avoid deep comparisons + for (let j = 0; j < EXPECTED_TRANSFORMATION_LENGTH; j++) { + if (JSON.stringify(transformations[j]) !== referenceTransforms[j]) { + return false; + } + } } return true; }frontend/javascripts/oxalis/view/left-border-tabs/layer_settings_tab.tsx (4)
228-229
: Consider improving the comment clarity.The comment could be more concise and clearer.
- // 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) . + // Reset nativelyRenderedLayerName if layer is natively rendered or lacks custom transforms (e.g., skeleton layer)
230-244
: Consider extracting transformation logic to a separate function.The transformation toggle logic is complex and would benefit from being extracted into a dedicated function for better maintainability.
+ const calculateTransformationChange = ( + state: OxalisState, + layer: APIDataLayer | APISkeletonLayer, + nextNativelyRenderedLayerName: string | null + ) => { + const activeTransformation = getTransformsForLayer( + state.dataset, + layer, + state.datasetConfiguration.nativelyRenderedLayerName, + ); + const nextTransform = getTransformsForLayer( + state.dataset, + layer, + nextNativelyRenderedLayerName, + ); + return getNewPositionAndZoomChangeFromTransformationChange( + activeTransformation, + nextTransform, + state, + ); + }; const toggleLayerTransforms = () => { const state = Store.getState(); const nextNativelyRenderedLayerName = isRenderedNatively ? null : layer.name; - const activeTransformation = getTransformsForLayer( - state.dataset, - layer, - state.datasetConfiguration.nativelyRenderedLayerName, - ); - const nextTransform = getTransformsForLayer( - state.dataset, - layer, - nextNativelyRenderedLayerName, - ); - const { scaleChange, newPosition } = getNewPositionAndZoomChangeFromTransformationChange( - activeTransformation, - nextTransform, - state, - ); + const { scaleChange, newPosition } = calculateTransformationChange( + state, + layer, + nextNativelyRenderedLayerName + );
252-260
: Consider moving styles to CSS.The inline styles should be moved to a CSS file for better maintainability and reusability.
+// Add to CSS file: +.transformation-icon { + width: 14px; + height: 14px; + margin-bottom: 4px; + margin-right: 5px; +} + +.transformation-icon--disabled { + cursor: not-allowed; + opacity: 0.5; +} + +.transformation-icon--enabled { + cursor: pointer; + opacity: 1.0; +} - const style = { - width: 14, - height: 14, - marginBottom: 4, - marginRight: 5, - ...(isDisabled - ? { cursor: "not-allowed", opacity: "0.5" } - : { cursor: "pointer", opacity: "1.0" }), - }; + const className = `transformation-icon ${ + isDisabled ? 'transformation-icon--disabled' : 'transformation-icon--enabled' + }`;
266-270
: Simplify the tooltip text construction.The template literal could be simplified for better readability.
- 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.${isDisabled ? "" : " Click to render this layer without any transforms."}` + const baseText = isRenderedNatively + ? "This layer is shown natively (i.e., without any transformations)" + : `This layer is rendered with ${typeToLabel[transform.type]} transformation`; + const actionText = isDisabled + ? "" + : isRenderedNatively + ? " Click to render this layer with its configured transforms" + : " Click to render this layer without any transforms"; + `${baseText}.${actionText}`
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
CHANGELOG.unreleased.md
(1 hunks)frontend/javascripts/oxalis/api/api_latest.ts
(1 hunks)frontend/javascripts/oxalis/controller/combinations/volume_handlers.ts
(3 hunks)frontend/javascripts/oxalis/model.ts
(2 hunks)frontend/javascripts/oxalis/model/accessors/dataset_layer_transformation_accessor.ts
(1 hunks)frontend/javascripts/oxalis/model/accessors/skeletontracing_accessor.ts
(3 hunks)frontend/javascripts/oxalis/model/bucket_data_handling/bounding_box.ts
(2 hunks)frontend/javascripts/oxalis/model/helpers/nml_helpers.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)
🚧 Files skipped from review as they are similar to previous changes (6)
- frontend/javascripts/oxalis/model/helpers/nml_helpers.ts
- frontend/javascripts/oxalis/model.ts
- frontend/javascripts/oxalis/model/bucket_data_handling/bounding_box.ts
- frontend/javascripts/oxalis/model/accessors/skeletontracing_accessor.ts
- frontend/javascripts/oxalis/api/api_latest.ts
- CHANGELOG.unreleased.md
🔇 Additional comments (9)
frontend/javascripts/oxalis/view/context_menu.tsx (1)
1050-1050
: LGTM: Proper handling of transformed layers.The code correctly disables node creation when the skeleton layer is transformed, which aligns with the PR objectives for dataset layer rotation functionality.
frontend/javascripts/oxalis/controller/combinations/volume_handlers.ts (3)
5-5
: LGTM! Import statement aligns with layer transformation requirements.The addition of
globalToLayerTransformedPosition
import supports the new dataset layer rotation feature.
66-66
: LGTM! Consistent usage of transformed positions.The code consistently uses the transformed position (
posInLayerSpace
) across all operations, maintaining correctness when working with rotated layers.Also applies to: 69-69
55-60
: Verify position transformation in segmentation operations.The transformation of global positions to layer space is crucial for correct segment ID lookup when layers are rotated. However, we should verify that this transformation is consistently applied across all segmentation operations.
✅ Verification successful
Position transformation is consistently applied in segmentation operations
The transformation of global positions to layer space is correctly and consistently implemented across all segmentation operations in the codebase. The reviewed changes align with the existing pattern where
globalToLayerTransformedPosition
is called before accessing segmentation data.🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Search for other segmentation-related operations that might need position transformation rg -l 'segmentationCube\.getMappedDataValue|getRenderedZoomStepAtPosition' --type ts # Search for direct usage of global positions that might need transformation ast-grep --pattern 'globalPos' --lang tsLength of output: 2781
Script:
#!/bin/bash # Check segmentation operations in api_latest.ts rg -A 5 'segmentationCube\.getMappedDataValue|getRenderedZoomStepAtPosition' frontend/javascripts/oxalis/api/api_latest.ts # Check implementation of segmentation operations in volume_handlers.ts rg -A 5 'segmentationCube\.getMappedDataValue|getRenderedZoomStepAtPosition' frontend/javascripts/oxalis/controller/combinations/volume_handlers.ts # Check for layer transformation usage rg -A 3 'globalToLayerTransformedPosition' --type tsLength of output: 3613
frontend/javascripts/oxalis/model/accessors/dataset_layer_transformation_accessor.ts (1)
74-98
: 🛠️ Refactor suggestionEnhance rotation handling precision and error handling.
The rotation calculation has several areas for improvement:
- Magic number
1e-6
should be defined as a constant- Edge cases in angle calculation need better handling
- Function lacks input validation
Apply this diff to improve the implementation:
+const ROTATION_EPSILON = 1e-6; + export function getRotationFromTransformationIn90DegreeSteps( transformation: CoordinateTransformation | undefined, axis: "x" | "y" | "z", ) { + if (axis == null) { + throw new Error("Axis must be specified"); + } + 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]]; + + // Handle edge cases where both sine and cosine are close to zero + if (Math.abs(sinOfAngle) < ROTATION_EPSILON && Math.abs(cosOfAngle) < ROTATION_EPSILON) { + return 0; + } + const rotation = - Math.abs(cosOfAngle) > 1e-6 // Avoid division by zero + Math.abs(cosOfAngle) > ROTATION_EPSILON ? Math.atan2(sinOfAngle, cosOfAngle) : sinOfAngle > 0 ? Math.PI / 2 : -Math.PI / 2;Likely invalid or redundant comment.
frontend/javascripts/oxalis/view/left-border-tabs/layer_settings_tab.tsx (4)
55-68
: LGTM! Well-organized imports for transformation support.The reorganization of imports improves code organization by:
- Centralizing transformation-related functions in a dedicated accessor
- Adding necessary flycam-related functions for position/zoom handling
198-201
: LGTM! Clear layer transformation capability checks.The checks for layer transformation support are well-implemented and clearly separated.
1191-1191
: Verify skeleton layer transformation behavior.Please ensure that skeleton layer transformations work correctly with the new transformation system, particularly when toggling between native and transformed rendering.
Also applies to: 1241-1241
✅ Verification successful
Skeleton layer transformation implementation verified.
The codebase shows proper integration between skeleton layer visibility controls and the transformation system, with dedicated state management, transformation accessors, and shader support for both native and transformed rendering.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Check for potential issues with skeleton layer transformations ast-grep --pattern 'getTransformsForLayer($_, { category: "skeleton" }, $_)'Length of output: 78
Script:
#!/bin/bash # Search for skeleton-related transformation code echo "=== Searching for skeleton transformation implementations ===" ast-grep --pattern 'transform($_) { $$$' echo "=== Checking for skeleton-specific layer handling ===" rg -l "skeleton.*transform|transform.*skeleton" --type typescript echo "=== Looking for transformation toggle logic ===" rg -l "toggleTransform|setTransform.*skeleton" --type typescriptLength of output: 602
Script:
#!/bin/bash # Search for transformation-related code in TS files echo "=== Searching for transformation implementations ===" fd -e ts -e tsx -x rg -l "transform.*skeleton|skeleton.*transform" echo "=== Checking layer settings implementations ===" fd -e ts -e tsx -x rg -l "showSkeletons.*transform|transform.*layer" echo "=== Looking for toggle/state management code ===" fd "layer_settings_tab.tsx" -x rg -A 5 "showSkeletons"Length of output: 2648
314-315
: Verify bounding box calculations with transformations.Ensure that bounding box calculations correctly handle transformed layers.
✅ Verification successful
Bounding box calculations correctly handle transformations
The implementation properly handles transformed layers by:
- Applying appropriate matrix transformations to coordinates
- Providing clear UI indicators for transformation state
- Supporting both affine and thin-plate-spline transformations
- Maintaining correct coordinate systems for display
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Check bounding box handling with transformations ast-grep --pattern 'layer.boundingBox.topLeft'Length of output: 912
Script:
#!/bin/bash # Search for layer transformation and bounding box related code echo "=== Searching for layer transformation code ===" rg -l "transform.*layer|layer.*transform" --type typescript echo -e "\n=== Searching for bounding box calculations ===" ast-grep --pattern 'boundingBox = ' echo -e "\n=== Looking for matrix transformation utilities ===" rg -l "matrix.*transform|transform.*matrix" --type typescriptLength of output: 66111
Script:
#!/bin/bash # Search for bounding box and transformation related code in the specific file echo "=== Checking layer_settings_tab.tsx ===" cat frontend/javascripts/oxalis/view/left-border-tabs/layer_settings_tab.tsx echo -e "\n=== Searching for transformation related code without file type restrictions ===" rg "transform.*layer|layer.*transform" frontend/javascripts/oxalis/view/ echo -e "\n=== Searching for bounding box calculations ===" rg "boundingBox" frontend/javascripts/oxalis/view/Length of output: 68937
frontend/javascripts/oxalis/model/accessors/dataset_layer_transformation_accessor.ts
Show resolved
Hide resolved
frontend/javascripts/oxalis/model/accessors/dataset_layer_transformation_accessor.ts
Show resolved
Hide resolved
Moreover, the newest commits should hopefully fix all code locations that try to get the segmentation information based on a global position. Please give this another PR testing try and check my newest changes 🙏 I am sorry, that this PR takes so many iterations. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great! I found some other stuff but commented that in the follow-up issue, because it's not caused by this PR. let's merge and iterate from there :)
There was a problem hiding this 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 (2)
frontend/javascripts/oxalis/view/left-border-tabs/layer_settings_tab.tsx (2)
220-225
: Consider adding a comment explaining the complex condition.The disabled state logic is complex and would benefit from a more detailed explanation of the business rules.
- // 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. + // The transformation toggle is disabled in two cases: + // 1. The layer cannot have transformations (e.g., skeleton layers, volume tracing layers without fallback) + // 2. The layer is currently rendered natively (no transformation) and has no transforms configured + // Note: Layers without transformation support automatically inherit the dataset transformation + // when all other layers have the same transformation.
252-260
: Consider extracting style object to constants.The style object could be moved to a constants section at the top of the file for better maintainability.
+const TRANSFORMATION_ICON_STYLE = { + width: 14, + height: 14, + marginBottom: 4, + marginRight: 5, +}; + +const getTransformationIconStyle = (isDisabled: boolean) => ({ + ...TRANSFORMATION_ICON_STYLE, + ...(isDisabled + ? { cursor: "not-allowed", opacity: "0.5" } + : { cursor: "pointer", opacity: "1.0" }), +}); const style = getTransformationIconStyle(isDisabled);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
CHANGELOG.unreleased.md
(1 hunks)frontend/javascripts/oxalis/view/left-border-tabs/layer_settings_tab.tsx
(7 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- CHANGELOG.unreleased.md
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: circleci_build
🔇 Additional comments (5)
frontend/javascripts/oxalis/view/left-border-tabs/layer_settings_tab.tsx (5)
55-68
: LGTM! Clean reorganization of imports.The imports have been properly reorganized to support the new transformation functionality, with clear separation of concerns between dataset, layer transformation, and flycam accessors.
198-202
: LGTM! Clear and well-structured layer transformation checks.The hooks are properly used to determine transformation capabilities and configuration state.
771-772
: LGTM! Icon spacing fixed as noted in previous review.The icon margin has been properly adjusted to maintain consistent spacing.
1191-1191
: LGTM! Proper integration of transformation support for skeleton layers.The skeleton layer now correctly supports transformations while maintaining its existing functionality.
Also applies to: 1241-1241
228-244
: 🛠️ Refactor suggestionAdd error handling for transformation changes.
The transformation toggle logic should handle potential errors when retrieving or applying transforms.
const toggleLayerTransforms = () => { const state = Store.getState(); + try { // 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( state.dataset, layer, state.datasetConfiguration.nativelyRenderedLayerName, ); + if (!activeTransformation) { + throw new Error('Failed to get active transformation'); + } const nextTransform = getTransformsForLayer( state.dataset, layer, nextNativelyRenderedLayerName, ); + if (!nextTransform) { + throw new Error('Failed to get next transformation'); + } const { scaleChange, newPosition } = getNewPositionAndZoomChangeFromTransformationChange( activeTransformation, nextTransform, state, ); dispatch( updateDatasetSettingAction("nativelyRenderedLayerName", nextNativelyRenderedLayerName), ); dispatch(setPositionAction(newPosition)); dispatch(setZoomStepAction(state.flycam.zoomStep * scaleChange)); + } catch (error) { + console.error('Error toggling layer transforms:', error); + Toast.error('Failed to toggle layer transformation'); + } };Likely invalid or redundant comment.
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.
Steps to test:
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.nativelyRenderedLayerName
settings.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 thenativelyRenderedLayerName
setting should be automatically disregarded as the layer does not exist in this annotation.TODOs / Problems:
[ ] Design a visualization of the result. For some first ideas look at the issue #7334--> deferred / not plannednativelyRenderedLayerName
in model init if this layer does not exist. (Might happen when switching annotations)Problems:
Issues:
(Please delete unneeded items, merge only when none are left open)