Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[feat][METEOR-996] Add TFS3 posture manager for multi-posture imaging #3005

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

patrickcleeve2
Copy link
Contributor

@patrickcleeve2 patrickcleeve2 commented Jan 23, 2025

Adds:

  • TFS3 posture manager for multi-posture imaging functionality.
  • SampleStage virtual component

JIRA: METEOR-996
Specification

@K4rishma
Copy link
Contributor

Should not we merge TFS2 with TFS3 posture manager and convert into a single posture manager? TFS3 is advanced version TFS2. With this logic, we will not need TFS2.

@@ -1298,3 +1298,17 @@ def __init__(
@staticmethod
def _estimate_matrix(x: numpy.ndarray, y: numpy.ndarray) -> numpy.ndarray:
return _optimal_rotation(x, y)

def _get_transforms(r: float) -> Tuple[numpy.ndarray, numpy.ndarray]:
Copy link
Contributor

Choose a reason for hiding this comment

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

As it is only for rotation and we are dealing with different transformations, maybe we can make it clear in the function call? For e.g. _get_3drotation_tranforms. Although I am not sure if adding a number to the function name is a good idea.

elif posture == FIB_IMAGING:
return stage_md[model.MD_FAV_FIB_POS_ACTIVE]
elif posture == MILLING:
md = stage_md[model.MD_FAV_MILL_POS_ACTIVE]
Copy link
Contributor

Choose a reason for hiding this comment

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

Three possible situations to keep the favorite positions in sync with other favorite positons

  1. Maybe nice to specify in _metadata.py that the milling favourite position is not calculated at regular stage bare tilt and rotation, etc ... It gets clarified in the function call that it is different from other favorite angle positons, or
  2. Keep the regular tilt, rotation in the favorite metadata and do the conversions later?, or
  3. Another special metadata to include this unique metadata

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point, I've added an additional explanation to the _metadata.py to explain the difference between it and the others. I think it is best to keep it defined as this, as it's waht the user expects to see (and the number they care about), as opposed to stage titl

:param pos: (dict str->float) the initial stage position.
:return: (dict str->float) the transformed position.
"""
# NOTE: this transform now always rotates around the z axis (180deg)
Copy link
Contributor

@K4rishma K4rishma Jan 31, 2025

Choose a reason for hiding this comment

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

In TFS3, should not the logic of transformation from SEM to Meteor and back used from TFS2? If we do not use it, then we cannot enforce imaging at a constant FM imaging plane.

or

Do the 3d coordinate transformations take care of it such the features saved in FM are robust?

Copy link
Contributor Author

@patrickcleeve2 patrickcleeve2 Feb 3, 2025

Choose a reason for hiding this comment

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

Combined with the raw-coordinates, saving the full position of the features (XYZRT) is enough to return to the same z-plane. See other comment about need the feature refactor too

@patrickcleeve2
Copy link
Contributor Author

Should not we merge TFS2 with TFS3 posture manager and convert into a single posture manager? TFS3 is advanced version TFS2. With this logic, we will not need TFS2.

I'm not sure if any system is currently using TFS2. But technically, you need the code from the feature refactor + tfs3 to recreate the behaviour, so until that is merged too I would just keep it.

@patrickcleeve2 patrickcleeve2 force-pushed the meteor-960-multi-posture-imaging branch from 7833e22 to 853bd84 Compare February 3, 2025 08:04
@patrickcleeve2 patrickcleeve2 force-pushed the meteor-960-multi-posture-imaging branch from 853bd84 to c264604 Compare February 4, 2025 04:50
@patrickcleeve2 patrickcleeve2 force-pushed the meteor-960-multi-posture-imaging branch 2 times, most recently from bbe92e3 to 0d4855f Compare February 5, 2025 05:00
from odemis.util.transform import RigidTransform, _get_transforms

# feature flags
USE_3D_TRANSFORMS = False
Copy link
Member

Choose a reason for hiding this comment

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

I'd rather not have "feature flags". It introduces extra complexity (especially over time, eg, which version has which feature). Either the code is implemented, and the code is used, or it shouldn't be merged yet.

Let's just use 3D transforms all the time.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think we can take such a simplified view of feature flags, especially when this is a smaller part of multiple pull requests to come. I've moved the flags into the MD_CALIB so they can be changed from the yaml file. The flags will be removed all togehter once all the features have been tested together more extensively


# feature flags
USE_3D_TRANSFORMS = False
USE_SCAN_ROTATION = False
Copy link
Member

Choose a reason for hiding this comment

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

It shouldn't be a constant. I assume here this is about using 180° scan rotation on the e-beam scanner to match the FIB imaging angle. This will be used on the SEM coordinate referential transformation? Ideally, each microscope type (eg tescan_1, tfs_3) should either use or not scan_rotation. Alternatively, it would be a flag (using metadata) on the e-beam component. Pick your preference.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this is just a flag to enable the scan rotation correction. the code to calculate the transform reads the scan rotations directly

Comment on lines 2845 to 2846
"""Move the stage vertically in the chamber. This is non-blocking.
From OpenFIBSEM"""
Copy link
Member

Choose a reason for hiding this comment

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

This function is quite difficult to grasp. It needs more info. In particular, when mentioning a vertical move, I think typically one would expect a "z" value. However, only "x" and "y" are used. This should be explained. Also, for relative moves, normally the argument is called "shift". Please use that name here too, because "pos" sounds too much like a "target position" / "absolute position".

Could you explain in the docstring how Y (in input) is related to a Z movement (in chamber referential)? Looking at the code, if rx = 0, then dy == 0 and dz == Y... which gives me the feeling that actually the two shifts input are x & z (in chamber referential). Is that correct? (if so, the key should be renamed "y" -> "z").

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've updated it to take x,z input shift and added more info to the docstring. x,y refer to the fact that this movement is calculated form the image view. Givne where this is, it's clearer that it is x,z

@patrickcleeve2 patrickcleeve2 force-pushed the meteor-960-multi-posture-imaging branch from 755927d to f844134 Compare February 20, 2025 23:44
Copy link
Contributor

@tepals tepals left a comment

Choose a reason for hiding this comment

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

Some minor comments already, I'll do a more in depth review tomorrow

port: "/dev/fake6",
address: null,
axes: ["x", "y", "z", "rx", "rz"],
ustepsize: [1.e-6, 1.e-6, 1.e-6, 1.2e-5, 1.2e-5], # unit/µstep
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess this is just a copy paste from other yaml files, but shouldn't this be unit/step, instead of unit/µstep?

md = stage_md[model.MD_FAV_MILL_POS_ACTIVE]
rx = calculate_stage_tilt_from_milling_angle(milling_angle=md["rx"],
pre_tilt=self.pre_tilt,
column_tilt=math.radians(52))
Copy link
Contributor

Choose a reason for hiding this comment

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

why is this 52 degrees, does it make sense to have this defined as a constant somewhere?

self._transforms[MILLING] = self._transforms[SEM_IMAGING]
self._inv_transforms[MILLING] = self._inv_transforms[SEM_IMAGING]

# add unknown as same as SEM IMAGING
Copy link
Contributor

Choose a reason for hiding this comment

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

can you extend the comment to explain why?

return self.to_sample_stage_from_stage_position2(pos)

# Convert position dict from dependant axes to original axes
# vpos = self._convert_to_sample_stage_from_stage([pos[self._axes_dep["x"]], pos[self._axes_dep["y"]]])
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this line can be removed


return transformed_pos

def _transformFromMeteorToSEM(self, pos: Dict[str, float]) -> Dict[str, float]:
Copy link
Contributor

Choose a reason for hiding this comment

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

can you make the names of the methods in this class consistent? You now have _transformFromMeteorToSEM and _transform_from_fm_to_fib, so a mix of Meteor/FM and camel/snake case

patrickcleeve2 and others added 3 commits February 27, 2025 12:40
Co-authored-by: Thera Pals <46314774+tepals@users.noreply.github.com>
Co-authored-by: Thera Pals <46314774+tepals@users.noreply.github.com>
Co-authored-by: Thera Pals <46314774+tepals@users.noreply.github.com>
MILLING: tf_sr,
UNKNOWN: tf_sr}

def _get_scan_rotation_matrix(self) -> Tuple[numpy.ndarray, numpy.ndarray]:
Copy link
Contributor

Choose a reason for hiding this comment

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

This doesn't return anything

Suggested change
def _get_scan_rotation_matrix(self) -> Tuple[numpy.ndarray, numpy.ndarray]:
def _get_scan_rotation_matrix(self):


logging.info(f"Position Posture: {POSITION_NAMES[position_posture]}, Target Posture: {POSITION_NAMES[posture]}")

self._posture_transforms = {
Copy link
Contributor

Choose a reason for hiding this comment

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

why do you have to update this dict in this function?

self.required_keys.add(model.MD_CALIB)
self.check_stage_metadata(required_keys=self.required_keys)
self.check_calib_data(required_keys={model.MD_SAMPLE_PRE_TILT, "dx", "dy"}) # TODO: add "SEM-Eucentric-Focus"
if not {"x", "y", "rz", "rx"}.issubset(self.stage.axes):
Copy link
Contributor

Choose a reason for hiding this comment

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

why not check if "z" is there?

model.MD_STAGE_POSITION_RAW: pos_dep}
)
except Exception as e:
logging.error("Failed to update %s with new position: %s", a, e)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think a is undefined, is this log correct?

f = ebeam_focus.moveAbs({"z": self.sem_eucentric_focus})
f.result()
except Exception as e:
logging.error("Failed to update ebeam-focus with new position: %s", e)
Copy link
Contributor

Choose a reason for hiding this comment

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

don't you want to log the position?

Suggested change
logging.error("Failed to update ebeam-focus with new position: %s", e)
logging.error("Failed to update ebeam-focus with new position: %s", {"z": self.sem_eucentric_focus})

ssp3 = self.pm._get_sample_pos(stage_pos) # old 2D method

# assert near Position
self.assertTrue(isNearPosition(ssp, ssp2, axes={"x", "y", "z", "rx", "rz"}))
Copy link
Contributor

Choose a reason for hiding this comment

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

you can also use odemis.util.testing.assert_pos_almost_equal for testing positions

Comment on lines +747 to +750
f = self.stage_bare.moveAbs(self.stage_grid_centers[POSITION_NAMES[GRID_1]])
f.result()
f = self.pm.cryoSwitchSamplePosition(SEM_IMAGING)
f.result()
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add a comment to explain why you first need to move to GRID_1?

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.

4 participants