Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
b132be7
tecan spark backend
xbtu2 Dec 20, 2025
fbec36f
use enum for endpoints
xbtu2 Dec 20, 2025
16edc58
add time
xbtu2 Dec 21, 2025
ad9843d
format
xbtu2 Dec 21, 2025
a799855
fix typo
xbtu2 Dec 21, 2025
4190426
Update pylabrobot/plate_reading/tecan/spark20m/spark_reader_async.py
xbtu2 Dec 21, 2025
c937c24
remove unused method
xbtu2 Dec 21, 2025
7560ed3
use plr usb io
xbtu2 Jan 13, 2026
1e2c30a
Merge branch 'main' into tecan_spark
xbtu2 Jan 13, 2026
0c99649
fix test
xbtu2 Jan 13, 2026
eaf2f3b
Merge branch 'main' into tecan_spark
xbtu2 Jan 14, 2026
24ec263
Use io.binary.Reader in spark_packet_parser
rickwierenga Jan 31, 2026
5dd91c8
Simplify Spark control architecture
rickwierenga Jan 31, 2026
b56ffdd
Merge remote-tracking branch 'origin/main' into tecan_spark
rickwierenga Jan 31, 2026
aaa6fae
Rename control attributes to include _control suffix
rickwierenga Jan 31, 2026
9747549
Refactor spark_packet_parser.py to use binary.py Reader
rickwierenga Jan 22, 2026
3b5f4b7
Fix floating point comparison in spark_processor_tests
rickwierenga Jan 31, 2026
2e1db3c
Make USB max_workers configurable per-instance
rickwierenga Jan 31, 2026
12af73b
Fix timeout conversion for zero-length packet write
rickwierenga Jan 31, 2026
31949fa
Merge remote-tracking branch 'origin/main' into tecan_spark
rickwierenga Jan 31, 2026
847d895
Use item_dx and item_dy properties in SparkBackend
rickwierenga Jan 31, 2026
e95d88d
fix send_commend and tests
xbtu2 Jan 31, 2026
a0da054
fix send_command error handling
xbtu2 Feb 1, 2026
d9e4c4e
Move Spark enums to dedicated enums.py module
rickwierenga Jan 31, 2026
d6e76aa
Add type annotations to spark20m module for mypy --strict
rickwierenga Feb 2, 2026
95a9bfa
Refactor spark processors from classes to functions
rickwierenga Feb 4, 2026
968531c
Use nan instead of 'Error' string in spark processors
rickwierenga Feb 4, 2026
f45693d
Rename camelCase variables/parameters to snake_case
rickwierenga Feb 4, 2026
7cefe4e
Use statistics.mean() and remove unnecessary * 1.0
rickwierenga Feb 4, 2026
e5de32f
Use reversed(range()) instead of range(n-1, -1, -1)
rickwierenga Feb 4, 2026
b559985
Make spark code more Pythonic
rickwierenga Feb 4, 2026
a0d970f
Remove 30 duplicate abbreviated methods from CameraControl
rickwierenga Feb 4, 2026
8e55bab
Make _format_scan_range a staticmethod
rickwierenga Feb 4, 2026
217887d
Remove misleading '# Unused' comment on focal_height parameter
rickwierenga Feb 4, 2026
461c0ed
Replace magic numbers with named constants in spark_processor
rickwierenga Feb 4, 2026
8181847
Add division by zero protection in fluorescence K calculation
rickwierenga Feb 5, 2026
e4d0322
Fix potential task leak in send_command
rickwierenga Feb 5, 2026
b6a779d
Remove duplicate enums from sensor_control, import from spark_enums
rickwierenga Feb 5, 2026
39a7e8b
Add device context to background read error messages
rickwierenga Feb 5, 2026
19c6463
Add warnings when absorbance ratios are invalid
rickwierenga Feb 5, 2026
cc2fab3
Include hex dump in packet parse error messages
rickwierenga Feb 5, 2026
97750cb
fix command strings and enums
xbtu2 Feb 5, 2026
09d4549
Merge branch 'main' into tecan_spark
xbtu2 Feb 5, 2026
4997d30
Properly await cancelled response_task in send_command
rickwierenga Feb 5, 2026
50aa783
Replace attempt-based retry with wall-clock timeout in _get_response
rickwierenga Feb 5, 2026
ab79ec1
Protect fluorescence RFU calculation from division by zero
rickwierenga Feb 5, 2026
707f5b1
Remove duplicate enum definitions from optics_control.py
rickwierenga Feb 5, 2026
f78e07d
Remove duplicate ScanDirection and ScanDarkState from measurement_con…
rickwierenga Feb 5, 2026
6d74c4d
Fix type annotation issues for mypy --strict
rickwierenga Feb 5, 2026
6179ef9
refactor io/usb.py
xbtu2 Feb 6, 2026
8c147f4
fix duplicate commands
xbtu2 Feb 6, 2026
77927c9
fix import order
xbtu2 Feb 6, 2026
45f3158
add endpoint override in io/usb.py; refactor read/write methods in sp…
xbtu2 Feb 7, 2026
3a3cc1c
fix import
xbtu2 Feb 7, 2026
73193e8
Address PR comments: propagate RespError via SparkError, add missing …
xbtu2 Mar 10, 2026
38b284a
spark20m: dynamic endpoint discovery, hardware test, early-fail on mi…
xbtu2 Mar 22, 2026
43ee628
Merge branch 'main' into tecan_spark
rickwierenga Mar 22, 2026
38938bc
Fix test failure and mypy error in Spark backend
rickwierenga Mar 22, 2026
5fd912b
Fix CI failures: formatting and optional USB imports
rickwierenga Mar 22, 2026
e5bc326
Merge branch 'main' into tecan_spark
rickwierenga Mar 22, 2026
3a727cd
Fix CI: only serialize USB endpoint addresses when set
rickwierenga Mar 22, 2026
a2a5282
Fix CI: sort imports and skip USB tests when pyusb not installed
rickwierenga Mar 22, 2026
349aa9c
Import SparkBackend unconditionally in plate_reading __init__
rickwierenga Mar 22, 2026
f42c44a
Rename SparkBackend to ExperimentalSparkBackend, add docs notebook
rickwierenga Mar 22, 2026
ce93eeb
Add tecan-spark to plate-reading toctree
rickwierenga Mar 22, 2026
e20f152
Fix import sorting in plate_reading __init__
rickwierenga Mar 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"cell_type": "markdown",
"id": "39d0c1a5",
"metadata": {},
"source": "# Plate reading\n\nPyLabRobot supports the following plate readers:\n\n```{toctree}\n:maxdepth: 1\n\nbmg-clariostar\nbyonoy/absorbance\nbyonoy/luminescence\nbyonoy\ncytation\npico\nsynergyh1\n```\n\nThis example uses the `PlateReaderChatterboxBackend`. When using a real machine, use the corresponding backend."
"source": "# Plate reading\n\nPyLabRobot supports the following plate readers:\n\n```{toctree}\n:maxdepth: 1\n\nbmg-clariostar\nbyonoy/absorbance\nbyonoy/luminescence\nbyonoy\ncytation\npico\nsynergyh1\ntecan-spark\n```\n\nThis example uses the `PlateReaderChatterboxBackend`. When using a real machine, use the corresponding backend."
},
{
"cell_type": "code",
Expand Down
210 changes: 210 additions & 0 deletions docs/user_guide/02_analytical/plate-reading/tecan-spark.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Tecan Spark 20M\n",
"\n",
"```{warning}\n",
"This backend is experimental. The API may change in future versions.\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"## Setup Instructions (Physical)\n",
"\n",
"The Tecan Spark 20M connects via USB. Ensure the device is powered on and connected to your computer.\n",
"\n",
"You will need `pyusb` and `libusb` installed:\n",
"\n",
"```bash\n",
"pip install pylabrobot[usb]\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"## Setup Instructions (Programmatic)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from pylabrobot.plate_reading import PlateReader, ExperimentalSparkBackend\n",
"from pylabrobot.resources import Coordinate\n",
"from pylabrobot.resources.corning.plates import Cor_96_wellplate_360ul_Fb\n",
"\n",
"backend = ExperimentalSparkBackend()\n",
"pr = PlateReader(\n",
" name=\"spark\",\n",
" size_x=200,\n",
" size_y=200,\n",
" size_z=100,\n",
" backend=backend,\n",
")\n",
"\n",
"plate = Cor_96_wellplate_360ul_Fb(name=\"test_plate\")\n",
"pr.assign_child_resource(plate, location=Coordinate.zero())"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"await pr.setup()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"## Usage\n",
"\n",
"### Loading Tray"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"await pr.open()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Place your plate on the carrier, then close.\n",
"await pr.close()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"### Measuring Absorbance"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"abs_result = await pr.read_absorbance(wavelength=450)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`abs_result` is a list of dicts containing wavelength, temperature, and a rows x cols data matrix."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"abs_result[0][\"data\"]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"### Measuring Fluorescence"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"fluo_result = await pr.read_fluorescence(\n",
" excitation_wavelength=485,\n",
" emission_wavelength=535,\n",
" focal_height=20000,\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"fluo_result[0][\"data\"]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"### Measuring Luminescence"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# WIP: luminescence support is not yet implemented."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"## Teardown"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"await pr.stop()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3.11.0"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
Loading
Loading