Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions copylot/gui/_qt/dockables/camera.py
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")
9 changes: 9 additions & 0 deletions copylot/gui/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ def __init__(self, *args, **kwargs):
self.laser_dock.setTitleBarWidget(QLabel("Laser"))
self.dock_list.append(self.laser_dock)

self.camera_dock = QDockWidget(self)
self.camera_dock.setTitleBarWidget(QLabel("Camera"))
self.dock_list.append(self.camera_dock)

for dock in self.dock_list:
_apply_dock_config(dock)

Expand Down Expand Up @@ -144,6 +148,10 @@ def __init__(self, *args, **kwargs):
DockPlaceholder(self, self.laser_dock, "laser", [self])
)

self.camera_dock.setWidget(
DockPlaceholder(self, self.camera_dock, "camera", [self])
)

# self.parameters_widget = ParametersDockWidget(self)
self.parameters_placeholder = DockPlaceholder(
self, self.parameters_dock, "parameters", [self]
Expand All @@ -156,6 +164,7 @@ def __init__(self, *args, **kwargs):
self.splitDockWidget(self.live_dock, self.timelapse_dock, Qt.Vertical)
self.splitDockWidget(self.timelapse_dock, self.water_dock, Qt.Vertical)
self.splitDockWidget(self.water_dock, self.laser_dock, Qt.Vertical)
self.splitDockWidget(self.laser_dock, self.camera_dock, Qt.Vertical)

# create status bar that is updated from live and timelapse control classes
self.status_bar = QStatusBar()
Expand Down
Empty file.
182 changes: 182 additions & 0 deletions copylot/hardware/prime_bsi_camera/camera.py
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
Copy link
Contributor Author

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 ?

Copy link
Collaborator

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.

Copy link
Contributor Author

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.

# 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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given the _speed_gain_table value won't change, I think this can be defined as a class attribute instead of property. What do you think @ieivanov ?

Copy link
Collaborator

Choose a reason for hiding this comment

The 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 port_speed_gain_table['CMOS'] to make the class generalize to other PVCAM cameras.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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
8 changes: 8 additions & 0 deletions copylot/microscope_config/configs/mantis.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,11 @@ name: mantis
hardware:
- ni_daq:
show_gui: true
- prime_bsi_camera:
TRIGGER_MODE: "NORMAL"
TRIGGERPOLARITY: "POSITIVE"
TRIGGER_CONNECTOR: "BNC"
TRIGGERTIMES: 1
TRIGGERDELAY: 0
TRIGGERSOURCE: "EXTERNAL"
TRIGGERACTIVE: "SYNCREADOUT"