-
Notifications
You must be signed in to change notification settings - Fork 0
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
Add AlliedVision camera service #133
Conversation
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.
@legger01 @linarphy this is coming along nice, I left you a couple of initial comments to look at (see in-code comments below).
You have very many linting errors in your code that say you are violating the PEP8 coding convention. For a full list of errors, scroll down to the bottom of the PR and identify the failed test, it is labelled with "Linting / Flake8 (pull_request)" and has a red cross next to it since it is failing. If you click on "Details" on the right of that, it will give you a detailed list of all the linting errors.
Most of these are too many blank lines, too many spaces etc. You can follow the detailed linting report to fix them, and you can also find some information about these conventions here if you want:
https://peps.python.org/pep-0008/
I can see in the Python API manual of Vimba that they do not care the least bit about PEP8, so let's not use that as a reference 😬
|
||
from catkit2.testbed.service import Service | ||
|
||
from vimba import * |
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.
In general, we are avoiding import *
statements, so please change this to explicit imports from the vimba
package.
from vimba import * | |
from vimba import FrameStatus, PixelFormat, Vimba |
|
||
|
||
def __init__(self): | ||
super().__init__('AlliedVision_camera') |
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.
The string passed to the super init defines the name of the service type. If you look at the various entries for "service_type" in the services.yml
over in thd-controls, you will notice that they are all lower case, and do not use camel case (eg, flir_camera
for the FLIR camera). Please adapt that for this camera as well.
super().__init__('AlliedVision_camera') | |
super().__init__('allied_vision_camera') |
|
||
# Create datastreams | ||
# Use the full sensor size here to always allocate enough shared memory. | ||
self.images = self.make_data_stream('images', 'float32', [self.height, self.width], |
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.
As discussed during our meeting, make sure to change this back to the full sensor width and height, as described in the comment in the line before. Keep in mind that the definition of "width" and "height", and/or "x" and "y" could potentially be inverted between catkit2 and vimba.
self.images = self.make_data_stream('images', 'float32', [self.height, self.width], | |
self.images = self.make_data_stream('images', 'float32', [self.sensor_height, self.sensor_width], self.NUM_FRAMES) |
A general question for @linarphy and @legger01: vimba uses context managers that allow you to access the camera features, and the camera, which you have implemented here. Since you are exiting the context every time you exit a service class method, have you checked that the camera parameters you have set previously don't get reinitialized? @ehpor do you see any issues with using context managers in a service like this, since it looks like it is not possible to acquire a camera handle and then keep it outside of the context manager? |
@ivalaginja @legger01 @linarphy The I'd use import contextlib
class AlliedVisionCamera(Service):
def __init__(self, exit_stack):
super().__init__('allied_vision_camera')
self.exit_stack = exit_stack
...
def open(self):
self.vimba = vimba.get_instance()
self.exit_stack.enter(self.vimba)
...
self.cam = self.vimba.get_all_cameras()[0]
self.exit_stack.enter(self.cam)
...
@property
def exposure_time(self):
return self.cam.ExposureTime.get()
...
if __name__ == '__main__':
with contextlib.ExitStack() as exit_stack:
service = AlliedVisionCamera(exit_stack)
service.run() |
Also, I'd use |
9bcff85
to
6c330f2
Compare
@ehpor could you have a look over this please? We still want to do some tests in the lab before marking the PR as ready, but since it is working now, would be great to hear from you whether we need to change anything. I personally am not sure for example whether the |
cam.queue_frame(frame) | ||
finally: | ||
# Stop acquisition. | ||
self.is_acquiring.submit_data(np.array([0], dtype='int8')) |
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 don't think this should be done in the frame handler, but as the last line of the acquisition loop.
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'm not sure about how everything works to be honest. I understood the allied vision api as being asynchronous with thread being started inside the start_streaming function, so for me the call is blocking and the try and catch scope should be in the handler, but we will make test to be sure. At least I remembered the service closed gracefully after our last commit.
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.
Oh, that's interesting. That's indeed how your code looks, with the start_streaming()
function being blocking. Their example look like it shouldn't be blocking, and that the thread started by them will call our frame handler whenever it receives a frame. But you have the camera, and you can test that easily XD. And I only checked that one example.
The main thing I want to avoid is rapid calls to start_streaming()
and end_streaming()
, which might seem to work but will give inconsistent frame times and generally unreliable behaviour.
if not has_correct_parameters: | ||
self.images.update_parameters('float32', [self.height, self.width], self.NUM_FRAMES) | ||
|
||
self.cam.start_streaming(handler=self.frame_handler, buffer_count=self.NUM_FRAMES) |
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'm not sure how this is supposed to work. I'd assume that you should wait on some event in between starting and stopping streaming. As is done in https://github.com/alliedvision/VimbaPython/blob/master/Examples/asynchronous_grab_opencv.py#L24
Also, I'd wrap this in a try-except, like they do there, for exception safety.
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.
Thanks, we will check the example you gave us to add the try-except and to adapt the code.
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.
@ehpor what would be an appropriate event to wait for between the start and stop of the camera streaming? To me it looks like it should be waiting for a True
in self.should_shut_down
, but I am unaware of the exact intended behavior of that flag.
|
||
def frame_handler(self, cam, frame): | ||
try: | ||
if self.should_be_acquiring.is_set() and not self.should_shut_down: |
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 don't think this should be inside the frame handler either.
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.
Also, I just noticed that the AlliedVision is not compatible with the base camera class in #131. So I'll have to refactor that a little bit to accommodate this camera. But that's unrelated to this PR, so don't worry about that.
dea55d4
to
5e235a2
Compare
14f5eab
to
7974c0a
Compare
I changed this back to a draft so that I can test in the lab on Monday. I find the loops and checks in here not super obvious and want to make sure it still all works after the changes. |
Tested and works @ehpor |
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.
LGTM
Add a service for AlliedVision's camera
Fixes #134
Follow-up in #143
Todo
vimba.get_camera_by_id(camera_id)
to open up the cameracontextlib.ExitStack
and use a single with statement outside of the class (see Emiel's comment)services.yml
there