genicam / harvesters

Image Acquisition Library for GenICam-based Machine Vision System
Apache License 2.0
513 stars 88 forks source link

Question to unexpected behavior when setting LineCount value in line scan camera #359

Closed neixlo closed 1 year ago

neixlo commented 2 years ago

Dear @kazunarikudo,

thanks for harvesters and your effort on this awesome opensource project!

I haven an issue which is unclear to me and which I try to understand better. It might not be an issue of harvester at all. Maybe this is expected or done by the camera itself.

Maybe you can help out and answer my question below. :)

Describe the Issue

Short: When setting a value (LineCount) in a remote device, another value (Height) is also updated with the same value. When setting Height value to a bigger value then LineCount there is an error.

Long: When setting the value LineCount in a Keyence Line Scanning Camera (VJ Series), the value Height is also updated with the same value. On one hand this makes sense because the height ( Height) of the image to be send from the remote device to the PC can't exceed the collected image size (LineCount) from the remote device. On the other hand it is unexpected (for me) that values which are not set explicitly are updated in the background. The result for me is, that if I set the Height before I set the LineCount, an error is thrown.

To Reproduce Steps to reproduce the behavior:

  1. Go to '...'
  2. Click on '....'
  3. Scroll down to '....'
  4. See error

Sample Code

from harvesters.core import Harvester

h = Harvester()
h.add_file("/full/path/to/the/GenTLproducer/libbgapi2_gige.cti")
h.update()

ia = h.create(0)
ia.remote_device.node_map.OperationMode.value = 'SetupMode'

new_value_1=12000
new_value_2=13000

ia.remote_device.node_map.LineCount.value = 10000
ia.remote_device.node_map.Height.value = 9000

print(f"\nOriginal LineCount Value: {ia.remote_device.node_map.LineCount.value}")
print(f"Original Height Value: {ia.remote_device.node_map.Height.value}")

print(f"LineCount on remote device get set to a new value of {new_value_1}")
ia.remote_device.node_map.LineCount.value = new_value_1

print(f"\nNew LineCount Value: {ia.remote_device.node_map.LineCount.value}")
print(f"New Height Value: {ia.remote_device.node_map.Height.value}")

print("\nThe hight value get's also updated.")

print("\nIf the Height is set larger than the LineCount the following error is thrown:")
ia.remote_device.node_map.Height.value = new_value_2

Traceback:

me@me:~/Projects/tests/GenICam$  cd /home/me/Projects/tests/GenICam ; /usr/bin/env /home/me/Projects/tests/GenICam/.venv/bin/python /home/me/.vscode/extensions/ms-python.python-2022.8.0/pythonFiles/lib/python/debugpy/launcher 38663 -- /home/me/Projects/tests/GenICam/bug_script_set_LineCount_Hight.py 

Original LineCount Value: 10000
Original Height Value: 9000
LineCount on remote device get set to a new value of 12000

New LineCount Value: 12000
New Height Value: 12000

The hight value get's also updated.

If the Height is set larger than the LineCount the following error is thrown:
Traceback (most recent call last):
  File "/usr/lib/python3.8/runpy.py", line 194, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/lib/python3.8/runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "/home/me/.vscode/extensions/ms-python.python-2022.8.0/pythonFiles/lib/python/debugpy/__main__.py", line 45, in <module>
    cli.main()
  File "/home/me/.vscode/extensions/ms-python.python-2022.8.0/pythonFiles/lib/python/debugpy/../debugpy/server/cli.py", line 444, in main
    run()
  File "/home/me/.vscode/extensions/ms-python.python-2022.8.0/pythonFiles/lib/python/debugpy/../debugpy/server/cli.py", line 285, in run_file
    runpy.run_path(target_as_str, run_name=compat.force_str("__main__"))
  File "/usr/lib/python3.8/runpy.py", line 265, in run_path
    return _run_module_code(code, init_globals, run_name,
  File "/usr/lib/python3.8/runpy.py", line 97, in _run_module_code
    _run_code(code, mod_globals, init_globals,
  File "/usr/lib/python3.8/runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "/home/me/Projects/tests/GenICam/bug_script_set_LineCount_Hight.py", line 30, in <module>
    ia.remote_device.node_map.Height.value = new_value_2
  File "/home/me/Projects/tests/GenICam/.venv/lib/python3.8/site-packages/genicam/genapi.py", line 2259, in <lambda>
    __setattr__ = lambda self, name, value: _swig_setattr(self, IInteger, name, value)
  File "/home/me/Projects/tests/GenICam/.venv/lib/python3.8/site-packages/genicam/genapi.py", line 101, in _swig_setattr
    return _swig_setattr_nondynamic(self, class_type, name, value, 0)
  File "/home/me/Projects/tests/GenICam/.venv/lib/python3.8/site-packages/genicam/genapi.py", line 93, in _swig_setattr_nondynamic
    object.__setattr__(self, name, value)
  File "/home/me/Projects/tests/GenICam/.venv/lib/python3.8/site-packages/genicam/genapi.py", line 2351, in _set_value
    self._primal_set_value(value)
  File "/home/me/Projects/tests/GenICam/.venv/lib/python3.8/site-packages/genicam/genapi.py", line 2270, in _primal_set_value
    return _genapi.IInteger__primal_set_value(self, Value, Verify)
_genapi.OutOfRangeException: Value = 13000 must be equal or smaller than Max = 12000. : OutOfRangeException thrown in node 'Height' while calling 'Height.SetValue()' (file 'IntegerT.h', line 79)

Expected Behavior It is not clear to me if harvester or the camera itself updates the Height if the LineCount is set.

Question: Is harvester setting just the LineCount value or is harvester also setting Height under the hood.

Configuration

Reproducibility

It is fully reproducible but one need a line scan camera (maybe the specific one from keyence).

Actions You Have Taken

Additional context This is much more a question than a issue of harvester I assume. I try to better understand what is happening.

kazunarikudo commented 2 years ago

@neixlo Hi, please excuse me for having kept you waiting for the last few months.

It is not clear to me if harvester or the camera itself updates the Height if the LineCount is set.

The behavior between GenICam feature nodes are completely closed by its XML file and there's no chance where Harvester slip in to hack it. I would recommend contacting KEYENCE representative so that you can make sure the behavior you've observed. If there's an action so that I can help you, please feel free to come back here. Thanks, Kazunari.

neixlo commented 1 year ago

@kazunarikudo, I appreciate that you came back to my issue and thank you for the response! I think the unexpected behavior (from my point of view) is actually intended by the KEYENCE camera.

Going to close this as resolved, and thanks again for coming back to it. :)

jpl-c commented 1 year ago

Hey @neixlo I just got the same camera as you and I'm struggling a bit with using Harvester to automate the image capture process. I was wondering if maybe you could answer a few questions I have? How do you find the list of callable attributes from ia.remote_device.node_map ? (like the OperationMode, LineCount, Height) Are you able to get the shape image with Lumitrax lighting if that is your setup? I can't seem to find what line of code refers to the different image types.

Thanks a lot!

I have :

neixlo commented 1 year ago

Hi @jpl-c ,

to your questions:

  1. You can loop through 'ia.remote_device.node_map' to get the supported functions and attributes of your decive. It doesn't matter which genicam camera you use. Another way, I'd recommend is using harvester-gui. Within this gui you get a nice overview on what you can do.
  2. Does the camera send the shape image? By default, just the normal image is sent. What I do is: 1. I set and adjust everything (focus adjustments, exposure, iso, change from normal mode to lumitrax, specify which image should be taken, etc.) with the VJ Editor (Keyence Software), save these settings in the UserSet, set this UserSet to be automatically loaded on startup, then I just connect with harvesters and do fine adjustments, start and stop of the image acquisition.

Hope this helps :)

jpl-c commented 1 year ago

Hi @neixlo, Thanks for your reply! I was able to get all the functions and attributes, but there are a whole lot! The GUI definitely helps understand things, thanks for the suggestion. Ok I see, and do you know a way of loading the UserSet (.xml file) in the python script just to make sure? Also, are you able to collect all of the images at once (normal, shape, custom filtered) or is it only one by one? It would make sense to me that you can get all of them together, but for the moment I'm only able to display the normal image

neixlo commented 1 year ago

Hi @jpl-c ,

I've never loaded the UserSet via python/harvesters but I think this is possible. I always set the UserSet in the VJ Editor, so that it's loaded automatically at startup.

I just use one of the images and my controler is configured to just send one. Still I think you should get all images (if the controller is sending it). You might need to have a look at the recieved image object and the shape, I recommend vscode and the debugger. Within harvester-gui, i'm not sure if you can display all images. May just the first image in the recieved object is shown there.

Hope this helps. :)

jpl-c commented 1 year ago

Okay thanks! I'll keep on trying and will keep you posted with what I can achieve. My guess is this might interest other people later on too :)

jpl-c commented 1 year ago

Here's an updated on my struggle to get multiple images from the camera : It turns out I needed to do several ia.fetch calls for a single capture. To prevent the camera from taking another picture during the fetch() call , I had to change the MultiCaptureUpdateImage node value to False and change the MultiCaptureImageType node value to the image type (a str) I wanted to retrieve from the camera. You can find more info on this in the Keyence VJ Series System Integration Manual : image

Regarding the User Set, it is actually not possible to save it and load it from a standalone software. The only way to load parameters is through the Feature List that is saved as an xml file. You have to read through the whole file and update each feature one by one in the same order as in the xml file (making sure you have W or RW access to the feature). Here is the code I used for that :

`def load_xml(self, filepath): tree = ET.parse(filepath) root = tree.getroot()

    if self._ia is None :
        raise Exception("Image acquirer needs to be initialised first. Please run init_ia() method first")

    self._ia.remote_device.node_map.OperationMode.value = "SetupMode"

    for i in range(len(root)):
        feature = root[i]
        name = feature.attrib["Name"]
        access = feature[1].text
        node_type = feature[0].text

        if node_type != "Command" and access != "R":
            value = feature[2].text
            if value == "TRUE":
                value = True
            elif value == "FALSE":
                value = False 
            setattr(getattr(self._ia.remote_device.node_map, name), "value", value) #Change the node value
    print(f"{filepath} successfully loaded to camera")
    self._ia.remote_device.node_map.OperationMode.value = "RunMode"
    return`

I hope this might give ideas to people who were struggling like me :)

Cheers,

Jpl