PrecisionImage.NET is designed is to transparently assume the role of an underlying heterogeneous computing framework for consuming applications. This means that behind the scenes the software employs a blend of GPU-accelerated and multithreaded CPU code. While this behavior occurs automatically and requires no additional effort, it is still entirely under the control of the developer. It is important, therefore, to understand how to target different GPU devices and threading models using PrecisionImage.NET should the need arise.
At the most basic level, the execution mode of any instantiated PrecisionImage.NET object is governed by the static Setup class. Specifically, it is the DefaultProcessingMode property of this class that defines the default behavior of PrecisionImage.NET objects. As the name implies, this property exposes the default ProcessingMode enumeration used to initialize every new computational object in the PrecisionImage.NET toolkit. This enumeration has 4 states:
Indicates that the object is to execute on a single worker thread. This mode is synchronous, with the calling thread blocking until the operation completes. All computations are performed internally at full (80-bit) precision prior to truncation and storage as 32-bit single-precision values in the containing SourceData object.
Indicates that the object is to use all logical cores on the host computer. This mode is synchronous, with the calling thread blocking until the operation completes. All computations are performed internally at full (80-bit) precision prior to truncation and storage as 32-bit single-precision values in the containing SourceData object.
Indicates that the object is to attempt execution on an acceleration device (typically the GPU) in single precision mode. This mode is synchronous, with the calling thread blocking until the operation completes. If no accelerator is available on the host computer the execution mode falls back to ProcessingMode.MultiThreadCPU.
Indicates that the object is to attempt execution on an acceleration device (typically the GPU) in double precision mode. This mode is synchronous, with the calling thread blocking until the operation completes. Specifying this processing mode implies that double precision is important; therefore, if no accelerator with full double-precision support is available on the host computer, the execution mode falls back to MultiThreadCPU rather than SinglePrecisionAccelerator.
All computational objects in PrecisionImage.NET expose a ProcessingMode property that initializes upon the object's instantiation to the current state of DefaultProcessingMode. However, the object's ProcessingMode can be changed at any point during the object's lifetime. Likewise, DefaultProcessingMode can be changed at any time (this will only affect the initialization of new objects).
DefaultProcessingMode itself is determined when the Setup class performs an initial query of the host system upon initialization and stores any accelerators internally as HardwareAccelerationDevice objects. A default accelerator is chosen (according to an internal C++ AMP heuristic) and stored as the DefaultHardwareAccelerator property. The number of available accelerators on the host computer can be retrieved using GetNumberOfAccelerationDevices, and an array containing all available HardwareAccelerationDevice devices on the host system can be retrieved using the GetHardwareAccelerationDevices method. These objects can then be assigned to the CurrentHardwareAccelerator property of any PrecisionImage object to target that particular acceleration device for code execution. If no suitable hardware acceleration device is found the default processing mode initializes to MultiThreadCPU.
In general, the execution time is short enough for most of the operations (especially with 4 or more logical cores) that the calling thread only blocks for an instant and the default mode can be used in a synchronous procedural workflow. In the case of a longer running operation, however, it is preferable not to block the UI thread. In these cases there are two approaches: instantiate the PrecisionImage.NET objects on the UI thread and pass them to a background worker thread for execution, or launch a background worker thread which in turn instantiates and invokes the PrecisionImage.NET objects internally. The only practical difference between the two methods involves whether the progress event is being handled to derive progress feedback during processing. To handle this case, many functional objects in PrecisionImage.NET expose both an OnProgressChanged event as well as a SynchronizationContext property called SyncContext.
When a functional object in the PrecisionImage namespace is instantiated it automatically stores the SynchronizationContext of its containing thread. If an event handler has been attached to the same object's OnProgressChanged event, the event will trigger the thread associated with the stored SynchronizationContext. This allows PrecisionImage.NET objects to be instantiated within either the UI thread or a background thread while providing for the facility to signal the proper thread with progress updates if necessary. For example, consider the case of a HistogramProcessor object instantiated on the UI thread:
By default the SyncContext property of "processor" will point to the SynchronizationContext of the UI thread within which it was created. If an event handler is then attached to the OnProgressChanged event of "processor" and this object is then passed to a background worker thread for execution, the processing will distribute across all logical CPUs while the background thread blocks until completion, leaving the UI thread free to handle the OnProgressChanged event updates.
On the other hand, if the above instantiation is performed in the background thread which then attaches an event handler to the OnProgressChanged event, the SyncContext property must be assigned the SynchronizationContext of the UI thread manually. To do so, first acquire the SynchronizationContext from within the UI thread as follows:
Then, pass this object to the background thread for use whenever it instantiates a new PrecisionImage.NET object that needs to provide progress feedback to the UI thread:
For an example of background processing and progress updating in action see the dental x-radiograph processing demo project. ("Wavelet-based denoising" code example on the website).