55import math
66import re
77import time
8+ from contextlib import contextmanager
89from dataclasses import dataclass
910from typing import Any , Callable , Coroutine , Dict , List , Literal , Optional , Tuple , Union , cast
1011
@@ -96,13 +97,26 @@ async def cached_func(x: float) -> float:
9697@dataclass
9798class Cytation5ImagingConfig :
9899 camera_serial_number : Optional [str ] = None
99- max_image_read_attempts : int = 8
100+ max_image_read_attempts : int = 50
100101
101102 # if not specified, these will be loaded from machine configuration (register with gen5.exe)
102103 objectives : Optional [List [Optional [Objective ]]] = None
103104 filters : Optional [List [Optional [ImagingMode ]]] = None
104105
105106
107+ @contextmanager
108+ def try_often (times : int = 50 ):
109+ """needed because the pyspin api is extremely unreliable."""
110+ for i in range (times ):
111+ 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" )
118+
119+
106120class Cytation5Backend (ImageReaderBackend ):
107121 """Backend for biotek cytation 5 image reader.
108122
@@ -1024,13 +1038,15 @@ async def set_auto_exposure(self, auto_exposure: Literal["off", "once", "continu
10241038
10251039 if self .cam .ExposureAuto .GetAccessMode () != PySpin .RW :
10261040 raise RuntimeError ("unable to write ExposureAuto" )
1027- self .cam .ExposureAuto .SetValue (
1028- {
1029- "off" : PySpin .ExposureAuto_Off ,
1030- "once" : PySpin .ExposureAuto_Once ,
1031- "continuous" : PySpin .ExposureAuto_Continuous ,
1032- }[auto_exposure ]
1033- )
1041+
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+ )
10341050
10351051 async def set_exposure (self , exposure : Exposure ):
10361052 """exposure (integration time) in ms, or "machine-auto" """
@@ -1049,19 +1065,23 @@ async def set_exposure(self, exposure: Exposure):
10491065 self ._exposure = "machine-auto"
10501066 return
10511067 raise ValueError ("exposure must be a number or 'auto'" )
1052- self .cam .ExposureAuto .SetValue (PySpin .ExposureAuto_Off )
1068+ with try_often ():
1069+ self .cam .ExposureAuto .SetValue (PySpin .ExposureAuto_Off )
10531070
10541071 # set exposure time (in microseconds)
10551072 if self .cam .ExposureTime .GetAccessMode () != PySpin .RW :
10561073 raise RuntimeError ("unable to write ExposureTime" )
10571074 exposure_us = int (exposure * 1000 )
1058- min_et = self .cam .ExposureTime .GetMin ()
1075+ with try_often ():
1076+ min_et = self .cam .ExposureTime .GetMin ()
10591077 if exposure_us < min_et :
10601078 raise ValueError (f"exposure must be >= { min_et } " )
1061- max_et = self .cam .ExposureTime .GetMax ()
1079+ with try_often ():
1080+ max_et = self .cam .ExposureTime .GetMax ()
10621081 if exposure_us > max_et :
10631082 raise ValueError (f"exposure must be <= { max_et } " )
1064- self .cam .ExposureTime .SetValue (exposure_us )
1083+ with try_often ():
1084+ self .cam .ExposureTime .SetValue (exposure_us )
10651085 self ._exposure = exposure
10661086
10671087 async def select (self , row : int , column : int ):
@@ -1206,17 +1226,9 @@ async def _acquire_image(
12061226 node_softwaretrigger_cmd = PySpin .CCommandPtr (nodemap .GetNode ("TriggerSoftware" ))
12071227 if not PySpin .IsWritable (node_softwaretrigger_cmd ):
12081228 raise RuntimeError ("unable to execute software trigger" )
1209- num_trigger_tries = 5
1210- for _ in range (num_trigger_tries ):
1211- try :
1212- node_softwaretrigger_cmd .Execute ()
1213- break
1214- except SpinnakerException :
1215- continue
1216- else :
1217- raise RuntimeError (f"Failed to execute software trigger after { num_trigger_tries } attempts" )
12181229
12191230 try :
1231+ node_softwaretrigger_cmd .Execute ()
12201232 timeout = int (self .cam .ExposureTime .GetValue () / 1000 + 1000 ) # from example
12211233 image_result = self .cam .GetNextImage (timeout )
12221234 if not image_result .IsIncomplete ():
@@ -1228,6 +1240,9 @@ async def _acquire_image(
12281240 except SpinnakerException as e :
12291241 # the image is not ready yet, try again
12301242 logger .debug ("Failed to get image: %s" , e )
1243+ self .stop_acquisition ()
1244+ self .start_acquisition ()
1245+
12311246 num_tries += 1
12321247 await asyncio .sleep (0.3 )
12331248 raise TimeoutError ("max_image_read_attempts reached" )
0 commit comments