Matrix<T>
codeare's main design goal was to provide an easy framework for implementing MRI algorithms. The fundamental supplement is a flexible and lightweight matrix class that is optimally unlimited in dimentsions. Further, C++ was chosen as the core language as it provides near-assembly speed with the ease of use and full blown functionality of an OO-Language.
A conscious decision was made that the matrix only includes essential methods, i.e. minimum necessary set of constructors and access functionality to element, columns, rows, slices ...
That is to say that any function on the matrix, should be a
real C-type function and any constructor of specialised
matrices should be a templated C-type constructor; i.e.
A = pinv(B);
rather than A =
B.pinv();
and A = zeros<double>
(300,100);
rather than A = Matrix<double>::zeros
(300,100)
. This is much closer to the way we write code
in MATLAB, python etc.
The matrix, thus, comes with default constructors, arithmetic
operators to provide for example A *= B;
, Access
operators to provide for example A(3,4) = 15.0
,
some index arithmetic and size and dimension
functionality. The complete documentation of the matrix member
functionality is found in the API description.
One drawback of C++ when compared to python and fortran etc. is the poor support of array slicing and range operators associated with the :-operator. This does not prevent the user to use python for data processing, though.
The bulk of operation on matrices are provided as static functions. The vast majority allows one to assign the result to a new matrix i.e.:
A = pinv(B);
A = fft (B);
A = dwt (B);
A = sin (B);
etc. However as the return type of C++ functions can only be a
generic type, a reference, a pointer or an object, the nice
and flexible MATLAB like coding is not feasible for methods
that return multiple matrices i.e. instead of [U,S,V] =
svd (A)
, matrix references need to be passed to such
functions: svd (A, U, S, V)
. Obviously one could
provide structs for such return complexes, however it has been
refrained from here to keep things simple and code maintenance
load low.
Database
A database instance is provided to allow one to keep matrices available across the running processes children known by their names.
For example one would declare a system wide known matrix
inside and module with
Matrix< complex<float> >& A =
DataBase::Instance()->AddMatrix
("A", NEW
Matrix< complex<float> >(300,100,8,8));
and retrieve a Matrix from the database with
Matrix< complex<float> >& A =
.
DataBase::Instance()->GetCXFL ("A");
Please note that the above NEW
call is not a
typo. It arises with the need to deviate from the standard C++
new object operator to provide smart pointers and has been
introduced to the matrix to keep memory management more robust.
Module
Processing code is put in so-called strategies which find
their representation in runtime loadable modules. Every
module's main Class must derive from
the ReconStrategy
base class.
Any such derived class is provided full access to
the Database
instance and to
the Configuration
super class that holds the meta
data. This is a very convenient feature that lets data be
handled on client application as well as remote application in
the very same fashion.
An empty template for such a strategy is found in
DummyRecon
located like all other modules in
the src/modules
directory. codeare brings along
as of now modules for reconstruction algorithms SENSE,
Non-Cartesian SENSE, Compressed Sensing, GRAPPA, as well as
for RF pulse design algorithms k-T-points and Time-reversal.
Connector<T>
One potentially very valuable feature of codeare is that it provides an asynchroenous communication to send and receive meta data as well as matrices in a nicely encapsulated fashion to keep the network communication away from the programmer.
That is to say that the reconstruction / data manipulation /
sequence runtime manipulation code can run on the invocing
machine but also on a remote server. The data transport and
transparency of the network functionality is achieved as of
now with CORBA
and in particular with
the omniORB
implementation.
There will be a tutorial in how to use this functionality for doing online and realtime data manipulation on scanned data as an intermediate step before the remaining data processing is done on the data.
The connector class provides the programmers with the abstraction from local and remote processing of data.
API
Details are found in the API.