This chapter describes nGI from the architectural and conceptual perspective. This chapter talks both about the overall architecture at a high level, as well as the concepts used within the code.

When you look at nGI from a high level you see a set of hierarchical directories:

Folder Purpose
ngi The top level or root director of nGI.
ngi/bin Binary files are grouped here.
ngi/book The source files of this book are contained in this directory and its subdirectories.
ngi/doc This directory contains the files that are used to generate the reference documentation. The reference documentation is built during the build process and is extracted from the source code files. This ensures that the reference documentation is always synchronized to the actual code.
ngi/images This directory contains the images used by various sample and test programs.
ngi/include This directory contains the nGI source code. Most of the code is in this directory, but some optional code components are in subdirectories.
ngi/modules Contains the interface code to cameras and frame grabbers.
ngi/samples This directory contains sample programs that should give you a quick start when using nGI. These samples show functional aspects of nGI and they often also show the intended way to use nGI.
ngi/tests We had testing in mind while devloping nGI. This directory contains the extensive test and benchmarking suite of nGI. Besides automated testing, this is also an invaluable source of education, since again the complete source code of the tests is available.
ngi/tools Contains tools that are needed during the build process.
ngi/wrappers Contains the wrapping code that is needed for automated creation of the .NET wrapper.

The following chapters tell you a little bit more about these architectural aspects of nGI.

Coding Guidelines

In general I do not believe very much in coding guidelines, since I have seen how they can go overboard. However, they can help to keep a code basis consistent, and so a few guidelines are used within nGI.

My golden rule is that coding guidelines are good if they can be put on one sheet of paper. If you need a book to write down the guidelines, then something is wrong. Therefore, you can expect this chapter to be fairly short.

So here are the guidelines:

  • nGI is a header only library. Everything is contained in header files (*.h). There are no implementation files. Period.
  • The line length of the nGI header files is 120 characters. Lines that are longer have to be broken to multiple lines.
  • If you write code that is to be included in nGI, follow the style of the existing nGI headers. In particular, do not use camelCasing or PascalCasing, but use_underscores to separate words in identifiers.
  • Doxygen is used to create the nGI reference documentation. Thus, everything public should be documented with the necessary doxygen commands. Don't go overboard with doxygen tags, just use the triple-slash (/// for brief and //! for detailed) comment style.

Testing

nGI is a big, complex piece of software. Any system this big needs considerable efforts to being tested. Within nGI we have tests that run automatically during a build, and we have sample code that can be used to ad-hoc test certain aspects.

nGI partitions its tests into so called unit tests, non-regression tests and benchmarks.

Unit tests focus on small units in the code and test their workings isolated from the rest of nGI.

Non-regression tests test the most common usage scenarios, and make sure that there are no regressions as the code evolves and changes over time.

Benchmarks test the running time of algorithms in real-world scenarios and make sure that the performance of nGI stays within defined bounds.

Unit Tests

Unit tests are tests written as small code snippets that test a certain 'unit'. Within nGI, these units are classes and their defined operations. nGI uses Boost.Test for the unit tests.

Non-regression Tests

Non-regression tests make sure that regressions are not allowed to creep in while the nGI code evolves and is developed further. It makes sure that those regressions are detected and that measures are taken to correct them. nGI uses Boost.Test for the non-regression testing as well.

The non-regression tests are organized in a way that they store their results into a result directory with the name now. The test then compares the result in now with a previously stored result in the directory previous. If both are the same, the test passes. If there are regressions, the test fails. This scheme provides very easy setup of non-regressions tests.

  1. Write a new test case.
  2. Run the new test case. It will fail, since it has nothing to compare to in directory previous.
  3. Copy the resulting file the test created in directory now to directory previous. This is now the basis to test further potential regressions against.
  4. Rerun the test. Now the test passes, and it will pass in further runs as long as there are no regressions introduced by errors in the code.

Benchmarking

Benchmarks are used to measure the relative performance of the nGI imaging algorithms. We are not so much interested in absolute performance, since this can depend very much on specific hardware.

The benchmarks are written as macros and the time is measured using nGI timing functions. The measured time is then logged with the output.

The benchmarks do not return their results as a time measure, such as some fraction of a second, but as a value specifying clocks per pixel -CPP. The CPP measure is the number of CPU clocks per pixel, which gives you an indication of how efficient some algorithm is implemented.

If you run the benchmark on your machine, the results might be different because you might be using a different CPU. The clocks per pixel are independent of the CPU clock speed, but they may be affected by the CPU architecture. Intel or AMD, as well as different models within the product lines of these manufacturers differ, so it is likely that you may get different results.

Graphics Engines

nGI provides flexible means to visualize images and associated graphical annotations. All the rendering commands go through a portability layer, which directs the commands to various graphics back ends. The graphics system used in nGI is two-dimensional.

Graphics Engines

The user can select the graphics engine or back end with a #define. The back ends have different properties that are explained in the following table.

Graphics Engine #define Operating System Hardware Acceleration Comment
Direct2d GRAPHICS_ENGINE_DIRECT2D Windows yes Direct2D is available on the Windows Vista and higher (Windows 7, Windows 8) operating systems. It supports an appropriate graphics card for acceleration.
GDI+ GRAPHICS_ENGINE_GDIPLUS Windows no -
OpenGL GRAPHICS_ENGINE_OPENGL portable yes -
Qt GRAPHICS_ENGINE_QT portable no If Qt is used for graphics, it is also used as the window system.

The CMake based build system allows selection of these graphics engines in the GUI.

Code Layers

nGI is implemented in three layers that are stacked on top of each other. A fourth layer is constituted by the applications you or other users write.

Code Layers

The template layer or core makes heavy use of C++ templates. Code written against this layer must take the templates into account and may look intimidating to the casual C++ user.

The native layer instantiates specific template parameters and flattens out the class hierarchies. This makes nGI usable with languages that can only consume a flat C interface.

The .NET layer is an implementation of the relevant glue code for .NET and can be programmed in C# or Visual Basic or any other language supporting .NET.

Template Layer (C++)

The template layer provides the most flexibility, but with this flexibility there comes a price. The heavy use of templates and template types may make this layer difficult to understand. Here is a little code sample that shows a small sample that loads an image and displays it in a window.

#include <ngi_image.h>
#include <ngi_rgb.h>
#include <ngi_locator.h>
#include <ngi_view.h>
#include <ngi_widget_image.h>
#include <ngi_display.h>
#include <ngi_demo_main.h>

using namespace ngi;

typedef image<rgb<unsigned char> > image_type;
typedef locator<image_type::value_type> locator_type;
typedef view<locator_type> view_type;
typedef widget_image<view_type> widget_type;
typedef display<widget_type> display_type;

int main(int argc, char* argv[])
{
    image_type img = import_image<image_type>(TEXT("fish.png"));
    display_type wnd(img.get_view(), TEXT("Fish"));

    run_message_loop();

    return 0;
}

You see that you need a lot of includes and type definitions that are not really intuitive, unless you know a lot about this layer. The code itself inside the main function looks pretty innocent, though. One line to load the image from a file, another line to create a window, and finally a line with a message loop to make the window operational.

.NET Layer

The same program written in C# using the .NET layer looks considerably simpler.

using System;
using System.Windows.Forms;
using Ngi;


namespace file_access_3
{
    class Program
    {
        static void Main(string[] args)
        {
            Image img = Image.ImportImage("fish.png");
            Display wnd = new Display(new WidgetImage(img.View), "Fish");

            Application.Run();
        }

    }
}

Compared with the template layer version the non-intuitive includes and type definitions are not needed at all. The code inside the Main function looks pretty similar, though.

Code Generation

Code for the higher layers (native layer, .NET layer) is generated automatically, using an XML based interface definition and some transformation templates. The interface description file written in XML controls the code generation. It consists of a high-level description of the classes and their behavior.

Native Layer Code Generation

The native layer consists of a Dll with a native C interface. This means that some types used in the nGI source code cannot be used as is, but must be converted into their native C counterparts. Data can flow in two directions, i.e. into the native Dll or out of the native Dll. As a simple example, return values flow out of the native Dll and function parameters usually flow into the native Dll.

The Native Layer

C has no notion of classes and object. They must therefore be somehow simulated. The native layer uses handles (implemented via void pointers to the original C++ objects) in order to manage the objects.

.NET Layer Code Generation

The .NET layer is split into a native part (written in C++, but with a flat, C-callable interface) and into a managed part (written in C#). Most of the .NET layer is generated automatically, as explained above, but some small portions of it are written by hand.

The Managed Layer

The whole business of the native Dll is to provide a means that can be called directly by C#, via the ImportDll mechanism.

Names in the C++ interface start with a lower case letter and words in these names are separated by an underscore, i.e. widget_image.

Names in the .NET layer are in PascalCasing, i.e. WidgetImage. The words are capitalized and tucked together. As such, the naming conventions used in the C++ and .NET layers are incompatible. A common naming convention would have been inconvenient, because the C++ community expects the naming convention used in the C++ Standard Library, while the .NET community expects the naming convention used in .NET. C++ names look alien to a .NET programmer, and vice versa.

Namespaces

The template layer of nGI is contained completely within namespace ngi. In order to get at definitions from namespace ngi you have some possibilities. For example:

Use the scoping operator ::

ngi::buffer buffer;

Use a using declaration:

using namespace ngi;
buffer buffer;

You should always use the scoping operator :: in header files, whereas it is acceptable to use the using declaration in implementation files. If you use a using declaration in a header file that is included in other files, you inject the using declaration into these other files, and you might get name clashes down the road that are really hard to track down.