views:

324

answers:

3

Hello,

I'm currently involved in a project where I have very large image volumes. This volumes have to processed very fast (adding, subtracting, thresholding, and so on). Additionally most of the volume are so large that they event don't fit into the memory of the system. For that reason I have created an abstract volume class (VoxelVolume) that host the volume and image data and overloads the operators so that it's possible to perform the regular mathematical operations on volumes. Thereby two more questions opened up which I will put into stackoverflow into two additional threads.

Here is my first question. My volume is implemented in a way that it only can contain float array data, but most of the containing data is from an UInt16 image source. Only operations on the volume can create float array images.

When I started implementing such a volume the class looked like following:

public abstract class VoxelVolume<T> 
{
...
}

but then I realized that overloading the operators or return values would get more complicated. An example would be:

public abstract class VoxelVolume<T>
{
...
    public static VoxelVolume<T> Import<T>(param string[] files) 
    {
    } 
}

also adding two overloading operators would be more complicated:

...
public static VoxelVolume<T> operator+(VoxelVolume<T> A, VoxelVolume<T> B)
{
...
}    

Let's assume I can overcome the problems described above, nevertheless I have different types of arrays that contain the image data. Since I have fixed my type in the volumes to float the is no problem and I can do an unsafe operation when adding the contents of two image volume arrays. I have read a few threads here and had a look around the web, but found no real good explanation of what to do when I want to add two arrays of different types in a fast way. Unfortunately every math operation on generics is not possible, since C# is not able to calculate the size of the underlying data type. Of course there might by a way around this problem by using C++/CLR, but currently everything I have done so far, runs in 32bit and 64bit without having to do a thing. Switching to C++/CLR seemed to me (pleased correct me if I'm wrong) that I'm bound to a certain platform (32bit) and I have to compile two assemblies when I let the application run on another platform (64bit). Is this true?

So asked shortly: How is it possible to add two arrays of two different types in a fast way. Is it true that the developers of C# haven't thought about this. Switching to a different language (C# -> C++) seems not to be an option.

I realize that simply performing this operation

float []A = new float[]{1,2,3};  
byte  []B = new byte[]{1,2,3};

float []C = A+B;

is not possible and unnecessary although it would be nice if it would work. My solution I was trying was following:

public static class ArrayExt
{
    public static unsafe TResult[] Add<T1, T2, TResult>(T1 []A, T2 []B)
    {
       // Assume the length of both arrays is equal
       TResult[] result = new TResult[A.Length];

       GCHandle h1 = GCHandle.Alloc (A, Pinned);
       GCHandle h2 = GCHandle.Alloc (B, Pinned);
       GCHandle hR = GCHandle.Alloc (C, Pinned);

       void *ptrA = h1.ToPointer();
       void *ptrB = h2.ToPointer();
       void *ptrR = hR.ToPointer();

       for (int i=0; i<A.Length; i++)
       {
          *((TResult *)ptrR) = (TResult *)((T1)*ptrA + (T2)*ptrB));
       }

       h1.Free();
       h2.Free();
       hR.Free();

       return result;
    }
}

Please excuse if the code above is not quite correct, I wrote it without using an C# editor. Is such a solution a shown above thinkable? Please feel free to ask if I made a mistake or described some things incompletely.

Thanks for your help
Martin

+1  A: 

If you have only a few types like float and UInt32, provide all needed conversion functions, for example from VoxelVolume<UInt32> to VoxelVolume<float> and do the math on VoxelVolume<float>. That should be fast enough for most practical cases. You could even provide a generic conversion function from VoxelVolume<T1> to VoxelVolume<T2> (if T1 is convertible to T2). On the other hand, if you really need a

public static VoxelVolume<T2> operator+(VoxelVolume<T1> A,VoxelVolume<T2> B)

with type conversion from T1 to T2 for each array element, what hinders you from writing such operators?

Doc Brown
Hello Doc Brown, it seems that your solution will get me into the right direction. I will think about it. Normally if I want to created such things where I can use every integral datatype that is available in C# it seemed reasonable to me, that I create the overloads for every possible datatype, but I think are right - breaking things down to only a few datatypes would help a lot.
msedi
+1  A: 

Import, being a member of a generic class, probably doesn't also need to be itself generic. If it does, you definitely shouldn't use the same name T for both the generic parameter to the class and the generic parameter to the function.

What you're probably looking is Marc Gravell's Generic Operators

As for your questions about C++/CLI, yes this could help if you use templates instead of generics because then all the possible values for typename T are controlled at compile time and the compiler looks up the operators for each. Also, you can use /clr:pure or /clr:safe in which case your code will be MSIL, runnable on AnyCPU just like C#.

Ben Voigt
Hi Ben, thank you for your comments. You are absolutely right. I didn't intend to use the same T for the operator. This was only an example and should show the complexity of what I wanted to do.
msedi
A: 

Admittedly, I didn't read the whole question (it's a quite a bit too long), but:

  1. VoxelVolume<T> where T : ISummand ... T a; a.Add(b)
  2. static float Sum (this VoxelVolume<float> self, VoxelVolume<float> other) {...}
  3. To add float to byte in any meaningful sense you have to convert byte to float. So convert array of bytes to array of floats and then add them, you only lose some memory.
ima
Hi Ima, you are right, it was way to long. I wanted to be as detailed as possible to show what I want to to. You are of course right that I can cast the values. But for me it seemed that I would also help me for future projects to have a more generic way to perform such operations.
msedi