Skip to content

Commit d60a5f4

Browse files
committed
[cytation] rewrite retry, note possible cause for error -1011
1 parent a73879f commit d60a5f4

File tree

1 file changed

+37
-29
lines changed

1 file changed

+37
-29
lines changed

pylabrobot/plate_reading/biotek_backend.py

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -104,17 +104,25 @@ class Cytation5ImagingConfig:
104104
filters: Optional[List[Optional[ImagingMode]]] = None
105105

106106

107-
@contextmanager
108-
def try_often(times: int = 50):
109-
"""needed because the pyspin api is extremely unreliable."""
110-
for i in range(times):
107+
def retry(func, *args, **kwargs):
108+
"""Call func with retries and logging."""
109+
max_tries = 10
110+
delay = 0.1
111+
tries = 0
112+
while True:
111113
try:
112-
yield
113-
break
114-
except PySpin.SpinnakerException as e:
115-
print(f"Attempt {i+1}/{times}: Unable to use spinnaker: {e}")
116-
else:
117-
raise RuntimeError("Unable to do it")
114+
return func(*args, **kwargs)
115+
except SpinnakerException as ex:
116+
tries += 1
117+
if tries >= max_tries:
118+
raise RuntimeError(f"Failed after {max_tries} tries") from ex
119+
logger.warning(
120+
"Retry %d/%d failed: %s",
121+
tries,
122+
max_tries,
123+
str(ex),
124+
)
125+
time.sleep(delay)
118126

119127

120128
class Cytation5Backend(ImageReaderBackend):
@@ -875,13 +883,13 @@ def _get_device_info(self, cam):
875883
def start_acquisition(self):
876884
if self.cam is None:
877885
raise RuntimeError("Camera is not initialized.")
878-
self.cam.BeginAcquisition()
886+
retry(self.cam.BeginAcquisition)
879887
self._acquiring = True
880888

881889
def stop_acquisition(self):
882890
if self.cam is None:
883891
raise RuntimeError("Camera is not initialized.")
884-
self.cam.EndAcquisition()
892+
retry(self.cam.EndAcquisition)
885893
self._acquiring = False
886894

887895
async def led_on(self, intensity: int = 10):
@@ -1039,14 +1047,14 @@ async def set_auto_exposure(self, auto_exposure: Literal["off", "once", "continu
10391047
if self.cam.ExposureAuto.GetAccessMode() != PySpin.RW:
10401048
raise RuntimeError("unable to write ExposureAuto")
10411049

1042-
with try_often():
1043-
self.cam.ExposureAuto.SetValue(
1044-
{
1045-
"off": PySpin.ExposureAuto_Off,
1046-
"once": PySpin.ExposureAuto_Once,
1047-
"continuous": PySpin.ExposureAuto_Continuous,
1048-
}[auto_exposure]
1049-
)
1050+
retry(
1051+
self.cam.ExposureAuto.SetValue,
1052+
{
1053+
"off": PySpin.ExposureAuto_Off,
1054+
"once": PySpin.ExposureAuto_Once,
1055+
"continuous": PySpin.ExposureAuto_Continuous,
1056+
}[auto_exposure],
1057+
)
10501058

10511059
async def set_exposure(self, exposure: Exposure):
10521060
"""exposure (integration time) in ms, or "machine-auto" """
@@ -1065,23 +1073,19 @@ async def set_exposure(self, exposure: Exposure):
10651073
self._exposure = "machine-auto"
10661074
return
10671075
raise ValueError("exposure must be a number or 'auto'")
1068-
with try_often():
1069-
self.cam.ExposureAuto.SetValue(PySpin.ExposureAuto_Off)
1076+
retry(self.cam.ExposureAuto.SetValue, PySpin.ExposureAuto_Off)
10701077

10711078
# set exposure time (in microseconds)
10721079
if self.cam.ExposureTime.GetAccessMode() != PySpin.RW:
10731080
raise RuntimeError("unable to write ExposureTime")
10741081
exposure_us = int(exposure * 1000)
1075-
with try_often():
1076-
min_et = self.cam.ExposureTime.GetMin()
1082+
min_et = retry(self.cam.ExposureTime.GetMin)
10771083
if exposure_us < min_et:
10781084
raise ValueError(f"exposure must be >= {min_et}")
1079-
with try_often():
1080-
max_et = self.cam.ExposureTime.GetMax()
1085+
max_et = retry(self.cam.ExposureTime.GetMax)
10811086
if exposure_us > max_et:
10821087
raise ValueError(f"exposure must be <= {max_et}")
1083-
with try_often():
1084-
self.cam.ExposureTime.SetValue(exposure_us)
1088+
retry(self.cam.ExposureTime.SetValue, exposure_us)
10851089
self._exposure = exposure
10861090

10871091
async def select(self, row: int, column: int):
@@ -1239,9 +1243,13 @@ async def _acquire_image(
12391243
return image_converted.GetNDArray() # type: ignore
12401244
except SpinnakerException as e:
12411245
# the image is not ready yet, try again
1242-
logger.debug("Failed to get image: %s", e)
1246+
logger.warning("Failed to get image: %s", e)
12431247
self.stop_acquisition()
12441248
self.start_acquisition()
1249+
if "[-1011]" in str(e):
1250+
logger.warning(
1251+
"[-1011] error might occur when the camera is plugged into a USB hub that does not have enough throughput."
1252+
)
12451253

12461254
num_tries += 1
12471255
await asyncio.sleep(0.3)

0 commit comments

Comments
 (0)