Skip to content

Conversation

@philippotto
Copy link
Member

@philippotto philippotto commented Oct 27, 2025

This PR:

  • renames
    • the term "volume layer" to "section labeller" (at least in the context of section labelling) to avoid confusions with a normal volume layer
    • "add to layer" -> "add to contour list"
      • other renamings
  • ensures annotation geometries are also transformed in the scene (especially, the contour geometry)
  • refactors some transformation related accessors
  • refactors the SectionLabeler as well as the VoxelBuffer class
  • adds a TransformedSectionLabeler which is able to map the used viewport with a transform to another plane to achieve the rotation
    • I also added tests for that, but not all of them succeed yet
    • I want to fix the issue first that some layers are squished when viewed natively because the dataset scale is not respected properly

URL of deployed dev instance (used for testing):

  • https://___.webknossos.xyz

Steps to test:

  • test that volume annotation works as before
  • CI

Issues:


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

  • Added changelog entry (create a $PR_NUMBER.md file in unreleased_changes or use ./tools/create-changelog-entry.py) no user facing changes

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 27, 2025

📝 Walkthrough

Walkthrough

Replaces VolumeLayer with SectionLabeler/TransformedSectionLabeler and adds transform-aware plane mapping; renames ADD_TO_LAYER → ADD_TO_CONTOUR_LIST across actions/reducers/sagas/tests; applies segmentation edits in layer-space via a new helper; tightens SceneController visibility; removes Drawing smoothing APIs; fixes VectorNIndices typos; extracts getRotationalTransformation for dataset rotation UI.

Changes

Cohort / File(s) Summary
Volume Tracing Action & Reducer Refactor
frontend/javascripts/viewer/model/actions/volumetracing_actions.ts, frontend/javascripts/viewer/model/reducers/volumetracing_reducer.ts, frontend/javascripts/viewer/model/reducers/volumetracing_reducer_helpers.ts, frontend/javascripts/viewer/model/helpers/action_logger_middleware.ts
Renamed action/type and related types from addToLayer / "ADD_TO_LAYER" to addToContourList / "ADD_TO_CONTOUR_LIST"; updated creators, union types, reducer routing, imports, and action blacklist.
Section Labeling Core & Voxel Buffer
frontend/javascripts/viewer/model/volumetracing/section_labeling.ts
Replaced VolumeLayer with SectionLabeler; added TransformedSectionLabeler; introduced plane-mapping helpers (mapTransformedPlane, mapTransformedPlane2), getFast3DCoordinateFn, immutable VoxelBuffer2D changes, many visibility/signature updates and new exported helpers/constants.
Sagas & Volume Helpers
frontend/javascripts/viewer/model/sagas/volumetracing_saga.tsx, frontend/javascripts/viewer/model/sagas/volume/helpers.ts, frontend/javascripts/viewer/model/sagas/volume/quick_select/quick_select_heuristic_saga.ts, frontend/javascripts/viewer/model/sagas/volume/volume_interpolation_saga.ts
Switched factory/usage from createVolumeLayercreateSectionLabeler; updated to use SectionLabeler/TransformedSectionLabeler, changed voxel-buffer creation and coordinate mappings, renamed finishLayerfinishSectionLabeler, and moved channels to ADD_TO_CONTOUR_LIST.
Controller: Segmentation & Handlers
frontend/javascripts/viewer/controller/combinations/volume_handlers.ts
Added getUntransformedSegmentationPosition to convert world → layer space; updated draw/erase/move/pick flows to compute and dispatch layer-space positions; switched dispatches to addToContourListAction.
Scene Controller Visibility & Transforms
frontend/javascripts/viewer/controller/scene_controller.ts
Made many SceneController fields explicit private/public, added flycamRotationEuler, replaced updateUserBoundingBoxesAndMeshesAccordingToTransforms with updateGeometriesToTransforms, and apply transforms to annotation tools geometry.
Transformation Helpers & Accessors
frontend/javascripts/viewer/model/accessors/dataset_layer_transformation_accessor.ts, frontend/javascripts/viewer/model/helpers/transformation_helpers.ts
Added combineCoordinateTransformations and memoized getOriginalTransformsForLayerOrNull; TPS now uses passed scaleFactor; minor refactor in transformPointUnscaled return placement.
Constants & Import Fixes
frontend/javascripts/viewer/constants.ts, frontend/javascripts/viewer/model/accessors/dataset_accessor.ts, frontend/javascripts/viewer/model/accessors/disabled_tool_accessor.ts, frontend/javascripts/viewer/model/bucket_data_handling/data_cube.ts
Fixed misspelling VectorNIndiciesVectorNIndices and updated imports; adjusted import paths referencing section_labeling.
Drawing Class API Change
frontend/javascripts/libs/drawing.ts
Removed smoothing globals/properties and public smoothing APIs (alpha, smoothLength, smoothLine, setSmoothLength, setAlpha); made drawHorizontalLine2d, addNextLine, paintBorder private; added null-safety in fillArea.
Dashboard Rotation UI
frontend/javascripts/dashboard/dataset/dataset_rotation_form_item.tsx
Extracted rotational transform construction into exported getRotationalTransformation(...); AxisRotationFormItem now uses it and imports AffineTransformation type.
Tests & Mocks
frontend/javascripts/test/* (e.g., test/reducers/volumetracing_reducer.spec.ts, test/sagas/volumetracing/*, test/model/transformed_section_labeler.spec.ts, test/global_mocks.ts)
Updated tests to use addToContourListAction and SectionLabeler where applicable; added tests for transformed-plane mapping; added Typography.Text mock in the antd mock.
Minor UI / Store
frontend/javascripts/viewer/view/right-border-tabs/segments_tab/segments_view.tsx, frontend/javascripts/viewer/store.ts
Renamed parameter to indicate layer-space (somePositionInLayerSpace) in setActiveCell dispatch; added inline comment noting somePosition is in layer space.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Areas needing extra attention:

  • frontend/javascripts/viewer/model/volumetracing/section_labeling.ts (TransformedSectionLabeler, plane-mapping, coordinate transforms)
  • frontend/javascripts/viewer/model/sagas/volumetracing_saga.tsx and frontend/javascripts/viewer/model/sagas/volume/helpers.ts (API and control-flow updates)
  • frontend/javascripts/viewer/controller/combinations/volume_handlers.ts (world↔layer conversions and error paths)
  • Cross-check all ADD_TO_LAYER → ADD_TO_CONTOUR_LIST rename sites (reducers, sagas, middleware, tests)
  • frontend/javascripts/libs/drawing.ts (removed public smoothing API that may affect consumers)

Possibly related PRs

Suggested labels

rendering

Suggested reviewers

  • MichaelBuessemeyer

Poem

🐰 I hopped through code with eager paws,

I swapped layers, mapped planes, and checked for flaws.
Contours now march in a tidy list,
Rotations pulled out — no step was missed.
Cheers to transforms, tests, and clean new laws!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main refactoring objective: preparing labelling code to support transformed layers through systematic renames and restructuring.
Description check ✅ Passed The description comprehensively details the changes including terminology renames, annotation geometry transformations, refactored accessors, and new TransformedSectionLabeler class with known testing limitations and scale issues.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch rotated-annotation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@philippotto philippotto changed the title [WIP] Allow to annotate even when layer is transformed (under certain constraints) Refactor labelling code in preparation to support transformed layers Nov 6, 2025
@philippotto philippotto marked this pull request as ready for review November 6, 2025 14:00
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: 7

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2974e9f and 6489ff1.

📒 Files selected for processing (26)
  • frontend/javascripts/dashboard/dataset/dataset_rotation_form_item.tsx (3 hunks)
  • frontend/javascripts/libs/drawing.ts (4 hunks)
  • frontend/javascripts/test/global_mocks.ts (1 hunks)
  • frontend/javascripts/test/model/transformed_section_labeler.spec.ts (1 hunks)
  • frontend/javascripts/test/reducers/volumetracing_reducer.spec.ts (4 hunks)
  • frontend/javascripts/test/sagas/volumetracing/bucket_eviction_helper.ts (3 hunks)
  • frontend/javascripts/test/sagas/volumetracing/volumetracing_saga.spec.ts (7 hunks)
  • frontend/javascripts/test/sagas/volumetracing/volumetracing_saga_integration_1.spec.ts (6 hunks)
  • frontend/javascripts/test/sagas/volumetracing/volumetracing_saga_integration_2.spec.ts (8 hunks)
  • frontend/javascripts/viewer/constants.ts (1 hunks)
  • frontend/javascripts/viewer/controller/combinations/volume_handlers.ts (2 hunks)
  • frontend/javascripts/viewer/controller/scene_controller.ts (4 hunks)
  • frontend/javascripts/viewer/model/accessors/dataset_accessor.ts (2 hunks)
  • frontend/javascripts/viewer/model/accessors/dataset_layer_transformation_accessor.ts (2 hunks)
  • frontend/javascripts/viewer/model/accessors/disabled_tool_accessor.ts (1 hunks)
  • frontend/javascripts/viewer/model/actions/volumetracing_actions.ts (3 hunks)
  • frontend/javascripts/viewer/model/bucket_data_handling/data_cube.ts (1 hunks)
  • frontend/javascripts/viewer/model/helpers/action_logger_middleware.ts (1 hunks)
  • frontend/javascripts/viewer/model/helpers/transformation_helpers.ts (1 hunks)
  • frontend/javascripts/viewer/model/reducers/volumetracing_reducer.ts (2 hunks)
  • frontend/javascripts/viewer/model/reducers/volumetracing_reducer_helpers.ts (1 hunks)
  • frontend/javascripts/viewer/model/sagas/volume/helpers.ts (6 hunks)
  • frontend/javascripts/viewer/model/sagas/volume/quick_select/quick_select_heuristic_saga.ts (2 hunks)
  • frontend/javascripts/viewer/model/sagas/volume/volume_interpolation_saga.ts (2 hunks)
  • frontend/javascripts/viewer/model/sagas/volumetracing_saga.tsx (8 hunks)
  • frontend/javascripts/viewer/model/volumetracing/section_labeling.ts (15 hunks)
🧰 Additional context used
🧠 Learnings (13)
📚 Learning: 2024-11-22T17:18:43.411Z
Learnt from: dieknolle3333
Repo: scalableminds/webknossos PR: 8168
File: frontend/javascripts/oxalis/geometries/materials/plane_material_factory.ts:568-585
Timestamp: 2024-11-22T17:18:43.411Z
Learning: In the file `frontend/javascripts/oxalis/geometries/materials/plane_material_factory.ts`, the uniform name `allResolutions` should remain unchanged to maintain consistency with the shader code.

Applied to files:

  • frontend/javascripts/test/model/transformed_section_labeler.spec.ts
  • frontend/javascripts/viewer/model/reducers/volumetracing_reducer_helpers.ts
  • frontend/javascripts/viewer/constants.ts
  • frontend/javascripts/viewer/model/volumetracing/section_labeling.ts
📚 Learning: 2025-04-01T09:45:17.527Z
Learnt from: MichaelBuessemeyer
Repo: scalableminds/webknossos PR: 8485
File: frontend/javascripts/oxalis/model/accessors/dataset_layer_transformation_accessor.ts:384-392
Timestamp: 2025-04-01T09:45:17.527Z
Learning: The function `isRotationAndMirrorMaybeOnly` in the dataset_layer_transformation_accessor.ts is intentionally designed to allow mirroring transformations (negative scale values). It uses the length comparison (`scale.length() === NON_SCALED_VECTOR.length()`) rather than component equality to permit mirrored axes while ensuring the overall scale magnitude remains the same.

Applied to files:

  • frontend/javascripts/test/model/transformed_section_labeler.spec.ts
  • frontend/javascripts/dashboard/dataset/dataset_rotation_form_item.tsx
  • frontend/javascripts/viewer/model/accessors/dataset_layer_transformation_accessor.ts
  • frontend/javascripts/viewer/model/helpers/transformation_helpers.ts
📚 Learning: 2025-08-14T14:12:03.293Z
Learnt from: hotzenklotz
Repo: scalableminds/webknossos PR: 8849
File: frontend/javascripts/viewer/view/action-bar/ai_job_modals/components/collapsible_split_merger_evaluation_settings.tsx:39-51
Timestamp: 2025-08-14T14:12:03.293Z
Learning: Since Ant Design version 5.18, Form.Item components support the layout prop (layout="horizontal", layout="vertical", etc.), allowing individual form items to override the parent Form's layout setting. This is a newer API addition that provides more granular control over form item layouts.

Applied to files:

  • frontend/javascripts/test/global_mocks.ts
  • frontend/javascripts/dashboard/dataset/dataset_rotation_form_item.tsx
📚 Learning: 2025-08-14T14:12:03.293Z
Learnt from: hotzenklotz
Repo: scalableminds/webknossos PR: 8849
File: frontend/javascripts/viewer/view/action-bar/ai_job_modals/components/collapsible_split_merger_evaluation_settings.tsx:39-51
Timestamp: 2025-08-14T14:12:03.293Z
Learning: Since Ant Design version 5.18, Form.Item components support the layout prop (layout="horizontal", layout="vertical", layout="inline"), allowing individual form items to override the parent Form's layout setting. This enables mixed layouts within a single form and was added as a new API feature in v5.18 according to the official changelog.

Applied to files:

  • frontend/javascripts/test/global_mocks.ts
  • frontend/javascripts/dashboard/dataset/dataset_rotation_form_item.tsx
📚 Learning: 2024-11-22T17:18:04.217Z
Learnt from: dieknolle3333
Repo: scalableminds/webknossos PR: 8168
File: frontend/javascripts/oxalis/model/sagas/proofread_saga.ts:1039-1039
Timestamp: 2024-11-22T17:18:04.217Z
Learning: In `frontend/javascripts/oxalis/model/sagas/proofread_saga.ts`, when calling `getMagInfo`, the use of `volumeTracingLayer.resolutions` is intentional and should not be changed to `volumeTracingLayer.mags`.

Applied to files:

  • frontend/javascripts/test/sagas/volumetracing/volumetracing_saga_integration_1.spec.ts
  • frontend/javascripts/test/reducers/volumetracing_reducer.spec.ts
  • frontend/javascripts/test/sagas/volumetracing/volumetracing_saga_integration_2.spec.ts
  • frontend/javascripts/viewer/model/reducers/volumetracing_reducer_helpers.ts
  • frontend/javascripts/viewer/model/sagas/volume/volume_interpolation_saga.ts
  • frontend/javascripts/viewer/constants.ts
  • frontend/javascripts/viewer/model/actions/volumetracing_actions.ts
  • frontend/javascripts/viewer/controller/combinations/volume_handlers.ts
  • frontend/javascripts/test/sagas/volumetracing/bucket_eviction_helper.ts
  • frontend/javascripts/viewer/model/reducers/volumetracing_reducer.ts
  • frontend/javascripts/test/sagas/volumetracing/volumetracing_saga.spec.ts
  • frontend/javascripts/viewer/model/sagas/volume/quick_select/quick_select_heuristic_saga.ts
  • frontend/javascripts/viewer/model/sagas/volumetracing_saga.tsx
  • frontend/javascripts/viewer/model/sagas/volume/helpers.ts
  • frontend/javascripts/viewer/model/volumetracing/section_labeling.ts
📚 Learning: 2025-10-21T10:54:16.468Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 8961
File: frontend/javascripts/viewer/model/actions/annotation_actions.ts:80-82
Timestamp: 2025-10-21T10:54:16.468Z
Learning: In frontend/javascripts/viewer/model/actions/annotation_actions.ts, the ShowManyBucketUpdatesWarningAction is intentionally not included in the AnnotationActionTypes union because it's a UI-only action that doesn't modify the annotation state through reducers.

Applied to files:

  • frontend/javascripts/test/reducers/volumetracing_reducer.spec.ts
  • frontend/javascripts/viewer/model/helpers/action_logger_middleware.ts
  • frontend/javascripts/test/sagas/volumetracing/volumetracing_saga_integration_2.spec.ts
  • frontend/javascripts/viewer/model/actions/volumetracing_actions.ts
  • frontend/javascripts/test/sagas/volumetracing/bucket_eviction_helper.ts
📚 Learning: 2025-05-30T12:36:09.930Z
Learnt from: philippotto
Repo: scalableminds/webknossos PR: 8542
File: frontend/javascripts/viewer/model/sagas/volumetracing_saga.tsx:0-0
Timestamp: 2025-05-30T12:36:09.930Z
Learning: The WebKnossos frontend follows Redux immutability principles where reducers act immutably and create new state objects when changes occur. This makes reference equality checks (like `prevVolumeTracing.segments !== volumeTracing.segments`) reliable and efficient for detecting actual state changes, allowing memoization to work correctly.

Applied to files:

  • frontend/javascripts/test/reducers/volumetracing_reducer.spec.ts
  • frontend/javascripts/viewer/model/reducers/volumetracing_reducer_helpers.ts
  • frontend/javascripts/viewer/model/reducers/volumetracing_reducer.ts
📚 Learning: 2025-05-07T06:17:32.810Z
Learnt from: philippotto
Repo: scalableminds/webknossos PR: 8602
File: frontend/javascripts/oxalis/model/volumetracing/volume_annotation_sampling.ts:365-366
Timestamp: 2025-05-07T06:17:32.810Z
Learning: The parameter in applyVoxelMap was renamed from `sliceCount` to `sliceOffset` to better reflect its purpose, but this doesn't affect existing call sites since JavaScript/TypeScript function calls are position-based.

Applied to files:

  • frontend/javascripts/viewer/model/reducers/volumetracing_reducer_helpers.ts
  • frontend/javascripts/viewer/model/sagas/volume/volume_interpolation_saga.ts
  • frontend/javascripts/viewer/model/bucket_data_handling/data_cube.ts
  • frontend/javascripts/viewer/model/volumetracing/section_labeling.ts
📚 Learning: 2025-07-22T12:56:47.507Z
Learnt from: daniel-wer
Repo: scalableminds/webknossos PR: 8787
File: frontend/javascripts/viewer/model/accessors/dataset_layer_transformation_accessor.ts:132-136
Timestamp: 2025-07-22T12:56:47.507Z
Learning: Three.js Vector3 has explicitly defined x, y, and z properties that can be safely accessed via dynamic string indexing when using union types like "x" | "y" | "z". The pattern `vector[axis] = value` where axis is "x" | "y" | "z" does not cause TypeScript strict mode errors because TypeScript can infer the property access is valid.

Applied to files:

  • frontend/javascripts/viewer/constants.ts
  • frontend/javascripts/viewer/model/accessors/dataset_accessor.ts
📚 Learning: 2025-09-08T13:33:47.097Z
Learnt from: MichaelBuessemeyer
Repo: scalableminds/webknossos PR: 8824
File: frontend/javascripts/viewer/view/context_menu.tsx:1033-1039
Timestamp: 2025-09-08T13:33:47.097Z
Learning: In frontend/javascripts/viewer/view/context_menu.tsx, the segmentIdLabel intentionally shows the unmapped segment ID (maybeUnmappedSegmentId) in the "within Segment ${id}" text when proofreading is active, as this can be helpful for users. The mapped segment ID (segmentIdAtPosition) is only used as a fallback when the unmapped ID is not available.

Applied to files:

  • frontend/javascripts/viewer/controller/combinations/volume_handlers.ts
📚 Learning: 2025-08-27T11:34:16.411Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 8796
File: frontend/javascripts/viewer/view/action-bar/ai_job_modals/forms/neuron_segmentation_form.tsx:68-77
Timestamp: 2025-08-27T11:34:16.411Z
Learning: The webknossos codebase uses `getDefaultLayerViewConfiguration` to ensure that layer configurations always exist in `datasetConfiguration.layers`, making direct access like `datasetConfiguration.layers[colorLayer.name]` safe across multiple files including api_latest.ts and load_histogram_data_saga.ts.

Applied to files:

  • frontend/javascripts/viewer/model/accessors/dataset_layer_transformation_accessor.ts
📚 Learning: 2024-11-25T14:38:49.345Z
Learnt from: dieknolle3333
Repo: scalableminds/webknossos PR: 8229
File: frontend/javascripts/oxalis/model/accessors/dataset_accessor.ts:348-354
Timestamp: 2024-11-25T14:38:49.345Z
Learning: For the `getDatasetExtentAsProduct` function in `dataset_accessor.ts`, input validation for negative or zero dimensions is not necessary.

Applied to files:

  • frontend/javascripts/viewer/model/accessors/dataset_layer_transformation_accessor.ts
  • frontend/javascripts/viewer/model/accessors/dataset_accessor.ts
📚 Learning: 2025-05-15T19:44:16.110Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 8492
File: frontend/javascripts/viewer/model/sagas/skeletontracing_saga.ts:671-674
Timestamp: 2025-05-15T19:44:16.110Z
Learning: Visibility updates for user bounding boxes are intentionally handled separately from other property updates, with dedicated update actions for visibility changes.

Applied to files:

  • frontend/javascripts/viewer/controller/scene_controller.ts
🧬 Code graph analysis (18)
frontend/javascripts/test/model/transformed_section_labeler.spec.ts (5)
frontend/javascripts/viewer/model/volumetracing/section_labeling.ts (1)
  • mapTransformedPlane (592-636)
frontend/javascripts/viewer/constants.ts (2)
  • OrthoView (66-66)
  • IdentityTransform (387-391)
frontend/javascripts/viewer/model/helpers/transformation_helpers.ts (1)
  • Transform (11-27)
frontend/javascripts/viewer/model/accessors/dataset_layer_transformation_accessor.ts (1)
  • combineCoordinateTransformations (191-212)
frontend/javascripts/dashboard/dataset/dataset_rotation_form_item.tsx (1)
  • getRotationalTransformation (225-240)
frontend/javascripts/test/sagas/volumetracing/volumetracing_saga_integration_1.spec.ts (1)
frontend/javascripts/viewer/model/actions/volumetracing_actions.ts (4)
  • addToContourListAction (180-184)
  • finishEditingAction (201-204)
  • setActiveCellAction (206-218)
  • startEditingAction (173-178)
frontend/javascripts/dashboard/dataset/dataset_rotation_form_item.tsx (2)
frontend/javascripts/viewer/model/accessors/dataset_layer_transformation_accessor.ts (4)
  • RotationAndMirroringSettings (76-79)
  • fromCenterToOrigin (107-113)
  • getRotationMatrixAroundAxis (123-144)
  • fromOriginToCenter (115-121)
frontend/javascripts/types/api_types.ts (1)
  • AffineTransformation (81-84)
frontend/javascripts/test/sagas/volumetracing/volumetracing_saga_integration_2.spec.ts (2)
frontend/javascripts/viewer/model/actions/volumetracing_actions.ts (5)
  • addToContourListAction (180-184)
  • finishEditingAction (201-204)
  • setActiveCellAction (206-218)
  • startEditingAction (173-178)
  • setContourTracingModeAction (377-381)
frontend/javascripts/viewer/model/actions/ui_actions.ts (1)
  • setToolAction (111-115)
frontend/javascripts/viewer/model/sagas/volume/volume_interpolation_saga.ts (1)
frontend/javascripts/viewer/model/sagas/volume/helpers.ts (1)
  • createSectionLabeler (282-301)
frontend/javascripts/viewer/model/actions/volumetracing_actions.ts (1)
frontend/javascripts/viewer/constants.ts (1)
  • Vector3 (14-14)
frontend/javascripts/viewer/controller/combinations/volume_handlers.ts (6)
frontend/javascripts/viewer/constants.ts (3)
  • Point2 (27-30)
  • OrthoView (66-66)
  • Vector3 (14-14)
frontend/javascripts/viewer/model/accessors/view_mode_accessor.ts (1)
  • calculateGlobalPos (338-338)
frontend/javascripts/viewer/model/actions/volumetracing_actions.ts (5)
  • setContourTracingModeAction (377-381)
  • startEditingAction (173-178)
  • addToContourListAction (180-184)
  • finishEditingAction (201-204)
  • resetContourAction (355-358)
frontend/javascripts/viewer/store.ts (1)
  • WebknossosState (575-595)
frontend/javascripts/viewer/model/accessors/dataset_layer_transformation_accessor.ts (1)
  • getTransformsForLayer (262-270)
frontend/javascripts/viewer/model/helpers/transformation_helpers.ts (2)
  • transformPointUnscaled (165-172)
  • invertTransform (71-87)
frontend/javascripts/viewer/model/accessors/dataset_layer_transformation_accessor.ts (2)
frontend/javascripts/viewer/constants.ts (1)
  • Vector3 (14-14)
frontend/javascripts/viewer/model/helpers/transformation_helpers.ts (2)
  • Transform (11-27)
  • createThinPlateSplineTransform (53-69)
frontend/javascripts/viewer/model/accessors/dataset_accessor.ts (1)
frontend/javascripts/viewer/constants.ts (1)
  • Vector3Indices (50-50)
frontend/javascripts/test/sagas/volumetracing/bucket_eviction_helper.ts (1)
frontend/javascripts/viewer/model/actions/volumetracing_actions.ts (1)
  • addToContourListAction (180-184)
frontend/javascripts/viewer/model/reducers/volumetracing_reducer.ts (1)
frontend/javascripts/viewer/model/reducers/volumetracing_reducer_helpers.ts (1)
  • addToContourListReducer (113-127)
frontend/javascripts/viewer/model/helpers/transformation_helpers.ts (1)
frontend/javascripts/viewer/constants.ts (1)
  • Vector3 (14-14)
frontend/javascripts/test/sagas/volumetracing/volumetracing_saga.spec.ts (1)
frontend/javascripts/viewer/model/sagas/volumetracing_saga.tsx (1)
  • finishSectionLabeler (362-387)
frontend/javascripts/viewer/model/sagas/volume/quick_select/quick_select_heuristic_saga.ts (1)
frontend/javascripts/viewer/model/sagas/volume/helpers.ts (1)
  • createSectionLabeler (282-301)
frontend/javascripts/viewer/controller/scene_controller.ts (3)
frontend/javascripts/viewer/constants.ts (2)
  • OrthoViewMap (69-69)
  • OrthoViewWithoutTDMap (70-70)
frontend/javascripts/viewer/geometries/helper_geometries.ts (3)
  • ContourGeometry (31-172)
  • QuickSelectGeometry (181-340)
  • LineMeasurementGeometry (343-459)
frontend/javascripts/viewer/controller/segment_mesh_controller.ts (1)
  • SegmentMeshController (80-707)
frontend/javascripts/viewer/model/sagas/volumetracing_saga.tsx (3)
frontend/javascripts/viewer/model/sagas/volume/helpers.ts (3)
  • createSectionLabeler (282-301)
  • labelWithVoxelBuffer2D (174-280)
  • BooleanBox (44-46)
frontend/javascripts/viewer/model/actions/volumetracing_actions.ts (4)
  • startEditingAction (173-178)
  • finishEditingAction (201-204)
  • addToContourListAction (180-184)
  • registerLabelPointAction (349-353)
frontend/javascripts/viewer/model/volumetracing/section_labeling.ts (1)
  • TransformedSectionLabeler (654-736)
frontend/javascripts/viewer/model/sagas/volume/helpers.ts (4)
frontend/javascripts/viewer/store.ts (1)
  • VolumeTracing (192-213)
frontend/javascripts/viewer/constants.ts (2)
  • OrthoView (66-66)
  • Vector3 (14-14)
frontend/javascripts/viewer/model/volumetracing/section_labeling.ts (1)
  • TransformedSectionLabeler (654-736)
frontend/javascripts/viewer/model/accessors/dataset_layer_transformation_accessor.ts (1)
  • getTransformsForLayer (262-270)
frontend/javascripts/viewer/model/volumetracing/section_labeling.ts (5)
frontend/javascripts/viewer/constants.ts (5)
  • Vector2 (13-13)
  • Vector3 (14-14)
  • OrthoView (66-66)
  • Vector3Indices (50-50)
  • Vector2Indices (49-49)
frontend/javascripts/viewer/model/scaleinfo.ts (1)
  • getBaseVoxelFactorsInUnit (26-33)
frontend/javascripts/viewer/model/helpers/position_converter.ts (1)
  • zoomedPositionToGlobalPosition (45-47)
frontend/javascripts/viewer/model/helpers/transformation_helpers.ts (3)
  • Transform (11-27)
  • transformPointUnscaled (165-172)
  • invertTransform (71-87)
frontend/javascripts/viewer/model/accessors/dataset_layer_transformation_accessor.ts (1)
  • invertAndTranspose (372-374)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build-smoketest-push
  • GitHub Check: backend-tests
🔇 Additional comments (25)
frontend/javascripts/test/global_mocks.ts (1)

143-145: LGTM!

The Typography mock follows the same pattern as other antd component mocks and appropriately ensures that tests referencing Typography.Text won't encounter resolution errors.

frontend/javascripts/viewer/model/accessors/disabled_tool_accessor.ts (2)

14-14: Import organization aligns with transformation refactor.

The reordering of IdentityTransform and addition of getTransformsPerLayer are consistent with the PR's transformation-related refactoring. Both imports are used in the getDisabledVolumeInfo function to check for segmentation layer transformations.

Also applies to: 22-22


336-340: Need to see the full implementation of _getTransformsPerLayer to confirm the behavior:

Let me look at the actual implementation directly in the repository:

Based on the script output and my analysis, I can now verify the concern. Let me examine the structure of the implementation more carefully by looking at what information we have:

From the earlier script output, I can see:

  • Line 337: const transformsPerLayer: Record<string, Transform> = {}; - initializes empty map
  • The function iterates through dataset.dataSource.dataLayers
  • Only entries with transforms would be added to this map
  • Missing entries return undefined

The key insight is that if one of the operands is null or undefined, the other must also be null or undefined to return true when using strict equality. Therefore, undefined !== IdentityTransform will always be true, which means untransformed layers (returning undefined from the map lookup) will be incorrectly treated as "transformed."

However, I need to determine if this is the intended behavior or a bug. Let me check the learnings to see if there's prior context:

The learnings mention that in isRotationAndMirrorMaybeOnly, the function is "intentionally designed" to use length comparison rather than component equality. This suggests designers are careful about transform semantics. Let me generate the final response based on the evidence:

Verify behavior of getTransformsPerLayer for layers without explicit transforms.

The code at lines 336-340 accesses getTransformsPerLayer(...)[segmentationTracingLayer.tracingId] without a fallback for missing keys. If a layer has no explicit transform, the map access returns undefined, making the comparison undefined !== IdentityTransform evaluate to true. This would incorrectly mark untransformed layers as "transformed," disabling volume tools for layers that should support editing.

Confirm whether layers without transforms should:

  1. Be absent from the returned map (current behavior, causes the issue), or
  2. Have an explicit IdentityTransform entry, or
  3. Use a nullish coalescing operator to default missing entries to IdentityTransform.

If missing entries should be treated as untransformed, apply a fix:

const isSegmentationTracingTransformed =
  segmentationTracingLayer != null &&
- getTransformsPerLayer(state.dataset, state.datasetConfiguration.nativelyRenderedLayerName)[
-   segmentationTracingLayer.tracingId
- ] !== IdentityTransform;
+ (getTransformsPerLayer(state.dataset, state.datasetConfiguration.nativelyRenderedLayerName)[
+   segmentationTracingLayer.tracingId
+ ] ?? IdentityTransform) !== IdentityTransform;
frontend/javascripts/libs/drawing.ts (1)

7-7: Good refactor: narrowing the public API surface.

Making drawHorizontalLine2d, addNextLine, and paintBorder private correctly encapsulates internal drawing implementation details. Verification confirms these methods are called only internally within the Drawing class and have no external callers elsewhere in the codebase.

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

6-6: LGTM: Import addition is necessary for the new function.

The AffineTransformation type import is correctly added to support the return type of getRotationalTransformation.


67-67: LGTM: Clean refactor improves code organization.

Replacing the inline transformation array construction with the extracted getRotationalTransformation function improves code reusability and maintainability.


225-240: LGTM: Well-structured utility function for rotational transformations.

This extracted function correctly implements the standard transformation sequence (center-to-origin → rotations → origin-to-center) and improves code organization. The export makes it available for reuse across the codebase, which aligns well with the PR's goal of supporting transformed layers.

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

169-171: LGTM - Stylistic refactor.

The return statement is now inside the else block rather than after it. No functional change.

frontend/javascripts/test/model/transformed_section_labeler.spec.ts (1)

1-244: Well-structured test suite for plane mapping transformations.

The tests cover identity, 90-degree rotations, and complex affine transformations. The skipped test at line 175 is acknowledged in the PR description as a known issue to be addressed.

frontend/javascripts/viewer/model/accessors/dataset_layer_transformation_accessor.ts (1)

187-212: LGTM - Extracted reusable transformation combination logic.

The new combineCoordinateTransformations function parameterizes the scale factor, making it reusable for testing (as seen in transformed_section_labeler.spec.ts) and enabling more flexible transformation handling.

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

20-20: LGTM - Typo correction.

Fixed misspelling: Vector3IndiciesVector3Indices, aligning with the corrected constant name in constants.ts.

Also applies to: 247-250

frontend/javascripts/viewer/model/bucket_data_handling/data_cube.ts (1)

37-40: LGTM - Updated import path.

Import path updated from volumetracing/volumelayer to volumetracing/section_labeling to align with the broader refactoring from VolumeLayer to SectionLabeler abstractions.

frontend/javascripts/viewer/model/sagas/volume/helpers.ts (3)

198-203: No action required — methods exist and are correctly implemented.

Verification confirms that both getTopLeft3DCoord() and getBottomRight3DCoord() methods exist on the VoxelBuffer2D class (in frontend/javascripts/viewer/model/volumetracing/section_labeling.ts at lines 62–63) and are correctly imported and called in helpers.ts.


229-229: Verified: getValueFromGlobal method exists on VoxelBuffer2D.

The method is properly defined in frontend/javascripts/viewer/model/volumetracing/section_labeling.ts:83 with signature getValueFromGlobal(globalX: number, globalY: number): number, and is correctly imported and used in the helpers.ts file.


282-300: Verify whether TransformedSectionLabeler optimizes identity transforms or whether unnecessary overhead should be avoided.

The refactored createSectionLabeler now always returns TransformedSectionLabeler, even when the underlying transform is identity (as returned by getTransformsForLayer on line 268 of dataset_layer_transformation_accessor.ts).

The issue: transformPointUnscaled performs type checking and applies transformations without detecting identity—even for the IdentityTransform case, matrix operations are executed. Additionally, TransformedSectionLabeler always wraps a base SectionLabeler, adding delegation overhead.

For layers without transforms, consider either:

  1. Early-returning a plain SectionLabeler from createSectionLabeler when transform is identity, or
  2. Optimizing transformPointUnscaled to detect and short-circuit identity transforms before performing matrix operations.
frontend/javascripts/viewer/constants.ts (1)

49-53: ✓ LGTM - Typo correction verified and complete.

Confirmed: All VectorNIndicies names (N=2-6) have been corrected to VectorNIndices. No remaining usages of the old misspelled names found anywhere in the codebase. The numeric values and export structure remain unchanged.

frontend/javascripts/test/sagas/volumetracing/bucket_eviction_helper.ts (1)

11-11: LGTM! Consistent action rename.

The import and dispatch calls have been correctly updated from addToLayerAction to addToContourListAction, aligning with the broader refactoring in this PR.

Also applies to: 63-63, 77-77

frontend/javascripts/viewer/model/helpers/action_logger_middleware.ts (1)

14-14: LGTM! Action blacklist updated correctly.

The action type string has been properly updated from "ADD_TO_LAYER" to "ADD_TO_CONTOUR_LIST" to match the renamed action.

frontend/javascripts/test/sagas/volumetracing/volumetracing_saga_integration_1.spec.ts (1)

24-24: LGTM! Comprehensive test updates.

All occurrences of addToLayerAction have been correctly updated to addToContourListAction throughout the integration tests, maintaining test consistency with the renamed action.

Also applies to: 72-72, 77-77, 132-132, 186-186, 236-236, 282-282

frontend/javascripts/viewer/model/reducers/volumetracing_reducer_helpers.ts (1)

113-127: LGTM! Reducer function renamed consistently.

The function has been renamed from addToLayerReducer to addToContourListReducer while maintaining the same signature and implementation, aligning with the action rename throughout the codebase.

frontend/javascripts/test/reducers/volumetracing_reducer.spec.ts (1)

253-253: LGTM! Test variables renamed consistently.

All test helper variables and function calls have been updated from addToLayerActionFn to addToContourListActionFn, maintaining test consistency with the action rename.

Also applies to: 265-267, 277-277, 281-283, 299-299, 301-303

frontend/javascripts/viewer/model/reducers/volumetracing_reducer.ts (1)

40-40: LGTM! Reducer updated consistently.

The import, action case, and reducer function call have all been correctly updated to use the new ADD_TO_CONTOUR_LIST action and addToContourListReducer function, maintaining correct reducer flow.

Also applies to: 595-597

frontend/javascripts/test/sagas/volumetracing/volumetracing_saga_integration_2.spec.ts (1)

20-20: LGTM! Extensive test suite updated correctly.

All dispatch calls throughout this comprehensive integration test suite have been correctly updated to use addToContourListAction, ensuring test consistency with the renamed action.

Also applies to: 65-65, 155-155, 319-319, 392-392, 467-468, 472-473, 528-529, 533-534, 539-540, 606-607, 610-611, 614-615

frontend/javascripts/viewer/model/sagas/volume/quick_select/quick_select_heuristic_saga.ts (1)

63-63: LGTM! VolumeLayer to SectionLabeler refactor applied correctly.

The refactoring from createVolumeLayer to createSectionLabeler has been properly implemented:

  • Import updated correctly
  • Function call updated with same argument structure
  • Variable renamed from volumeLayer to sectionLabeler
  • Method calls (createVoxelBuffer2D, globalCoordToMag2DFloat) remain compatible

This change aligns with the broader PR objective of refactoring to support transformed layers.

Also applies to: 515-527

frontend/javascripts/viewer/model/sagas/volume/volume_interpolation_saga.ts (1)

377-385: Potential coordinate scaling inconsistency in voxel buffer positioning.

On line 380, targetOffsetW is added directly without scaling:

V3.add(relevantBoxMag1.min, transpose([0, 0, targetOffsetW]))

However, on line 375, the same targetOffsetW is scaled by labeledMag[thirdDim]:

relevantBoxMag1.min[thirdDim] + labeledMag[thirdDim] * targetOffsetW

And on line 335, slice offsets are also scaled by the mag:

transpose([0, 0, -directionFactor * interpolationDepth * labeledMag[thirdDim]])

Since targetOffsetW represents a slice index and relevantBoxMag1.min is in global (mag1) coordinates, the offset should be scaled when converting to global coordinates. For consistency and correctness (especially with TransformedSectionLabeler that may depend on all 3D coordinates), line 380 should likely be:

-        sectionLabeler.globalCoordToMag2DFloat(
-          V3.add(relevantBoxMag1.min, transpose([0, 0, targetOffsetW])),
-        ),
+        sectionLabeler.globalCoordToMag2DFloat(
+          V3.add(relevantBoxMag1.min, transpose([0, 0, targetOffsetW * labeledMag[thirdDim]])),
+        ),

Run the following script to check if globalCoordToMag2DFloat uses all three dimensions or only the first two:

Comment on lines +716 to +697
globalCoordToMag2DFloat(position: Vector3): Vector2 {
return this.base.globalCoordToMag2DFloat(position);
}
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 | 🟠 Major

Transform positions before querying the base labeler.

globalCoordToMag2DFloat is still delegating the untouched coordinate to the base SectionLabeler, so rotated viewers report the wrong (u, v). Transform the point first.

-    return this.base.globalCoordToMag2DFloat(position);
+    const transformed = this.applyTransform(position);
+    return this.base.globalCoordToMag2DFloat(transformed);
📝 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
globalCoordToMag2DFloat(position: Vector3): Vector2 {
return this.base.globalCoordToMag2DFloat(position);
}
globalCoordToMag2DFloat(position: Vector3): Vector2 {
const transformed = this.applyTransform(position);
return this.base.globalCoordToMag2DFloat(transformed);
}
🤖 Prompt for AI Agents
In frontend/javascripts/viewer/model/volumetracing/section_labeling.ts around
lines 716-718, the method currently forwards the untransformed world coordinate
to the base labeler causing incorrect (u,v) for rotated viewers; before calling
this.base.globalCoordToMag2DFloat, convert the input position into the base
labeler’s coordinate system by applying the section/view transform (the existing
transform helper on this — e.g. the per-section transform or localize function
used elsewhere in this class) and pass the transformed point to the base method
so the returned 2D coordinates reflect viewer rotation.

Comment on lines +720 to +705
getCircleVoxelBuffer2D(position: Vector3): VoxelBuffer2D {
let scale = this.adaptScaleFn(
getBaseVoxelFactorsInUnit(Store.getState().dataset.dataSource.scale),
);

return this.base.getCircleVoxelBuffer2D(position, scale);
}
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 | 🔴 Critical

Apply the transform before computing the circle buffer.

Right now the circle buffer is created in the original coordinate system, so on transformed planes the brush lands at the wrong place. Transform the input first.

-    return this.base.getCircleVoxelBuffer2D(position, scale);
+    const transformed = this.applyTransform(position);
+    return this.base.getCircleVoxelBuffer2D(transformed, scale);
📝 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
getCircleVoxelBuffer2D(position: Vector3): VoxelBuffer2D {
let scale = this.adaptScaleFn(
getBaseVoxelFactorsInUnit(Store.getState().dataset.dataSource.scale),
);
return this.base.getCircleVoxelBuffer2D(position, scale);
}
getCircleVoxelBuffer2D(position: Vector3): VoxelBuffer2D {
let scale = this.adaptScaleFn(
getBaseVoxelFactorsInUnit(Store.getState().dataset.dataSource.scale),
);
const transformed = this.applyTransform(position);
return this.base.getCircleVoxelBuffer2D(transformed, scale);
}
🤖 Prompt for AI Agents
In frontend/javascripts/viewer/model/volumetracing/section_labeling.ts around
lines 720-726, the code currently passes the original world position into
getCircleVoxelBuffer2D causing misalignment on transformed planes; instead,
apply the section/plane transform to the input position first (i.e. compute
transformedPosition by applying the existing transform/matrix for this section —
if no helper exists add one that multiplies the Vector3 by the section's
transform matrix), then call
this.base.getCircleVoxelBuffer2D(transformedPosition, scale) (keep the scale
computation as-is and ensure the transformedPosition is in the coordinate space
expected by getCircleVoxelBuffer2D).

Comment on lines 738 to 726
function getFast3DCoordinateFn(
plane: OrthoView,
thirdDimensionValue: number,
_isSwapped: boolean,
): (coordX: number, coordY: number, out: Vector3 | Float32Array) => void {
switch (plane) {
case OrthoViews.PLANE_XY:
return (coordX, coordY, out) => {
out[0] = coordX;
out[1] = coordY;
out[2] = thirdDimensionValue;
};

case OrthoViews.PLANE_YZ:
return (coordX, coordY, out) => {
out[0] = thirdDimensionValue;
out[1] = coordY;
out[2] = coordX;
};

case OrthoViews.PLANE_XZ:
return (coordX, coordY, out) => {
out[0] = coordX;
out[1] = thirdDimensionValue;
out[2] = coordY;
};

default: {
throw new Error("Unknown plane id");
}
}
let [u, v, w] = Dimensions.getIndices(plane);
return (coordX, coordY, out) => {
out[u] = coordX;
out[v] = coordY;
out[w] = thirdDimensionValue;
};
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 | 🔴 Critical

Respect the swapped orientation when mapping back to 3D.

_isSwapped is effectively ignored, so whenever we detect a swap the generated 3D coordinates are still written with the original axis order, corrupting the geometry. Handle the swap explicitly.

-  let [u, v, w] = Dimensions.getIndices(plane);
-  return (coordX, coordY, out) => {
-    out[u] = coordX;
-    out[v] = coordY;
-    out[w] = thirdDimensionValue;
-  };
+  const [u, v, w] = Dimensions.getIndices(plane);
+  const isSwapped = _isSwapped;
+  return (coordX, coordY, out) => {
+    if (isSwapped) {
+      out[u] = coordY;
+      out[v] = coordX;
+    } else {
+      out[u] = coordX;
+      out[v] = coordY;
+    }
+    out[w] = thirdDimensionValue;
+  };
📝 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
function getFast3DCoordinateFn(
plane: OrthoView,
thirdDimensionValue: number,
_isSwapped: boolean,
): (coordX: number, coordY: number, out: Vector3 | Float32Array) => void {
switch (plane) {
case OrthoViews.PLANE_XY:
return (coordX, coordY, out) => {
out[0] = coordX;
out[1] = coordY;
out[2] = thirdDimensionValue;
};
case OrthoViews.PLANE_YZ:
return (coordX, coordY, out) => {
out[0] = thirdDimensionValue;
out[1] = coordY;
out[2] = coordX;
};
case OrthoViews.PLANE_XZ:
return (coordX, coordY, out) => {
out[0] = coordX;
out[1] = thirdDimensionValue;
out[2] = coordY;
};
default: {
throw new Error("Unknown plane id");
}
}
let [u, v, w] = Dimensions.getIndices(plane);
return (coordX, coordY, out) => {
out[u] = coordX;
out[v] = coordY;
out[w] = thirdDimensionValue;
};
function getFast3DCoordinateFn(
plane: OrthoView,
thirdDimensionValue: number,
_isSwapped: boolean,
): (coordX: number, coordY: number, out: Vector3 | Float32Array) => void {
const [u, v, w] = Dimensions.getIndices(plane);
const isSwapped = _isSwapped;
return (coordX, coordY, out) => {
if (isSwapped) {
out[u] = coordY;
out[v] = coordX;
} else {
out[u] = coordX;
out[v] = coordY;
}
out[w] = thirdDimensionValue;
};
}
🤖 Prompt for AI Agents
In frontend/javascripts/viewer/model/volumetracing/section_labeling.ts around
lines 738 to 748, the getFast3DCoordinateFn ignores the _isSwapped flag so
returned coordinates write axes in the original order and corrupt geometry when
swapped; update the function to respect _isSwapped by swapping the u and v
indices (or swapping coordX/coordY mapping) before returning the closure so that
when _isSwapped is true the closure writes coordX to the v index and coordY to
the u index (ensuring out[u]/out[v]/out[w] assignments reflect the swapped
orientation).

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 (4)
frontend/javascripts/test/sagas/volumetracing/volumetracing_saga.spec.ts (4)

94-136: Consider updating test description to reflect new terminology.

The test name "should create a volume layer" still uses the old terminology. Consider renaming it to "should create a section labeler" to align with the refactored API.

Apply this diff to update the test description:

-  it("should create a volume layer (saga test)", () => {
+  it("should create a section labeler (saga test)", () => {

138-192: Consider updating test description to reflect new terminology.

The test name "should add values to volume layer" still uses the old terminology. Consider renaming it to "should add values to section labeler" to align with the refactored API.

Apply this diff to update the test description:

-  it("should add values to volume layer (saga test)", () => {
+  it("should add values to section labeler (saga test)", () => {

194-259: Consider updating test description and comment to reflect new terminology.

Both the test name and the comment on line 242 still use the old terminology ("volume layer" and "finishLayer"). Consider updating them to align with the refactored API.

Apply this diff to update the test description and comment:

-  it("should finish a volume layer (saga test)", () => {
+  it("should finish a section labeler (saga test)", () => {
     // ... (lines omitted for brevity)
-    // Validate that finishLayer was called
+    // Validate that finishSectionLabeler was called

261-328: Consider updating test description and comment to reflect new terminology.

Both the test name and the comment on line 314 still use the old terminology ("volume layer" and "finishLayer"). Consider updating them to align with the refactored API.

Apply this diff to update the test description and comment:

-  it("should finish a volume layer in delete mode (saga test)", () => {
+  it("should finish a section labeler in delete mode (saga test)", () => {
     // ... (lines omitted for brevity)
-    // Validate that finishLayer was called
+    // Validate that finishSectionLabeler was called
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6489ff1 and 5aaa02c.

📒 Files selected for processing (2)
  • frontend/javascripts/test/sagas/volumetracing/volumetracing_saga.spec.ts (9 hunks)
  • frontend/javascripts/viewer/model/sagas/volume/helpers.ts (6 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/javascripts/viewer/model/sagas/volume/helpers.ts
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2024-11-22T17:18:04.217Z
Learnt from: dieknolle3333
Repo: scalableminds/webknossos PR: 8168
File: frontend/javascripts/oxalis/model/sagas/proofread_saga.ts:1039-1039
Timestamp: 2024-11-22T17:18:04.217Z
Learning: In `frontend/javascripts/oxalis/model/sagas/proofread_saga.ts`, when calling `getMagInfo`, the use of `volumeTracingLayer.resolutions` is intentional and should not be changed to `volumeTracingLayer.mags`.

Applied to files:

  • frontend/javascripts/test/sagas/volumetracing/volumetracing_saga.spec.ts
📚 Learning: 2025-05-30T12:36:09.930Z
Learnt from: philippotto
Repo: scalableminds/webknossos PR: 8542
File: frontend/javascripts/viewer/model/sagas/volumetracing_saga.tsx:0-0
Timestamp: 2025-05-30T12:36:09.930Z
Learning: The WebKnossos frontend follows Redux immutability principles where reducers act immutably and create new state objects when changes occur. This makes reference equality checks (like `prevVolumeTracing.segments !== volumeTracing.segments`) reliable and efficient for detecting actual state changes, allowing memoization to work correctly.

Applied to files:

  • frontend/javascripts/test/sagas/volumetracing/volumetracing_saga.spec.ts
🧬 Code graph analysis (1)
frontend/javascripts/test/sagas/volumetracing/volumetracing_saga.spec.ts (3)
frontend/javascripts/test/fixtures/dataset_server_object.ts (1)
  • sampleTracingLayer (63-67)
frontend/javascripts/test/helpers/sagaHelpers.ts (1)
  • execCall (21-26)
frontend/javascripts/viewer/model/sagas/volumetracing_saga.tsx (1)
  • finishSectionLabeler (362-387)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: frontend-tests
  • GitHub Check: build-smoketest-push
  • GitHub Check: backend-tests
🔇 Additional comments (4)
frontend/javascripts/test/sagas/volumetracing/volumetracing_saga.spec.ts (4)

1-1: LGTM: Import updates align with API refactoring.

The imports correctly reflect the refactored API, including the new SectionLabeler class, the renamed finishSectionLabeler function, and the test fixtures needed for the updated mock setup.

Also applies to: 17-17, 22-22, 27-28


46-46: LGTM: Action rename reflects API changes.

The rename from addToLayerAction to addToContourListActionFn correctly reflects the broader terminology shift from "layer" to "contour list" throughout the codebase.


49-56: LGTM: Mock dataset setup is correct.

The mockedDataset setup using immutability-helper correctly provides the necessary test fixture for the saga, replacing the data layers with sampleTracingLayer for testing purposes.


330-418: LGTM: Remaining tests are correctly structured.

The tests for busy state handling and mapping locking are correctly implemented and don't require terminology updates since they focus on different aspects of the saga behavior.

Copy link
Contributor

@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.

Thanks for all this hard refactoring work 👍
Honestly, to me the whole mapTransformedPlane stuff is a little unclear. Maybe we could call about this so I can understand it better :)

Comment on lines +93 to +103
print(): void {
const lines = [];
for (let y = 0; y < this.width; y++) {
const line = [];
for (let x = 0; x < this.width; x++) {
line.push(this.getValue(x, y));
}
lines.push(line);
}
console.log("VoxelBuffer content:", lines.join("\n"));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you want to keep this in production code / merge this?

Copy link
Member Author

Choose a reason for hiding this comment

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

yes, I think, it doesn't hurt.

[OrthoViews.PLANE_XZ]: new THREE.Vector3(0, 1, 0),
};

export function mapTransformedPlane(
Copy link
Contributor

Choose a reason for hiding this comment

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

I do not 100% understand what this function is doing exactly. Is it trying to detect the best plane representing the result of a transformed plane? So in case the xy plane is rotated by 90° in some way this function detects that the xy viewport now behaves like the yz viewport and would show data that would without transformation be shown in this viewport? In this case yz plane is returned?

Copy link
Member Author

Choose a reason for hiding this comment

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

yes, your assumption is correct 👍

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 (1)
frontend/javascripts/viewer/controller/combinations/volume_handlers.ts (1)

46-69: Consider clearer naming for the coordinate transformation helper.

While the function logic is correct, the naming could be improved:

  1. Parameter name: globalPosRounded is misleading since line 186 passes globalPos.floating (not rounded). Consider renaming to simply globalPos.

  2. Function name: getUntransformedSegmentationPosition is ambiguous—"untransformed" might suggest identity transformation, but it actually means "in layer space" (transformed from global space). Consider globalToLayerSegmentationPosition or getSegmentationPositionInLayerSpace for clarity.

The comment on lines 51-52 is helpful and correctly describes the conversion from world to layer space.

-export function getUntransformedSegmentationPosition(
+export function globalToLayerSegmentationPosition(
   state: WebknossosState,
-  globalPosRounded: Vector3,
+  globalPos: Vector3,
 ) {
   /*
    * Converts the given position from world space to layer space.
    */
   const { nativelyRenderedLayerName } = state.datasetConfiguration;
   const maybeLayer = Model.getVisibleSegmentationLayer();
   if (maybeLayer == null) {
     throw new Error("Segmentation layer does not exist");
   }

   const layer = getLayerByName(state.dataset, maybeLayer.name);
   const segmentationTransforms = getTransformsForLayer(
     state.dataset,
     layer,
     nativelyRenderedLayerName,
   );
   const layerPos = transformPointUnscaled(invertTransform(segmentationTransforms))(
-    globalPosRounded,
+    globalPos,
   );
   return layerPos;
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 184a219 and 401b91a.

📒 Files selected for processing (4)
  • frontend/javascripts/viewer/controller/combinations/volume_handlers.ts (3 hunks)
  • frontend/javascripts/viewer/model/actions/volumetracing_actions.ts (4 hunks)
  • frontend/javascripts/viewer/store.ts (1 hunks)
  • frontend/javascripts/viewer/view/right-border-tabs/segments_tab/segments_view.tsx (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • frontend/javascripts/viewer/store.ts
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-05-07T06:17:32.810Z
Learnt from: philippotto
Repo: scalableminds/webknossos PR: 8602
File: frontend/javascripts/oxalis/model/volumetracing/volume_annotation_sampling.ts:365-366
Timestamp: 2025-05-07T06:17:32.810Z
Learning: The parameter in applyVoxelMap was renamed from `sliceCount` to `sliceOffset` to better reflect its purpose, but this doesn't affect existing call sites since JavaScript/TypeScript function calls are position-based.

Applied to files:

  • frontend/javascripts/viewer/view/right-border-tabs/segments_tab/segments_view.tsx
📚 Learning: 2025-09-08T13:33:47.097Z
Learnt from: MichaelBuessemeyer
Repo: scalableminds/webknossos PR: 8824
File: frontend/javascripts/viewer/view/context_menu.tsx:1033-1039
Timestamp: 2025-09-08T13:33:47.097Z
Learning: In frontend/javascripts/viewer/view/context_menu.tsx, the segmentIdLabel intentionally shows the unmapped segment ID (maybeUnmappedSegmentId) in the "within Segment ${id}" text when proofreading is active, as this can be helpful for users. The mapped segment ID (segmentIdAtPosition) is only used as a fallback when the unmapped ID is not available.

Applied to files:

  • frontend/javascripts/viewer/view/right-border-tabs/segments_tab/segments_view.tsx
  • frontend/javascripts/viewer/controller/combinations/volume_handlers.ts
📚 Learning: 2025-10-21T10:54:16.468Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 8961
File: frontend/javascripts/viewer/model/actions/annotation_actions.ts:80-82
Timestamp: 2025-10-21T10:54:16.468Z
Learning: In frontend/javascripts/viewer/model/actions/annotation_actions.ts, the ShowManyBucketUpdatesWarningAction is intentionally not included in the AnnotationActionTypes union because it's a UI-only action that doesn't modify the annotation state through reducers.

Applied to files:

  • frontend/javascripts/viewer/model/actions/volumetracing_actions.ts
📚 Learning: 2024-11-22T17:18:04.217Z
Learnt from: dieknolle3333
Repo: scalableminds/webknossos PR: 8168
File: frontend/javascripts/oxalis/model/sagas/proofread_saga.ts:1039-1039
Timestamp: 2024-11-22T17:18:04.217Z
Learning: In `frontend/javascripts/oxalis/model/sagas/proofread_saga.ts`, when calling `getMagInfo`, the use of `volumeTracingLayer.resolutions` is intentional and should not be changed to `volumeTracingLayer.mags`.

Applied to files:

  • frontend/javascripts/viewer/model/actions/volumetracing_actions.ts
  • frontend/javascripts/viewer/controller/combinations/volume_handlers.ts
🧬 Code graph analysis (3)
frontend/javascripts/viewer/view/right-border-tabs/segments_tab/segments_view.tsx (2)
frontend/javascripts/viewer/constants.ts (2)
  • Vector3 (14-14)
  • AdditionalCoordinate (2-2)
frontend/javascripts/viewer/model/actions/volumetracing_actions.ts (1)
  • setActiveCellAction (206-218)
frontend/javascripts/viewer/model/actions/volumetracing_actions.ts (1)
frontend/javascripts/viewer/constants.ts (1)
  • Vector3 (14-14)
frontend/javascripts/viewer/controller/combinations/volume_handlers.ts (5)
frontend/javascripts/viewer/model/accessors/view_mode_accessor.ts (2)
  • calculateGlobalPos (338-338)
  • PositionWithRounding (95-95)
frontend/javascripts/viewer/model/actions/volumetracing_actions.ts (3)
  • startEditingAction (173-178)
  • addToContourListAction (180-184)
  • setActiveCellAction (206-218)
frontend/javascripts/viewer/store.ts (1)
  • WebknossosState (575-595)
frontend/javascripts/viewer/model/accessors/dataset_layer_transformation_accessor.ts (1)
  • getTransformsForLayer (262-270)
frontend/javascripts/viewer/model/helpers/transformation_helpers.ts (2)
  • transformPointUnscaled (165-172)
  • invertTransform (71-87)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build-smoketest-push
  • GitHub Check: backend-tests
🔇 Additional comments (6)
frontend/javascripts/viewer/view/right-border-tabs/segments_tab/segments_view.tsx (1)

256-262: LGTM! Parameter rename improves clarity.

The rename from somePosition to somePositionInLayerSpace makes the coordinate space explicit, which is helpful given the transformation-related refactoring in this PR. The dispatch call correctly passes the layer-space position to setActiveCellAction.

frontend/javascripts/viewer/model/actions/volumetracing_actions.ts (2)

17-17: LGTM! Type definitions updated consistently.

The type rename from AddToLayerAction to AddToContourListAction and the corresponding union update align with the PR's terminology refactoring from "add to layer" → "add to contour list".

Also applies to: 83-83


173-184: LGTM! Action renamed and coordinate space clarified.

The rename from addToLayerAction to addToContourListAction (with action type "ADD_TO_CONTOUR_LIST") is consistent with the PR's terminology updates. The added "// in layer space" comments on lines 176 and 208 are valuable documentation for the transformation refactoring.

Also applies to: 206-218

frontend/javascripts/viewer/controller/combinations/volume_handlers.ts (3)

36-44: LGTM! Draw flow correctly uses layer-space coordinates.

The refactored handleDrawStart properly converts the global position to layer space via getUntransformedSegmentationPosition before dispatching startEditingAction and addToContourListAction, which is consistent with these actions expecting layer-space positions.


71-78: LGTM! Erase and move flows correctly handle coordinate transformations.

Both handleEraseStart and handleMoveForDrawOrErase follow the same pattern as handleDrawStart, properly converting global positions to layer space before dispatching actions. This is consistent with the PR's transformation refactoring.

Also applies to: 79-84


89-94: Pick cell flow looks correct now; minor question about precision.

The coordinate transformation flow appears correct and the double-inversion issue mentioned in past review comments has been resolved:

  1. handlePickCell obtains the global position via calculateGlobalPos (line 91)
  2. Passes globalPos (in global space) to handlePickCellFromGlobalPosition (line 93)
  3. The handler uses globalPos.rounded for segment ID lookup (line 180), which internally converts to layer space
  4. Uses globalPos.floating for the position passed to setActiveCellAction (line 186), converting to layer space

Minor question: Is the use of globalPos.floating (line 186) vs globalPos.rounded (line 180) for setActiveCellAction intentional? Using floating-point precision for storing the segment's somePosition might provide sub-voxel accuracy, but it's worth confirming this is the desired behavior.

Also applies to: 176-204

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/libs/drawing.ts (3)

7-22: LGTM! Good refactoring to reduce public API surface.

Making drawHorizontalLine2d private is appropriate since it's an internal helper method for the other drawing operations.


87-129: LGTM! Good refactoring to reduce public API surface.

Making addNextLine private is appropriate since it's an internal helper for the scanline flood-fill algorithm and not meant for external use.


131-155: LGTM! Good refactoring to reduce public API surface.

Making paintBorder private is appropriate since it's an internal helper for the rotated rectangle rasterization algorithm.

frontend/javascripts/viewer/model/sagas/volumetracing_saga.tsx (2)

265-273: Simplify the action destructuring pattern.

The current pattern creates an object just to immediately destructure it. Consider simplifying:

-      const { addToContourListAction, finishEditingAction } = {
-        addToContourListAction: currentAction.type === "ADD_TO_CONTOUR_LIST" ? currentAction : null,
-        finishEditingAction: currentAction.type === "FINISH_EDITING" ? currentAction : null,
-      };
-      if (finishEditingAction) break;
-
-      if (!addToContourListAction || addToContourListAction.type !== "ADD_TO_CONTOUR_LIST") {
-        throw new Error("Unexpected action. Satisfy typescript.");
-      }
+      if (currentAction.type === "FINISH_EDITING") {
+        break;
+      }
+      
+      if (currentAction.type !== "ADD_TO_CONTOUR_LIST") {
+        throw new Error("Unexpected action. Satisfy typescript.");
+      }
+      
+      const addToContourListAction = currentAction;

This eliminates the intermediate object creation and the redundant type check at Line 271.


363-388: LGTM! Function refactoring correctly delegates to the labeler.

The renamed finishSectionLabeler properly uses the labeler's methods to obtain the filling buffer, plane, and centroid. The centroid retrieval via getUnzoomedCentroid() correctly applies inverse transformation when using TransformedSectionLabeler.

Minor: Remove unnecessary null check.

At Line 371, the sectionLabeler == null check is unnecessary since the parameter type doesn't include null/undefined:

-  if (sectionLabeler == null || sectionLabeler.isEmpty()) {
+  if (sectionLabeler.isEmpty()) {
frontend/javascripts/viewer/controller/combinations/volume_handlers.ts (2)

46-69: Rename parameter for clarity.

The parameter globalPosRounded is misleading because this function is called with both rounded positions (lines 39, 74, 82) and floating-point positions (line 233). Since the function simply transforms the input coordinate from world space to layer space regardless of rounding, consider renaming it to globalPos or worldPos for clarity.

Apply this diff:

 export function getUntransformedSegmentationPosition(
   state: WebknossosState,
-  globalPosRounded: Vector3,
+  globalPos: Vector3,
 ) {
   /*
    * Converts the given position from world space to layer space.
    */
   const { nativelyRenderedLayerName } = state.datasetConfiguration;
   const maybeLayer = Model.getVisibleSegmentationLayer();
   if (maybeLayer == null) {
     throw new Error("Segmentation layer does not exist");
   }

   const layer = getLayerByName(state.dataset, maybeLayer.name);
   const segmentationTransforms = getTransformsForLayer(
     state.dataset,
     layer,
     nativelyRenderedLayerName,
   );
   const layerPos = transformPointUnscaled(invertTransform(segmentationTransforms))(
-    globalPosRounded,
+    globalPos,
   );
   return layerPos;
 }

219-247: Consider consolidating Store.getState() calls.

The function calls Store.getState() twice (lines 228 and 232). Consider retrieving the state once at the beginning to improve efficiency.

Apply this diff:

 export function handlePickCellFromGlobalPosition(
   globalPos: PositionWithRounding,
   additionalCoordinates: AdditionalCoordinate[],
 ) {
+  const state = Store.getState();
   const segmentId = getSegmentIdForPosition(globalPos.rounded);

   if (segmentId === 0) {
     return;
   }
-  const visibleSegmentationLayer = getVisibleSegmentationLayer(Store.getState());
+  const visibleSegmentationLayer = getVisibleSegmentationLayer(state);
   if (visibleSegmentationLayer == null) {
     return;
   }
-  const state = Store.getState();
   const layerPos = getUntransformedSegmentationPosition(state, globalPos.floating);
   Store.dispatch(setActiveCellAction(segmentId, layerPos, additionalCoordinates));

   Store.dispatch(
     updateSegmentAction(
       segmentId,
       {
         isVisible: true,
       },
       visibleSegmentationLayer.name,
       undefined,
       true,
     ),
   );
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 401b91a and f041791.

📒 Files selected for processing (10)
  • frontend/javascripts/libs/drawing.ts (4 hunks)
  • frontend/javascripts/test/model/transformed_section_labeler.spec.ts (1 hunks)
  • frontend/javascripts/test/reducers/volumetracing_reducer.spec.ts (4 hunks)
  • frontend/javascripts/viewer/constants.ts (1 hunks)
  • frontend/javascripts/viewer/controller/combinations/volume_handlers.ts (3 hunks)
  • frontend/javascripts/viewer/model/accessors/dataset_accessor.ts (2 hunks)
  • frontend/javascripts/viewer/model/accessors/disabled_tool_accessor.ts (1 hunks)
  • frontend/javascripts/viewer/model/bucket_data_handling/data_cube.ts (1 hunks)
  • frontend/javascripts/viewer/model/sagas/volumetracing_saga.tsx (8 hunks)
  • frontend/javascripts/viewer/store.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • frontend/javascripts/viewer/store.ts
  • frontend/javascripts/test/reducers/volumetracing_reducer.spec.ts
  • frontend/javascripts/viewer/model/bucket_data_handling/data_cube.ts
  • frontend/javascripts/viewer/model/accessors/disabled_tool_accessor.ts
  • frontend/javascripts/viewer/constants.ts
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2024-11-25T14:38:49.345Z
Learnt from: dieknolle3333
Repo: scalableminds/webknossos PR: 8229
File: frontend/javascripts/oxalis/model/accessors/dataset_accessor.ts:348-354
Timestamp: 2024-11-25T14:38:49.345Z
Learning: For the `getDatasetExtentAsProduct` function in `dataset_accessor.ts`, input validation for negative or zero dimensions is not necessary.

Applied to files:

  • frontend/javascripts/viewer/model/accessors/dataset_accessor.ts
📚 Learning: 2025-07-22T12:56:47.507Z
Learnt from: daniel-wer
Repo: scalableminds/webknossos PR: 8787
File: frontend/javascripts/viewer/model/accessors/dataset_layer_transformation_accessor.ts:132-136
Timestamp: 2025-07-22T12:56:47.507Z
Learning: Three.js Vector3 has explicitly defined x, y, and z properties that can be safely accessed via dynamic string indexing when using union types like "x" | "y" | "z". The pattern `vector[axis] = value` where axis is "x" | "y" | "z" does not cause TypeScript strict mode errors because TypeScript can infer the property access is valid.

Applied to files:

  • frontend/javascripts/viewer/model/accessors/dataset_accessor.ts
📚 Learning: 2024-11-22T17:18:04.217Z
Learnt from: dieknolle3333
Repo: scalableminds/webknossos PR: 8168
File: frontend/javascripts/oxalis/model/sagas/proofread_saga.ts:1039-1039
Timestamp: 2024-11-22T17:18:04.217Z
Learning: In `frontend/javascripts/oxalis/model/sagas/proofread_saga.ts`, when calling `getMagInfo`, the use of `volumeTracingLayer.resolutions` is intentional and should not be changed to `volumeTracingLayer.mags`.

Applied to files:

  • frontend/javascripts/viewer/model/sagas/volumetracing_saga.tsx
  • frontend/javascripts/viewer/controller/combinations/volume_handlers.ts
📚 Learning: 2024-11-22T17:18:43.411Z
Learnt from: dieknolle3333
Repo: scalableminds/webknossos PR: 8168
File: frontend/javascripts/oxalis/geometries/materials/plane_material_factory.ts:568-585
Timestamp: 2024-11-22T17:18:43.411Z
Learning: In the file `frontend/javascripts/oxalis/geometries/materials/plane_material_factory.ts`, the uniform name `allResolutions` should remain unchanged to maintain consistency with the shader code.

Applied to files:

  • frontend/javascripts/test/model/transformed_section_labeler.spec.ts
📚 Learning: 2025-04-01T09:45:17.527Z
Learnt from: MichaelBuessemeyer
Repo: scalableminds/webknossos PR: 8485
File: frontend/javascripts/oxalis/model/accessors/dataset_layer_transformation_accessor.ts:384-392
Timestamp: 2025-04-01T09:45:17.527Z
Learning: The function `isRotationAndMirrorMaybeOnly` in the dataset_layer_transformation_accessor.ts is intentionally designed to allow mirroring transformations (negative scale values). It uses the length comparison (`scale.length() === NON_SCALED_VECTOR.length()`) rather than component equality to permit mirrored axes while ensuring the overall scale magnitude remains the same.

Applied to files:

  • frontend/javascripts/test/model/transformed_section_labeler.spec.ts
📚 Learning: 2025-09-08T13:33:47.097Z
Learnt from: MichaelBuessemeyer
Repo: scalableminds/webknossos PR: 8824
File: frontend/javascripts/viewer/view/context_menu.tsx:1033-1039
Timestamp: 2025-09-08T13:33:47.097Z
Learning: In frontend/javascripts/viewer/view/context_menu.tsx, the segmentIdLabel intentionally shows the unmapped segment ID (maybeUnmappedSegmentId) in the "within Segment ${id}" text when proofreading is active, as this can be helpful for users. The mapped segment ID (segmentIdAtPosition) is only used as a fallback when the unmapped ID is not available.

Applied to files:

  • frontend/javascripts/viewer/controller/combinations/volume_handlers.ts
🧬 Code graph analysis (4)
frontend/javascripts/viewer/model/accessors/dataset_accessor.ts (1)
frontend/javascripts/viewer/constants.ts (1)
  • Vector3Indices (50-50)
frontend/javascripts/viewer/model/sagas/volumetracing_saga.tsx (4)
frontend/javascripts/viewer/model/sagas/volume/helpers.ts (3)
  • createSectionLabeler (282-302)
  • labelWithVoxelBuffer2D (174-280)
  • BooleanBox (44-46)
frontend/javascripts/viewer/model/actions/volumetracing_actions.ts (4)
  • startEditingAction (173-178)
  • finishEditingAction (201-204)
  • addToContourListAction (180-184)
  • registerLabelPointAction (349-353)
frontend/javascripts/viewer/model/accessors/tool_accessor.ts (4)
  • isBrushTool (170-172)
  • AnnotationTool (91-105)
  • AnnotationTool (109-109)
  • isVolumeDrawingTool (162-169)
frontend/javascripts/viewer/model/volumetracing/section_labeling.ts (1)
  • TransformedSectionLabeler (633-715)
frontend/javascripts/test/model/transformed_section_labeler.spec.ts (5)
frontend/javascripts/viewer/model/volumetracing/section_labeling.ts (1)
  • mapTransformedPlane (587-631)
frontend/javascripts/viewer/constants.ts (2)
  • OrthoView (66-66)
  • IdentityTransform (387-391)
frontend/javascripts/viewer/model/helpers/transformation_helpers.ts (1)
  • Transform (11-27)
frontend/javascripts/viewer/model/accessors/dataset_layer_transformation_accessor.ts (1)
  • combineCoordinateTransformations (191-212)
frontend/javascripts/dashboard/dataset/dataset_rotation_form_item.tsx (1)
  • getRotationalTransformation (225-240)
frontend/javascripts/viewer/controller/combinations/volume_handlers.ts (6)
frontend/javascripts/viewer/model/accessors/view_mode_accessor.ts (2)
  • calculateGlobalPos (368-368)
  • PositionWithRounding (95-95)
frontend/javascripts/viewer/model/actions/volumetracing_actions.ts (6)
  • setContourTracingModeAction (377-381)
  • startEditingAction (173-178)
  • addToContourListAction (180-184)
  • finishEditingAction (201-204)
  • resetContourAction (355-358)
  • setActiveCellAction (206-218)
frontend/javascripts/viewer/store.ts (1)
  • WebknossosState (576-596)
frontend/javascripts/viewer/model/accessors/dataset_accessor.ts (2)
  • getLayerByName (147-166)
  • getVisibleSegmentationLayer (430-440)
frontend/javascripts/viewer/model/accessors/dataset_layer_transformation_accessor.ts (1)
  • getTransformsForLayer (262-270)
frontend/javascripts/viewer/model/helpers/transformation_helpers.ts (2)
  • transformPointUnscaled (165-172)
  • invertTransform (71-87)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build-smoketest-push
  • GitHub Check: backend-tests
🔇 Additional comments (8)
frontend/javascripts/viewer/model/sagas/volumetracing_saga.tsx (4)

5-5: LGTM! Import changes align with the refactoring.

The imports correctly bring in the new SectionLabeler/TransformedSectionLabeler types and the createSectionLabeler helper function to support the refactored labeling approach.

Also applies to: 82-83, 90-90


239-245: LGTM! Correct usage of createSectionLabeler.

The refactored call correctly instantiates a SectionLabeler or TransformedSectionLabeler based on the transform configuration. The third dimension extractor lambda appropriately captures the value from the start position.


251-255: LGTM! Labeler method calls correctly delegate transformation logic.

The calls to getCircleVoxelBuffer2D, getRectangleVoxelBuffer2D, updateArea, and getPlane correctly pass positions to the labeler, which handles coordinate transformation internally when using TransformedSectionLabeler. This maintains proper separation of concerns.

Also applies to: 291-291, 295-323


665-688: LGTM! Contour geometry maintenance updated for renamed action.

The function now correctly listens for ADD_TO_CONTOUR_LIST instead of ADD_TO_LAYER, consistent with the action rename across the codebase.

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

20-20: LGTM! Typo fix applied consistently.

The spelling correction from Vector3Indicies to Vector3Indices is applied consistently in both the import and usage. No functional changes.

Also applies to: 247-247

frontend/javascripts/viewer/controller/combinations/volume_handlers.ts (2)

6-34: LGTM! Imports align with transform-aware refactoring.

The new imports for transformation helpers, layer accessors, and position types are appropriate for converting the volume handling code to work with transformed layer coordinates.


36-44: LGTM! Drawing and erasing handlers correctly transform to layer space.

Both handleDrawStart and handleEraseStart now correctly:

  1. Calculate the global position from the viewport coordinates
  2. Transform it to layer space via getUntransformedSegmentationPosition
  3. Dispatch actions with layer-space coordinates

This aligns with the PR's objective to support transformed layers.

Also applies to: 71-78

frontend/javascripts/test/model/transformed_section_labeler.spec.ts (1)

16-172: Thorough transform coverage pays off

Nice work locking down canonical, rotated, and chained affine cases (including adaptScale ordering) for TransformedSectionLabeler; this gives strong confidence in the refactor.

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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f041791 and 4821048.

📒 Files selected for processing (1)
  • frontend/javascripts/viewer/controller/combinations/volume_handlers.ts (3 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2024-11-22T17:18:04.217Z
Learnt from: dieknolle3333
Repo: scalableminds/webknossos PR: 8168
File: frontend/javascripts/oxalis/model/sagas/proofread_saga.ts:1039-1039
Timestamp: 2024-11-22T17:18:04.217Z
Learning: In `frontend/javascripts/oxalis/model/sagas/proofread_saga.ts`, when calling `getMagInfo`, the use of `volumeTracingLayer.resolutions` is intentional and should not be changed to `volumeTracingLayer.mags`.

Applied to files:

  • frontend/javascripts/viewer/controller/combinations/volume_handlers.ts
🧬 Code graph analysis (1)
frontend/javascripts/viewer/controller/combinations/volume_handlers.ts (8)
frontend/javascripts/viewer/constants.ts (4)
  • Point2 (27-30)
  • OrthoView (66-66)
  • Vector3 (14-14)
  • AdditionalCoordinate (2-2)
frontend/javascripts/viewer/model/accessors/view_mode_accessor.ts (2)
  • calculateGlobalPos (368-368)
  • PositionWithRounding (95-95)
frontend/javascripts/viewer/model/actions/volumetracing_actions.ts (6)
  • setContourTracingModeAction (377-381)
  • startEditingAction (173-178)
  • addToContourListAction (180-184)
  • finishEditingAction (201-204)
  • resetContourAction (355-358)
  • setActiveCellAction (206-218)
frontend/javascripts/viewer/store.ts (1)
  • WebknossosState (576-596)
frontend/javascripts/viewer/model.ts (2)
  • getLayerByName (217-223)
  • getVisibleSegmentationLayer (121-135)
frontend/javascripts/viewer/model/accessors/dataset_accessor.ts (2)
  • getLayerByName (147-166)
  • getVisibleSegmentationLayer (430-440)
frontend/javascripts/viewer/model/accessors/dataset_layer_transformation_accessor.ts (1)
  • getTransformsForLayer (262-270)
frontend/javascripts/viewer/model/helpers/transformation_helpers.ts (2)
  • transformPointUnscaled (165-172)
  • invertTransform (71-87)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: build-smoketest-push
  • GitHub Check: frontend-tests
  • GitHub Check: backend-tests
🔇 Additional comments (4)
frontend/javascripts/viewer/controller/combinations/volume_handlers.ts (4)

6-34: LGTM! Imports properly support transformation logic.

The new imports correctly bring in the layer transformation utilities, position types, and renamed action creators needed for the refactoring.


36-44: LGTM! Draw flow correctly transforms to layer space.

The function properly computes layer-space coordinates from the global position before dispatching editing actions, which is consistent with the documented action contracts.


46-69: LGTM! Transformation helper correctly inverts layer transforms.

This new exported function properly converts world-space positions to layer-space by inverting the segmentation layer's transforms. The error thrown when the segmentation layer is missing is appropriate for these user-initiated handlers.


71-84: LGTM! Erase and move handlers correctly apply transformations.

Both functions follow the established pattern of converting screen positions to global, then to layer space before dispatching actions.

Comment on lines +219 to +234
export function handlePickCellFromGlobalPosition(
globalPos: PositionWithRounding,
additionalCoordinates: AdditionalCoordinate[],
) {
const visibleSegmentationLayer = getVisibleSegmentationLayer(Store.getState());
if (visibleSegmentationLayer == null) {
return;
}
const posInLayerSpace = globalToLayerTransformedPosition(
globalPos,
visibleSegmentationLayer.name,
"segmentation",
Store.getState(),
);

const segmentId = getSegmentIdForPosition(globalPos);
const segmentId = getSegmentIdForPosition(globalPos.rounded);

if (segmentId === 0) {
return;
}
Store.dispatch(setActiveCellAction(segmentId, posInLayerSpace, additionalCoordinates));
const visibleSegmentationLayer = getVisibleSegmentationLayer(Store.getState());
if (visibleSegmentationLayer == null) {
return;
}
const state = Store.getState();
const layerPos = getUntransformedSegmentationPosition(state, globalPos.floating);
Store.dispatch(setActiveCellAction(segmentId, layerPos, additionalCoordinates));
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 | 🟡 Minor

Consider using globalPos.rounded consistently for both segment ID and position.

Lines 223 and 233 use different precision levels:

  • Line 223: globalPos.rounded determines which segment was clicked
  • Line 233: globalPos.floating computes the layer-space position to store

With non-linear transforms (thin plate spline), the rounded and floating global positions could map to different voxels in layer space after transformation. This means the stored somePosition might not correspond to the actual segment identified by segmentId, which could cause confusion or bugs downstream.

Consider using globalPos.rounded on line 233 for consistency:

- const layerPos = getUntransformedSegmentationPosition(state, globalPos.floating);
+ const layerPos = getUntransformedSegmentationPosition(state, globalPos.rounded);

Alternatively, if floating-point precision is important for the stored position, add a comment explaining why the precision difference is intentional and safe.

📝 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
export function handlePickCellFromGlobalPosition(
globalPos: PositionWithRounding,
additionalCoordinates: AdditionalCoordinate[],
) {
const visibleSegmentationLayer = getVisibleSegmentationLayer(Store.getState());
if (visibleSegmentationLayer == null) {
return;
}
const posInLayerSpace = globalToLayerTransformedPosition(
globalPos,
visibleSegmentationLayer.name,
"segmentation",
Store.getState(),
);
const segmentId = getSegmentIdForPosition(globalPos);
const segmentId = getSegmentIdForPosition(globalPos.rounded);
if (segmentId === 0) {
return;
}
Store.dispatch(setActiveCellAction(segmentId, posInLayerSpace, additionalCoordinates));
const visibleSegmentationLayer = getVisibleSegmentationLayer(Store.getState());
if (visibleSegmentationLayer == null) {
return;
}
const state = Store.getState();
const layerPos = getUntransformedSegmentationPosition(state, globalPos.floating);
Store.dispatch(setActiveCellAction(segmentId, layerPos, additionalCoordinates));
export function handlePickCellFromGlobalPosition(
globalPos: PositionWithRounding,
additionalCoordinates: AdditionalCoordinate[],
) {
const segmentId = getSegmentIdForPosition(globalPos.rounded);
if (segmentId === 0) {
return;
}
const visibleSegmentationLayer = getVisibleSegmentationLayer(Store.getState());
if (visibleSegmentationLayer == null) {
return;
}
const state = Store.getState();
const layerPos = getUntransformedSegmentationPosition(state, globalPos.rounded);
Store.dispatch(setActiveCellAction(segmentId, layerPos, additionalCoordinates));
🤖 Prompt for AI Agents
In frontend/javascripts/viewer/controller/combinations/volume_handlers.ts around
lines 219-234 the code uses globalPos.rounded to pick the segmentId but
globalPos.floating to compute layerPos, which can map to different voxels under
non-linear transforms; change the call to getUntransformedSegmentationPosition
to use globalPos.rounded (or otherwise ensure the same precision is used for
both segmentId and stored position), verify the argument type matches (convert
if necessary), and update or add a brief inline comment/tests if you
intentionally keep floating precision explaining why it is safe.

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

♻️ Duplicate comments (2)
frontend/javascripts/viewer/model/volumetracing/section_labeling.ts (2)

633-705: Apply transforms in TransformedSectionLabeler before delegating to base for 2D coords and circles

TransformedSectionLabeler exposes the same external “global coordinate” API as SectionLabeler but only some methods apply the transform:

  • updateArea and getRectangleVoxelBuffer2D correctly call this.applyTransform(...) before delegating.
  • globalCoordToMag2DFloat and getCircleVoxelBuffer2D currently forward the untransformed position to the base labeler, which expects coordinates in the transformed plane.

On rotated/mapped layers this will yield incorrect (u, v) coordinates and misaligned brush circles, even though the rest of the pipeline (plane selection, voxel buffer labeling) is transform-aware. This is a functional correctness issue for transformed views.

A minimal fix that keeps identity-transforms working is:

-  globalCoordToMag2DFloat(position: Vector3): Vector2 {
-    return this.base.globalCoordToMag2DFloat(position);
-  }
+  globalCoordToMag2DFloat(position: Vector3): Vector2 {
+    const transformed = this.applyTransform(position);
+    return this.base.globalCoordToMag2DFloat(transformed);
+  }-  getCircleVoxelBuffer2D(position: Vector3): VoxelBuffer2D {
-    let scale = this.adaptScaleFn(
-      getBaseVoxelFactorsInUnit(Store.getState().dataset.dataSource.scale),
-    );
-
-    return this.base.getCircleVoxelBuffer2D(position, scale);
-  }
+  getCircleVoxelBuffer2D(position: Vector3): VoxelBuffer2D {
+    const transformed = this.applyTransform(position);
+    const scale = this.adaptScaleFn(
+      getBaseVoxelFactorsInUnit(Store.getState().dataset.dataSource.scale),
+    );
+    return this.base.getCircleVoxelBuffer2D(transformed, scale);
+  }

This makes TransformedSectionLabeler consistently treat its arguments as untransformed global positions, with all geometry handled in the transformed base space.


560-631: Scale adaptation in mapTransformedPlane uses originalPlane instead of mapped plane

mapTransformedPlane computes bestView (the mapped plane) from the transformed normal, but adaptScaleFn still calls Dimensions.transDim(scale, originalPlane) and applies the swap based on the pre-mapping swapped flag. For transforms that change the canonical plane (e.g. XY → YZ), this mixes the wrong axes and is very likely why some transformed layers appear “squished” despite correct plane detection.

Aligning the scale mapping with the mapped plane and only swapping when we stayed in the same plane would fix this:

-  const swapped = isAlmostZero(basis.u.dot(u2));
+  const swapped = isAlmostZero(basis.u.dot(u2));-  const adaptScaleFn = (scale: Vector3): Vector2 => {
-    const transposed = Dimensions.transDim(scale, originalPlane);
-    if (swapped) {
-      return [transposed[1], transposed[0]];
-    } else {
-      return [transposed[0], transposed[1]];
-    }
-  };
-
-  return [bestView, bestView === originalPlane && swapped, adaptScaleFn];
+  const adaptScaleFn = (scale: Vector3): Vector2 => {
+    const transposed = Dimensions.transDim(scale, bestView);
+    if (bestView === originalPlane && swapped) {
+      return [transposed[1], transposed[0]];
+    }
+    return [transposed[0], transposed[1]];
+  };
+
+  return [bestView, bestView === originalPlane && swapped, adaptScaleFn];

This keeps the isSwapped flag semantics while making the scale consistent with the plane actually used by the base SectionLabeler.

🧹 Nitpick comments (3)
frontend/javascripts/dashboard/dataset/dataset_rotation_form_item.tsx (1)

225-240: Approve extraction; consider relocating to transformation accessor module.

The function correctly extracts the transformation construction logic and reduces duplication. However, since this is now an exported utility function, consider moving it to frontend/javascripts/viewer/model/accessors/dataset_layer_transformation_accessor.ts alongside the related transformation helpers (fromCenterToOrigin, fromOriginToCenter, getRotationMatrixAroundAxis) for better organization and discoverability.

If you relocate the function, apply a change like this:

In dataset_layer_transformation_accessor.ts, add:

export function getRotationalTransformation(
  datasetBoundingBox: BoundingBox,
  rotationValues: {
    x: RotationAndMirroringSettings;
    y: RotationAndMirroringSettings;
    z: RotationAndMirroringSettings;
  },
): AffineTransformation[] {
  return [
    fromCenterToOrigin(datasetBoundingBox),
    getRotationMatrixAroundAxis("x", rotationValues["x"]),
    getRotationMatrixAroundAxis("y", rotationValues["y"]),
    getRotationMatrixAroundAxis("z", rotationValues["z"]),
    fromOriginToCenter(datasetBoundingBox),
  ];
}

Then import it in this file instead:

 import {
   AXIS_TO_TRANSFORM_INDEX,
   EXPECTED_TRANSFORMATION_LENGTH,
   IDENTITY_TRANSFORM,
   type RotationAndMirroringSettings,
   doAllLayersHaveTheSameRotation,
   fromCenterToOrigin,
   fromOriginToCenter,
   getRotationMatrixAroundAxis,
+  getRotationalTransformation,
   transformationEqualsAffineIdentityTransform,
 } from "viewer/model/accessors/dataset_layer_transformation_accessor";
frontend/javascripts/viewer/model/sagas/volume/helpers.ts (1)

282-302: createSectionLabeler wiring is coherent with transformed-layer design

createSectionLabeler correctly pulls dataset and datasetConfiguration from the store, resolves the segmentation layer via getLayerByName, and derives transforms with getTransformsForLayer before constructing a TransformedSectionLabeler. Always returning the transformed variant (with identity transforms as a no-op) simplifies saga code and is a reasonable design choice.

If you later need to avoid the overhead for untransformed datasets, you could branch on an identity transform and return a plain SectionLabeler, but that’s an optimisation, not a blocker.

frontend/javascripts/viewer/model/volumetracing/section_labeling.ts (1)

172-201: SectionLabeler’s new plane/3D-coordinate plumbing is coherent

The introduction of plane, thirdDimensionValue, and fast3DCoordinateFunction together with get2DCoordinate, getUnzoomedCentroid, and createVoxelBuffer2D provides a clean way to map between 2D slice coordinates and 3D positions for any orthogonal plane. The fast coordinate function and use of Vector2Indices/Vector3Indices make the code less error-prone across XY/YZ/XZ. No issues here beyond the transform-specific concerns already called out.

If isSwapped stays unused inside SectionLabeler, consider documenting its intended future use or removing it to avoid confusion.

Also applies to: 365-367, 516-553, 717-727

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4821048 and 0fb28a6.

📒 Files selected for processing (28)
  • frontend/javascripts/dashboard/dataset/dataset_rotation_form_item.tsx (3 hunks)
  • frontend/javascripts/libs/drawing.ts (4 hunks)
  • frontend/javascripts/test/global_mocks.ts (1 hunks)
  • frontend/javascripts/test/model/transformed_section_labeler.spec.ts (1 hunks)
  • frontend/javascripts/test/reducers/volumetracing_reducer.spec.ts (4 hunks)
  • frontend/javascripts/test/sagas/volumetracing/bucket_eviction_helper.ts (3 hunks)
  • frontend/javascripts/test/sagas/volumetracing/volumetracing_saga.spec.ts (9 hunks)
  • frontend/javascripts/test/sagas/volumetracing/volumetracing_saga_integration_1.spec.ts (6 hunks)
  • frontend/javascripts/test/sagas/volumetracing/volumetracing_saga_integration_2.spec.ts (8 hunks)
  • frontend/javascripts/viewer/constants.ts (1 hunks)
  • frontend/javascripts/viewer/controller/combinations/volume_handlers.ts (3 hunks)
  • frontend/javascripts/viewer/controller/scene_controller.ts (4 hunks)
  • frontend/javascripts/viewer/model/accessors/dataset_accessor.ts (2 hunks)
  • frontend/javascripts/viewer/model/accessors/dataset_layer_transformation_accessor.ts (2 hunks)
  • frontend/javascripts/viewer/model/accessors/disabled_tool_accessor.ts (1 hunks)
  • frontend/javascripts/viewer/model/actions/volumetracing_actions.ts (3 hunks)
  • frontend/javascripts/viewer/model/bucket_data_handling/data_cube.ts (1 hunks)
  • frontend/javascripts/viewer/model/helpers/action_logger_middleware.ts (1 hunks)
  • frontend/javascripts/viewer/model/helpers/transformation_helpers.ts (1 hunks)
  • frontend/javascripts/viewer/model/reducers/volumetracing_reducer.ts (2 hunks)
  • frontend/javascripts/viewer/model/reducers/volumetracing_reducer_helpers.ts (1 hunks)
  • frontend/javascripts/viewer/model/sagas/volume/helpers.ts (6 hunks)
  • frontend/javascripts/viewer/model/sagas/volume/quick_select/quick_select_heuristic_saga.ts (2 hunks)
  • frontend/javascripts/viewer/model/sagas/volume/volume_interpolation_saga.ts (2 hunks)
  • frontend/javascripts/viewer/model/sagas/volumetracing_saga.tsx (8 hunks)
  • frontend/javascripts/viewer/model/volumetracing/section_labeling.ts (15 hunks)
  • frontend/javascripts/viewer/store.ts (1 hunks)
  • frontend/javascripts/viewer/view/right-border-tabs/segments_tab/segments_view.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (14)
  • frontend/javascripts/viewer/model/helpers/action_logger_middleware.ts
  • frontend/javascripts/viewer/model/helpers/transformation_helpers.ts
  • frontend/javascripts/viewer/model/reducers/volumetracing_reducer.ts
  • frontend/javascripts/test/reducers/volumetracing_reducer.spec.ts
  • frontend/javascripts/test/sagas/volumetracing/volumetracing_saga_integration_2.spec.ts
  • frontend/javascripts/viewer/model/accessors/dataset_accessor.ts
  • frontend/javascripts/viewer/model/accessors/disabled_tool_accessor.ts
  • frontend/javascripts/viewer/store.ts
  • frontend/javascripts/viewer/constants.ts
  • frontend/javascripts/viewer/model/actions/volumetracing_actions.ts
  • frontend/javascripts/viewer/view/right-border-tabs/segments_tab/segments_view.tsx
  • frontend/javascripts/test/model/transformed_section_labeler.spec.ts
  • frontend/javascripts/viewer/model/accessors/dataset_layer_transformation_accessor.ts
  • frontend/javascripts/libs/drawing.ts
🧰 Additional context used
🧠 Learnings (11)
📚 Learning: 2025-05-07T06:17:32.810Z
Learnt from: philippotto
Repo: scalableminds/webknossos PR: 8602
File: frontend/javascripts/oxalis/model/volumetracing/volume_annotation_sampling.ts:365-366
Timestamp: 2025-05-07T06:17:32.810Z
Learning: The parameter in applyVoxelMap was renamed from `sliceCount` to `sliceOffset` to better reflect its purpose, but this doesn't affect existing call sites since JavaScript/TypeScript function calls are position-based.

Applied to files:

  • frontend/javascripts/viewer/model/bucket_data_handling/data_cube.ts
  • frontend/javascripts/viewer/model/reducers/volumetracing_reducer_helpers.ts
  • frontend/javascripts/viewer/model/sagas/volume/volume_interpolation_saga.ts
  • frontend/javascripts/viewer/model/volumetracing/section_labeling.ts
📚 Learning: 2024-11-22T17:18:04.217Z
Learnt from: dieknolle3333
Repo: scalableminds/webknossos PR: 8168
File: frontend/javascripts/oxalis/model/sagas/proofread_saga.ts:1039-1039
Timestamp: 2024-11-22T17:18:04.217Z
Learning: In `frontend/javascripts/oxalis/model/sagas/proofread_saga.ts`, when calling `getMagInfo`, the use of `volumeTracingLayer.resolutions` is intentional and should not be changed to `volumeTracingLayer.mags`.

Applied to files:

  • frontend/javascripts/test/sagas/volumetracing/volumetracing_saga_integration_1.spec.ts
  • frontend/javascripts/viewer/model/reducers/volumetracing_reducer_helpers.ts
  • frontend/javascripts/viewer/model/sagas/volume/quick_select/quick_select_heuristic_saga.ts
  • frontend/javascripts/test/sagas/volumetracing/bucket_eviction_helper.ts
  • frontend/javascripts/test/sagas/volumetracing/volumetracing_saga.spec.ts
  • frontend/javascripts/viewer/model/sagas/volume/volume_interpolation_saga.ts
  • frontend/javascripts/viewer/controller/combinations/volume_handlers.ts
  • frontend/javascripts/viewer/model/sagas/volume/helpers.ts
  • frontend/javascripts/viewer/model/sagas/volumetracing_saga.tsx
  • frontend/javascripts/viewer/model/volumetracing/section_labeling.ts
📚 Learning: 2025-05-30T12:36:09.930Z
Learnt from: philippotto
Repo: scalableminds/webknossos PR: 8542
File: frontend/javascripts/viewer/model/sagas/volumetracing_saga.tsx:0-0
Timestamp: 2025-05-30T12:36:09.930Z
Learning: The WebKnossos frontend follows Redux immutability principles where reducers act immutably and create new state objects when changes occur. This makes reference equality checks (like `prevVolumeTracing.segments !== volumeTracing.segments`) reliable and efficient for detecting actual state changes, allowing memoization to work correctly.

Applied to files:

  • frontend/javascripts/viewer/model/reducers/volumetracing_reducer_helpers.ts
  • frontend/javascripts/test/sagas/volumetracing/volumetracing_saga.spec.ts
📚 Learning: 2025-08-14T14:12:03.293Z
Learnt from: hotzenklotz
Repo: scalableminds/webknossos PR: 8849
File: frontend/javascripts/viewer/view/action-bar/ai_job_modals/components/collapsible_split_merger_evaluation_settings.tsx:39-51
Timestamp: 2025-08-14T14:12:03.293Z
Learning: Since Ant Design version 5.18, Form.Item components support the layout prop (layout="horizontal", layout="vertical", etc.), allowing individual form items to override the parent Form's layout setting. This is a newer API addition that provides more granular control over form item layouts.

Applied to files:

  • frontend/javascripts/test/global_mocks.ts
  • frontend/javascripts/dashboard/dataset/dataset_rotation_form_item.tsx
📚 Learning: 2025-08-14T14:12:03.293Z
Learnt from: hotzenklotz
Repo: scalableminds/webknossos PR: 8849
File: frontend/javascripts/viewer/view/action-bar/ai_job_modals/components/collapsible_split_merger_evaluation_settings.tsx:39-51
Timestamp: 2025-08-14T14:12:03.293Z
Learning: Since Ant Design version 5.18, Form.Item components support the layout prop (layout="horizontal", layout="vertical", layout="inline"), allowing individual form items to override the parent Form's layout setting. This enables mixed layouts within a single form and was added as a new API feature in v5.18 according to the official changelog.

Applied to files:

  • frontend/javascripts/test/global_mocks.ts
  • frontend/javascripts/dashboard/dataset/dataset_rotation_form_item.tsx
📚 Learning: 2025-10-21T10:54:16.468Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 8961
File: frontend/javascripts/viewer/model/actions/annotation_actions.ts:80-82
Timestamp: 2025-10-21T10:54:16.468Z
Learning: In frontend/javascripts/viewer/model/actions/annotation_actions.ts, the ShowManyBucketUpdatesWarningAction is intentionally not included in the AnnotationActionTypes union because it's a UI-only action that doesn't modify the annotation state through reducers.

Applied to files:

  • frontend/javascripts/test/sagas/volumetracing/bucket_eviction_helper.ts
📚 Learning: 2025-04-01T09:45:17.527Z
Learnt from: MichaelBuessemeyer
Repo: scalableminds/webknossos PR: 8485
File: frontend/javascripts/oxalis/model/accessors/dataset_layer_transformation_accessor.ts:384-392
Timestamp: 2025-04-01T09:45:17.527Z
Learning: The function `isRotationAndMirrorMaybeOnly` in the dataset_layer_transformation_accessor.ts is intentionally designed to allow mirroring transformations (negative scale values). It uses the length comparison (`scale.length() === NON_SCALED_VECTOR.length()`) rather than component equality to permit mirrored axes while ensuring the overall scale magnitude remains the same.

Applied to files:

  • frontend/javascripts/dashboard/dataset/dataset_rotation_form_item.tsx
  • frontend/javascripts/viewer/model/volumetracing/section_labeling.ts
📚 Learning: 2025-09-08T13:33:47.097Z
Learnt from: MichaelBuessemeyer
Repo: scalableminds/webknossos PR: 8824
File: frontend/javascripts/viewer/view/context_menu.tsx:1033-1039
Timestamp: 2025-09-08T13:33:47.097Z
Learning: In frontend/javascripts/viewer/view/context_menu.tsx, the segmentIdLabel intentionally shows the unmapped segment ID (maybeUnmappedSegmentId) in the "within Segment ${id}" text when proofreading is active, as this can be helpful for users. The mapped segment ID (segmentIdAtPosition) is only used as a fallback when the unmapped ID is not available.

Applied to files:

  • frontend/javascripts/viewer/controller/combinations/volume_handlers.ts
📚 Learning: 2025-05-15T19:44:16.110Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 8492
File: frontend/javascripts/viewer/model/sagas/skeletontracing_saga.ts:671-674
Timestamp: 2025-05-15T19:44:16.110Z
Learning: Visibility updates for user bounding boxes are intentionally handled separately from other property updates, with dedicated update actions for visibility changes.

Applied to files:

  • frontend/javascripts/viewer/controller/scene_controller.ts
📚 Learning: 2025-08-27T11:34:16.411Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 8796
File: frontend/javascripts/viewer/view/action-bar/ai_job_modals/forms/neuron_segmentation_form.tsx:68-77
Timestamp: 2025-08-27T11:34:16.411Z
Learning: The webknossos codebase uses `getDefaultLayerViewConfiguration` to ensure that layer configurations always exist in `datasetConfiguration.layers`, making direct access like `datasetConfiguration.layers[colorLayer.name]` safe across multiple files including api_latest.ts and load_histogram_data_saga.ts.

Applied to files:

  • frontend/javascripts/viewer/model/sagas/volume/helpers.ts
📚 Learning: 2024-11-22T17:18:43.411Z
Learnt from: dieknolle3333
Repo: scalableminds/webknossos PR: 8168
File: frontend/javascripts/oxalis/geometries/materials/plane_material_factory.ts:568-585
Timestamp: 2024-11-22T17:18:43.411Z
Learning: In the file `frontend/javascripts/oxalis/geometries/materials/plane_material_factory.ts`, the uniform name `allResolutions` should remain unchanged to maintain consistency with the shader code.

Applied to files:

  • frontend/javascripts/viewer/model/volumetracing/section_labeling.ts
🧬 Code graph analysis (11)
frontend/javascripts/test/sagas/volumetracing/volumetracing_saga_integration_1.spec.ts (1)
frontend/javascripts/viewer/model/actions/volumetracing_actions.ts (4)
  • addToContourListAction (184-188)
  • finishEditingAction (205-208)
  • setActiveCellAction (210-222)
  • startEditingAction (177-182)
frontend/javascripts/viewer/model/sagas/volume/quick_select/quick_select_heuristic_saga.ts (1)
frontend/javascripts/viewer/model/sagas/volume/helpers.ts (1)
  • createSectionLabeler (282-302)
frontend/javascripts/test/sagas/volumetracing/bucket_eviction_helper.ts (1)
frontend/javascripts/viewer/model/actions/volumetracing_actions.ts (1)
  • addToContourListAction (184-188)
frontend/javascripts/test/sagas/volumetracing/volumetracing_saga.spec.ts (3)
frontend/javascripts/test/fixtures/dataset_server_object.ts (1)
  • sampleTracingLayer (63-67)
frontend/javascripts/test/helpers/sagaHelpers.ts (1)
  • execCall (21-26)
frontend/javascripts/viewer/model/sagas/volumetracing_saga.tsx (1)
  • finishSectionLabeler (364-389)
frontend/javascripts/viewer/model/sagas/volume/volume_interpolation_saga.ts (1)
frontend/javascripts/viewer/model/sagas/volume/helpers.ts (1)
  • createSectionLabeler (282-302)
frontend/javascripts/dashboard/dataset/dataset_rotation_form_item.tsx (2)
frontend/javascripts/viewer/model/accessors/dataset_layer_transformation_accessor.ts (4)
  • RotationAndMirroringSettings (76-79)
  • fromCenterToOrigin (107-113)
  • getRotationMatrixAroundAxis (123-144)
  • fromOriginToCenter (115-121)
frontend/javascripts/types/api_types.ts (1)
  • AffineTransformation (81-84)
frontend/javascripts/viewer/controller/combinations/volume_handlers.ts (5)
frontend/javascripts/viewer/model/accessors/view_mode_accessor.ts (2)
  • calculateGlobalPos (368-368)
  • PositionWithRounding (95-95)
frontend/javascripts/viewer/model/accessors/dataset_accessor.ts (2)
  • getLayerByName (147-166)
  • getVisibleSegmentationLayer (430-440)
frontend/javascripts/viewer/model.ts (2)
  • getLayerByName (215-221)
  • getVisibleSegmentationLayer (119-133)
frontend/javascripts/viewer/model/accessors/dataset_layer_transformation_accessor.ts (1)
  • getTransformsForLayer (262-270)
frontend/javascripts/viewer/model/helpers/transformation_helpers.ts (2)
  • transformPointUnscaled (165-172)
  • invertTransform (71-87)
frontend/javascripts/viewer/controller/scene_controller.ts (3)
frontend/javascripts/viewer/constants.ts (2)
  • OrthoViewMap (69-69)
  • OrthoViewWithoutTDMap (70-70)
frontend/javascripts/viewer/geometries/helper_geometries.ts (3)
  • ContourGeometry (31-172)
  • QuickSelectGeometry (181-340)
  • LineMeasurementGeometry (343-459)
frontend/javascripts/viewer/controller/segment_mesh_controller.ts (1)
  • SegmentMeshController (80-707)
frontend/javascripts/viewer/model/sagas/volume/helpers.ts (4)
frontend/javascripts/viewer/store.ts (1)
  • VolumeTracing (193-219)
frontend/javascripts/viewer/constants.ts (2)
  • OrthoView (66-66)
  • Vector3 (14-14)
frontend/javascripts/viewer/model/volumetracing/section_labeling.ts (1)
  • TransformedSectionLabeler (633-715)
frontend/javascripts/viewer/model/accessors/dataset_layer_transformation_accessor.ts (1)
  • getTransformsForLayer (262-270)
frontend/javascripts/viewer/model/sagas/volumetracing_saga.tsx (4)
frontend/javascripts/viewer/model/sagas/volume/helpers.ts (3)
  • createSectionLabeler (282-302)
  • labelWithVoxelBuffer2D (174-280)
  • BooleanBox (44-46)
frontend/javascripts/viewer/model/actions/volumetracing_actions.ts (4)
  • startEditingAction (177-182)
  • finishEditingAction (205-208)
  • addToContourListAction (184-188)
  • registerLabelPointAction (363-367)
frontend/javascripts/viewer/model/accessors/tool_accessor.ts (4)
  • isBrushTool (170-172)
  • AnnotationTool (91-105)
  • AnnotationTool (109-109)
  • isVolumeDrawingTool (162-169)
frontend/javascripts/viewer/model/volumetracing/section_labeling.ts (1)
  • TransformedSectionLabeler (633-715)
frontend/javascripts/viewer/model/volumetracing/section_labeling.ts (5)
frontend/javascripts/viewer/constants.ts (5)
  • Vector2 (13-13)
  • Vector3 (14-14)
  • OrthoView (66-66)
  • Vector3Indices (50-50)
  • Vector2Indices (49-49)
frontend/javascripts/viewer/model/scaleinfo.ts (1)
  • getBaseVoxelFactorsInUnit (26-33)
frontend/javascripts/viewer/model/helpers/position_converter.ts (1)
  • zoomedPositionToGlobalPosition (45-47)
frontend/javascripts/viewer/model/helpers/transformation_helpers.ts (3)
  • Transform (11-27)
  • transformPointUnscaled (165-172)
  • invertTransform (71-87)
frontend/javascripts/viewer/model/accessors/dataset_layer_transformation_accessor.ts (1)
  • invertAndTranspose (372-374)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: frontend-tests
  • GitHub Check: build-smoketest-push
  • GitHub Check: backend-tests
🔇 Additional comments (19)
frontend/javascripts/test/global_mocks.ts (1)

143-145: LGTM!

The addition of the Typography.Text mock follows the same pattern as other antd component mocks in this file. The empty object placeholder is consistent with the existing mocking strategy.

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

6-6: LGTM: Import added for new function signature.

The AffineTransformation type import is correctly added to support the return type of the new getRotationalTransformation function.


67-67: LGTM: Good refactor to use extracted helper.

Replacing the inline transformation construction with the extracted getRotationalTransformation function reduces duplication and improves maintainability.

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

46-69: LGTM! Clean world-to-layer coordinate conversion.

The new helper function properly handles the transformation pipeline: retrieves the segmentation layer, obtains the appropriate transform (considering the natively rendered layer), and applies the inverse transform to convert from world space to layer space. Error handling for missing layers is also in place.

frontend/javascripts/viewer/controller/scene_controller.ts (1)

570-608: LGTM! Transform coverage extended to annotation tools.

The renamed method now correctly applies transforms to the annotationToolsGeometryGroup in addition to user bounding boxes and meshes. This ensures that annotation-specific geometries (contour, quick select, etc.) are properly transformed, which aligns with the PR's goal of supporting transformed layers.

frontend/javascripts/viewer/model/bucket_data_handling/data_cube.ts (1)

37-40: LGTM! Import path update aligns with refactoring.

The import path change from volumelayer to section_labeling is consistent with the broader migration to the new labeling abstractions introduced in this PR.

frontend/javascripts/viewer/model/sagas/volume/volume_interpolation_saga.ts (1)

370-386: LGTM! Consistent migration to SectionLabeler API.

The switch from createVolumeLayer to createSectionLabeler is properly implemented with matching updates to variable names and method calls (createVoxelBuffer2D, globalCoordToMag2DFloat). The logic remains unchanged, reflecting a clean API migration.

frontend/javascripts/test/sagas/volumetracing/bucket_eviction_helper.ts (1)

11-11: LGTM! Action rename applied consistently.

The test helper correctly uses the renamed addToContourListAction in both dispatch calls, aligning with the broader action API refactor from ADD_TO_LAYER to ADD_TO_CONTOUR_LIST.

Also applies to: 63-63, 76-76

frontend/javascripts/test/sagas/volumetracing/volumetracing_saga_integration_1.spec.ts (1)

24-24: LGTM! Integration tests updated consistently.

All test cases correctly use the renamed addToContourListAction throughout the file, maintaining consistency with the broader action API refactor.

Also applies to: 72-72, 77-77, 132-132, 186-186, 236-236, 282-282

frontend/javascripts/viewer/model/reducers/volumetracing_reducer_helpers.ts (1)

131-148: LGTM! Reducer renamed to match action refactor.

The function rename from addToLayerReducer to addToContourListReducer aligns with the broader migration from ADD_TO_LAYER to ADD_TO_CONTOUR_LIST. The implementation logic remains unchanged, reflecting a clean naming update.

frontend/javascripts/viewer/model/sagas/volume/quick_select/quick_select_heuristic_saga.ts (1)

515-527: LGTM! Quick select updated to use SectionLabeler.

The migration from createVolumeLayer to createSectionLabeler is implemented consistently with the same pattern used in volume_interpolation_saga.ts. Variable names and method calls are properly updated to use the new SectionLabeler API.

frontend/javascripts/test/sagas/volumetracing/volumetracing_saga.spec.ts (4)

17-55: Saga wiring and imports look consistent with new SectionLabeler flow

The updated imports (including finishSectionLabeler, SectionLabeler, sampleTracingLayer, and defaultVolumeState) and the addToContourListActionFn alias line up with the refactored saga API and renamed action. No issues from a typing or wiring perspective.


49-56: createSectionLabeler saga driving and mocked dataset are sound

Manually advancing the createSectionLabeler generator and feeding in mockedDataset and a minimal { nativelyRenderedLayerName: undefined } config matches the saga’s select-based contract and ensures getLayerByName can resolve volumeTracingId. The final labeller.getPlane() assertion is an appropriate sanity check for the plane mapping.

Also applies to: 129-136


174-192: SectionLabeler usage in “add values” test matches the new constructor

Instantiating SectionLabeler with (tracingId, OrthoViews.PLANE_XY, 10, [1, 1, 1], false) matches the new signature, and the ADD_TO_CONTOUR_LIST events drive updateArea as expected, making the minCoord and maxCoord assertions meaningful. This provides good coverage for the new bounding-box logic.


230-328: finishSectionLabeler expectations correctly reflect the new API

Both “finish volume layer” tests now assert a call(finishSectionLabeler, …) with the right arguments (SectionLabeler instance, tool, contour mode, overwrite mode, zoom step, and wroteVoxelsBox). This keeps the spec aligned with the refactored saga semantics.

frontend/javascripts/viewer/model/sagas/volume/helpers.ts (1)

12-18: VoxelBuffer2D integration in labelWithVoxelBuffer2D looks correct

The switch to voxelBuffer.getTopLeft3DCoord()/getBottomRight3DCoord() and getValueFromGlobal(globalA, globalB) is consistent with the new VoxelBuffer2D contract. The third-dimension increment before constructing the BoundingBox preserves the inclusive 2D area while making the 3D box non-empty, and the usage of dimensionIndices matches the plane-dependent layout.

Also applies to: 27-31, 174-259

frontend/javascripts/viewer/model/sagas/volumetracing_saga.tsx (3)

5-7: editVolumeLayerAsync correctly adopts SectionLabeler / contour-list semantics

Switching to createSectionLabeler with a getThirdDimValue callback, routing brush labeling through currentSectionLabeler.getCircleVoxelBuffer2D(...) and currentSectionLabeler.getPlane(), and listening on ["ADD_TO_CONTOUR_LIST", "FINISH_EDITING"] aligns the saga with the new section-labeling abstraction and action naming without changing the high-level control flow. The destructuring of addToContourListAction/finishEditingAction and the position usage look consistent.

Also applies to: 83-92, 159-259


261-335: finishSectionLabeler encapsulates finishing logic cleanly

The new finishSectionLabeler generator correctly guards against null/empty labelers, calls labelWithVoxelBuffer2D only for volume drawing tools, and uses sectionLabeler.getPlane() and getFillingVoxelBuffer2D(activeTool) to drive finishing on the proper transformed plane. Emitting registerLabelPointAction(sectionLabeler.getUnzoomedCentroid()) preserves previous behaviour in a cleaner, reusable helper.

Also applies to: 364-389


671-693: Contour geometry maintenance correctly tracks ADD_TO_CONTOUR_LIST

Listening for ["ADD_TO_CONTOUR_LIST", "RESET_CONTOUR"] and rebuilding the contour from volumeTracing.contourList keeps scene geometry in sync with the renamed action and the reducer semantics. The conditional on active trace tool and active segmentation tracing remains intact.

Comment on lines +34 to 42
readonly map: Uint8Array;
readonly width: number;
readonly height: number;
readonly minCoord2d: Vector2;
readonly getFast3DCoordinate: (arg0: number, arg1: number, arg2: Vector3 | Float32Array) => void;

static empty(): VoxelBuffer2D {
return new VoxelBuffer2D(
new Uint8Array(0),
0,
0,
[0, 0],
() => [0, 0, 0],
() => {},
);
return new VoxelBuffer2D(new Uint8Array(0), 0, 0, [0, 0], () => {});
}
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 | 🟡 Minor

Fix VoxelBuffer2D.print iteration bounds

The new VoxelBuffer2D shape and helpers are fine, but print() iterates y only up to this.width, which is wrong for non-square buffers and will skip or misread rows.

-  print(): void {
-    const lines = [];
-    for (let y = 0; y < this.width; y++) {
-      const line = [];
-      for (let x = 0; x < this.width; x++) {
-        line.push(this.getValue(x, y));
-      }
-      lines.push(line);
-    }
-    console.log("VoxelBuffer content:", lines.join("\n"));
-  }
+  print(): void {
+    const lines = [];
+    for (let y = 0; y < this.height; y++) {
+      const line = [];
+      for (let x = 0; x < this.width; x++) {
+        line.push(this.getValue(x, y));
+      }
+      lines.push(line);
+    }
+    console.log("VoxelBuffer content:", lines.join("\n"));
+  }

This is only a debugging helper, so it’s a minor correctness issue.

Also applies to: 71-82, 93-103

🤖 Prompt for AI Agents
In frontend/javascripts/viewer/model/volumetracing/section_labeling.ts around
lines 34-42 (and similarly at 71-82 and 93-103), the VoxelBuffer2D.print helper
incorrectly iterates the y loop up to this.width; change the outer loop to
iterate y from 0 to this.height and keep the inner loop x from 0 to this.width
so rows are printed correctly for non-square buffers, and update any similar
loops in the other mentioned ranges to use this.height for row iteration and
this.width for column iteration.

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.

3 participants