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

fix: use cartesian coordinate system in WellPlatePlan (-x left, +x right, -y down and +y up) #175

Merged
merged 10 commits into from
Jul 6, 2024

Conversation

fdrgsp
Copy link
Contributor

@fdrgsp fdrgsp commented Jul 5, 2024

linked to #173

I removed all the change of sign we had in the WellPlatePlan code and only invert the y axis when we apply the transformation so that the y axis is -y down and +y up. This way we use the same cartesian coordinate system that we are already using in the _PointsPlans.

Screenshot 2024-07-05 at 4 15 45 PM

Copy link

codecov bot commented Jul 5, 2024

Codecov Report

Attention: Patch coverage is 84.61538% with 2 lines in your changes missing coverage. Please review.

Project coverage is 95.65%. Comparing base (c8996b7) to head (a5fc170).

Files Patch % Lines
src/useq/_plate.py 84.61% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #175      +/-   ##
==========================================
+ Coverage   95.54%   95.65%   +0.10%     
==========================================
  Files          15       15              
  Lines        1056     1058       +2     
==========================================
+ Hits         1009     1012       +3     
+ Misses         47       46       -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

return (transformed[:2].T).reshape(coords.shape) # type: ignore[no-any-return]
transformed_coords = (transformed[:2].T).reshape(coords.shape)
# invert Y values
transformed_coords[:, 0] = -transformed_coords[:, 0]
Copy link
Member

Choose a reason for hiding this comment

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

probably better to do this in the affine transform itself, just invert the scaling factor in the y axis:

scaling[:2, :2] = np.diag([self.plate.well_spacing[0], -self.plate.well_spacing[1]])

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But is it the same? The output won't be identical...

Copy link
Member

Choose a reason for hiding this comment

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

theoretically it should be? did you try and it wasn't?

Copy link
Member

Choose a reason for hiding this comment

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

yeah, it does work, but the example I gave above was xy, instead of yx... use this:

        # we invert the Y axis here to go from linearly increasing "index coordinates"
        # to cartesian "plate" coordinates (where y position decreases with increasing index)
        scale_y, scale_x = self.plate.well_spacing
        scaling[:2, :2] = np.diag([-scale_y, scale_x])

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yup, I tried that as well but then the test give a slightly different result and if you use a rotation and plot the plate it is rotating in the opposite direction...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok I think I was not convinced for another reason...I was a bit confused with the output numbers of the positions. But I think it was because we had to also consider the units of a1_center_xy. I think we can say that a1_center_xy is in µm since most likely the stage coords are in µm (and the well_points_plan too) and we covert it during translation.

Copy link
Member

Choose a reason for hiding this comment

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

yeah, it's confusing! Should we consider expressing well sizes in microns too just to keep everything consistent? Is this the only place in useq where we use mm?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes I believe is the only place we use mm...you're right, maybe it is worth using µm for everything at this point...

Copy link
Member

Choose a reason for hiding this comment

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

i know that's also problematic since all plates specs will be in mm... maybe skip that for now

Copy link
Contributor Author

Choose a reason for hiding this comment

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

then I think this is ready for review

@tlambert03
Copy link
Member

please also fix a new scaling issue that we now have in random points:

                if value.max_width == np.inf:
                    kwargs["max_width"] = plate.well_size[0] - (value.fov_width or 0.1)
                if value.max_height == np.inf:
                    kwargs["max_height"] = plate.well_size[1] - (
                        value.fov_height or 0.1
                    )

the plate.well_sizes need to be scaled by 1000

src/useq/_plate.py Outdated Show resolved Hide resolved
@fdrgsp
Copy link
Contributor Author

fdrgsp commented Jul 6, 2024

@tlambert03 out of curiosity, if you run the test for the plate locally, does test_plate_plan_serialization fail for you?

@tlambert03
Copy link
Member

nope, and not on CI either obviously... whats your traceback

@fdrgsp
Copy link
Contributor Author

fdrgsp commented Jul 6, 2024

nope, and not on CI either obviously... whats your traceback

the well_points_plan is always defaulting to Position()...

    def test_plate_plan_serialization() -> None:
        pp = useq.WellPlatePlan(
            plate=96,
            a1_center_xy=(500, 200),
            rotation=5,
            selected_wells=np.s_[1:5:2, :6:3],
            well_points_plan=useq.RandomPoints(num_points=10),
        )
        js = pp.model_dump_json()
        pp2 = useq.WellPlatePlan.model_validate_json(js)
>       assert pp2 == pp
E       AssertionError: assert WellPlatePlan(plate=WellPlate(rows=8, columns=12, well_spacing=(9.0, 9.0), well_size=(6.4, 6.4)), a1_center_xy=(500.0, 200.0), rotation=5.0, selected_wells=(slice(1, 5, 2), slice(None, 6, 3)), well_points_plan=Position()) == WellPlatePlan(plate=WellPlate(rows=8, columns=12, well_spacing=(9.0, 9.0), well_size=(6.4, 6.4)), a1_center_xy=(500.0, 200.0), rotation=5.0, selected_wells=(slice(1, 5, 2), slice(None, 6, 3)), well_points_plan=RandomPoints(num_points=10, max_width=6399.9, max_height=6399.9))
E
E         At index 0 diff: Position(name='B1') != Position(x=2010.7489791050302, y=-10177.428033516471, name='B1_0000')
E         Right contains 36 more items, first extra item: Position(x=1776.901041996079, y=-9937.020701044301, name='B1_0004')
E
E         Full diff:
E         - WellPlatePlan(plate=WellPlate(rows=8, columns=12, well_spacing=(9.0, 9.0), well_size=(6.4, 6.4)), a1_center_xy=(500.0, 200.0), rotation=5.0, selected_wells=(slice(1, 5, 2), slice(None, 6, 3)), well_points_plan=RandomPoints(num_points=10, max_width=6399.9, max_height=6399.9))
E         ?                                                                                                                                                                                                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E         + WellPlatePlan(plate=WellPlate(rows=8, columns=12, well_spacing=(9.0, 9.0), well_size=(6.4, 6.4)), a1_center_xy=(500.0, 200.0), rotation=5.0, selected_wells=(slice(1, 5, 2), slice(None, 6, 3)), well_points_plan=Position())
E         ?                                                                                                                                                                                                                   ^^^^^^^^^^^

@tlambert03
Copy link
Member

the well_points_plan is always defaulting to Position()...

strange... not sure. what's in your env?
it must be environment, since it's working on CI, but I wonder what it could be.

src/useq/_plate.py Outdated Show resolved Hide resolved
@fdrgsp
Copy link
Contributor Author

fdrgsp commented Jul 6, 2024

the well_points_plan is always defaulting to Position()...

strange... not sure. what's in your env? it must be environment, since it's working on CI, but I wonder what it could be.

ok fixed it, not sure what was the conflict...

Copy link
Member

@tlambert03 tlambert03 left a comment

Choose a reason for hiding this comment

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

good stuff, thanks

@tlambert03 tlambert03 merged commit 49be9ad into pymmcore-plus:main Jul 6, 2024
20 checks passed
@fdrgsp fdrgsp deleted the fix_sign branch July 6, 2024 01:33
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.

2 participants