matham / rotpy

Python bindings for the Spinnaker SDK to enable Pythonic control of Teledyne/FLIR/Point Grey USB and GigE cameras.
MIT License
17 stars 0 forks source link

Add more efficient image data access #2

Closed matham closed 1 year ago

matham commented 1 year ago

Fixes #1.

With these changes:

from timeit import timeit
from rotpy.image import Image

empty_img = Image.create_empty()
full_image = Image.create_image(640, 480, 0, 0, 'Mono16')
buffer = bytearray(b'\0') * full_image.get_image_data_size()

setup = '''
from rotpy.image import Image
from __main__ import empty_img, full_image, buffer
'''
stmt_empty = '''
image = Image.create_empty()
'''
stmt_full = '''
image = Image.create_image(640, 480, 0, 0, 'Mono16')
'''
stmt_get_data = '''
data = full_image.get_image_data()
'''
stmt_get_data_mview = '''
data = full_image.get_image_data_memoryview()
'''
stmt_copy_data_mview = '''
full_image.copy_image_data(buffer)
'''

print(f'Empty image: {timeit(stmt=stmt_empty, setup=setup, number=1000):f} sec / 1000')
print(f'Full image: {timeit(stmt=stmt_full, setup=setup, number=1000):f} sec / 1000')
print(f'Get data: {timeit(stmt=stmt_get_data, setup=setup, number=1000):f} sec / 1000')
print(f'Get data memoryview: {timeit(stmt=stmt_get_data_mview, setup=setup, number=1000):f} sec / 1000')
print(f'Copy to buffer: {timeit(stmt=stmt_copy_data_mview, setup=setup, number=1000):f} sec / 1000')

prints:

Empty image: 0.001878 sec / 1000
Full image: 0.002359 sec / 1000
Get data: 0.045437 sec / 1000
Get data memoryview: 0.001034 sec / 1000
Copy to buffer: 0.026611 sec / 1000
arminbahl commented 1 year ago

Absolutely amazing! Thanks for the quick help.

I gave it some test with our camera. The aim is to have the image data in the pre-allocated NumPy array _imgbuffer, as fast as possible. This array is a multiprocessing shared buffer with which several other processing pipelines communicate. So having any unnecessary copying in between slows us down considerably.

The new way of handling the image buffer is indeed much faster:

from rotpy.system import SpinSystem
from rotpy.camera import CameraList
import time
import numpy as np
import rotpy

print(rotpy.__version__)

# Camera is setup to give 2048x2048 at 8bit
img_buffer = np.zeros(4194304, dtype=np.uint8)

system = SpinSystem()

cameras = CameraList.create_from_system(system, update_cams=True, update_interfaces=True)
camera = cameras.create_camera_by_index(0)

camera.init_cam()

camera.begin_acquisition()
grabResult = camera.get_next_image(timeout=5)

t0= time.perf_counter()
for _ in range(1000):
    img = grabResult.get_image_data()
print("Just getting the image takes", (time.perf_counter()-t0) , "ms")

t0= time.perf_counter()
for _ in range(1000):
    img = grabResult.get_image_data()
    img_buffer[:] = img
print("Getting the image and copying it to the buffer takes", (time.perf_counter()-t0) , "ms")

t0= time.perf_counter()
for _ in range(1000):
    grabResult.copy_image_data(img_buffer[:])
print("Directly copying the image data to the buffer takes", (time.perf_counter()-t0) , "ms")

grabResult.release()
camera.end_acquisition()
camera.deinit_cam()
camera.release()

print(img_buffer)

0.2.1.dev0 Just getting the image takes 0.8784996 ms Getting the image and copying it to the buffer takes 1.1981604 ms Directly copying the image data to the buffer takes 0.22289840000000005 ms [22 21 24 ... 22 24 20]

arminbahl commented 1 year ago

Another note: I had quite some trouble compiling the new library from the github source. Here is how I got it to work:

Add to env variables:

ROTPY_INCLUDE="C:\Program Files\FLIR Systems\Spinnaker\include"
ROTPY_LIB="C:\Program Files\FLIR Systems\Spinnaker\lib64\vs2015"

git clone https://github.com/matham/rotpy
cd rotpy
python setup.py install

Copy all dll files from: C:\Program Files\FLIR Systems\Spinnaker\bin64\vs2015 to C:\Users\ag-bahl\Anaconda3\envs\py39\share\rotpy\spinnaker\bin

copy all files from: C:\Program Files\FLIR Systems\Spinnaker\cti64\vs2015 to C:\Users\ag-bahl\Anaconda3\envs\py39\share\rotpy\spinnaker\cti