Skip to content

Commit 86f367e

Browse files
committed
merge stop; make cam and spin private
1 parent 70437e1 commit 86f367e

3 files changed

Lines changed: 56 additions & 71 deletions

File tree

pylabrobot/plate_reading/__init__.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
from .agilent_biotek_backend import BioTekPlateReaderBackend
2-
from .agilent_biotek_cytation_backend import CytationBackend, CytationImagingConfig
3-
from .agilent_biotek_cytation_backend import Cytation5Backend, Cytation5ImagingConfig
2+
from .agilent_biotek_cytation_backend import (
3+
Cytation5Backend,
4+
Cytation5ImagingConfig,
5+
CytationBackend,
6+
CytationImagingConfig,
7+
)
48
from .agilent_biotek_synergyh1_backend import SynergyH1Backend
59
from .chatterbox import PlateReaderChatterboxBackend
610
from .clario_star_backend import CLARIOstarBackend

pylabrobot/plate_reading/agilent_biotek_backend.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,13 @@ async def setup(self) -> None:
100100
self._shaking = False
101101
self._shaking_task: Optional[asyncio.Task] = None
102102

103+
async def stop(self) -> None:
104+
logger.info(f"{self.__class__.__name__} stopping")
105+
await self.stop_shaking()
106+
await self.io.stop()
107+
108+
self._slow_mode = None
109+
103110
@property
104111
def version(self) -> str:
105112
if self._version is None:
@@ -146,14 +153,14 @@ async def _purge_buffers(self) -> None:
146153
await self.io.usb_purge_rx_buffer()
147154
await self.io.usb_purge_tx_buffer()
148155

149-
async def _read_until(self, char: bytes, timeout: Optional[float] = None) -> bytes:
156+
async def _read_until(self, terminator: bytes, timeout: Optional[float] = None) -> bytes:
150157
"""If timeout is None, use self.timeout"""
151158
if timeout is None:
152159
timeout = self.timeout
153160
x = None
154161
res = b""
155162
t0 = time.time()
156-
while x != char:
163+
while x != terminator:
157164
x = await self.io.read(1)
158165
res += x
159166

@@ -586,10 +593,3 @@ async def stop_shaking(self) -> None:
586593
# Task cancellation is expected here; safe to ignore this exception.
587594
pass
588595
self._shaking_task = None
589-
590-
async def stop(self) -> None:
591-
logger.info(f"{self.__class__.__name__} stopping")
592-
await self.stop_shaking()
593-
await self.io.stop()
594-
595-
self._slow_mode = None

pylabrobot/plate_reading/agilent_biotek_cytation_backend.py

Lines changed: 41 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,11 @@ def __init__(
8686
) -> None:
8787
super().__init__(timeout=timeout, device_id=device_id)
8888

89-
self.spinnaker_system: Optional["PySpin.SystemPtr"] = None
90-
self.cam: Optional["PySpin.CameraPtr"] = None
89+
self._spinnaker_system: Optional["PySpin.SystemPtr"] = None
90+
self._cam: Optional["PySpin.CameraPtr"] = None
9191
self.imaging_config = imaging_config or CytationImagingConfig()
9292
self._filters: Optional[List[Optional[ImagingMode]]] = self.imaging_config.filters
9393
self._objectives: Optional[List[Optional[Objective]]] = self.imaging_config.objectives
94-
9594
self._exposure: Optional[Exposure] = None
9695
self._focal_height: Optional[FocalPosition] = None
9796
self._gain: Optional[Gain] = None
@@ -101,30 +100,12 @@ def __init__(
101100
self._pos_x: Optional[float] = None
102101
self._pos_y: Optional[float] = None
103102
self._objective: Optional[Objective] = None
104-
105103
self._acquiring = False
106104

107105
async def setup(self, use_cam: bool = False) -> None:
108106
logger.info(f"{self.__class__.__name__} setting up")
109107

110-
await self.io.setup()
111-
await self.io.usb_reset()
112-
await self.io.set_latency_timer(16)
113-
await self.io.set_baudrate(9600) # 0x38 0x41
114-
await self.io.set_line_property(8, 2, 0) # 8 data bits, 2 stop bits, no parity
115-
SIO_RTS_CTS_HS = 0x1 << 8
116-
await self.io.set_flowctrl(SIO_RTS_CTS_HS)
117-
await self.io.set_rts(True)
118-
119-
# see if we need to adjust baudrate. This appears to be the case sometimes.
120-
try:
121-
self._version = await self.get_firmware_version()
122-
except TimeoutError:
123-
await self.io.set_baudrate(38_461) # 4e c0
124-
self._version = await self.get_firmware_version()
125-
126-
self._shaking = False
127-
self._shaking_task: Optional[asyncio.Task] = None
108+
await super().setup()
128109

129110
if use_cam:
130111
try:
@@ -184,8 +165,8 @@ async def _set_up_camera(self) -> None:
184165
logger.debug(f"{self.__class__.__name__} setting up camera")
185166

186167
# -- Retrieve singleton reference to system object (Spinnaker) --
187-
self.spinnaker_system = PySpin.System.GetInstance()
188-
version = self.spinnaker_system.GetLibraryVersion()
168+
self._spinnaker_system = PySpin.System.GetInstance()
169+
version = self._spinnaker_system.GetLibraryVersion()
189170
logger.debug(
190171
f"{self.__class__.__name__} Library version: %d.%d.%d.%d",
191172
version.major,
@@ -195,7 +176,7 @@ async def _set_up_camera(self) -> None:
195176
)
196177

197178
# -- Get the camera by serial number, or the first. --
198-
cam_list = self.spinnaker_system.GetCameras()
179+
cam_list = self._spinnaker_system.GetCameras()
199180
num_cameras = cam_list.GetSize()
200181
logger.debug(f"{self.__class__.__name__} number of cameras detected: %d", num_cameras)
201182

@@ -208,22 +189,22 @@ async def _set_up_camera(self) -> None:
208189
self.imaging_config.camera_serial_number is not None
209190
and serial_number == self.imaging_config.camera_serial_number
210191
):
211-
self.cam = cam
192+
self._cam = cam
212193
logger.info(f"{self.__class__.__name__} using camera with serial number %s", serial_number)
213194
break
214195
else: # if no specific camera was found by serial number so use the first one
215196
if num_cameras > 0:
216-
self.cam = cam_list.GetByIndex(0)
197+
self._cam = cam_list.GetByIndex(0)
217198
logger.info(
218199
f"{self.__class__.__name__} using first camera with serial number %s",
219200
info["DeviceSerialNumber"],
220201
)
221202
else:
222203
logger.error(f"{self.__class__.__name__}: No cameras found")
223-
self.cam = None
204+
self._cam = None
224205
cam_list.Clear()
225206

226-
if self.cam is None:
207+
if self._cam is None:
227208
raise RuntimeError(
228209
f"{self.__class__.__name__}: No camera found. Make sure the camera is connected and the serial "
229210
"number is correct."
@@ -232,7 +213,7 @@ async def _set_up_camera(self) -> None:
232213
# -- Initialize camera --
233214
for _ in range(10):
234215
try:
235-
self.cam.Init() # SpinnakerException: Spinnaker: Could not read the XML URL [-1010]
216+
self._cam.Init() # SpinnakerException: Spinnaker: Could not read the XML URL [-1010]
236217
break
237218
except: # noqa
238219
await asyncio.sleep(0.1)
@@ -242,7 +223,7 @@ async def _set_up_camera(self) -> None:
242223
"Failed to initialize camera. Make sure the camera is connected and the "
243224
"Spinnaker SDK is installed correctly."
244225
)
245-
nodemap = self.cam.GetNodeMap()
226+
nodemap = self._cam.GetNodeMap()
246227

247228
# -- Configure trigger to be software --
248229
# This is needed for longer exposure times (otherwise 27.8ms is the maximum)
@@ -433,24 +414,24 @@ async def _load_objectives(self):
433414
raise RuntimeError(f"{self.__class__.__name__}: Unsupported version: {self.version}")
434415

435416
def _stop_camera(self) -> None:
436-
if self.cam is not None:
417+
if self._cam is not None:
437418
if self._acquiring:
438419
self.stop_acquisition()
439420

440421
self._reset_trigger()
441422

442-
self.cam.DeInit()
443-
self.cam = None
444-
if self.spinnaker_system is not None:
445-
self.spinnaker_system.ReleaseInstance()
423+
self._cam.DeInit()
424+
self._cam = None
425+
if self._spinnaker_system is not None:
426+
self._spinnaker_system.ReleaseInstance()
446427

447428
def _reset_trigger(self):
448-
if self.cam is None:
429+
if self._cam is None:
449430
return
450431

451432
# adopted from example
452433
try:
453-
nodemap = self.cam.GetNodeMap()
434+
nodemap = self._cam.GetNodeMap()
454435
node_trigger_mode = PySpin.CEnumerationPtr(nodemap.GetNode("TriggerMode"))
455436
if not PySpin.IsReadable(node_trigger_mode) or not PySpin.IsWritable(node_trigger_mode):
456437
return
@@ -512,19 +493,19 @@ def _get_device_info(self, cam):
512493
return device_info
513494

514495
def start_acquisition(self):
515-
if self.cam is None:
496+
if self._cam is None:
516497
raise RuntimeError(f"{self.__class__.__name__}: Camera is not initialized.")
517498
if self._acquiring:
518499
return
519-
retry(self.cam.BeginAcquisition)
500+
retry(self._cam.BeginAcquisition)
520501
self._acquiring = True
521502

522503
def stop_acquisition(self):
523-
if self.cam is None:
504+
if self._cam is None:
524505
raise RuntimeError(f"{self.__class__.__name__}: Camera is not initialized.")
525506
if not self._acquiring:
526507
return
527-
retry(self.cam.EndAcquisition)
508+
retry(self._cam.EndAcquisition)
528509
self._acquiring = False
529510

530511
async def led_on(self, intensity: int = 10):
@@ -610,14 +591,14 @@ async def set_position(self, x: float, y: float):
610591
await asyncio.sleep(0.1)
611592

612593
async def set_auto_exposure(self, auto_exposure: Literal["off", "once", "continuous"]):
613-
if self.cam is None:
594+
if self._cam is None:
614595
raise ValueError("Camera not initialized. Run setup(use_cam=True) first.")
615596

616-
if self.cam.ExposureAuto.GetAccessMode() != PySpin.RW:
597+
if self._cam.ExposureAuto.GetAccessMode() != PySpin.RW:
617598
raise RuntimeError("unable to write ExposureAuto")
618599

619600
retry(
620-
self.cam.ExposureAuto.SetValue,
601+
self._cam.ExposureAuto.SetValue,
621602
{
622603
"off": PySpin.ExposureAuto_Off,
623604
"once": PySpin.ExposureAuto_Once,
@@ -632,7 +613,7 @@ async def set_exposure(self, exposure: Exposure):
632613
logger.debug("Exposure time is already set to %s", exposure)
633614
return
634615

635-
if self.cam is None:
616+
if self._cam is None:
636617
raise ValueError("Camera not initialized. Run setup(use_cam=True) first.")
637618

638619
# either set auto exposure to continuous, or turn off
@@ -642,19 +623,19 @@ async def set_exposure(self, exposure: Exposure):
642623
self._exposure = "machine-auto"
643624
return
644625
raise ValueError("exposure must be a number or 'auto'")
645-
retry(self.cam.ExposureAuto.SetValue, PySpin.ExposureAuto_Off)
626+
retry(self._cam.ExposureAuto.SetValue, PySpin.ExposureAuto_Off)
646627

647628
# set exposure time (in microseconds)
648-
if self.cam.ExposureTime.GetAccessMode() != PySpin.RW:
629+
if self._cam.ExposureTime.GetAccessMode() != PySpin.RW:
649630
raise RuntimeError("unable to write ExposureTime")
650631
exposure_us = int(exposure * 1000)
651-
min_et = retry(self.cam.ExposureTime.GetMin)
632+
min_et = retry(self._cam.ExposureTime.GetMin)
652633
if exposure_us < min_et:
653634
raise ValueError(f"exposure must be >= {min_et}")
654-
max_et = retry(self.cam.ExposureTime.GetMax)
635+
max_et = retry(self._cam.ExposureTime.GetMax)
655636
if exposure_us > max_et:
656637
raise ValueError(f"exposure must be <= {max_et}")
657-
retry(self.cam.ExposureTime.SetValue, exposure_us)
638+
retry(self._cam.ExposureTime.SetValue, exposure_us)
658639
self._exposure = exposure
659640

660641
async def select(self, row: int, column: int):
@@ -669,7 +650,7 @@ async def select(self, row: int, column: int):
669650

670651
async def set_gain(self, gain: Gain):
671652
"""gain of unknown units, or "machine-auto" """
672-
if self.cam is None:
653+
if self._cam is None:
673654
raise ValueError("Camera not initialized. Run setup(use_cam=True) first.")
674655

675656
if gain == self._gain:
@@ -679,7 +660,7 @@ async def set_gain(self, gain: Gain):
679660
if not (gain == "machine-auto" or 0 <= gain <= 30):
680661
raise ValueError("gain must be between 0 and 30 (inclusive), or 'auto'")
681662

682-
nodemap = self.cam.GetNodeMap()
663+
nodemap = self._cam.GetNodeMap()
683664

684665
# set/disable automatic gain
685666
node_gain_auto = PySpin.CEnumerationPtr(nodemap.GetNode("GainAuto"))
@@ -738,7 +719,7 @@ async def set_objective(self, objective: Objective):
738719
self._objective = objective
739720

740721
async def set_imaging_mode(self, mode: ImagingMode, led_intensity: int):
741-
if self.cam is None:
722+
if self._cam is None:
742723
raise ValueError("Camera not initialized. Run setup(use_cam=True) first.")
743724

744725
if mode == self._imaging_mode:
@@ -789,8 +770,8 @@ async def _acquire_image(
789770
color_processing_algorithm: int = SPINNAKER_COLOR_PROCESSING_ALGORITHM_HQ_LINEAR,
790771
pixel_format: int = PixelFormat_Mono8,
791772
) -> Image:
792-
assert self.cam is not None
793-
nodemap = self.cam.GetNodeMap()
773+
assert self._cam is not None
774+
nodemap = self._cam.GetNodeMap()
794775

795776
assert self.imaging_config is not None, "Need to set imaging_config first"
796777

@@ -802,8 +783,8 @@ async def _acquire_image(
802783

803784
try:
804785
node_softwaretrigger_cmd.Execute()
805-
timeout = int(self.cam.ExposureTime.GetValue() / 1000 + 1000) # from example
806-
image_result = self.cam.GetNextImage(timeout)
786+
timeout = int(self._cam.ExposureTime.GetValue() / 1000 + 1000) # from example
787+
image_result = self._cam.GetNextImage(timeout)
807788
if not image_result.IsIncomplete():
808789
processor = PySpin.ImageProcessor()
809790
processor.SetColorProcessing(color_processing_algorithm)
@@ -861,7 +842,7 @@ async def capture(
861842

862843
assert overlap is None, "not implemented yet"
863844

864-
if self.cam is None:
845+
if self._cam is None:
865846
raise ValueError("Camera not initialized. Run setup(use_cam=True) first.")
866847

867848
await self.set_plate(plate)
@@ -931,7 +912,7 @@ def image_size(magnification: float) -> Tuple[float, float]:
931912
if auto_stop_acquisition:
932913
self.stop_acquisition()
933914

934-
exposure_ms = float(self.cam.ExposureTime.GetValue()) / 1000
915+
exposure_ms = float(self._cam.ExposureTime.GetValue()) / 1000
935916
assert self._focal_height is not None, "Focal height not set. Run set_focus() first."
936917
focal_height_val = float(self._focal_height)
937918

0 commit comments

Comments
 (0)