Skip to content
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

Camera IO update #165

Merged
merged 8 commits into from
Oct 9, 2024
29 changes: 16 additions & 13 deletions example/camera_io/live_view_mono_Basler.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
"""
Example script that connects to and shows a live view from an
8 bit Basler monochrome camera.

"""Example script that connects to and shows a live view from a 12 bit Basler monochrome camera.
"""

import argparse
Expand All @@ -12,7 +9,7 @@

def main():
parser = argparse.ArgumentParser(
prog='run_and_save_images_Basler_color', description='Shows live view from Basler monochrome camera.'
prog='live_view_mono_Basler', description='Shows live view from Basler monochrome camera.'
)
parser.add_argument(
'camera_index',
Expand All @@ -21,24 +18,30 @@ def main():
help='Camera index (0-indexed in order of camera serial number) to run.',
)
parser.add_argument('--calibrate', action='store_true', help='calibrate camera exposure before capture')
parser.add_argument('-e', metavar='exposure', type=float, default=None, help='Camera exposure value')
parser.add_argument('-e', metavar='exposure', type=int, default=None, help='Camera exposure value')
parser.add_argument('-g', metavar='gain', type=int, default=None, help='Camera gain value')
args = parser.parse_args()

# Connect to camera
cam = ImageAcquisitionMono(args.camera_index, 'Mono12')

# Set exposure
if args.e is not None:
cam.exposure_time = args.e

# Set gain
if args.g is not None:
cam.gain = args.g

# Calibrate exposure and set frame rate
cam.frame_rate = 10
if args.calibrate:
cam.calibrate_exposure()

if args.e is not None:
cam.exposure_time = args.e
print('Exposure time range: ', cam.cap.ExposureTimeRaw.Min, '-', cam.cap.ExposureTimeRaw.Max)
print('Gain range: ', cam.cap.GainRaw.Min, '-', cam.cap.GainRaw.Max)

print('Exposure time:', cam.exposure_time)
print('Frame rate:', cam.frame_rate)
print('Gain:', cam.gain)
print('')
print('Exposure time set:', cam.exposure_time)
print('Gain set:', cam.gain)

# Show live view image
LiveView(cam, highlight_saturation=False)
Expand Down
52 changes: 52 additions & 0 deletions example/camera_io/run_and_save_images_Basler_mono.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""Example program that captures images from a 12 bit monochrome Basler
camera and saves the images in TIFF format. Camera is run at minimum gain.
"""

import argparse
import imageio.v3 as imageio

from opencsp.common.lib.camera.ImageAcquisition_DCAM_mono import ImageAcquisition


def main():
parser = argparse.ArgumentParser(
prog='run_and_save_images_Basler_mono',
description='Captures N frames from a Basler camera. Saves images as 16 bit integers in TIFF format with filenames of the form: xx.tiff',
)
parser.add_argument(
'camera_index', type=int, help='Camera index (0-indexed in order of camera serial number) to run.'
)
parser.add_argument('num_images', type=int, help='Number of images to capture and save.')
parser.add_argument('--calibrate', action='store_true', help='Calibrate camera exposure before capture.')
parser.add_argument(
'-p',
'--prefix',
metavar='prefix',
default='',
type=str,
help='Image save prefix to be appended before the filename.',
)
args = parser.parse_args()

# Connect to camera
cam = ImageAcquisition(args.camera_index, 'Mono12')

# Calibrate exposure and set frame rate
if args.calibrate:
cam.calibrate_exposure()

print('Exposure time:', cam.exposure_time)
print('Gain:', cam.gain)

# Capture and save frames
for idx in range(args.num_images):
frame = cam.get_frame()
# Save by packing 12 bit image into 16 bit image
imageio.imwrite(f'{args.prefix}{idx:02d}.tiff', frame * 2**4)

# Close camera
cam.close()


if __name__ == '__main__':
main()
2 changes: 2 additions & 0 deletions opencsp/common/lib/camera/ImageAcquisitionAbstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,12 +152,14 @@ def _check_saturated(im):

# Checks that the minimum value is under-exposed
self.exposure_time = exposure_values[0]
lt.debug(f'Trying minimum exposure: {exposure_values[0]}')
im = self.get_frame()
if _check_saturated(im):
lt.error_and_raise(ValueError, 'Minimum exposure value is too high; image still saturated.')

# Checks that the maximum value is over-exposed
self.exposure_time = exposure_values[-1]
lt.debug(f'Trying maximum exposure: {exposure_values[-1]}')
im = self.get_frame()
if not _check_saturated(im):
lt.error_and_raise(ValueError, 'Maximum exposure value is too low; image not saturated.')
Expand Down
13 changes: 11 additions & 2 deletions opencsp/common/lib/camera/ImageAcquisition_DCAM_mono.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,13 @@ def __init__(self, instance: int = 0, pixel_format: str = 'Mono8'):
# Set exposure values to be stepped over when performing exposure calibration
shutter_min = self.cap.ExposureTimeRaw.Min
shutter_max = self.cap.ExposureTimeRaw.Max
self._shutter_cal_values = np.linspace(shutter_min, shutter_max, 2**13).astype(int)

self._shutter_cal_values: np.ndarray = np.linspace(shutter_min, shutter_max, 2**13).astype(int)

# Apply model-specific processing for Basler mono cameras
if devices[instance].GetModelName() == 'acA3088-16gm':
# If type acA3088-16gm, make sure shutter values are multiple of 25
self._shutter_cal_values = (self._shutter_cal_values.astype(float) / 25).astype(int) * 25

@classmethod
def _check_pypylon_version(cls):
Expand Down Expand Up @@ -104,7 +110,10 @@ def instance_matches(self, possible_matches: list[ImageAcquisitionAbstract]) ->
def get_frame(self) -> np.ndarray:
# Start frame capture
self.cap.StartGrabbingMax(1)
grabResult = self.cap.RetrieveResult(5000, pylon.TimeoutHandling_ThrowException)
exposure_time_ms = self.cap.ExposureTimeRaw.Max / 1000 # exposure time, ms
# Grab the frame
# After 1.5 times the expected image acquisition time, throw a pylon.TimeoutHandling_ThrowException
grabResult = self.cap.RetrieveResult(int(exposure_time_ms * 1.5), pylon.TimeoutHandling_ThrowException)

# Access image data
if grabResult.GrabSucceeded():
Expand Down