-
Notifications
You must be signed in to change notification settings - Fork 11
HW: PyVCAM adapter #108
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
HW: PyVCAM adapter #108
Changes from all commits
e2128b4
fab668d
327c066
da92032
3b9f130
258850a
7efbebf
8d6dd6b
833f0ab
8054a56
176e85d
32fd68b
39093ef
1511e36
803bd11
a932461
1c6531c
e2ae109
daf5a37
abfc3ce
391af98
c5c0ca7
ecd9a36
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| from qtpy.QtCore import Qt | ||
| from qtpy.QtWidgets import ( | ||
| QWidget, | ||
| QHBoxLayout, | ||
| QGridLayout, | ||
| QLabel, | ||
| QLineEdit, | ||
| ) | ||
|
|
||
|
|
||
| class CameraDockWidget(QWidget): | ||
| def __init__(self, parent): | ||
| super(QWidget, self).__init__(parent) | ||
| self.parent = parent | ||
|
|
||
| self.main_layout = QHBoxLayout() | ||
|
|
||
| self.parameters_layout = QGridLayout() | ||
| row_index = 0 | ||
| parameter_label = QLabel("Exposure duration:", self) | ||
| parameter_editbox = QLineEdit(str(0.01), self) | ||
|
|
||
| self.parameters_layout.addWidget( | ||
| parameter_label, row_index, 0, alignment=Qt.AlignTop | ||
| ) | ||
| self.parameters_layout.addWidget( | ||
| parameter_editbox, row_index, 1, alignment=Qt.AlignTop | ||
| ) | ||
|
|
||
| self.main_layout.addLayout(self.parameters_layout) | ||
|
|
||
| self.setLayout(self.main_layout) | ||
|
|
||
| @property | ||
| def parameters(self): | ||
| raise NotImplementedError("camera.parameters not yet implemented") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,182 @@ | ||
| from pyvcam import pvc, constants | ||
| from pyvcam.camera import Camera | ||
|
|
||
|
|
||
| class PrimeBSICamera: | ||
| def __init__( | ||
| self, | ||
| camera_name: str = None, | ||
| # TODO: create properties for these | ||
| # scan_mode: str = None, | ||
| # scan_dir: str = None, | ||
| # binning: tuple = (1, 1), | ||
| # trigger_mode: str = None, | ||
| # exposure_out: str = None, | ||
| enable_metadata: bool = True, | ||
| ): | ||
| """ | ||
|
|
||
| Parameters | ||
| ---------- | ||
| camera_name : str | ||
| Camera name, available through available_cameras(). | ||
| If not provided, the next available camera will be initialized. | ||
| enable_metadata : bool | ||
| Toggles camera metadata on and off. By default, it is True. | ||
| """ | ||
| pvc.init_pvcam() | ||
|
|
||
| try: | ||
| if camera_name: | ||
| self.cam = Camera.select_camera(camera_name) | ||
| else: | ||
| self.cam = next(Camera.detect_camera()) | ||
| except: | ||
| pvc.uninit_pvcam() | ||
| raise | ||
|
|
||
| self.cam.open() | ||
|
|
||
| # Prime BSI Express camera has only one port called 'CMOS' at index 0 | ||
| self.cam.readout_port = 0 | ||
|
|
||
| # Hard-coding these values since they won't change | ||
| # Values can be parsed from self.cam.port_speed_gain_table['CMOS'] | ||
| self._speed_gain_table = {'200 MHz': {'speed_index': 0, | ||
| 'Full well': {'gain_index': 1, 'bit_depth': 11}, | ||
| 'Balanced': {'gain_index': 2, 'bit_depth': 11}, | ||
| 'Sensitivity': {'gain_index': 3, 'bit_depth': 11} | ||
| }, | ||
| '100 MHz': {'speed_index': 1, | ||
| 'HDR': {'gain_index': 1, 'bit_depth': 16}, | ||
| 'CMS': {'gain_index': 2, 'bit_depth': 12} | ||
| } | ||
| } | ||
|
|
||
| self.serial_number = self.cam.serial_no | ||
| self.sensor_size = self.cam.sensor_size | ||
| self.chip_name = self.cam.chip_name | ||
| self.cam.meta_data_enabled = enable_metadata | ||
|
|
||
| def __del__(self): | ||
| self.cam.finish() | ||
| self.cam.close() | ||
| pvc.uninit_pvcam() | ||
|
|
||
| @staticmethod | ||
| def available_cameras(): | ||
| pvc.init_pvcam() | ||
| cameras = Camera.get_available_camera_names() | ||
|
|
||
| pvc.uninit_pvcam() | ||
| return cameras | ||
|
|
||
| @property | ||
| def speed_gain_table(self): | ||
| return self._speed_gain_table | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. given the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds good! By hardcoding this table we're making this class specific to the Prime BSI Express camera, which was our original intention. At a later point we can parse this table from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. absolutely, or we might have separate adapters for the other PVCAM cameras. Either way will be easy to accommodate those. |
||
|
|
||
| # Will implement later | ||
| # def available_scan_modes(self): | ||
| # scan_modes = self.cam.__prog_scan_modes | ||
| # | ||
| # print(f"Available scan modes: {scan_modes}") | ||
| # | ||
| # return scan_modes | ||
|
|
||
| @property | ||
| def exposure_time(self): | ||
| return self.cam.exp_time | ||
|
|
||
| @exposure_time.setter | ||
| def exposure_time(self, exposure_time): | ||
| self.cam.exp_time = exposure_time | ||
|
|
||
| @property | ||
| def readout_speed(self): | ||
| readout_speed_name = None | ||
|
|
||
| readout_speed_index = self.cam.speed_table_index | ||
| for key, value in self.speed_gain_table.items(): | ||
| if value['speed_index'] == readout_speed_index: | ||
| readout_speed_name = key | ||
|
|
||
| return readout_speed_name | ||
|
|
||
| @readout_speed.setter | ||
| def readout_speed(self, new_speed): | ||
| success = False | ||
|
|
||
| for key, value in self.speed_gain_table.items(): | ||
| if key == new_speed: | ||
| success = True | ||
| self.cam.speed_table_index = value['speed_index'] | ||
|
|
||
| if not success: | ||
| raise ValueError('Invalid readout speed. ' | ||
| 'Please check speed_gain_table for valid settings.') | ||
|
|
||
| @property | ||
| def gain(self): | ||
| gain_name = None | ||
|
|
||
| for key, value in self.speed_gain_table[self.readout_speed].items(): | ||
| if isinstance(value, dict): | ||
| if value['gain_index'] == self.cam.gain: | ||
| gain_name = key | ||
|
|
||
| return gain_name | ||
|
|
||
| @gain.setter | ||
| def gain(self, gain): | ||
| success = False | ||
|
|
||
| for key, value in self.speed_gain_table[self.readout_speed].items(): | ||
| if key == gain: | ||
| success = True | ||
| self.cam.gain = value['gain_index'] | ||
|
|
||
| if not success: | ||
| raise ValueError('Invalid gain. ' | ||
| 'Please check speed_gain_table for valid settings.') | ||
|
|
||
| @property | ||
| def bit_depth(self): | ||
| return self.speed_gain_table[self.readout_speed][self.gain]['bit_depth'] | ||
|
|
||
| def reset_rois(self): | ||
| """Restores ROI to the default.""" | ||
| self.cam.reset_rois() | ||
|
|
||
| def set_roi(self, s1: int, p1: int, width: int, height: int): | ||
| """ | ||
| Adds a new ROI with given parameters to the list of ROIs. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| s1 : int | ||
| Serial coordinate of the first corner | ||
| p1 : int | ||
| Parallel coordinate of the first corner | ||
| width : int | ||
| Width of ROI in number of pixels | ||
| height : int | ||
| Height of ROI in number of pixels | ||
|
|
||
| """ | ||
| self.cam.set_roi(s1, p1, width, height) | ||
|
|
||
| def live_run(self, exposure: int = 20): | ||
| """ | ||
| Live mode run method. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| exposure : int | ||
|
|
||
| Returns | ||
| ------- | ||
|
|
||
| """ | ||
| self.cam.start_live(exposure) | ||
|
|
||
| return self.cam.poll_frame | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think these shouldn't be properties. You mentioned you don't want to change these attributes often but more like you want to set it at the beginning only. That's why I intended to have them have them as arguments here instead of properties. What do you think @ieivanov ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see your point. I think we should keep the
__init__as simple as possible. We often don't know what are possible values for these properties and need to initialize the camera to find out. Also, we occasionally change them after we start up the camera - it would be inconvenient to have to unload the camera to change these properties.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's alright, as soon as these values are member attributes we can change them later anytime and we will need a camera restart method anyway which we can use to update these attributes on the lower level driver APIs. We can revisit this once this class implementation is closer to the end.