Instantiating Picam() to never call NicePicamLib directly

49 views
Skip to first unread message

Jason Scheeler

unread,
Jun 16, 2021, 2:27:48 PM6/16/21
to Instrumental instrument-control library
As a follow up to my previous post: I would like to use the Picam() class, and all the methods therein, so that NicePicamLib never has to be called directly and the picam.py driver can be used as intended. I've included a script of what my "ideal" code would look like where I never have to call NicePicamLib. I get an error when instantiating Picam() and have included the traceback error in the ideal_simple_acquire_error.txt file. Successfully instantiating a Picam() object could make the PicamCamera() instantiation problem in my previous post much easier to solve, or go away all together? Not exactly sure, but any help on this is appreciated. Thanks!
ideal_simple_acquire.py
ideal_simple_acquire_error.txt

Nate Bogdanowicz

unread,
Jun 23, 2021, 1:26:03 PM6/23/21
to Instrumental instrument-control library
Hi Jason. Sorry for the delay, but I've been traveling for the past week. I took a look at this last week, but I'm not very familiar with this driver and saw no obvious quick fix, so this will take a bit of investigation. I don't have access to any of these cameras, so I may have to install the picam software and try to use a simulated camera.

I'll try to make time to take a look at this after work and/or this weekend.

Nate Bogdanowicz

unread,
Jun 26, 2021, 5:10:19 PM6/26/21
to Instrumental instrument-control library
I managed to get Picam installed on my Linux box, which required some tweaks to the default install. I also had to add an entry in `_build_picam.py` for Linux, and update some of the code in `picam.py` to be compatible with Python 3. Note that future releases of Instrumental will drop support for Python 2, since it's been deprecated for awhile now.

With that all done, I was able to get a demo camera to work using this code:

import instrumental.drivers.cameras.picam as sdk
picam = sdk.Picam(usedemo=True)
cam = picam.cameras['demo_camera']
data = cam.get_data()

I'm assuming you're currently using Windows and Python 2.7? Also, what versions of Instrumental and NiceLib are you using?

Jason Scheeler

unread,
Jun 28, 2021, 2:12:16 PM6/28/21
to Instrumental instrument-control library
I'm using Windows and the Python version I'm using is 3.7.3. Also I'm using version 0.6 for both instrumental and NiceLib. I updated both of these modules and tried to run your code but had no luck. But it's promising you were able to get a demo camera working, maybe it's just a Windows problem then? 

Nate Bogdanowicz

unread,
Jun 29, 2021, 11:21:12 AM6/29/21
to Instrumental instrument-control library
I installed the Picam SDK on Windows yesterday and did not encounter the errors you did. However, this driver (as of the 0.6 release) does have some minor Python 3 incompatibilities. I've started a branch called "picam" (note this is *not* capitalized) for reworking this driver, and pushed it to GitHub. In order to use this new code as I update it, you'll have to install from the git source, rather than the release version. Once you have git installed, you can do the following (in the terminal) to install the picam branch:

cd Instrumental
git checkout picam
python setup.py install

Once you've installed the picam branch, run the following in your python terminal:

import instrumental.drivers.cameras.picam as sdk
lib = sdk.NicePicamLib
lib.InitializeLibrary()
lib.ConnectDemoCamera(1203, 'demo')
ids = lib.GetAvailableCameraIDs()
cam = lib.Camera(ids[0])
cam.GetParameterValueType(sdk.PicamEnums.Parameter.Rois.value)

What value does this call to GetParameterValueType return for you? On my system it gives 5, which corresponds to ValueType.Rois. It seems like your system is giving 0, which is not a valid ValueType enum.

This makes me wonder if your demo camera is opening properly at all. So, what value does cam._handles give you after running the above? I just tried it, and if I just pass in 0 as the camera handle to GetParameterValueType, it will return 0 without giving an error.

Jason Scheeler

unread,
Jun 29, 2021, 2:54:20 PM6/29/21
to Instrumental instrument-control library
I was able to successfully reproduce what you wanted me to try: cam.GetParameterValueType(sdk.Picam...) returned a 5, and passing 0 to cam.GetParameterValueType returned a 0 with no error.

Continuing to operate on the picam branch, I was also able to instantiate an sdk.Picam() object which is a step in the right direction relative to where I was at a couple weeks ago. I continued with my ideal_simple_acquire.py script by running:

import instrumental.drivers.cameras.picam as sdk
picam = sdk.Picam()
picam.connect_demo_camera(1203, 'demo')
deviceArray, deviceCount = picam.get_available_camera_IDs()

but got the error:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: not enough values to unpack (expected 2, got 1)

but a quick workaround is:

deviceArray = picam.get_available_camera_IDs()
deviceCount = len(deviceArray)

(the doc string says this function returns an ID_array and the ID_array count, so I think it may be worth opening up an issue on github. Anyway --)  Getting a PicamCamera() object is the ultimate goal as that allows me to get and set camera parameters (e.g. sensor temperature, exposure time) and also returns a 2D numpy array of the sensor values. I attempted to get that by trying:

proEM = picam.open_camera(deviceArray[0], 'proEM')

but got the error:

Traceback (most recent call last):
  File "C:\Users\Jason\Anaconda3\lib\site-packages\nicelib\nicelib.py", line 197, in make_c_args
    c_args.append(handler.make_c_arg(self.ffi, py_arg))
  File "C:\Users\Jason\Anaconda3\lib\site-packages\nicelib\nicelib.py", line 279, in make_c_arg
    return _wrap_inarg(ffi, self.c_argtype, arg_value)
  File "C:\Users\Jason\Anaconda3\lib\site-packages\nicelib\nicelib.py", line 756, in _wrap_inarg
    "got '{}'".format(argtype, arg))
TypeError: A value castable to (or a valid initializer for) '<ctype 'struct PicamCameraID *'>' is required, got '<PicamCameraID(model=<Model.ProEMHS512BExcelon: 1203>, serial_number=b'demo:Demo')>'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Users\Jason\Instrumental\instrumental\drivers\cameras\picam.py", line 970, in open_camera
    nice_cam = NicePicamLib.Camera(cam_id)
  File "C:\Users\Jason\Anaconda3\lib\site-packages\nicelib\nicelib.py", line 662, in __init__
    handles = self._init_func(*args) if self._init_func else args
  File "C:\Users\Jason\Anaconda3\lib\site-packages\nicelib\nicelib.py", line 1142, in __call__
    c_args = self.sig.make_c_args(args)
  File "C:\Users\Jason\Anaconda3\lib\site-packages\nicelib\nicelib.py", line 203, in make_c_args
    raise TypeError(msg)

so it looks like picam.get_available_camera_IDs() returns the actual PicamCameraID (which makes sense) but picam.open_camera() wants the pointer to the PicamCameraID. A workaround is to work through NicePicamLib directly:

lib = sdk.NicePicamLib
ids, ids_count = lib.GetAvailableCameraIDs()
proEM = picam.open_camera(ids[0], 'proEM')

but then get this error:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Users\Jason\Instrumental\instrumental\drivers\cameras\picam.py", line 971, in open_camera
    self.add_camera(cam_name, nice_cam)
  File "C:\Users\Jason\Instrumental\instrumental\drivers\cameras\picam.py", line 965, in add_camera
    self.cameras[cam_name] = PicamCamera(cam_name, nice_cam, self)
TypeError: __init__() takes 3 positional arguments but 4 were given

and that's where I'm at currently. I tried looking through open_camera, add_camera, and PicamCamera but couldn't find any workarounds.

Not exactly sure this is the direction you wanted to take with the picam branch, but I thought I should update you on my progress. I think we are getting somewhere, though!

Jason Scheeler

unread,
Jun 29, 2021, 3:45:09 PM6/29/21
to Instrumental instrument-control library
I also forgot to mention that after running "python setup.py install" I got the message "No C compiler was found, so cffi modules were not built. If you would like to use cffi-based drivers that require compilation, first install a suitable compiler, then reinstall Instrumental. See the cffi installation documentation for more details on installing an appropriate compiler for your platform." I didn't know exactly what this meant, so I continued on anyway (with marginal success, see above). Did you receive this message as well? Do you think this is something I should worry about or ignore? 

Nate Bogdanowicz

unread,
Jun 30, 2021, 11:36:51 AM6/30/21
to Instrumental instrument-control library
You don't have to worry about that compiler-related message. This particular driver doesn't require a C compiler (in fact, we only have one so far that does).

Yes, as you've seen, my picam branch modifications are still a work-in-progress. This driver uses a project of mine called NiceLib to wrap Picam's C library. The usual approach when using it is to first create mid-level bindings (e.g. NicePicamLib) which straightforwardly adapt each C function into a more Python-friendly form, then create a high-level interface on top of that. Generally speaking, this high-level interface shouldn't expose certain low- and mid-level things like CFFI structs and pointers.

The picam driver's high-level interface currently exposes a bunch of the lower-level stuff, and I'm in the process of figuring out how to best rewrite it and only bits and pieces are done so far.

Can you tell me which parts you're most interested in using, i.e. which functions? That way I can prioritize rewriting them first to get you something that works for you ASAP.

Jason Scheeler

unread,
Jul 6, 2021, 11:17:27 AM7/6/21
to Instrumental instrument-control library
I apologize for the delayed response, I was on vacation and didn't have access to a computer for a few days.

Before I left for vacation, I (and one of my lab mates who is much more knowledgeable about coding than me) were able to play around with the source code on the picam branch and instantiate Picam and PicamCamera objects and get a 2D array of data from a demo camera. We also used the high level functions for setting and getting the sensor temperature and those were functional as well. However, there are a few of the higher level functions which don't work including set_adc_speed, set_shutter_mode, and a few other set_ functions. These functions don't actually throw an error but upon doing the corresponding get_ function, the parameter remains unchanged. It's not clear if the functions themselves aren't working properly or if the demo camera is the problem -- maybe using these functions on a real camera would give expected behavior.

All this is to say that for now I'm planning on working with the changes that I made to the picam.py source code on my local computer and going from there. If I run into big problems down the road I will cross those bridges when they come to me, whether that be taking a harder look at picam.py myself or reaching out to you again. I appreciate all your help!

Nate Bogdanowicz

unread,
Jul 12, 2021, 12:32:11 PM7/12/21
to Instrumental instrument-control library
Since I was already pretty deep in it, I decided to continue work on the picam branch. I just pushed a whole bunch of changes this morning.

The interface has changed significantly -- you can see the recent commit messages for more details on this. Many of the old methods have been removed, and you can now interact with the Picam "Parameters" through the camera's "params" attribute. Note also that the high-level "library" is auto-instantiated when  you import the module now, so no need to instantiate it. Here's a simple example usage:

from instrumental.drivers.cameras.picam import sdk, PicamEnums, list_instruments
sdk.connect_demo_camera(PicamEnums.Model.ProEMHS512BExcelon, 'demo')
list_instruments()  # You should see the demo camera's ParamSet here
cam = list_instruments()[0].create()  # We can create the camera from its ParamSet
arr = cam.grab_image()


Here's an example of using the "params" attribute:

cam.params.ShutterTimingMode.get_value()  #  => gives ShutterTimingMode.AlwaysOpen for me
cam.params.ShutterTimingMode.set_value(PicamEnums.ShutterTimingMode.AlwaysClosed)
cam.params.ShutterTimingMode.get_value()  #  verify the change

Jason Scheeler

unread,
Jul 16, 2021, 3:59:03 PM7/16/21
to Instrumental instrument-control library
I was able to translate the code that I had working from the first installation of the picam branch into the most up-to-date version. It went smoothly interacting with the demo camera. We tested it out on the actual camera earlier this week and were able to grab images, change ROIs, etc. Exciting!

There was one slight hiccup, though. When trying to grab an image of an ROI, grab_image kept returning the full sensor even though we had done set_roi previously to something that wasn't the full sensor. We looked into picam.py and noticed that start_capture kept setting the ROI to the default values, which is the entire sensor. So we just commented out lines 675-677 and the expected behavior occurred. Not sure if what was happening is intended behavior and we just weren't setting up the ROI correctly, but we got it to work with minimal changes.

The only other thing was that in line 815, "Roi" in self.params.Roi.get_value() was missing an "s" so we added that. As of yet everything else works perfectly.

Thanks for all the time and effort you've put in to getting picam.py up and running. It will help me and my research group a great deal.

Nate Bogdanowicz

unread,
Jul 24, 2021, 12:19:37 PM7/24/21
to Instrumental instrument-control library
Great, that's good to hear. If I recall correctly, all of the Camera drivers always set the Roi within start_capture, based on the left/right/top/bottom/cx/cy/width/height parameters passed in. I can see how it might be more convenient to just set the Roi once and have it persist, so I'll consider how best to do that.
Reply all
Reply to author
Forward
0 new messages