11#!/usr/bin/env python
22# -*- coding: utf-8 -*-
33
4- ## Copyright (C) 2017 David Pinto <david.pinto@bioch.ox.ac.uk>
5- ## Copyright (C) 2016 Mick Phillips <mick.phillips@gmail.com>
6- ##
7- ## Microscope is free software: you can redistribute it and/or modify
8- ## it under the terms of the GNU General Public License as published by
9- ## the Free Software Foundation, either version 3 of the License, or
10- ## (at your option) any later version.
11- ##
12- ## Microscope is distributed in the hope that it will be useful,
13- ## but WITHOUT ANY WARRANTY; without even the implied warranty of
14- ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15- ## GNU General Public License for more details.
16- ##
17- ## You should have received a copy of the GNU General Public License
18- ## along with Microscope. If not, see <http://www.gnu.org/licenses/>.
4+ # Copyright (C) 2017-2020 David Pinto <david.pinto@bioch.ox.ac.uk>
5+ # Copyright (C) 2016-2020 Mick Phillips <mick.phillips@gmail.com>
6+ #
7+ # Microscope is free software: you can redistribute it and/or modify
8+ # it under the terms of the GNU General Public License as published by
9+ # the Free Software Foundation, either version 3 of the License, or
10+ # (at your option) any later version.
11+ #
12+ # Microscope is distributed in the hope that it will be useful,
13+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+ # GNU General Public License for more details.
16+ #
17+ # You should have received a copy of the GNU General Public License
18+ # along with Microscope. If not, see <http://www.gnu.org/licenses/>.
1919
2020"""Classes for control of microscope components.
2121
5555(TRIGGER_AFTER , TRIGGER_BEFORE , TRIGGER_DURATION , TRIGGER_SOFT ) = range (4 )
5656
5757# Mapping of setting data types to descriptors allowed-value description types.
58- # For python 2 and 3 compatibility, we convert the type into a descriptor string.
59- # This avoids problems with, say a python 2 client recognising a python 3
60- # <class 'int'> as a python 2 <type 'int'>.
58+ # For python 2 and 3 compatibility, we convert the type into a descriptor
59+ # string. This avoids problems with, say a python 2 client recognising a
60+ # python 3 <class 'int'> as a python 2 <type 'int'>.
6161DTYPES = {'int' : ('int' , tuple ),
6262 'float' : ('float' , tuple ),
6363 'bool' : ('bool' , type (None )),
7070 str : ('str' , int ),
7171 tuple : ('tuple' , type (None ))}
7272
73- # A utility function to call callables or return value of non-callables.
74- # noinspection PyPep8
75- _call_if_callable = lambda f : f () if callable (f ) else f
73+
74+ def call_if_callable (f ):
75+ """Call callables, or return value of non-callables."""
76+ return f () if callable (f ) else f
7677
7778
7879class _Setting ():
7980 # TODO: refactor into subclasses to avoid if isinstance .. elif .. else.
8081 # Settings classes should be private: devices should use a factory method
81- # rather than instantiate settings directly; most already use add_setting for this.
82- def __init__ (self , name , dtype , get_func , set_func = None , values = None , readonly = False ):
82+ # rather than instantiate settings directly; most already use add_setting
83+ # for this.
84+ def __init__ (self , name , dtype , get_func , set_func = None , values = None ,
85+ readonly = False ):
8386 """Create a setting.
8487
8588 :param name: the setting's name
@@ -102,7 +105,8 @@ def __init__(self, name, dtype, get_func, set_func=None, values=None, readonly=F
102105 if dtype not in DTYPES :
103106 raise Exception ('Unsupported dtype.' )
104107 elif not (isinstance (values , DTYPES [dtype ][1 :]) or callable (values )):
105- raise Exception ("Invalid values type for %s '%s': expected function or %s" %
108+ raise Exception ("Invalid values type for %s '%s':"
109+ "expected function or %s" %
106110 (dtype , name , DTYPES [dtype ][1 :]))
107111 self .dtype = DTYPES [dtype ][0 ]
108112 self ._get = get_func
@@ -211,11 +215,9 @@ def __init__(self, index=None):
211215 def __del__ (self ):
212216 self .shutdown ()
213217
214-
215218 def get_is_enabled (self ):
216219 return self .enabled
217220
218-
219221 def _on_disable (self ):
220222 """Do any device-specific work on disable.
221223
@@ -268,7 +270,8 @@ def make_safe(self):
268270 """Put the device into a safe state."""
269271 pass
270272
271- def add_setting (self , name , dtype , get_func , set_func , values , readonly = False ):
273+ def add_setting (self , name , dtype , get_func , set_func , values ,
274+ readonly = False ):
272275 """Add a setting definition.
273276
274277 :param name: the setting's name
@@ -291,7 +294,8 @@ class with getter, setter, etc., and adding Setting instances as
291294 if dtype not in DTYPES :
292295 raise Exception ('Unsupported dtype.' )
293296 elif not (isinstance (values , DTYPES [dtype ][1 :]) or callable (values )):
294- raise Exception ("Invalid values type for %s '%s': expected function or %s" %
297+ raise Exception ("Invalid values type for %s '%s':"
298+ "expected function or %s" %
295299 (dtype , name , DTYPES [dtype ][1 :]))
296300 else :
297301 self ._settings [name ] = _Setting (name , dtype , get_func , set_func ,
@@ -465,7 +469,6 @@ def enable(self):
465469 _logger .debug ("... enabled." )
466470 return self .enabled
467471
468-
469472 def disable (self ):
470473 """Disable the data capture device.
471474
@@ -501,13 +504,14 @@ def _send_data(self, client, data, timestamp):
501504 # this function name as an argument to set_client, but
502505 # not sure how to subsequently resolve this over Pyro.
503506 client .receiveData (data , timestamp )
504- except (Pyro4 .errors .ConnectionClosedError , Pyro4 .errors .CommunicationError ):
507+ except (Pyro4 .errors .ConnectionClosedError ,
508+ Pyro4 .errors .CommunicationError ):
505509 # Client not listening
506510 _logger .info ("Removing %s from client stack: disconnected." ,
507511 client ._pyroUri )
508512 self ._clientStack = list (filter (client .__ne__ , self ._clientStack ))
509513 self ._liveClients = self ._liveClients .difference ([client ])
510- except :
514+ except Exception :
511515 raise
512516
513517 def _dispatch_loop (self ):
@@ -525,12 +529,13 @@ def _dispatch_loop(self):
525529 err = e
526530 else :
527531 try :
528- self ._send_data (client , self ._process_data (data ), timestamp )
532+ self ._send_data (client , self ._process_data (data ),
533+ timestamp )
529534 except Exception as e :
530535 err = e
531536 if err :
532- # Raising an exception will kill the dispatch loop. We need another
533- # way to notify the client that there was a problem.
537+ # Raising an exception will kill the dispatch loop. We need
538+ # another way to notify the client that there was a problem.
534539 _logger .error ("in _dispatch_loop:" , exc_info = err )
535540 self ._dispatch_buffer .task_done ()
536541
@@ -543,13 +548,13 @@ def _fetch_loop(self):
543548 data = self ._fetch_data ()
544549 except Exception as e :
545550 _logger .error ("in _fetch_loop:" , exc_info = e )
546- # Raising an exception will kill the fetch loop. We need another
547- # way to notify the client that there was a problem.
551+ # Raising an exception will kill the fetch loop. We need
552+ # another way to notify the client that there was a problem.
548553 timestamp = time .time ()
549554 self ._put (e , timestamp )
550555 data = None
551556 if data is not None :
552- # *** TODO*** Add support for timestamp from hardware.
557+ # TODO Add support for timestamp from hardware.
553558 timestamp = time .time ()
554559 self ._put (data , timestamp )
555560 else :
@@ -602,7 +607,6 @@ def set_client(self, new_client):
602607 else :
603608 _logger .info ("Current client is %s." , str (self ._client ))
604609
605-
606610 @keep_acquiring
607611 def update_settings (self , settings , init = False ):
608612 """Update settings, toggling acquisition if necessary."""
@@ -679,6 +683,7 @@ def __init__(self, **kwargs):
679683 self .get_roi ,
680684 self .set_roi ,
681685 None )
686+
682687 def _process_data (self , data ):
683688 """Apply self._transform to data."""
684689 flips = (self ._transform [0 ], self ._transform [1 ])
@@ -708,7 +713,8 @@ def set_transform(self, transform):
708713 if isinstance (transform , str ):
709714 transform = literal_eval (transform )
710715 self ._client_transform = transform
711- lr , ud , rot = (self ._readout_transform [i ] ^ transform [i ] for i in range (3 ))
716+ lr , ud , rot = (self ._readout_transform [i ] ^ transform [i ]
717+ for i in range (3 ))
712718 if self ._readout_transform [2 ] and self ._client_transform [2 ]:
713719 lr = not lr
714720 ud = not ud
@@ -758,7 +764,7 @@ def _get_binning(self):
758764 pass
759765
760766 def get_binning (self ):
761- """Return a tuple of (horizontal, vertical), corrected for transform."""
767+ """Return a tuple of (horizontal, vertical) corrected for transform."""
762768 binning = self ._get_binning ()
763769 if self ._transform [2 ]:
764770 # 90 degree rotation
@@ -808,9 +814,9 @@ def set_roi(self, roi):
808814 maxw , maxh = self .get_sensor_shape ()
809815 binning = self .get_binning ()
810816 left , top , width , height = roi
811- if not width : # 0 or None
817+ if not width : # 0 or None
812818 width = maxw // binning .h
813- if not height : # 0 o rNone
819+ if not height : # 0 o rNone
814820 height = maxh // binning .v
815821 if self ._transform [2 ]:
816822 roi = ROI (left , top , height , width )
@@ -843,6 +849,7 @@ class TriggerType(Enum):
843849 FALLING_EDGE = 2
844850 PULSE = 3
845851
852+
846853class TriggerMode (Enum ):
847854 ONCE = 1
848855 BULB = 2
@@ -866,6 +873,7 @@ class TriggerTargetMixIn(metaclass=abc.ABCMeta):
866873 @property
867874 def trigger_mode (self ) -> TriggerMode :
868875 return self ._trigger_mode
876+
869877 @property
870878 def trigger_type (self ) -> TriggerType :
871879 return self ._trigger_type
@@ -889,11 +897,11 @@ class SerialDeviceMixIn(metaclass=abc.ABCMeta):
889897 """
890898 def __init__ (self , ** kwargs ):
891899 super ().__init__ (** kwargs )
892- ## TODO: We should probably construct the connection here but
893- ## the Serial constructor takes a lot of arguments, and
894- ## it becomes tricky to separate those from arguments to
895- ## the constructor of other parent classes.
896- self .connection = None # serial.Serial (to be constructed by child)
900+ # TODO: We should probably construct the connection here but
901+ # the Serial constructor takes a lot of arguments, and
902+ # it becomes tricky to separate those from arguments to
903+ # the constructor of other parent classes.
904+ self .connection = None # serial.Serial (to be constructed by child)
897905 self ._comms_lock = threading .RLock ()
898906
899907 def _readline (self ):
@@ -962,8 +970,8 @@ def __init__(self, **kwargs) -> None:
962970 """
963971 super ().__init__ (** kwargs )
964972
965- self ._patterns = None # type: typing.Optional[numpy.ndarray]
966- self ._pattern_idx = - 1 # type: int
973+ self ._patterns = None # type: typing.Optional[numpy.ndarray]
974+ self ._pattern_idx = - 1 # type: int
967975
968976 @property
969977 @abc .abstractmethod
@@ -1008,7 +1016,7 @@ def queue_patterns(self, patterns: numpy.ndarray) -> None:
10081016 """
10091017 self ._validate_patterns (patterns )
10101018 self ._patterns = patterns
1011- self ._pattern_idx = - 1 # none is applied yet
1019+ self ._pattern_idx = - 1 # none is applied yet
10121020
10131021 def next_pattern (self ) -> None :
10141022 """Apply the next pattern in the queue.
@@ -1018,7 +1026,7 @@ def next_pattern(self) -> None:
10181026 if self ._patterns is None :
10191027 raise Exception ("no pattern queued to apply" )
10201028 self ._pattern_idx += 1
1021- self .apply_pattern (self ._patterns [self ._pattern_idx ,:])
1029+ self .apply_pattern (self ._patterns [self ._pattern_idx , :])
10221030
10231031 def initialize (self ) -> None :
10241032 pass
@@ -1087,29 +1095,28 @@ def set_power_mw(self, mw):
10871095
10881096
10891097class FilterWheelBase (Device , metaclass = abc .ABCMeta ):
1090- def __init__ (self , filters : typing .Union [typing .Mapping [int , str ], typing . Iterable ] = [],
1091- positions : int = 0 , ** kwargs ) -> None :
1098+ def __init__ (self , filters : typing .Union [typing .Mapping [int , str ],
1099+ typing . Iterable ] = [], positions : int = 0 , ** kwargs ) -> None :
10921100 super ().__init__ (** kwargs )
10931101 if isinstance (filters , dict ):
10941102 self ._filters = filters
10951103 else :
1096- self ._filters = {i :f for (i , f ) in enumerate (filters )}
1104+ self ._filters = {i : f for (i , f ) in enumerate (filters )}
10971105 self ._inv_filters = {val : key for key , val in self ._filters .items ()}
10981106 if not hasattr (self , '_positions' ):
1099- self ._positions = positions # type: int
1107+ self ._positions = positions # type: int
11001108 # The position as an integer.
11011109 # Deprecated: clients should call get_position and set_position;
11021110 # still exposed as a setting until cockpit uses set_position.
11031111 self .add_setting ('position' ,
11041112 'int' ,
11051113 self .get_position ,
11061114 self .set_position ,
1107- lambda : (0 , self .get_num_positions ()) )
1108-
1115+ lambda : (0 , self .get_num_positions ()))
11091116
11101117 def get_num_positions (self ) -> int :
11111118 """Returns the number of wheel positions."""
1112- return (max ( self ._positions , len (self ._filters )))
1119+ return (max (self ._positions , len (self ._filters )))
11131120
11141121 @abc .abstractmethod
11151122 def get_position (self ) -> int :
@@ -1122,7 +1129,7 @@ def set_position(self, position: int) -> None:
11221129 pass
11231130
11241131 def get_filters (self ) -> typing .List [typing .Tuple [int , str ]]:
1125- return [(k ,v ) for k ,v in self ._filters .items ()]
1132+ return [(k , v ) for k , v in self ._filters .items ()]
11261133
11271134
11281135class ControllerDevice (Device , metaclass = abc .ABCMeta ):
@@ -1143,7 +1150,6 @@ class ControllerDevice(Device, metaclass=abc.ABCMeta):
11431150 their shutdown and initialisation.
11441151
11451152 """
1146-
11471153 def initialize (self ) -> None :
11481154 super ().initialize ()
11491155 for d in self .devices .values ():
0 commit comments