Camera Time Stamps and High Buffer RAM

Working using CVB in Delphi.

  1. What is the best way to extract a precise time stamp for any given frame from a ring buffer on any one of a series of open cameras, please? I tried to use a function I thought I’d found relating to it but it just returned 0 for every frame! We need this to be able to sync up the frames as best as possible across a series of cameras.

  2. In addition, what is the best way to allocate high buffer memory? For eg at the moment I am trying to allocate large buffers for the seven cameras, above. On our highest spec machine it seems to not be able to cope with more than about 700 frame buffers across 7 cameras. This equates to about 4-4.5 GB of RAM but this particular machine has 64GB of RAM so is nowhere near maxing out. I would like to get to approx 1200 buffers per camera if possible (this will actually hopefully be severely reduced in the long run but this is the absolute worst-case scenario). The resolution of each camera is 1280 x 584

Thanks

Daniel

Hi @danielquintic

  1. It depends on your hardware and the driver if you are able to get a timestamp. If supported and available, it should be possible to get the timestamp with the G2GetGrabStatus function with the GRAB_INFO_CMD value GRAB_INFO_TIMESTAMP.

  2. If I calculate your given resolution with seven 8Bit Mono cameras and 700 buffers each, It would be around 3500 MB. This is roughly the amount of memory one 32 Bit process can allocate.
    Are you executing your application as a 32Bit process? With a 64 Bit process, you should be able to allocate a lot more memory.

Thanks for the replies.

  1. This was the function I was trying to use with GRAB_INFO_TIMESTAMP but the return value seemed to always be 0. Does that suggest my hardware/camera isn’t compatible? How can I check for certain whether or not my hardware/camera is compatible? Or do I need to do something with GRAB_INFO_CMD as well?

  2. Your assumptions are roughly correct as far as I can see it. We are already compiling as a 64 bit program, hence my confusion in the first place. Would Delphi or CVB itself place a limit on it?

Thanks

Daniel

  1. You can try the CppStreamConsole from the Cvb++ Image Manager Tutorials. This acquires images and the timestamp. If this doesn’t work as well, your camera probably does not send a timestamp.

  2. I can’t speak for Delphi, but CVB does not place a memory limit

  1. I have run CppStreamConsole and it pops up a brief command line window of about 10 lines of integers with a lot of digits as shown below. Are these the time stamps? Does this suggest this camera does time stamps? This pops up for about a second then closes of its own accord.

  2. OK Thanks for clarifying.

image

Yes, these are the timestamps of your camera. So we know that the camera returns timestamps.

So there seems to be something wrong with your code or there is a problem with the Delphi API which we are not aware off.

My Delphi knowledge is limited. But you can share your code and maybe I or someone of my colleagues can see if there is something wrong with your code.

That’s good news, then! What time increment are the readouts in? (It must be very fine increments if the numbers are so many digits long!)

It is quite likely there is something wrong with my code as I don’t believe there are any Delphi code examples of this provided so I have just thrown together my best attempt at what to do. Please see below for me trying to extract the time stamp of the TCVImage just before I save it (the reason there is an array is because I have an array of TCVImages - one for each camera).

variables relevant to this

theImg : intptr_t;
dRequestAnswer : double;

code extract

theImg := cvRingBuffers[camIn].GetBufferImage(buffID);
cvImgs[camIn].Image := theImg;

G2GetGrabStatus(IMG(cvImgs[camIn].Image), GRAB_INFO_TIMESTAMP, dRequestAnswer);
diagform.Memo1.Lines.Add('dRequestAnswer ' + myfloattostrf(dRequestAnswer,6));

cvImgs[camIn].SaveImage(name);
ReleaseObject (IMG(theImg));

At present, the diag line always just prints out 0.

In most cases these are nano seconds. You have an increment of 13,5ms per frame, which lets me assume that your camera runs with 74 fps.

The code line with G2GetGrabStatus seems to be OK. Your issue is probably that you mix up the usage of a buffer image and using the G2GetGrabStatus. The G2GetGrabStatus is used together with a G2Wait call.

In your case using the BufferImage you need to use the function RBGetRingBufferInfo with the RINGBUFFER_INFO_CMD RINGBUFFER_INFO_TIMESTAMP.

Additionally, you should check the return value of G2GetGrabStatus or then RBGetRingBufferInfo to know that the function call actually worked.

Yes that is the max fps at max resolution of this camera so that sounds promising.

I am very new to everything CVB and to be honest I am still a bit confused by the difference and need for all the different CVB components. I need a ring buffer of 1000+ frames in my program but if I have interpreted the Delphi examples correctly I need a TCVImage control and a TCVRingBuffer control - you seem to need to keep constantly handing one to the other - is that right?! (For eg in my code above you have to hand the ring buffer image to the image control to be able to save the image, but to set up the buffer in the first place I had to do the same thing but the opposite way round!)

Aside from this confusion, I have just tried your suggestion, but it’s the same result. Here is my new code (I had to comment out the res = 0 check as it’s clearly not firing properly):

theImg := cvRingBuffers[camIn].GetBufferImage(buffID);
cvImgs[camIn].Image := theImg;

res := RBGetRingBufferInfo(IMG(theImg), buffID, RINGBUFFER_INFO_TIMESTAMP, dRequestAnswer);
//    if res = 0 then
//    begin
      diagform.Memo1.Lines.Add('res ' + inttostr(res));
      diagform.Memo1.Lines.Add('dRequestAnswer ' + myfloattostrf(dRequestAnswer,6));
//    end;

cvImgs[camIn].SaveImage(name);
ReleaseObject (IMG(theImg));

We need to go one step back and not talk about the specific issues and more about the general application design. But this is something which could be hard over the forum. We need to understand your boundaries, why you need to use Delphi or if you have other options as well. This should be done with a direct contact over our support.

Therefore, I recommend getting in direct contact with us over our support.
Your normal contact will probably be uk.support@stemmer-imaging.com
It is also fine to contact us in Germany over:
de.support@stemmer-imaging.com

Please refer to this forum topic as well.

Some additional comments about Delphi and ActiveX controls. We cancelled our Support for Delphi a while ago because it is used very rarely by our customers and most modern applications in this area are done with C++, C# and also Python.
We address this with our new APIs CVB++, CVB.NET and CVBPy.

ActiveX’s controls are more and more a pain to use and should if possible not be used in new applications, and we strongly recommend not using them any more.

It’s hopefully not too tricky. It’s a ring buffer of 1000 frames at 100fps which loop round until they are stopped, at which point the whole ring buffer is dumped to .bmp files on the hard drive for further analysis. But as there are multiple cameras, each one has its own ring buffer and needs a way of syncing the frames up as best as possible across the cameras, hence me asking about the best way to extract the time stamps.

What does the support email route offer more than the forum, please? Presumably I will be explaining my issues to someone either way?

As regards “Why Delphi,” all our software has been written in Delphi for 20+ years so we’d prefer not to have to change now! We were initially very pleased that CVB was immediately compatible with Delphi so as to avoid any intermediate steps. It’s disappointing that Delphi support is stopping, especially, as far as I gather, CVB was originally written using Delphi.

As regards this specific segment of code, can you not see any issues with it?

As regards avoiding ActiveX controls, what would you suggest we use if we are not to use those?

Hint: Allocating anything in the vicinity of 2 GB and beyond will require some tweaking. User space memory can be brought up to 3 GB on 32 bit Windows by changing (see here for example). So 3500 MB would be a lost cause on Windows 32 (I know there are techniques to go beyond that by switching blocks of memory similar to how HIMEM.SYS used to do it in DOS, but those have implications on speed and won’t work for everything).

CVB will be able to handle 3,5 GByte without problems (we’ve had projects where we exceeded 64 GByte already; works, but buffer allocation becomes quite time consuming… :wink: ). I do not think that the 64 bit Delphi compilers will restrict that in any way.

This tiny sample does not actually du anything else than take 10 image in a loop and output the timestamps for all of them (code heavily abbreviated):

[...]

int main(int argc, char* argv[])
{
  try
  {
    [...]
    stream->Start();

    for (int i = 0; i < 10; ++i)
    {
      // wait for an image with a timeout of 10 seconds
      auto waitResult = stream->WaitFor(std::chrono::seconds(10));
      if (waitResult.Status == Cvb::WaitStatus::Timeout)
        throw std::runtime_error("acquisition timeout");

      // get the timestamp of the image (usually a large integer number mapped to double - so better use fixed formatting)
      std::cout << "Acquired image... " << std::fixed << waitResult.Image->RawTimestamp() << std::endl;
    }

    // synchronously stop the stream
    stream->Stop();
  }
  catch (const std::exception& error)
  {
    std::cout << error.what() << std::endl;
  }
}

It uses the CVB++ API. Behind the RawTimestamp call is basically G2GetGrabStatus which for GEV or U3V cameras maps to the time stamp information that was provided by the transport layer.

Long story short: The camera supports time stamps.

I am not sure if this is the best way to do it - the passing back and forth of image handles between the TCVImage control and the TCVRingBuffer control should not really be necessary. The primary purpose of the TCVImage is to control the acquisition and that of TCVRingBuffer is to control the ring buffer, so you should just be able to write something like this:

cvImg[camIn].LoadImage(...)
cvRingBuffers[camIn].Image = cvImg[camIn].Image

and then set Grab as you need to start/stop the acquisition. Acquisition will always go into a ring buffer (each driver by default has it; the size can be set either in the %CVBDATA%\Drivers\GenICam.ini file or through the NumBuffers property of TCVRingBuffer.

Without TCVRingBuffer you would always simply access the most recent image, which is of course not always what you want. TCVRingBuffer allows you to go back in time. It should also provide a time stamp of the buffers through the GetTimeStamp method (note that you have to use GetBufferSequence method to establish the correct mapping from “acquisition order” to “buffer index”). See also RingBuffer Control: Methods (commonvisionblox.com)

Can you give this approach a try please?

Both approaches (GetTimeStamp from my previous post and RBGetRingBufferInfo should as far as I see be equivalent. I understand you have tried the latter without success - I would still like to see if GetTimeStamp brings us any further.

Almost. The TCVImage and TCVRingBuffer do serve different purposes and if you want to make use of both of these, you’ll need in fact to use both (for details scroll up a little bit). What you don’t need to (and in fact should not) do is pass the image handle back and forth between the two.

I can understand the frustration that shines through your words here, and I am very sorry for that. On the other hand, we have to deal with a situation that is characterized by two things we have little influence on:

  • It has over the years become more and more difficult to hire new programmers with Delphi Know-How or at least motivation to learn this language on a high enough level to write production-quality code and provide support and services.
  • At the same time we have seen a steep decline in the use of Delphi in our customer base.

Both developments correlate with statistics like Tiobe or Redmonk that try to measure how widely a language is used. For us this meant that we had to weigh our options, and at some point we took the decision to discontinue support for Delphi in :cvb: starting with version 14.00.000 which was published in summer 2022.

I know that this will be little consolation to our remaining Delphi customers - I am merely stating this hoping to raise some understanding for the decision we made here. We will also continue to try and support our remaining Delphi customers to a reasonable extent (which was certainly what @Sebastian had in mind when he pointed out the support contacts), but we have to acknowledge that there are limits to what we can provide today.

The ActiveX controls are generally speaking merely convenience wrappers for functionality found in the C-API (for clarification: If I say “C-API” here I am not referring to using C; I am referring ot the C-style DLL interface of CVCImg.dll, CVCDriver.dll etc. that you tap into if you use the function definitions in iCVCImg.pas etc.). As the C-API gives more control over what is happening where and when we tend to nudge people away from the ActiveX controls. Our primary recommendation would be to use CVB++, CVB.Net or CVBpy instead (these are object-oriented convenience wrappers for C++, C# and Python), but with Delphi a similar option does not exist, and if the ActiveX controls do what you need them to do continue with them, and if not keep the C-API alternative in mind.

Thank you for all these replies!

Please allow me to try to respond to them all in one message!

As regards memory limits we are making a 64 bit program, so as you say you there shouldn’t be a limit.

What is RawTimestamp? It’s not a function I can see documented in the doc files - how do I try that?

I think a large part of why I was handing it back and forth to a TCVImage was because I couldn’t see how to SaveImage to a .bmp out of any given frame in a TCVRingBuffer. Only TCVImage seems to come with a SaveImage method.

I also have a TCVGrabber in there as well which I hand the TCVImage image back and forth to in the initial stages of setting up different TCVImages for different CamPorts - that might not be helping either!

I have just tried the following…

theImg := cvRingBuffers[camIn].GetBufferImage(buffID);
dRequestAnswer := cvRingBuffers[camIn].GetTimestamp(buffID);

…which seems logical as you say as regards extracting a time stamp from a given buffer ID, however, this is still returning 0 for every frame for me. I wonder if it’s something to do with confusing it due to all the back and forth between buffer and image.

I am already using GetBufferSequence to get the required buffID.

When you get time stamps out, are they comparable across multiple cameras or does each camera have its own time sequence anyway, as if they’re not comparable then this is probably not going to help anyway, in terms of syncing up the frames!

Hi @danielquintic

Yeah, sorry for that - I read the string of posts top down and replied as I went along. In hindsight not the cleanest approach :slightly_smiling_face:

RawTimestamp is a member function of the StreamImage class in the C++ API that the tutorial hinted at by @Sebastian is using. It is basically just a wrapper for what you can achiever with the C-API function G2GetGrabStatus or RBGetRingBufferInfo so don’t burden yourself with that particular function.

True. But you can save any image from any ActiveX control through the function WriteImageFile from iCVCImg.pas. Just cast the 64 bit integer that you read from the Image property of your control to a Pointer.

Probably not. I’d actually recommend to set it all up initially and then work with what you have. So if you have 2 cameras, use 2 TCVImage controls, use the TCVGrabber for configuration and the TCVRingBuffer for the ring buffer access. Something along the lines of

imgCtrl1.LoadImage(driverName);
grabberCtrl.Image := imgCtrl1.Image;
grabberCtrl.SetCamPort(1);
imgCtrl2.Image := grabberCtrl.Image;
imgCtrl1.LoadImage(driverName);
// from this point on, 
//    imgCtrl2 is linked to cam port 1
//    imgCtrl1 is linked to cam port 0

ringBufferCtrl1.Image := imgCtrl1.Image;
ringBufferCtrl2.Image := imgCtrl2.Image;

(untested pseudocode… “don’t try this at home” :slightly_smiling_face:)

After an initialization like that

  • acquisition from port 0 is controlled through imgCtrl1
  • acquisition from port 1 is controlled through imgCtrl2
  • the ring buffer access goes through ringBufferCtrl1 and ringBufferCtrl2

and no passing back and forth of image handles should be necessary while running the application. Passing handles is possible - but won’t speed things up and might make it slighly unclear what is going on.

Sounds and looks reasonable.

Phew… That’s something I cannot answer off the top of my head. I know that if you use PTP (with cameras and switches that support this) then it’s possibly to nicely sync the clocks of cameras and have comparable timestamps on them. With other devices I am not so sure, but maybe @Sebastian or @Chris can help me out here - they are a lot more knowledgeable when it comes to these things than I am.

At first, thanks @illusive for jumping in here.

@danielquintic
As @illusive already indicated, the clocks of the cameras are not synced by default.
For this, the mentioned PTP functionality (Precision Time Protocol) can be used to sync all the timestamps.

But the cameras need to support that.

Another approach could be to synchronize the cameras with a hardware trigger. One main camera triggers all the other buddy cameras. When you have a defined trigger start you acquire all images at the same time and the images in the buffer are then synchronized as well except you loose images. To resync to lost images, you can use the given image ID in every frame.

With this new hardware topic we are leaving the scope of this forum which is only CVB related. If you need additional assistance, I strongly recommend contacting our support team. It is quite possible that I will be involved as a 2nd Level Support anyway.