Skip to content

Conversation

@ilyushkin
Copy link
Contributor

  • Pass the same reference image from Odemis to fibsemOS that is used for placing milling markers in the Odemis FIBSEM tab.
  • The reference image expected by fibsemOS should be correctly cropped to the size of the reduced area used for beam shift calculation and should contain correct metadata.
  • Skip reference image alignment in Odemis, as it is instead performed by fibsemOS using the passed reference image.

@coderabbitai
Copy link

coderabbitai bot commented Dec 17, 2025

📝 Walkthrough

Walkthrough

The pull request refactors the FibSEM milling workflow to integrate CryoFeature objects as the primary source of reference images. The FibsemOSMillingTaskManager constructor is simplified to accept no parameters, while async_run and run_milling_tasks_fibsemos now require a CryoFeature parameter. The milling execution flow is modified to obtain the reference image from the feature, convert it via fibsem's from_odemis_image, set metadata path information, crop to the reduced alignment area, and then invoke milling. The millmng.py coordinator removes the separate alignment step invocation and passes the feature directly to the fibsemos milling function instead of handling path separately. Tests are updated to reflect the new signatures and verify reference image resolution from features.

Sequence Diagram

sequenceDiagram
    participant Caller as Caller
    participant Manager as FibsemOSMillingTaskManager
    participant Feature as CryoFeature
    participant Converter as from_odemis_image
    participant Structures as fibsem.structures
    participant Microscope as FibsemMicroscope

    Caller->>Manager: async_run(tasks, feature, path)
    Manager->>Feature: _get_reference_image(feature)
    Feature-->>Manager: reference_image (DataArray)
    Manager->>Converter: from_odemis_image(reference_image)
    Converter-->>Manager: FibsemImage
    Manager->>Structures: Set ref_img.metadata.image_settings.path
    Structures-->>Manager: Updated FibsemImage
    Manager->>Manager: _crop_to_reduced_area(stage.alignment.rect)
    Manager->>Microscope: mill_stages([stage], ref_img)
    Microscope-->>Manager: Milling complete
    Manager-->>Caller: Future result
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 56.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and specifically describes the main change: passing the correct reference image to fibsemOS, which aligns with the primary objective and changes throughout the changeset.
Description check ✅ Passed The PR description is directly related to the changeset, explaining the motivation and implementation details of passing the reference image to fibsemOS with proper cropping and metadata handling.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/odemis/acq/milling/fibsemos.py (2)

211-230: Critical: Missing feature parameter in __init__ signature.

The docstring at line 217 documents a feature parameter, and line 230 assigns self.feature = feature, but feature is not declared in the method signature. This will cause a NameError at runtime.

Apply this diff to add the missing parameter:

     def __init__(self, future: futures.Future,
                  tasks: List[MillingTaskSettings],
+                 feature: CryoFeature,
                  path: Optional[str] = None):
         """
         :param future: the future that will be executing the task
         :param tasks: The milling tasks to run (in order)
         :param feature: Cryo feature for milling
         :param path: The path to save the images (optional)
         """

314-326: Critical: Missing feature parameter in run_milling_tasks_fibsemos signature.

The docstring at line 319 documents a feature parameter, but it's not in the function signature (lines 314-315), and it's not passed to FibsemOSMillingTaskManager at line 326.

Apply this diff to add the missing parameter:

 def run_milling_tasks_fibsemos(tasks: List[MillingTaskSettings],
+                               feature: CryoFeature,
                                path: Optional[str] = None) -> futures.Future:
     """
     Run multiple milling tasks in order via fibsemOS.
     :param tasks: List of milling tasks to be executed in order
     :param feature: Cryo feature for milling
     :param path: The path to save the images
     :return: ProgressiveFuture
     """
     # Create a progressive future with running sub future
     future = model.ProgressiveFuture()
     # create milling task
-    millmng = FibsemOSMillingTaskManager(future, tasks, path)
+    millmng = FibsemOSMillingTaskManager(future, tasks, feature, path)
🧹 Nitpick comments (1)
src/odemis/acq/milling/fibsemos.py (1)

43-51: Several imported types appear unused.

FibsemImageMetadata, BeamType, ImageSettings, and MicroscopeState are imported but not used in the current code. Consider removing them if they're not needed for type hints or future use.

📜 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 2984f3c.

📒 Files selected for processing (3)
  • src/odemis/acq/feature.py (1 hunks)
  • src/odemis/acq/milling/fibsemos.py (5 hunks)
  • src/odemis/acq/milling/millmng.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/odemis/acq/milling/fibsemos.py (1)
src/odemis/acq/feature.py (1)
  • CryoFeature (144-240)
🪛 GitHub Actions: Linting
src/odemis/acq/feature.py

[error] 1-1: PNG metadata check detected forbidden metadata chunks in file during PNG metadata validation step (Contains forbidden metadata chunks).

src/odemis/acq/milling/millmng.py

[error] 1-1: PNG metadata check detected forbidden metadata chunks in file during PNG metadata validation step (Contains forbidden metadata chunks).

src/odemis/acq/milling/fibsemos.py

[error] 1-1: PNG metadata check detected forbidden metadata chunks in file during PNG metadata validation step (Contains forbidden metadata chunks).

🪛 Ruff (0.14.8)
src/odemis/acq/milling/fibsemos.py

230-230: Undefined name feature

(F821)

⏰ 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 (1)
src/odemis/acq/feature.py (1)

346-346: LGTM!

Simple typo fix in the docstring - "featuers" → "features".

@ilyushkin ilyushkin force-pushed the msd-262-pass-correct-reference-image-to-fibsemos branch from 2984f3c to 1b2a215 Compare January 21, 2026 11:51
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: 2

🤖 Fix all issues with AI agents
In `@src/odemis/acq/milling/fibsemos.py`:
- Around line 268-279: from_odemis_image(self.feature.reference_image) can raise
if self.feature.reference_image is not hydrated; update the ref image loading
logic around where ref_img is created to first try
from_odemis_image(self.feature.reference_image), and if that returns None or
raises, attempt to load the saved image from self.feature.path (use the same
loader used elsewhere for persisted images) and assign it to ref_img, preserving
metadata.image_settings.path and reduced_area; if the saved image is also
missing, raise/raise a ValueError or RuntimeError with a clear message
indicating the feature ID/path and that the reference image is unavailable so
callers can handle the failure.
- Around line 267-292: The _run method currently dereferences self.feature and
self.path without setting them per-run; ensure self.feature and self.path are
bound from the run inputs at the start of each call to _run (or passed into _run
as parameters) before any use (e.g., before the logging line "Running milling
stage: {stage.name}" and before creating ref_img via from_odemis_image). Assign
the run-specific feature to self.feature and the run-specific path to self.path
(or use local variables and replace usages of self.feature/self.path in this
block) so that ref_img.metadata.image_settings.path and
from_odemis_image(self.feature.reference_image) always use the current run's
values and avoid stale metadata or AttributeError.
♻️ Duplicate comments (1)
src/odemis/acq/milling/millmng.py (1)

434-440: Pass feature/path into the fibsemOS runner.

run_milling_tasks_fibsemos now relies on per-feature reference images and metadata path, so calling it without feature (and the feature path) will either crash or produce incorrect metadata.

✅ Proposed fix
-        if fibsemos.FIBSEMOS_INSTALLED:
-            self._future.running_subf = run_milling_tasks_fibsemos(tasks=milling_tasks)
+        if fibsemos.FIBSEMOS_INSTALLED:
+            self._future.running_subf = run_milling_tasks_fibsemos(
+                tasks=milling_tasks,
+                feature=feature,
+                path=feature.path,
+            )
         else:
             filename = self.get_filename(feature, "Milling-Tasks")
             self._future.running_subf = run_milling_tasks(tasks=milling_tasks,
                                                      fib_stream=self.fib_stream,
                                                      filename=filename)

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

🤖 Fix all issues with AI agents
In `@src/odemis/acq/milling/fibsemos.py`:
- Around line 75-80: The function _crop_to_reduced_area is missing a type
annotation for the rect parameter; update its signature to add an appropriate
type for rect (e.g., use typing.Any if the exact fibsemOS type isn't importable
or import and reference the specific MillingAlignment.rect type/class) so the
signature reads something like def _crop_to_reduced_area(ref_img: 'FibsemImage',
rect: Any) -> 'FibsemImage': and add the required import (from typing import
Any) if using Any.
🧹 Nitpick comments (8)
src/odemis/acq/milling/fibsemos.py (6)

21-21: Unused import REFERENCE_IMAGE_FILENAME.

REFERENCE_IMAGE_FILENAME is imported but not used anywhere in this file.

🔧 Suggested fix
-from odemis.acq.feature import CryoFeature, REFERENCE_IMAGE_FILENAME
+from odemis.acq.feature import CryoFeature

43-51: Unused imports from fibsem.structures.

FibsemImageMetadata, BeamType, ImageSettings, and MicroscopeState are imported but not used in this file. Only FibsemMillingSettings, Point, and FibsemImage appear to be needed.

🔧 Suggested fix
 from fibsem.structures import (
     FibsemMillingSettings,
     Point,
     FibsemImage,
-    FibsemImageMetadata,
-    BeamType,
-    ImageSettings,
-    MicroscopeState,
 )

91-99: Consider validating crop bounds produce a non-empty result.

After clamping, if x0 >= x1 or y0 >= y1 (due to invalid rect coordinates or extreme rounding), the crop will produce an empty array that could cause unexpected behavior downstream.

🔧 Suggested fix
     # clamp to valid range just in case of rounding
     x0 = max(0, min(w, x0))
     x1 = max(0, min(w, x1))
     y0 = max(0, min(h, y0))
     y1 = max(0, min(h, y1))

+    if x0 >= x1 or y0 >= y1:
+        raise ValueError(f"Invalid crop region: ({x0}, {y0}) to ({x1}, {y1})")
+
     # crop along the last two axes, DataArray slicing behaves like numpy
     ref_img.data = ref_img.data[..., y0:y1, x0:x1]

252-262: Consider initializing self.path and self.feature in __init__.

These attributes are set in async_run but not initialized in __init__. While _run is only invoked via async_run, initializing them to None would make the class state explicit and prevent potential AttributeError if these attributes are accessed before a run starts.

🔧 Suggested fix
         # per-run state (set in async_run)
         self.milling_stages: List["FibsemMillingStage"] = []
         self._future: Optional[futures.Future] = None
+        self.path: Optional[str] = None
+        self.feature: Optional[CryoFeature] = None

316-322: Add docstring parameters for feature and path.

The docstring on line 322 should document the new feature and path parameters per coding guidelines (reStructuredText style).

🔧 Suggested fix
     def async_run(self,
                   *,
                   future: futures.Future,
                   tasks: List[MillingTaskSettings],
                   feature: CryoFeature,
                   path: Optional[str] = None) -> futures.Future:
-        """Prepare and start a milling run asynchronously (one run at a time)."""
+        """Prepare and start a milling run asynchronously (one run at a time).
+
+        :param future: The future to track progress.
+        :param tasks: The milling tasks to execute.
+        :param feature: The CryoFeature providing the reference image.
+        :param path: Optional output path for fibsemOS metadata/images.
+        :return: The future tracking the milling run.
+        """

352-360: Add docstring parameters for feature and path.

The docstring should document the new feature and path parameters.

🔧 Suggested fix
 def run_milling_tasks_fibsemos(tasks: List[MillingTaskSettings], feature: CryoFeature, path: Optional[str] = None) -> futures.Future:
-    """Run the given milling tasks asynchronously using a persistent fibsemOS manager."""
+    """Run the given milling tasks asynchronously using a persistent fibsemOS manager.
+
+    :param tasks: The milling tasks to execute.
+    :param feature: The CryoFeature providing the reference image.
+    :param path: Optional output path for fibsemOS metadata/images.
+    :return: A future tracking the milling run.
+    """
src/odemis/acq/test/fibsemos_reference_image_test.py (2)

1-9: Remove unused imports.

os and REFERENCE_IMAGE_FILENAME are imported but not used in this test file.

🔧 Suggested fix
-import os
 import tempfile
 import unittest

 import numpy

 from odemis import model
-from odemis.acq.feature import CryoFeature, REFERENCE_IMAGE_FILENAME
+from odemis.acq.feature import CryoFeature
 from odemis.acq.milling.fibsemos import _get_reference_image

21-43: Replace setattr with direct assignment and remove redundant assignments.

Per static analysis (B010), setattr with constant attribute names should be replaced with direct assignment. Additionally, reference_image is already initialized to None in CryoFeature.__init__, so explicitly setting it is unnecessary.

Also note that test_raises_clear_error_if_path_missing and test_raises_clear_error_if_file_missing test the same code path as test_raises_if_missing_in_memory since _get_reference_image only checks reference_image and doesn't use path. Consider if these tests are necessary or if the names should be updated to reflect what they actually test.

🔧 Suggested fix
     def test_raises_if_missing_in_memory(self):
         feature = CryoFeature(name="f1", stage_position={}, fm_focus_position={})
-        setattr(feature, "reference_image", None)
+        # reference_image is already None after __init__

         with self.assertRaises(ValueError):
             _get_reference_image(feature)

     def test_raises_clear_error_if_path_missing(self):
         feature = CryoFeature(name="f1", stage_position={}, fm_focus_position={})
-        setattr(feature, "reference_image", None)
-        setattr(feature, "path", None)
+        # reference_image and path are already None after __init__

         with self.assertRaises(ValueError):
             _get_reference_image(feature)

     def test_raises_clear_error_if_file_missing(self):
         feature = CryoFeature(name="f1", stage_position={}, fm_focus_position={})
-        setattr(feature, "reference_image", None)
+        # reference_image is already None after __init__

         with tempfile.TemporaryDirectory() as tmpdir:
             feature.path = tmpdir
             with self.assertRaises(ValueError):
                 _get_reference_image(feature)

@ilyushkin ilyushkin force-pushed the msd-262-pass-correct-reference-image-to-fibsemos branch from 9fb81be to ef4a9f3 Compare January 21, 2026 15:44
@ilyushkin ilyushkin requested a review from pieleric January 21, 2026 15:44
@ilyushkin ilyushkin force-pushed the msd-262-pass-correct-reference-image-to-fibsemos branch from ef4a9f3 to 0649cc9 Compare January 22, 2026 12:26
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

🤖 Fix all issues with AI agents
In `@src/odemis/acq/milling/test/fibsemos_reference_image_test.py`:
- Around line 27-42: The three tests (test_raises_clear_error_if_path_missing,
test_raises_clear_error_if_file_missing, test_raises_if_missing_in_memory) all
exercise the same code path in _get_reference_image (it raises when
feature.reference_image is None), so remove or consolidate the redundant ones
(keep a single clear test that asserts ValueError when
CryoFeature.reference_image is None) or rename them to indicate they are
variants of the same scenario; also stop using setattr and assign attributes
directly (e.g., feature.reference_image = None and feature.path = tmpdir) to
satisfy static analysis and make the intent clear.
🧹 Nitpick comments (2)
src/odemis/acq/milling/fibsemos.py (1)

76-100: Document the in-place mutation behavior more explicitly.

The function mutates ref_img.data in place and returns the same instance. While the docstring mentions "The same image instance with cropped data," consider clarifying this is an in-place mutation, as callers might not expect their original image to be modified.

💡 Suggested docstring improvement
 def _crop_to_reduced_area(ref_img: 'FibsemImage', rect: 'FibsemRectangle') -> 'FibsemImage':
-    """Crop a fibsemOS image to the provided reduced-area rectangle.
+    """Crop a fibsemOS image to the provided reduced-area rectangle (in-place).
 
     :param ref_img: The image to crop.
     :param rect: Rectangle with fractional coordinates (left, top, width, height).
-    :return: The same image instance with cropped data.
+    :return: The same image instance with its data attribute cropped in-place.
     """
src/odemis/acq/milling/test/fibsemos_reference_image_test.py (1)

20-25: Replace setattr with direct attribute assignment.

As flagged by static analysis (B010), using setattr with a constant attribute name provides no benefit over direct assignment and reduces readability.

♻️ Suggested fix
     def test_raises_if_missing_in_memory(self):
         feature = CryoFeature(name="f1", stage_position={}, fm_focus_position={})
-        setattr(feature, "reference_image", None)
+        feature.reference_image = None
 
         with self.assertRaises(ValueError):
             _get_reference_image(feature)

@ilyushkin ilyushkin force-pushed the msd-262-pass-correct-reference-image-to-fibsemos branch 2 times, most recently from 15bd844 to caede8f Compare January 22, 2026 12:47
@ilyushkin ilyushkin force-pushed the msd-262-pass-correct-reference-image-to-fibsemos branch from caede8f to 4f5f353 Compare January 26, 2026 16:12
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