Skip to content

Add Molecular Devices SpectraMax Gemini EM backend#1033

Merged
rickwierenga merged 6 commits into
PyLabRobot:mainfrom
brad-usredoxlabs:usredoxlabs
May 17, 2026
Merged

Add Molecular Devices SpectraMax Gemini EM backend#1033
rickwierenga merged 6 commits into
PyLabRobot:mainfrom
brad-usredoxlabs:usredoxlabs

Conversation

@brad-usredoxlabs
Copy link
Copy Markdown
Contributor

Summary

Adds a Molecular Devices SpectraMax Gemini EM plate reader backend, including exports, backend tests, and user-facing documentation.

Validation

  • python3 -m unittest pylabrobot.plate_reading.molecular_devices.backend_tests
  • Direct Linux hardware smoke test over /dev/ttyUSB0: setup, status, temperature, open, close
  • Direct Linux hardware validation over /dev/ttyUSB0: fluorescence, luminescence, TRF endpoint, TRF kinetic, emission spectrum, excitation spectrum, fill wellscan, and partial-region fluorescence

Hardware Results

  • Fluorescence: 1 read, 8 x 12
  • Luminescence: 1 read, 8 x 12
  • TRF endpoint: 1 read, 8 x 12
  • TRF kinetic: 2 reads, each 8 x 12
  • Emission spectrum: 2 reads, each 8 x 12
  • Excitation spectrum: 2 reads, each 8 x 12
  • Fill wellscan: 9 reads, each 6 x 6, with scan point/x/y metadata
  • Partial fluorescence B2:G7: 1 read, 6 x 6

nan values appeared as expected where the reader returned blanks.

Notes

Hardware validation was run on a Linux laptop directly connected to the SpectraMax Gemini EM through a USB-to-RS-232 adapter. ruff was not run locally because python3 -m ruff is not installed in this environment.

@brad-usredoxlabs brad-usredoxlabs marked this pull request as ready for review May 12, 2026 23:28
rickwierenga and others added 2 commits May 14, 2026 13:06
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Reset WELLSCANMODE in read_fluorescence so a prior wellscan does not
  leak state into the next read.
- Reject non-None focal_height in read_fluorescence, matching the
  precedent set by read_luminescence and read_fluorescence_wellscan.
- Derive read_fluorescence_wellscan center_x/center_y from plate
  geometry; previously the defaults were captured from a single dev
  plate and silently wrong elsewhere.
- Compare wells by id-set in _assert_full_plate and _get_well_region
  so the full-plate fast path is order-independent.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@rickwierenga
Copy link
Copy Markdown
Member

Pushed 0d80c88 addressing four review findings:

  1. State leak between readsread_fluorescence now calls _set_wellscan_mode(False). Previously it was the only read method that didn't, so a prior read_fluorescence_wellscan would leave WELLSCANMODE=ON and a subsequent plain fluorescence read would inherit that state.

  2. Silent focal_height ignoreread_fluorescence now raises NotImplementedError when focal_height is not None, matching the existing behavior of read_luminescence and read_fluorescence_wellscan. Tests that passed focal_height=0 to read_fluorescence have been updated to drop the argument, with a new test_fluorescence_rejects_focal_height covering the strict path.

  3. Plate-specific wellscan defaultsread_fluorescence_wellscan now derives center_x/center_y from plate.get_item(0).location (same logic as _set_plate_region) when not supplied. The previous defaults (14.380, 20.235) were captured from one dev plate and silently wrong for any other geometry. The OEM-validated sequence test now passes those values explicitly to preserve the captured command stream.

  4. Order-sensitive well comparison_assert_full_plate and _get_well_region now compare by {id(well) for ...} set semantics. Previously wells != plate.get_all_items() used Python list equality, so a reordered full-plate list would fail the fast-path and either raise NotImplementedError (wellscan) or fall through to the rectangular check unnecessarily.

All 69 tests in backend_tests.py pass locally.

Prefix the Gemini-specific public read methods that are not part of
the PlateReaderBackend spec with experimental_:

- experimental_read_fluorescence_wellscan
- experimental_read_fluorescence_emission_spectrum
- experimental_read_fluorescence_excitation_spectrum
- experimental_read_time_resolved_fluorescence

read_time_resolved_fluorescence exists on the parent
MolecularDevicesBackend, so the un-prefixed name now raises
NotImplementedError pointing users to the experimental version
rather than silently falling through to the parent implementation,
which uses incompatible response-field counts for !READTYPE on
the Gemini EM.

Tests and the user-guide notebook are updated to call the renamed
methods.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@rickwierenga
Copy link
Copy Markdown
Member

@brad-usredoxlabs really good PR!

pushed some changes and converted the hello world from markdown to notebook so it's easier to run. could you please verify this works?

also renamed the non-standard-PLR methods to experimental_ because we want to figure out a good call+return type system for them. In this way, it is clear to users this function might change in the future while still being useful today.

rickwierenga and others added 2 commits May 14, 2026 13:44
read_fluorescence_polarization uses **backend_kwargs to absorb the
parent signature instead of restating it. Match the existing pattern
used by read_absorbance (also a stub) and add # type: ignore[override].

This was a pre-existing type-check failure on the PR; the
experimental_ rename in 0f39187 happened to fix the analogous
error on read_time_resolved_fluorescence and surfaced this one
as the only remaining mypy issue.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Use backend.read_fluorescence(plate=plate, ...) directly to match the
luminescence/TRF/spectra/wellscan style already used in the notebook —
the PlateReader wrapper requires focal_height, which the Gemini EM does
not support. Also adds drawer open/close demonstration cells.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@rickwierenga rickwierenga merged commit 06d502b into PyLabRobot:main May 17, 2026
21 checks passed
@rickwierenga
Copy link
Copy Markdown
Member

thanks and congrats on first PR!

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