Skip to content

Conversation

@pieleric
Copy link
Member

The SEM scan rotation has an indirect effect on the FM movement: the FM
image is always transposed to match the SEM (and FIB) image, by
adjusting the microscope file. So if there is 180° scan rotation, the
FM relative moves need to be inverted.

This also ensure that old systems (where there is no scan rotation) do
still move in the right direction when using the new sample stage code.

@pieleric pieleric changed the title [fix] Tescan Posture Manager: FM sample stage move should depend on SEM rotation [MSD-237][fix] Tescan Posture Manager: FM sample stage move should depend on SEM rotation Dec 19, 2025
@coderabbitai
Copy link

coderabbitai bot commented Dec 19, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

Scan-rotation compensation was added to the Tescan posture manager: _get_scan_rotation now returns 0 with info-level logging when beams are unavailable, and MeteorTescan1PostureManager._update_conversion computes a scan-rotation (sr), sets scanner rotation metadata via _set_scanner_rotation_cor(sr), and composes tf_sr with existing tilt transforms so fm/sem/milling transforms incorporate scan-rotation compensation (tf_fm becomes tf_tilt @ tf_sr). Redundant recomputation of sr was removed. Logging for unavailable e-/ion-beams was downgraded from warning to info. Tests were extended with setup scaffolding and a new test validating relative FM posture movements.

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 71.43% 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 describes the main change: adding SEM rotation dependency to FM sample stage movement in the Tescan Posture Manager.
Description check ✅ Passed The description is directly related to the changeset, explaining the rationale for scan rotation compensation and its effects on FM movements.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/odemis/acq/test/move_tescan_test.py (1)

123-131: Consider adding strict=True to zip() for safer iteration.

The static analysis tool flagged this zip() call without an explicit strict= parameter. While both lists currently have the same length, adding strict=True would catch accidental mismatches if the test data is modified in the future.

🔎 Proposed fix
-        for m_sample, m_bare in zip(sample_stage_moves, stage_bare_moves):
+        for m_sample, m_bare in zip(sample_stage_moves, stage_bare_moves, strict=True):
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ed4659c and b20c904.

📒 Files selected for processing (2)
  • src/odemis/acq/move.py (2 hunks)
  • src/odemis/acq/test/move_tescan_test.py (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/odemis/acq/test/move_tescan_test.py (5)
src/odemis/acq/test/move_jeol_test.py (1)
  • setUpClass (49-58)
src/odemis/acq/test/move_tfs1_test.py (1)
  • setUpClass (45-57)
src/odemis/acq/test/move_util_test.py (1)
  • setUpClass (48-52)
src/odemis/acq/move.py (2)
  • getCurrentPostureLabel (127-134)
  • getCurrentPostureLabel (384-409)
src/odemis/util/testing.py (1)
  • assert_pos_almost_equal (172-183)
src/odemis/acq/move.py (1)
src/odemis/util/transform.py (1)
  • get_rotation_transforms (1302-1325)
🪛 Ruff (0.14.8)
src/odemis/acq/test/move_tescan_test.py

123-123: zip() without an explicit strict= parameter

Add explicit value for parameter strict=

(B905)

⏰ 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). (4)
  • GitHub Check: Agent
  • GitHub Check: CodeQL analysis (python)
  • GitHub Check: build (ubuntu-22.04)
  • GitHub Check: build (ubuntu-24.04)
🔇 Additional comments (3)
src/odemis/acq/test/move_tescan_test.py (1)

46-55: Good test isolation pattern.

The setUpClass captures initial metadata and setUp restores it before each test, ensuring test isolation. This follows the established pattern in other test files.

src/odemis/acq/move.py (2)

639-640: Appropriate logging level for expected configuration.

Downgrading from warning to info is correct here since missing e-beam/ion-beam is an expected configuration on older systems, not an error condition. This aligns with the PR objective of ensuring older systems continue to work correctly.


1664-1673: Scan rotation compensation looks correct.

The implementation correctly:

  1. Retrieves scan rotation from the beams (falling back to 0° if unavailable)
  2. Propagates the rotation correction to scanners via _set_scanner_rotation_cor
  3. Creates a Z-axis rotation transform with negated angle (rz=-sr) to compensate
  4. Composes with tilt transform for FM imaging (tf_fm = tf_tilt @ tf_sr)

This aligns with the PR objective that FM relative moves must be inverted when a 180° scan rotation is present.

Comment on lines 36 to 37
METEOR_TESCAN1_FIBSEM_CONFIG = CONFIG_PATH + "sim/meteor-tescan-fibsem-full-sim.odm.yaml"

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

rg -n "METEOR_TESCAN1_FIBSEM_CONFIG" --type py

Repository: delmic/odemis

Length of output: 189


Remove unused constant or use it in a test.

METEOR_TESCAN1_FIBSEM_CONFIG is defined on line 36 but never used in this file or elsewhere in the codebase. Either remove it or incorporate it into an actual test case.

🤖 Prompt for AI Agents
In src/odemis/acq/test/move_tescan_test.py around lines 36-37, the constant
METEOR_TESCAN1_FIBSEM_CONFIG is defined but unused; either remove the constant
to eliminate dead code, or update/add a test to reference it (for example pass
it into the test fixture/setup that loads CONFIG_PATH files or use it in a
parametrized test that validates loading/parsing of that YAML). If you choose to
use it, ensure the test asserts expected behavior after loading the file and
imports CONFIG_PATH or uses the same path-building logic so the constant is
actually exercised.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes an issue where the FM (Fluorescence Microscope) sample stage movement didn't properly account for SEM (Scanning Electron Microscope) scan rotation. The FM image is transposed to match the SEM/FIB images, so when there's a 180° scan rotation, FM relative moves need to be inverted accordingly. This fix ensures correct movement behavior on both new systems (with scan rotation) and old systems (without scan rotation).

Key changes:

  • Modified the transformation matrix calculation order to apply scan rotation compensation to FM imaging mode
  • Changed FM transformation from tf_reverse @ tf_tilt to tf_tilt @ tf_sr to include scan rotation
  • Added comprehensive tests to verify FM posture relative movements match expected hardware behavior

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/odemis/acq/move.py Updated _update_conversion to compute scan rotation earlier and apply it to FM transformation matrix; changed logging level from warning to info for missing beam components
src/odemis/acq/test/move_tescan_test.py Added test infrastructure (setUp/setUpClass) to manage metadata state between tests; added new test test_rel_move_fm_posture to validate FM relative movements with scan rotation

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

…EM rotation

The SEM scan rotation has an indirect effect on the FM movement: the FM
image is always transposed to match the SEM (and FIB) image, by
adjusting the microscope file. So if there is 180° scan rotation, the
FM relative moves need to be inverted.

This also ensure that old systems (where there is no scan rotation) do
still move in the right direction when using the new sample stage code.
@pieleric pieleric force-pushed the fix-tescan-posture-manager-fm-sample-stage-move-should-depend-on-sem-rotation branch from b20c904 to 87ff61d Compare December 19, 2025 13:15
Copy link

@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)
src/odemis/acq/test/move_tescan_test.py (1)

105-129: Consider adding strict=True to zip() for safety.

The test correctly validates relative FM posture movements and uses hardware-verified expected values. However, for robustness, consider adding strict=True to the zip() call on line 121 (Python 3.10+) to ensure both lists have the same length.

🔎 Proposed refactor
-        for m_sample, m_bare in zip(sample_stage_moves, stage_bare_moves):
+        for m_sample, m_bare in zip(sample_stage_moves, stage_bare_moves, strict=True):
             old_bare_pos = self.stage.position.value
             self.linked_stage.moveRel(m_sample).result()
             new_bare_pos = self.stage.position.value
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b20c904 and 87ff61d.

📒 Files selected for processing (2)
  • src/odemis/acq/move.py (2 hunks)
  • src/odemis/acq/test/move_tescan_test.py (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/odemis/acq/test/move_tescan_test.py (5)
src/odemis/acq/test/move_jeol_test.py (1)
  • setUpClass (49-58)
src/odemis/acq/test/move_tfs1_test.py (1)
  • setUpClass (45-57)
src/odemis/acq/test/move_util_test.py (1)
  • setUpClass (48-52)
src/odemis/acq/move.py (4)
  • cryoSwitchSamplePosition (136-151)
  • getCurrentPostureLabel (127-134)
  • getCurrentPostureLabel (384-409)
  • moveRel (2473-2483)
src/odemis/util/testing.py (1)
  • assert_pos_almost_equal (172-183)
src/odemis/acq/move.py (2)
src/odemis/util/transform.py (1)
  • get_rotation_transforms (1302-1325)
src/odemis/model/_components.py (1)
  • model (570-571)
🪛 Ruff (0.14.8)
src/odemis/acq/test/move_tescan_test.py

121-121: zip() without an explicit strict= parameter

Add explicit value for parameter strict=

(B905)

⏰ 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 (ubuntu-22.04)
  • GitHub Check: build (ubuntu-24.04)
🔇 Additional comments (6)
src/odemis/acq/test/move_tescan_test.py (3)

23-24: LGTM!

The testing module import is properly added to support the new test's position comparison logic.


44-53: LGTM!

The setup methods properly implement test isolation by capturing and restoring stage metadata. This ensures each test starts with a clean state, even if previous tests modified the metadata.


57-60: LGTM!

The clarifying comments explain important behavioral differences between posture switching (which re-reads metadata) and sample stage moves (which use cached transformations). This context is valuable for understanding test setup requirements.

src/odemis/acq/move.py (3)

639-640: LGTM!

The logging level change from warning to info is appropriate. When e-beam/ion-beam components are unavailable (e.g., in simulation or older systems), this is an expected condition, not a problem. Returning 0° scan rotation as default ensures backward compatibility with systems that don't have scan rotation.


1664-1678: LGTM! Scan rotation compensation properly implemented.

The changes correctly introduce scan rotation compensation for FM imaging:

  1. Metadata sync (line 1666): Sets MD_ROTATION_COR on scanners to ensure total image rotation is 0°
  2. FM transform (line 1678): Now uses tf_tilt @ tf_sr instead of tf_reverse @ tf_tilt
  3. Rationale (lines 1673-1677): Clear explanation that although scan rotation doesn't directly affect FM optics, the camera image is transposed to match SEM orientation, so movements must account for it

The negative rotation -sr in line 1667 correctly compensates for the scan rotation. Systems without scan rotation (sr=0°) get an identity transform, ensuring backward compatibility.


1684-1695: LGTM! Consistent scan rotation compensation across postures.

The scan rotation transform tf_sr is correctly applied to both SEM (line 1684) and MILLING (line 1695) postures, ensuring consistent movement behavior across all imaging modes. The transform composition order (tf_reverse @ tf_tilt @ tf_sr) properly accounts for:

  • Tescan stage convention being opposite to Odemis (tf_reverse)
  • Stage tilt geometry (tf_tilt)
  • Scan rotation compensation (tf_sr)

@pieleric pieleric merged commit 09c5db0 into delmic:master Jan 8, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants