-
Notifications
You must be signed in to change notification settings - Fork 39
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
base: master
Are you sure you want to change the base?
[feat][METEOR-996] Add TFS3 posture manager for multi-posture imaging #3005
Conversation
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]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Three possible situations to keep the favorite positions in sync with other favorite positons
- 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
- Keep the regular tilt, rotation in the favorite metadata and do the conversions later?, or
- Another special metadata to include this unique metadata
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
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. |
7833e22
to
853bd84
Compare
853bd84
to
c264604
Compare
bbe92e3
to
0d4855f
Compare
src/odemis/acq/move.py
Outdated
from odemis.util.transform import RigidTransform, _get_transforms | ||
|
||
# feature flags | ||
USE_3D_TRANSFORMS = False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
src/odemis/acq/move.py
Outdated
|
||
# feature flags | ||
USE_3D_TRANSFORMS = False | ||
USE_SCAN_ROTATION = False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is just a flag to enable the scan rotation correction. the code to calculate the transform reads the scan rotations directly
0d4855f
to
deeb427
Compare
src/odemis/acq/move.py
Outdated
"""Move the stage vertically in the chamber. This is non-blocking. | ||
From OpenFIBSEM""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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").
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
755927d
to
f844134
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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"]]]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this line can be removed
|
||
return transformed_pos | ||
|
||
def _transformFromMeteorToSEM(self, pos: Dict[str, float]) -> Dict[str, float]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
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]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't return anything
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 = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
don't you want to log the position?
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"})) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can also use odemis.util.testing.assert_pos_almost_equal
for testing positions
f = self.stage_bare.moveAbs(self.stage_grid_centers[POSITION_NAMES[GRID_1]]) | ||
f.result() | ||
f = self.pm.cryoSwitchSamplePosition(SEM_IMAGING) | ||
f.result() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a comment to explain why you first need to move to GRID_1?
Adds:
JIRA: METEOR-996
Specification