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

Finish setting up an ophyd_async OAV #857

Merged
merged 81 commits into from
Nov 4, 2024
Merged

Conversation

noemifrisina
Copy link
Collaborator

@noemifrisina noemifrisina commented Oct 21, 2024

Closes #716
Closes #824

  • Create an ophyd_async MJPG area detector plugin to replace old one
  • Re-write snapshot devices to inherit from new MJPG
  • Add snapshots to ophyd_async OAV
  • Use async OAV in beamline device instantiation
  • Add a small CAM plugin with minimum requirements for hyperion
  • Add unit tests, fix system tests to work with async device
  • Remove all obsolete code and tests

Instructions to reviewer on how to test:

  1. Check that all the functionality of the old ophyd OAV device and MJPG plugin is covered by the new ophyd_async code
  2. Check connection for all mx beamlines still works
  3. Run tests and confirm they pass
  4. Confirm tests in mx-bluesky still pass (see mx-bluesky#594)

Checks for reviewer

  • Would the PR title make sense to a scientist on a set of release notes
  • If a new device has been added does it follow the standards
  • If changing the API for a pre-existing device, ensure that any beamlines using this device have updated their Bluesky plans accordingly
  • Have the connection tests for the relevant beamline(s) been run via dodal connect ${BEAMLINE}

Copy link

codecov bot commented Oct 21, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 95.61%. Comparing base (b0e233c) to head (598d256).
Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #857      +/-   ##
==========================================
+ Coverage   95.53%   95.61%   +0.07%     
==========================================
  Files         124      125       +1     
  Lines        5313     5246      -67     
==========================================
- Hits         5076     5016      -60     
+ Misses        237      230       -7     

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

Copy link
Contributor

@DominicOram DominicOram left a comment

Choose a reason for hiding this comment

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

Great, thanks. Almost there but I added a few comments

Comment on lines 24 to 30
self.color_mode = epics_signal_rw(ColorMode, "GC_BalRatioSelector")
self.acquire_period = epics_signal_rw(float, "AcquirePeriod")
self.acquire_time = epics_signal_rw(float, "AcquireTime")
self.gain = epics_signal_rw(float, "Gain")

self.array_size_x = epics_signal_r(int, "ArraySizeX_RBV")
self.array_size_y = epics_signal_r(int, "ArraySizeY_RBV")
Copy link
Contributor

Choose a reason for hiding this comment

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

Must: these aren't connecting in dodal connect i03 as they need the PV prefix

Comment on lines +29 to +30
self.x_size = epics_signal_r(int, prefix + "ArraySize1_RBV")
self.y_size = epics_signal_r(int, prefix + "ArraySize2_RBV")
Copy link
Contributor

Choose a reason for hiding this comment

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

Must: these aren't connecting in dodal connect i03 as you're missing a colon

Comment on lines 32 to 34
# TODO check type of these two
self.input_rbpv = epics_signal_r(str, prefix + "NDArrayPort_RBV")
self.input_plugin = epics_signal_rw(str, prefix + "NDArrayPort")
Copy link
Contributor

Choose a reason for hiding this comment

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

Should: These two PVs aren't used anyway, lets just remove them

await self.last_saved_path.set(path, wait=True)

@AsyncStatus.wrap
async def trigger(self):
"""This takes a snapshot image from the MJPG stream and send it to the
post_processing method, expected to be implemented by a child of this class.

It is the responsibility of the child class to save any resulting images.
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: by calling _save_image

Comment on lines 72 to 78
if not response.ok:
LOGGER.error(
f"OAV responded with {response.status}: {response.reason}."
)
raise ClientConnectionError(
f"OAV responded with {response.status}: {response.reason}."
)
Copy link
Contributor

Choose a reason for hiding this comment

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

Should: I think we can just do response.raise_for_status() or actually ClientSession(raise_for_status=True)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I tried using this response.raise_for_status() and couldn't make it work, all the tests kept failing because they couldn't get past it - even when mocking it. Haven't managed to figure out why yet but this at least did the job. Didn't know it could be passed as an argument to ClientSession so will try that

) -> np.ndarray:
beam_distance_px: Pixel = oav_params.calculate_beam_distance(*pixel)
beam_distance_px: Pixel = calculate_beam_distance((beam_centre), *pixel)
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: These brackets seem unrequired

oav_config = OAVConfig(ZOOM_LEVELS_XML, DISPLAY_CONFIGURATION)
async with DeviceCollector():
oav = OAV("", config=oav_config, name="oav")
await oav.connect()
Copy link
Contributor

Choose a reason for hiding this comment

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

Should: DeviceCollector already does the connect for you

autospec=True,
)
@patch("dodal.devices.areadetector.plugins.MJPG.Image")
async def test_snapshot_trigger_fails_in_post_processing_withouth_raising_error(
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: Typo on without

Copy link
Contributor

Choose a reason for hiding this comment

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

Should: I also think we probably do want it to raise

Comment on lines +94 to +99
# Set new directory and test that it's created
set_mock_value(snapshot.directory, "new_dir")

await snapshot.trigger()

mock_mkdir.assert_called_once()
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: I would test this in a separate test that's just testing a new folder is created i.e. like test_given_directory_not_existing_when_snapshot_triggered_then_directory_created

Comment on lines 84 to 92
mock_get.return_value.__aenter__.return_value = (mock_response := AsyncMock())
mock_response.ok = True
mock_response.read.return_value = (test_data := b"TEST")

mock_aio_open = mock_aiofiles.open
mock_aio_open.return_value.__aenter__.return_value = (mock_file := AsyncMock())

mock_open = patch_image.open
mock_open.return_value.__aenter__.return_value = test_data
Copy link
Contributor

Choose a reason for hiding this comment

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

Should: You do this mocking a few times, I think pull it into a helper function

Copy link
Contributor

@DominicOram DominicOram left a comment

Choose a reason for hiding this comment

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

Great, thanks! Couple of minor suggestions, take them or leave them

Comment on lines +55 to +60
allowed_zoom_levels = await self._get_allowed_zoom_levels()
if level_to_set not in allowed_zoom_levels:
raise ZoomLevelNotFoundError(
f"{level_to_set} not found, expected one of {allowed_zoom_levels}"
)
await self.level.set(level_to_set, wait=True)
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 long term we shouldn't be doing this check. I've added #884

image, top_left_x, top_left_y, box_witdh, num_boxes_x, num_boxes_y
)

path = path_join(directory_str, f"{filename_str}_outer_overlay.png")
Copy link
Contributor

Choose a reason for hiding this comment

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

Could: We could reuse the IMG_FORMAT for this

image, top_left_x, top_left_y, box_witdh, num_boxes_x, num_boxes_y
)

path = path_join(directory_str, f"{filename_str}_grid_overlay.png")
Copy link
Contributor

Choose a reason for hiding this comment

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

Could: As above, we could use IMG_FORMAT

@noemifrisina noemifrisina merged commit cc5c259 into main Nov 4, 2024
19 checks passed
@noemifrisina noemifrisina deleted the 824_mjpg-async-device branch November 4, 2024 13:15
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.

Make an ophyd-async MJPG plugin for the oav Convert the OAV to ophyd-async
2 participants