Async Saving of Images

I think the answer is easier shown with a project: AcqSave.zip (11.9 KB). The Stemmer.Cvb and Stemmer.Cvb.Wpf assemblies need to be installed also (see the downloads). The app expects the camera to be in software trigger mode. If it is not it will save a lot of images :slight_smile:.

In the project I implemented the ISoftwareTrigger via a Button. The MainWindow also shows a live image. The MainViewModel provides the live Image and commands for triggering and app-closing. The whole program logic is in the TriggerSaver model class.

The MainWindow creates the MainViewModel which in turn creates the TriggerSaver which opens the first GenICam device it finds. Then the app is run async via WPF’s Dispatcher. Thus the main logic runs on the UI thread. That is not a bad thing as all the code is written async. The Image.Save is executed via .Net’s task pool (Task.Run).

The :cvb: Device is encapsulated in the model class TriggerSaver. Only the live Image is publicly available plus the use cases of running the app and sending (double) software triggers. Depending on the camera used you probably want to modify the TriggerSaver.SendSoftwareTrigger method.

The app logic is written in a way that we wait for two images and save them as soon as they arrive. We utilize :cvb:'s IRingBuffer interface to keep the acquisition code simple without loosing images. We use RingBufferLockMode.On and manually unlock acquired Images via the using-block in SaveAndUnlockImageAsync. Depending on your acquisition rate and hard disk/ssd used you might need to increase the buffer count on the ring buffer to handle processing peaks (see TriggerSaver.SetupDevice).

Here is the core logic:

public async Task RunAsync(CancellationToken cancellationToken)
{
  var stream = _device.Stream;
  stream.RingBuffer.LockMode = RingBufferLockMode.On;

  stream.Start();
      
  try
  {
    long counter = 0;
    while (!cancellationToken.IsCancellationRequested)
    {
      try
      {
        var savesFinished = new Task[2];
        for (int i = 0; i < 2; i++)
        {
          var imageTask = stream.WaitForAsync(AcquisitionTimeout);
          savesFinished[i] = SaveAndUnlockImageAsync(imageTask, counter++);
        }

        await Task.WhenAll(savesFinished);            
      }
      catch (TimeoutException)
      {
        // swallow timeouts, but we need to check for cancellation
      }
    }
  }
  finally
  {
    stream.Abort();
  }
}

private async Task SaveAndUnlockImageAsync(Task<StreamImage> imageTask, long imageNumber)
{
  Debug.Assert(imageTask != null);

  using (var image = await imageTask)
  {
    await SaveImageAsync(image, imageNumber);
  }
}

private static Task SaveImageAsync(Image image, long imageNumber)
{
  Debug.Assert(image != null);

  return Task.Run(() =>
    image.Save(Path.Combine(StorageDirectory, $"{imageNumber}.bmp"))
  );
}
2 Likes