Dummy DetectorΒΆ

This section contains the dummydetector script.

Download file: dummydetector.py

  1"""
  2Dummy detector driver
  3
  4This file is part of lab-control-lib
  5(c) 2023-2024 Pierre Thibault (pthibault@units.it)
  6"""
  7
  8import numpy as np
  9import time
 10import os
 11
 12from lclib import register_driver, proxydevice
 13from lclib.camera import CameraBase
 14
 15# BASE_PATH = "C:\\data\\"
 16BASE_PATH = os.path.expanduser('~/dummylab-data/')
 17os.makedirs(BASE_PATH, exist_ok=True)
 18
 19__all__ = ['Dummydetector']
 20
 21ADDRESS = ('localhost', 5060)  # Address for the proxy driver
 22
 23@register_driver
 24@proxydevice(address=ADDRESS)
 25class Dummydetector(CameraBase):
 26    """
 27    Dummy detector class
 28    """
 29
 30    DEFAULT_BROADCAST_ADDRESS = (ADDRESS[0], 9500)  # address to broadcast images for viewers
 31    BASE_PATH = BASE_PATH  # All data is saved in subfolders of this one
 32    PIXEL_SIZE = 50        # Physical pixel pitch in micrometers
 33    SHAPE = (256, 512)     # Native array shape (vertical, horizontal)
 34    MAX_FPS = 15           # The real max FPS is higher (especially in binning mode) but this seems sufficient.
 35    LOCAL_DEFAULT_CONFIG = {'binning':(1,1),
 36                            'save_path': 'snaps/',
 37                            'gain_mode':'high'  # Example of camera-specific parameter
 38                            }
 39    
 40    # python <3.9
 41    DEFAULT_CONFIG = CameraBase.DEFAULT_CONFIG.copy()
 42    DEFAULT_CONFIG.update(LOCAL_DEFAULT_CONFIG)
 43
 44    def __init__(self, broadcast_address=None):
 45        """
 46        Initialization.
 47        """
 48        super().__init__(broadcast_address=broadcast_address)
 49
 50        self.detector = None
 51        self.init_device()
 52
 53    def init_device(self):
 54        """
 55        Initialize camera
 56        """
 57
 58        self.detector = 'Would be some kind of API object'
 59        self.logger.info('Detector is ready')
 60
 61        # Apply saved configuration
 62        self.operation_mode = self.operation_mode  # Calls getter/setter
 63        self.exposure_time = self.config['exposure_time']
 64        self.binning = self.config['binning']
 65        self.exposure_number = self.config['exposure_number']
 66        self.initialized = True
 67
 68    def _arm(self):
 69        """
 70        Prepare the camera for acquisition
 71        """
 72        self.logger.debug('Detector going live.')
 73        # self.detector.go_live() # Or whatever
 74
 75    def _trigger(self):
 76        """
 77        Acquisition.
 78        """
 79        det = self.detector
 80
 81        n_exp = self.exposure_number
 82        exp_time = self.exposure_time
 83
 84        self.logger.debug('Triggering detector.')
 85        # det.software_trigger() # or whatever
 86
 87        self.logger.debug('Starting acquisition loop.')
 88        frame_counter = 0
 89        while True:
 90            if not self.rolling:
 91                self.print(f'\r{frame_counter}/{n_exp}', end='')
 92
 93            # Trigger metadata collection
 94            self.grab_metadata.set()
 95
 96            # det.wait_for_new_frame() # or whatever
 97            time.sleep(self.exposure_time)
 98
 99            # Get metadata
100            if not self.monitor.connected:
101                self.logger.error("Not connected to monitor! No metadata will available!")
102                self.metadata = {}
103            else:
104                self.metadata = self.monitor.return_meta(request_ID=self.name)
105
106            # Read out buffer
107            # frame, meta = det.read_buffer() # or whatever
108            frame = np.random.uniform(size=self.shape)
109            meta = {'frame_counter':frame_counter}
110            self.logger.debug(f'Acquired frame from buffer.')
111
112            # Add frame to the queue
113            self.enqueue_frame(frame, meta)
114
115            # increment count
116            frame_counter += 1
117
118            if frame_counter == n_exp:
119                # Exit if we have reached the requested nuber of exposures
120                break
121
122            if self.rolling and self.stop_rolling_flag:
123                # Exit if rolling and stop was requested
124                break
125
126            if self.abort_flag.is_set():
127                break
128        self.print()
129
130    def _disarm(self):
131        # self.detector.disarm() # or whatever
132        return
133
134    def _rearm(self):
135        # self.detector.rearm() # or whatever
136        return
137
138    def _get_exposure_time(self):
139        return self.config['exposure_time']
140
141    def _set_exposure_time(self, value):
142        # self.detector.set_exposure_time(value) # or whatever
143        self.config['exposure_time'] = value
144
145    def _get_exposure_number(self):
146        return self.config['exposure_number']
147
148    def _set_exposure_number(self, value):
149        # self.detector.set_num_of_exposures(value) # or whatever
150        self.config['exposure_number'] = value
151
152    def _get_operation_mode(self):
153        opmode = {'gain_mode': self.config['gain_mode']}
154        return opmode
155
156    def _set_operation_mode(self, opmode):
157        """
158        Set operation mode.
159        """
160        # self.detector.set_gain(opmode['gain_mode']) # or whatever
161        self.config.update(opmode)
162
163    def _get_binning(self):
164        return self.config['binning']
165
166    def _set_binning(self, value):
167        self.config['binning'] = tuple(value)
168
169    def _get_psize(self):
170        bins = tuple(self.binning)
171        if bins == (1,1):
172            return self.PIXEL_SIZE
173        elif bins == (2,2):
174            return 2*self.PIXEL_SIZE
175        else:
176            raise RuntimeError("Unknown (or not implemented) binning for pixel size calculation.")
177
178    def _get_shape(self) -> tuple:
179        bins = tuple(self.binning)
180        if bins == (1,1):
181            return self.SHAPE
182        elif bins == (2,2):
183            return self.SHAPE[0]//2, self.SHAPE[1]//2
184        else:
185            raise RuntimeError("Unknown (or not implemented) binning for shape calculation.")