You are here: Start » AVL.NET » Code Migration Guide to version 4.11

Code Migration Guide to version 4.11

Introduction

Aurora Vision Library 4.11 brings many optimizations and API changes that may make user code invalid after upgrade from older Aurora Vision Library versions. The same applies to the Macrofilter .Net Interface assemblies generated in the Aurora Vision Studio. This article highlights the most important changes and shows how to update code in the applications that reference the AVL.NET assemblies and Macrofilter .Net Interface assemblies.

out Modifier in Output Parameters

Almost all AVL functions have some arguments that present some calculation results, e.g. AddImages has an outImage parameter which holds a sum of two images when the function exits. Until Aurora Vision Library 4.11 all output arguments had out modifier which resulted in creating new objects (Image in case of AddImages.outImage) each time the function was called. From Aurora Vision Library 4.11 the out modifier is gone for the majority of reference types. This has several implications:

  • output variables need to be assigned before function call,
  • output variables need to be non-null values,
  • output variables must not be disposed

In all cases the default constructors are enough to initialize such variables, even if the size of the output object is unknown since the real data will be filled in the function, e.g.:

Before 4.11
From 4.11
//...
Image sumImage; // just declaration
try
{
    AVL.AddImages(image1, image2, 1.0f, out sumImage);
    //...
}
finally
{
    if (sumImage != null)
        sumImage.Dispose();
}
using (Image sumImage = new Image())
{
    //...
    AVL.AddImages(image1, image2, 1.0f, sumImage);
    //...
}

This allows significantly reduce the memory consumption in scenarios when such an objects may be reused, e.g. a size of the frame being acquired from the camera rarely change over the application lifetime and can be filled into to the same image buffer each time the new frame comes, instead of creating the whole new image for every single frame.

out modifier is still present in case of value types and several lightweight reference types, e.g. Object2D, RegionOfInterest, etc.

Nullable References

C# language has built-in support for nullable references so variables of reference types, apart from type-specific values, may be assigned with the special value of null. This is often confusing especially for AVL methods that accept optional/conditional values, e.g. Regions. There was no way other than referring to the documentation to know if method accepts null for a particular argument or not. That is why Aurora Vision Library 4.11 introduces explicit types that reflect if a variable may or may not accept null value: INullable<T> generic interface and NullableRef<T> and SafeNullableRef<T> generic types. API of those types is almost the same as the API of the System.Nullable<T> (value types-restricted nullable wrapper), i.e. all have public Value and HasValue properties.

Inputs

The most noticeable change in the API is for ROI parameter which in almost all AVL functions is optional. Now it is clearly visible from method signature what values may be assigned, e.g. ThresholdImage now accepts a NullableRef<Region> as a ROI instead of Region. In case of input parameters however, it is still possible to pass both the null value (auto conversion to an empty NullableRef<Region>) and an instance of Region (auto conversion to non-empty NullableRef<Region>). Possible calls to the ThresholdImage are as follows:

using (var roi = new Region())
{
    // threshold in whole image
    AVL.ThresholdImage(..., null, ...);
    AVL.ThresholdImage(..., new NullableRef<Region>(), ...);

    // threshold in roi
    AVL.ThresholdImage(..., roi, ...);
    AVL.ThresholdImage(..., new NullableRef<Region>(roi), ...);
    AVL.ThresholdImage(..., AvlNet.Nullable.Create(roi),  ...);
}
//...

using (var safeRoi = new SafeNullableRef<Region>())
{
    //...
    AVL.ThresholdImage(..., safeRoi, ...);
}

Outputs

Breaking change is in case of optional and conditional outputs where appropriate NullableRef<T> instantiation must be passed:

Before 4.11
From 4.11
string text;
AVL.DecodeQRCode(matrix, out text);
if (text != null)
{
    // do something with text
}
var text = new NullableRef<string>();
AVL.DecodeQRCode(matrix, text);
if (text.HasValue)
{
    // do something with text.Value
}

In case of disposable values it is suggested to use SafeNullableRef<T> that takes the responsibility of disposing the underlying object (if exists) when either the object is Reset inside the function or the whole object is disposed, e.g.:

Before 4.11
From 4.11
Path npath = null;
try
{
    AVL.FitPathToEdges(..., out npath);
    if (npath != null)
    {
        //do something with npath
    }
}
finally
{
    if (npath != null)
        npath.Dispose();
}
using (var npath = new SafeNullableRef<Path>())
{
    AVL.FitPathToEdges(..., npath);
    if (npath.HasValue)
    {
        //do something with npath.Value
    }
}

It is extremely important when passing a nullable object which already has a value to the function, where the nullable may be reset. Let's imagine, that there are two consecutive calls to the FitPathToEdges, in which second fails in the fitting (results in an empty path):

Before 4.11
From 4.11
Path npath = null;
try
{
    // first call to the FitPathToEdges
    // detects a npath...
    AVL.FitPathToEdges(..., out npath);
    if (npath != null)
    {
        // do something with npath

        // dispose previous path
        npath.Dispose();

        // ... but the second one does not.
        AVL.FitPathToEdges(..., out npath);
        if (npath != null)
        {
            //do something with npath
        }
    }
}
finally
{
    if (npath != null)
        npath.Dispose();
}
using (var npath = new SafeNullableRef<Path>())
{
    // first call to the FitPathToEdges
    // detects a npath...
    AVL.FitPathToEdges(..., npath);
    if (npath.HasValue)
    {
        // ... but the second one does not.
        // The npath will be reset to an empty
        // value with automatic disposal of the
        // Path object in the npath.Value property.
        AVL.FitPathToEdges(..., npath);
        if (npath.HasValue)
        {
            //do something with npath.Value
        }
    }
}

If instead of the SafeNullableRef<Path>, the NullableRef<Path> was passed, the Path object in the path.Value property was not be disposed when reset, thus could be a subject to a memory leak.

Arrays

Until Aurora Vision Library 4.11 the only collection type used in the API was an array (System.Array with its syntactic sugar of T[]), e.g. Image[] in JoinImages_OfArray. While usage in the function inputs was not so problematic, usage in the function outputs could be nonoptimal and forced callers to manually manage disposable element lifetimes. Therefore in Aurora Vision Library 4.11 explicit arrays in the API are replaced with a System.Collections.Generic.IList<T> interface and new collection type SafeList<T> is introduced to help easily manage element lifetime.

Inputs

Since System.Array implements a generic System.Collections.Generic.IList<T> interface, previous call implementation will still compile:

Before 4.11
From 4.11
Image[] images;
// ...
AVL.JoinImages_OfArray(images, ...);
// ...
Image[] images;
images = new Image[0];
// ...
AVL.JoinImages_OfArray(images, ...);
// ...

Aurora Vision Library 4.11 in this case just enables one to use not only arrays but any collection that implements the System.Collections.Generic.IList<T> interface, e.g. System.Collections.Generic.List<T>, SafeList<T>, etc.

Outputs

Since there is no out modifier any longer in the function output of any collection type, objects being passed to the function need to meet the same rules as in case of any AVL function output. In particular, one may pass just an empty list and expect the function change it's size accordingly:

Before 4.11
From 4.11
Point2D[] points;
Box box;
// ...
Point2D[] croppedPoints;
AVL.CropPointArray(points, box, out croppedPoints);
foreach (var point in croppedPoints)
{
    // do something with point
}
Point2D[] points;
Box box;
// ...
var croppedPoints = new List<Point2D>();
AVL.CropPointArray(points, box, croppedPoints);
foreach (var point in croppedPoints)
{
    // do something with point
}

As calling function may change the collection size, great care needs to be taken when passing collection of disposable objects, e.g. Image, Region, Path, etc. In those cases it is suggested to use SafeList<T>, especially if the collection is the only place where the references to those objects are kept, e.g.:

Before 4.11
From 4.11
Region[] blobs = null;
try
{
    AVL.SplitRegionIntoBlobs(..., out blobs);

    foreach (var blob in blobs)
    {
        // do something with blob
    }
}
finally
{
    if (blobs != null)
    {
        foreach (var blob in blobs)
            blob.Dispose();
    }
}
using (var blobs = new SafeList<Region>())
{
    AVL.SplitRegionIntoBlobs(..., blobs);

    foreach (var blob in blobs)
    {
        // do something with blob
    }
}

View Properties

There were types that contained get methods for component objects, e.g. RegionOfInterest's GetPolygon(). In Aurora Vision Library 4.11, whenever possible, such types expose getter-only properties that refers to the original component objects, that may be modified in-place.

Before 4.11
From 4.11
RegionOfInterest roi;
Point2D[] points;
// ...
if (roi.Tag == RegionOfInterestType.Polygon)
{
    // update the polygon shape
    using (var poly = roi.GetPolygon())
    {
        poly.Clear();
        poly.AddRange(points);
        roi.SetPolygon(poly);
    }
    // ...
    // draw the polygon
    using (var poly = roi.GetPolygon())
        AVL.DrawPath(..., poly, ...);
}
RegionOfInterest roi;
Point2D[] points;
// ...
if (roi.Tag == RegionOfInterestType.Polygon)
{
    // update the polygon shape
    roi.Polygon.Clear();
    roi.Polygon.AddRange(points);
    // ...
    // draw the polygon
    AVL.DrawPath(..., roi.Polygon, ...);
}

AVL.NET Designers dependency

Avl.Net.Designers.dll requires another assemblies to be referenced in the project - Avl.Net.Amr.dll and Avl.Net.Kit.dll. Those assemblies can be found in the same directory as the other Avl.Net.* assemblies.

AVL Function Cancellation

The function cancellation logic has been unified with the one known from the native AVL. From now on, user needs to manually call the function cancellation off, to enable consecutive functions to be executed. This solves an issue of unexpected function execution even if explicit cancellation has been called.

In the following example the CreateModels() method runs in a separate thread, in which some long-lasting AVL functions are executed. The Cancel() method is responsible for interrupting those methods. Before Aurora Vision Library 4.11, in the worst case scenario, the Cancel() method should have been called twice to actually interrupt the pending AVL functions. From Aurora Vision Library 4.11 it is enough to call the Cancel() method once. However, before any other AVL function is called, it is required to call the cancellation off, to prevent those methods to be discarded at startup.

Before 4.11
From 4.11
public void CreateModels()
{
    var image = new Image();
    var model = new SafeNullableRef<EdgeModel>();
    // Create two edge models
    AVL.CreateEdgeModel1(...);
    AVL.CreateEdgeModel1(...);
    //...
}

public void Cancel()
{
    // cancel all AVL functions that
    // are currently running
    ProcessHelper.CancelCurrentWork();
}
public void CreateModels()
{
    var image = new Image();
    var model = new SafeNullableRef<EdgeModel>();
    // Call the last cancellation off
    ProcessHelper.ResetIsCurrentWorkCancelled();

    // Create two edge models
    AVL.CreateEdgeModel1(...);
    AVL.CreateEdgeModel1(...);
    //...
}

public void Cancel()
{
    // Cancel all AVL functions that are
    // currently running and will run,
    // unless ProcessHelper.ResetIsCurrentWorkCancelled()
    // will be called
    ProcessHelper.CancelCurrentWork();
}
Previous: AVL.NET Performance Tips Next: Usage Examples