Multidimensional arrays


#1

I know that ImageJ macro language only supports 1D arrays, and the frequent suggestion is that 2D arrays can be simulated by images, 3D arrays by image stacks, and so on. In fact, the method of images is not very practical. With a bit of effort I wrote a macro (shown below) that solves the problem and works without any limitation of array dimensionality and size. But the code but is not elegant. I wonder if anyone may indicate a better method.

// This macro enables the creation of up to 10 n-dimensional matrices
// however the number of matrices may be easily increased.
//
// Matrices are conventionally numbered 0 to 9, and are created by the function create_mat(string)
// example: create_mat("M8,12,29,129");
// where M8 means the matrix #8 and 12,29,129 are the wanted dimensions, with base-0 indexed elements
// Please note that the function argument is a single string
//
// For any of the 10 matrices, there is a pair of specific functions to assign and to recover values
// For example, to assign the value 44 to the element 8,15,110 of the matrix #8:
// setM8("8,15,110,44");
// and to recover the value from the same element:
// value = getM8("8,15,110");
//
// a DEMO is shown
//
// DECLARATIONS //////////////////////////////////////////////
// required
var M0, M1, M2, M3, M4, M5, M6, M7, M8, M9;
var DimM0, DimM1, DimM2, DimM3, DimM4, DimM5, DimM6, DimM7, DimM8, DimM9;

// DEMO 
// creating a 50x50x50 matrix 
create_mat("M8,50,50,50"); 

// writing and reading all 125,000 matrix elements - on my PC it takes less than 1.8 seconds
t1 = getTime();

for(i=0; i<50; i++)
{    for(j=0; j<50; j++)
    {    for(h=0; h<50; h++)
        {    s = toString(h) + "," + toString(j) + "," + toString(i); 
            a = h *100 + j * 10 + i;
            setM8(s,a);

            w = getM8(s);
            if(a != w) exit("error");
        }
    }
}

t2 = getTime();
print("time passed =", t2-t1, "milliseconds"); 
// END OF DEMO =================================================

// FUNCTIONS ***************************************************
function create_mat(stringa)
{    data = split(stringa,",");
    q = 1;
    for(j=1; j<data.length; j++)     // it starts from j=1, the element data[0] contains the matrix name
    {    q = q*parseFloat(data[j]);
    }
    print("the matrix ", data[0], " contains ", (data.length - 1)," dimensions, and a total of ", q, " elements");
    
    if(data[0]=="M0") { M0 = newArray(q); DimM0 = data; } // DimM0 etc. are vectors which contain the matrix dimensions
    if(data[0]=="M1") { M1 = newArray(q); DimM1 = data; } 
    if(data[0]=="M2") { M2 = newArray(q); DimM2 = data; } 
    if(data[0]=="M3") { M3 = newArray(q); DimM3 = data; } 
    if(data[0]=="M4") { M4 = newArray(q); DimM4 = data; } 
    if(data[0]=="M5") { M5 = newArray(q); DimM5 = data; } 
    if(data[0]=="M6") { M6 = newArray(q); DimM6 = data; } 
    if(data[0]=="M7") { M7 = newArray(q); DimM7 = data; } 
    if(data[0]=="M8") { M8 = newArray(q); DimM8 = data; } 
    if(data[0]=="M9") { M9 = newArray(q); DimM9 = data; } 
}

function setM0(stringa, valuea) { DimDummy = DimM0; k = evaluate(DimDummy, stringa); M0[k] = valuea; }
function setM1(stringa, valuea) { DimDummy = DimM1; k = evaluate(DimDummy, stringa); M1[k] = valuea; }
function setM2(stringa, valuea) { DimDummy = DimM2; k = evaluate(DimDummy, stringa); M2[k] = valuea; }
function setM3(stringa, valuea) { DimDummy = DimM3; k = evaluate(DimDummy, stringa); M3[k] = valuea; }
function setM4(stringa, valuea) { DimDummy = DimM4; k = evaluate(DimDummy, stringa); M4[k] = valuea; }
function setM5(stringa, valuea) { DimDummy = DimM5; k = evaluate(DimDummy, stringa); M5[k] = valuea; }
function setM6(stringa, valuea) { DimDummy = DimM6; k = evaluate(DimDummy, stringa); M6[k] = valuea; }
function setM7(stringa, valuea) { DimDummy = DimM7; k = evaluate(DimDummy, stringa); M7[k] = valuea; }
function setM8(stringa, valuea) { DimDummy = DimM8; k = evaluate(DimDummy, stringa); M8[k] = valuea; }
function setM9(stringa, valuea) { DimDummy = DimM9; k = evaluate(DimDummy, stringa); M9[k] = valuea; }

function getM0(stringa) { DimDummy = DimM0; k = evaluate(DimDummy, stringa); return M0[k]; }
function getM1(stringa) { DimDummy = DimM1; k = evaluate(DimDummy, stringa); return M1[k]; }
function getM2(stringa) { DimDummy = DimM2; k = evaluate(DimDummy, stringa); return M2[k]; }
function getM3(stringa) { DimDummy = DimM3; k = evaluate(DimDummy, stringa); return M3[k]; }
function getM4(stringa) { DimDummy = DimM4; k = evaluate(DimDummy, stringa); return M4[k]; }
function getM5(stringa) { DimDummy = DimM5; k = evaluate(DimDummy, stringa); return M5[k]; }
function getM6(stringa) { DimDummy = DimM6; k = evaluate(DimDummy, stringa); return M6[k]; }
function getM7(stringa) { DimDummy = DimM7; k = evaluate(DimDummy, stringa); return M7[k]; }
function getM8(stringa) { DimDummy = DimM8; k = evaluate(DimDummy, stringa); return M8[k]; }
function getM9(stringa) { DimDummy = DimM9; k = evaluate(DimDummy, stringa); return M9[k]; }

function evaluate(DimDummy, stringa)
{    set_str = split(stringa,",");
    n=set_str.length;
    set_numb = newArray(n);
    for(j=0; j<n; j++) set_numb[j] = parseFloat(set_str[j]);
    k = 0;
    for(v=n-1; v>0; v=v-1)
    {    m = 1;
        for(d=v; d>0; d--) m = m * DimDummy[d];
        k = k + set_numb[v] * m;
    }
    k = k + set_numb[0];
    return k;
}
// END OF FUNCTIONS ******************************************

#2

I was wondering why you suggest this. I’ve found using images as multi-dimensional arrays very nice, as you can sort your data across multiple dimensions, and by saving the image, you have a single file with all of the information. It also makes for some neat data visualization. If you are getting bogged down with the macro flipping between windows, you can either store a set of data in a 1D array and dump it into the image row by row, or a much better solution is to run the macro in batch mode, so that ImageJ isn’t trying to handle windows as all (the data is deposited into the image matrix directly).


#3

Hi @GiacomoDiaz,

There is always more elegant, and while I agree with @Llamero that using images makes it easy and nice because you can “visualise” your arrays, I have a suggestion to make your approach a bit more streamlined.


/* Creates new 1D array that will house nD data
 * the first 1+n elements contain the number of 
 * dimensions and the size of each dimension
 * eg a 4x4x3x6x7 array would start with
 * (4,4,4,3,6,7, ...) the ... containing the actual values
 */
function createMatrix(dims) {
	size1D = 1;
	nDims = dims.length;
	for(i=0; i<nDims;i++) {
		size1D *= dims[i];
	}
	arr = newArray(size1D+1+nDims);
	arr[0] = nDims;
	for(i=0; i<nDims;i++) {
		arr[i+1] = dims[i];
	}

	return arr;
}

/* Returns the value at a given array index */
function getMatrixValue(mat, pos) {
	D = getDims(mat);
	pos = getPos(pos, D);
	return mat[pos+mat[0]+1];
}

/* Sets the value at the current Array index */
function setMatrixValue(mat,pos, val) {
	//Pos is an array of the same number of dims as the array
	D = getDims(mat);
	pos = getPos(pos, D);
	print("Position", pos);
	mat[pos+mat[0]+1] = val;

	return mat;
}

/* Convenience function to get the number of dimensions of the array */
function getDims(mat) {
	D = newArray(mat[0]);
	for(i=1;i<=mat[0];i++) {
		D[i-1] = mat[i];
	}
	return D;
}

/* returns the position of the nD index as a 1D index */
function getPos(posA, dims) {
	pos=0;
	for(i=0 ; i<dims.length ; i++) {
		fac = 1;
		for(j=i+1 ; j<dims.length ; j++) {
			fac *= dims[j];
		}
		pos+= fac*posA[i];
	}
	
	return pos;
}


/*
 * quick demo to see that it is filling them correctly.
 */
 
 
D = newArray(2,2,2,3);
mat = createMatrix(D);
Array.print(mat);
o=1;
for(i=0;i<2;i++) {
	for(j=0;j<2;j++) {
		for(k=0;k<2;k++) {
			for(l=0;l<3;l++) {
				P = newArray(i,j,k,l);
				
				mat = setMatrixValue(mat, P, o++);
				pos = getPos(P,D);
				print("Pos", pos);
				Array.print(P);Array.print(mat);
			}
		}	
	}
}

This way you can have the matrix as an actual Array variable in your code, makes it a little easier on the eyes and removes the limit of 10 Matrices.

@Llamero, how many dimensions can we put in images using the macro language? I’d reckon 5 (x,y,c,z,t), so this approach is indeed useful in the off case that you might need >6D arrays in the macro language

NOTE: @GiacomoDiaz, I would probably recommend that you move to a more suitable real scripting language if that’s the kind of thing you are going to be doing often :wink:

Best

Oli


#4

If I need a LOT of dimensions, one solution would be to just write a plugin where nDimensions becomes effectively limited only by RAM. In the macro code, one way I’ve solved this in the past is to do the same trick you suggested, where two dimensions are represented in a single row (array) in the image. To prevent any accidents later on (such as a few months down the road I decided to change the size of one of the dimensions), I would have the macro reserve the first column to encode the actual dimension sizes. That way, when I did the analysis in R, it would read this header, and know exactly how to convert the 2D matrix into a hyper-dimensional matrix, no matter what I did down the road. The more you can use a computer to keep you from making silly mistakes, the better.

One more nice thing about encoding data as an image; things like moving averages, low pass filters, derivatives, etc., all become standard image operations (i.e. mean filter, Gaussian blur, edge detection, etc.). This allows for the iniital data processing to also be part of the macro.


#5

Oli,
thanks. That’s what I was looking for.
Giacomo