Speed up ImageJ

imagej
gpu
clij

#1

Hello everyone,

I’m using ImageJ to control the quality of 2D objects in my company.
I wrote a macro that works perfectly.
(open image / calibrate rotation and translation / open a second image : the “perfect” image of the object / substract images / Analyze particles)
If there are particles, there is a defect, otherwise, the product is OK.

This process takes approximately 25 seconds.

I’m using 12000p*12000p uncompressed tif images.
it already runs in batch mode, and i think I cannot simplify it more.

My question is : Can this macro be speeded up with a better PC ?
More RAM ? Or this would does’nt change anything ?

Sorry for my bad English, and sorry if my questions are stupid, I’m a beginner.

Best regards,
Robin


#2

If you post the macro, we can take a look…


#3

Good day Robin,

ImageJ macros are mainly meant for generating a sequence of processing steps. These steps are usually performs by (built-in) ImageJ-plugins. If however, you perform pixelwise operations that are coded by the macro itself, you will observe rather slow operation.

In general, writing an ImageJ-plugin will speed-up the processing but be aware of the fact that if your macro merely calls ImageJ-plugins, speed gain will be minor.

As Gabriel wrote, tell us about the structure of your macro and we shall see…

Regards

Herbie


#4

Thanks, this is my macro :
I guess many of the tasks are not written correctly but i’m not familiar with programming…

macro "AutoRun" {


    //.......................................A RENSEIGNER................................................

    //.....................DONNEES A RECEVOIR LORS DE L'ACQUISITION DU CODE BARRE........................



    //...................................PARAMETRES PAR DEFAUT...........................................
    //.....................................DU POCHOIR CALIBRE............................................

    //................................VALEURS EXACTES NECESSAIRES........................................


    //	X1	:	L'abcisse de la mire 1 (par rapport au bord gauche) (en mm)
    //	Y1	:	L'ordonnee de la mire 1 (par rapport au bord superieur) (en mm)
    //	X2	:	L'abcisse de la mire 2 (par rapport au bord gauche) (en mm)
    //	Y2	:	L'ordonnee de la mire 2 (par rapport au bord superieur) (en mm)


    //Remplacer les valeurs actuelles par les valeurs reelles connues dans les champs suivants



    X1 = 47.5760
    Y1 = 23.9278

    X2 = 500
    Y2 = 333.5404




    //.............................Debut du programme : Ne pas modifier................................


    setBatchMode(true);



    //Calcul de l'angle de la droite mire/mire par rapport A l'horizontale



    open("C:/PROGRAMMEFINAL/PochoirReel.tif");

    run("Set Scale...", "distance=1 known=1 pixel=1 unit=mm global");

    run("Set Measurements...", "area centroid redirect=None decimal=4");

    setOption("BlackBackground", false);
    run("8-bit");
    run("Threshold...");
    setAutoThreshold("Default");
    call("ij.plugin.frame.ThresholdAdjuster.setMode", "B&W");
    wait(1);
    setThreshold(70, 170);
    run("Make Binary");
    run("Open");
    run("Analyze Particles...", "size=0.1-Infinity display exclude summarize");
    wait(1);



    //Obtention coordonnees de toutes les mires


    setResult("X", nResults, 0);
    setResult("X", nResults, 0);
    setResult("X", nResults, 0);
    setResult("X", nResults, 0);
    setResult("X", nResults, 0);
    setResult("X", nResults, 0);
    setResult("X", nResults, 0);
    setResult("X", nResults, 0);
    setResult("X", nResults, 0);
    setResult("X", nResults, 0);


    //Determiner la mire la plus en haut a gauche et la mire la plus en bas a droite
    //Par defaut, il peut y avoir 10 mires MAXIMUM. (On peut en rajouter dans le code si besoin)(si il y a 3 mires, 7 "mires imaginaires" sont creees en 0,0)
    //Si les coordonnees de la mire sont nulles (si il y a moins de 10 mires), la "mire imaginaire" est placee exactement au milieu du pochoir, afin de ne pas changer les MIN et MAX.


    a0 = getResult("X", 0);
    if (a0 != 0) {
        b0 = getResult("Y", 0);
        c0 = a0 + b0;
    } else {
        c0 = 12200;
    }
    a1 = getResult("X", 1);
    if (a1 != 0) {
        b1 = getResult("Y", 1);
        c1 = a1 + b1;
    } else {
        c1 = 12200;
    }
    a2 = getResult("X", 2);
    if (a2 != 0) {
        b2 = getResult("Y", 2);
        c2 = a2 + b2;
    } else {
        c2 = 12200;
    }
    a3 = getResult("X", 3);
    if (a3 != 0) {
        b3 = getResult("Y", 3);
        c3 = a3 + b3;
    } else {
        c3 = 12200;
    }
    a4 = getResult("X", 4);
    if (a4 != 0) {
        b4 = getResult("Y", 4);
        c4 = a4 + b4;
    } else {
        c4 = 12200;
    }
    a5 = getResult("X", 5);
    if (a5 != 0) {
        b5 = getResult("Y", 5);
        c5 = a5 + b5;
    } else {
        c5 = 12200;
    }
    a6 = getResult("X", 6);
    if (a6 != 0) {
        b6 = getResult("Y", 6);
        c6 = a6 + b6;
    } else {
        c6 = 12200;
    }
    a7 = getResult("X", 7);
    if (a7 != 0) {
        b7 = getResult("Y", 7);
        c7 = a7 + b7;
    } else {
        c7 = 12200;
    }
    a8 = getResult("X", 8);
    if (a8 != 0) {
        b8 = getResult("Y", 8);
        c8 = a8 + b8;
    } else {
        c8 = 12200;
    }
    a9 = getResult("X", 9);
    if (a9 != 0) {
        b9 = getResult("Y", 9);
        c9 = a9 + b9;
    } else {
        c9 = 12200;
    }


    //Recherche du "c" le plus faible, et du "c" le plus �lev� (la mire la plus en haut � gauche, et la plus en bas � droite)


    min = minOf(c0, minOf(c1, minOf(c2, minOf(c3, minOf(c4, minOf(c5, minOf(c6, minOf(c7, minOf(c8, c9)))))))));
    max = maxOf(c0, maxOf(c1, maxOf(c2, maxOf(c3, maxOf(c4, maxOf(c5, maxOf(c6, maxOf(c7, maxOf(c8, c9)))))))));


    //Obtention premiere mire

    if (min == c0) {
        x1 = a0;
        y1 = b0;
    }
    if (min == c1) {
        x1 = a1;
        y1 = b1;
    }
    if (min == c2) {
        x1 = a2;
        y1 = b2;
    }
    if (min == c3) {
        x1 = a3;
        y1 = b3;
    }
    if (min == c4) {
        x1 = a4;
        y1 = b4;
    }
    if (min == c5) {
        x1 = a5;
        y1 = b5;
    }
    if (min == c6) {
        x1 = a6;
        y1 = b6;
    }
    if (min == c7) {
        x1 = a7;
        y1 = b7;
    }
    if (min == c8) {
        x1 = a8;
        y1 = b8;
    }
    if (min == c9) {
        x1 = a9;
        y1 = b9;
    }


    //Obtention deuxieme mire

    if (max == c0) {
        x2 = a0;
        y2 = b0;
    }
    if (max == c1) {
        x2 = a1;
        y2 = b1;
    }
    if (max == c2) {
        x2 = a2;
        y2 = b2;
    }
    if (max == c3) {
        x2 = a3;
        y2 = b3;
    }
    if (max == c4) {
        x2 = a4;
        y2 = b4;
    }
    if (max == c5) {
        x2 = a5;
        y2 = b5;
    }
    if (max == c6) {
        x2 = a6;
        y2 = b6;
    }
    if (max == c7) {
        x2 = a7;
        y2 = b7;
    }
    if (max == c8) {
        x2 = a8;
        y2 = b8;
    }
    if (max == c9) {
        x2 = a9;
        y2 = b9;
    }


    makeLine(x1, y1, x2, y2);

    getLine(x1, y1, x2, y2, lineWidth);
    getPixelSize(unit, width, height, depth);
    x1 *= width;
    y1 *= height;
    x2 *= width;
    y2 *= height;
    angle = getAngle(x1, y1, x2, y2);
    length = sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));


    row = nResults();
    setResult("Angle", row, angle);
    setResult("Length", row, length);
    updateResults();

    function getAngle(x1, y1, x2, y2) {
        q1 = 0;
        q2orq3 = 2;
        q4 = 3; //quadrant
        dx = x2 - x1;
        dy = y1 - y2;
        if (dx != 0) angle = atan(dy / dx);
        else if (dy >= 0) angle = PI / 2;
        else angle = -PI / 2;

        angle = (180 / PI) * angle;
        if (dx >= 0 && dy >= 0) quadrant = q1;
        else if (dx < 0) quadrant = q2orq3;
        else quadrant = q4;
        if (quadrant == q2orq3) angle = angle + 180.0;
        else if (quadrant == q4) angle = angle + 360.0;
        return angle;

    }


    //Affectation "alpha" : l'image reelle doit etre tournee de "alpha" degres

    beta = 360 - atan((Y2 - Y1) / (X2 - X1)) * 180 / PI;
    alpha = -(beta - getResult("Angle", nResults - 1));
    run("Rotate... ", "angle=alpha grid=1 interpolation=Bilinear");


    //Calibration de l'echelle


    run("Set Scale...", "distance=1 known=1 pixel=1 unit=mm global");
    run("Make Binary");


    //Recherche position des mires

    run("Clear Results");
    run("Analyze Particles...", "size=0.1-Infinity display exclude summarize");

    setResult("X", nResults, 0);
    setResult("X", nResults, 0);
    setResult("X", nResults, 0);
    setResult("X", nResults, 0);
    setResult("X", nResults, 0);
    setResult("X", nResults, 0);
    setResult("X", nResults, 0);
    setResult("X", nResults, 0);
    setResult("X", nResults, 0);
    setResult("X", nResults, 0);


    //Determiner la mire la plus en haut a gauche et la mire la plus en bas a droite
    //Par defaut, il peut y avoir 10 mires MAXIMUM. (On peut en rajouter dans le code si besoin)(si il y a 3 mires, 7 "mires imaginaires" sont creees en 0,0)
    //Si les coordonnees de la mire sont nulles (si il y a moins de 10 mires), la "mire imaginaire" est placee exactement au milieu du pochoir, afin de ne pas changer les MIN et MAX.


    a00 = getResult("X", 0);
    if (a00 != 0) {
        b00 = getResult("Y", 0);
        c00 = a00 + b00;
    } else {
        c00 = 12200;
    }
    a10 = getResult("X", 1);
    if (a10 != 0) {
        b10 = getResult("Y", 1);
        c10 = a10 + b10;
    } else {
        c10 = 12200;
    }
    a20 = getResult("X", 2);
    if (a20 != 0) {
        b20 = getResult("Y", 2);
        c20 = a20 + b20;
    } else {
        c20 = 12200;
    }
    a30 = getResult("X", 3);
    if (a30 != 0) {
        b30 = getResult("Y", 3);
        c30 = a30 + b30;
    } else {
        c30 = 12200;
    }
    a40 = getResult("X", 4);
    if (a40 != 0) {
        b40 = getResult("Y", 4);
        c40 = a40 + b40;
    } else {
        c40 = 12200;
    }
    a50 = getResult("X", 5);
    if (a50 != 0) {
        b50 = getResult("Y", 5);
        c50 = a50 + b50;
    } else {
        c50 = 12200;
    }
    a60 = getResult("X", 6);
    if (a60 != 0) {
        b60 = getResult("Y", 6);
        c60 = a60 + b60;
    } else {
        c60 = 12200;
    }
    a70 = getResult("X", 7);
    if (a70 != 0) {
        b70 = getResult("Y", 7);
        c70 = a70 + b70;
    } else {
        c70 = 12200;
    }
    a80 = getResult("X", 8);
    if (a80 != 0) {
        b80 = getResult("Y", 8);
        c80 = a80 + b80;
    } else {
        c80 = 12200;
    }
    a90 = getResult("X", 9);
    if (a90 != 0) {
        b90 = getResult("Y", 9);
        c90 = a90 + b90;
    } else {
        c90 = 12200;
    }

    min = minOf(c00, minOf(c10, minOf(c20, minOf(c30, minOf(c40, minOf(c50, minOf(c60, minOf(c70, minOf(c80, c90)))))))));
    max = maxOf(c00, maxOf(c10, maxOf(c20, maxOf(c30, maxOf(c40, maxOf(c50, maxOf(c60, maxOf(c70, maxOf(c80, c90)))))))));


    //Obtention premiere mire

    if (min == c00) {
        x10 = a00;
        y10 = b00;
    }
    if (min == c10) {
        x10 = a10;
        y10 = b10;
    }
    if (min == c20) {
        x10 = a20;
        y10 = b20;
    }
    if (min == c30) {
        x10 = a30;
        y10 = b30;
    }
    if (min == c40) {
        x10 = a40;
        y10 = b40;
    }
    if (min == c50) {
        x10 = a50;
        y10 = b50;
    }
    if (min == c60) {
        x10 = a60;
        y10 = b60;
    }
    if (min == c70) {
        x10 = a70;
        y10 = b70;
    }
    if (min == c80) {
        x10 = a80;
        y10 = b80;
    }
    if (min == c90) {
        x10 = a90;
        y10 = b90;
    }


    //Obtention deuxieme mire

    if (max == c00) {
        x20 = a00;
        y20 = b00;
    }
    if (max == c10) {
        x20 = a10;
        y20 = b10;
    }
    if (max == c20) {
        x20 = a20;
        y20 = b20;
    }
    if (max == c30) {
        x20 = a30;
        y20 = b30;
    }
    if (max == c40) {
        x20 = a40;
        y20 = b40;
    }
    if (max == c50) {
        x20 = a50;
        y20 = b50;
    }
    if (max == c60) {
        x20 = a60;
        y20 = b60;
    }
    if (max == c70) {
        x20 = a70;
        y20 = b70;
    }
    if (max == c80) {
        x20 = a80;
        y20 = b80;
    }
    if (max == c90) {
        x20 = a90;
        y20 = b90;
    }








    //Affectation de la variable e : La distance entre les deux mires (en pixel)


    e = sqrt((x20 - x10) * (x20 - x10) + (y20 - y10) * (y20 - y10));


    //Affectation de la variable E : La distance reelle entre les deux mires (en mm)


    E = sqrt((X2 - X1) * (X2 - X1) + (Y2 - Y1) * (Y2 - Y1));




    run("Set Scale...", "distance=e known=E pixel=1 unit=mm global");


    //Recherche du d�calage : obtention de la position actuelle


    //Affectation de xp et yp : l'image reelle doit etre decalee de xp pixel en x et yp pixels en y
    //Dans la ligne suivante, X et Y sont les coordonnees de la deuxieme mire (en mm)


    xp = (X2 * e / E) - x20;
    yp = (Y2 * e / E) - y20;


    run("Clear Results");
    close("PochoirReel.tif");

    open("C:/PROGRAMMEFINAL/PochoirReel.tif");
    run("Make Binary");
    run("Rotate... ", "angle=alpha grid=1 interpolation=Bilinear");
    run("Translate...", "x=xp y=yp interpolation=None");

    open("C:/PROGRAMMEFINAL/PochoirCAO.tif");
    run("Make Binary");


    //Soustraction des deux images pour faire apparaitre les defauts


    imageCalculator("Subtract create", "PochoirCAO.tif", "PochoirReel.tif");
    run("Make Binary");
    run("Open");

    //Analyse des defauts
    //Les decalages de la machine ont ete supprimes grace a l'operation d'ouverture : erosion + dilatation
    //Seuls les defauts apparaissent desormais


    run("Analyze Particles...", "size=0.1-Infinity display exclude summarize");
    setResult("X", nResults, 0);
    setResult("X", nResults, 0);
    setResult("X", nResults, 0);
    setResult("X", nResults, 0);
    setResult("X", nResults, 0);
    x1 = getResult("X", 0);


    //Affectation des variables : Coordonnees des defauts


    //Test : Au moins un defaut

    if (x1 != 0) {


        y1 = getResult("Y", 0);
        xa = (x1 * e / E - 4);
        ya = (y1 * e / E - 4);
        x2 = getResult("X", 1);
        y2 = getResult("Y", 1);
        xb = (x2 * e / E - 4);
        yb = (y2 * e / E - 4);
        x3 = getResult("X", 2);
        y3 = getResult("Y", 2);
        xc = (x3 * e / E - 4);
        yc = (y3 * e / E - 4);
        x4 = getResult("X", 3);
        y4 = getResult("Y", 3);
        xd = (x4 * e / E - 4);
        yd = (y4 * e / E - 4);
        x5 = getResult("X", 4);
        y5 = getResult("Y", 4);
        xe = (x5 * e / E - 4);
        ye = (y5 * e / E - 4);


        //Affichage des defauts


        selectWindow("PochoirReel.tif");
        run("Invert");
        if (xa != -4) {
            open("C:/PROGRAMMEFINAL/TOOLS/horizontal1.tif");
            open("C:/PROGRAMMEFINAL/TOOLS/vertical1.tif");
            selectWindow("PochoirReel.tif");
            run("Add Image...", "image=vertical1.tif x=xa y=0 opacity=70");
            run("Add Image...", "image=horizontal1.tif x=0 y=ya opacity=70");
        }
        if (xb != -4) {
            open("C:/PROGRAMMEFINAL/TOOLS/horizontal2.tif");
            open("C:/PROGRAMMEFINAL/TOOLS/vertical2.tif");
            selectWindow("PochoirReel.tif");
            run("Add Image...", "image=vertical2.tif x=xb y=0 opacity=70");
            run("Add Image...", "image=horizontal2.tif x=0 y=yb opacity=70");
        }
        if (xc != -4) {
            open("C:/PROGRAMMEFINAL/TOOLS/horizontal3.tif");
            open("C:/PROGRAMMEFINAL/TOOLS/vertical3.tif");
            selectWindow("PochoirReel.tif");
            run("Add Image...", "image=vertical3.tif x=xc y=0 opacity=70");
            run("Add Image...", "image=horizontal3.tif x=0 y=yc opacity=70");
        }
        if (xd != -4) {
            open("C:/PROGRAMMEFINAL/TOOLS/horizontal4.tif");
            open("C:/PROGRAMMEFINAL/TOOLS/vertical4.tif");
            selectWindow("PochoirReel.tif");
            run("Add Image...", "image=vertical4.tif x=xd y=0 opacity=70");
            run("Add Image...", "image=horizontal4.tif x=0 y=yd opacity=70");
        }
        if (xe != -4) {
            open("C:/PROGRAMMEFINAL/TOOLS/horizontal5.tif");
            open("C:/PROGRAMMEFINAL/TOOLS/vertical5.tif");
            selectWindow("PochoirReel.tif");
            run("Add Image...", "image=vertical5.tif x=xe y=0 opacity=70");
            run("Add Image...", "image=horizontal5.tif x=0 y=ye opacity=70");
        }
    }


    //Affichage "POCHOIR OK" si aucun defaut detecte


    if (x1 == 0) {
        open("C:/PROGRAMMEFINAL/TOOLS/POCHOIROK.tif");
        selectWindow("PochoirReel.tif");
        run("Invert");
        run("Add Image...", "image=POCHOIROK.tif x=3100 y=5100 opacity=100");
    }


    //Sauvegarde de l'image avec affichage des defauts

    run("Flatten");
    saveAs("Tiff", "C:/PROGRAMMEFINAL/AffichageDefauts.tif");


    //Fermeture de l'application


    run("Quit");
}

#5

Robin,

this code is a mess.

Although I see a lot of sub-optimum code, I couldn’t find a single operation that may be responsible for the slow performance.

I strongly recommend to let someone who knows coding ImageJ-macros to have a look at the code and optimize it. I’m sure it will help to make the code faster and easier to support.

Perhaps someone of the Forum-administartors can format the macro code that presently is really near to impossible to study.

Regards

Herbie


#6

Done. Formatted the code.


#7

I know.

As the application will need a new PC, i was just wondering if a higher performance PC will run the application faster or not.

Thank you for your time.
Regards
Robin


#8

Thanks Marcel!

Herbie


#9

Of course the speed depends on the CPU-speed but real jumps are only possible by massive multi-threading and this depends on the used plugins because not all of them support multi-threading.

In short, you have to find out yourself which of the calls support multi-threading and most often this requires looking at the source-code.

I don’t expect a great speed jump when using a new PC, except the present one is older than 10 years. More RAM is always a good idea.

Regards

Herbie


#10

Thank you very much for formatting the code !!

This application will run on a production site, and a new PC running the application all day long will be necessary.

I take note for the multi-threading, I’m gonna take a look by myself.

And I will go for a bit more RAM.

Many thanks for the fast and profesionnal answers.

Best regards

Robin


#11

The easiest and most effective thing you can do is reduce the image size. Your macro will run MUCH faster with, for example, 4000x4000 images, which have 1/9th as many pixels to process.


#12

Unfortunately, I need this quality, there is groups of 1, 2 or 4 pixels that i need to see.

Thanks for the tip


#13

Just for the record:

From the description of the task I concluded that a reduction of the image size is out of discussion. Consequently, I didn’t mention it, although it has the greatest impact on the computation time.

Regards

Herbie


#14

What isthe physical dimension of those pixels?


#15

0.05mm
And my total area is 600x600mm,
So im using 12000x12000p images


#16

Robin,
I was intrigued by part of your code:

    //Obtention premiere mire

    if (min == c00) {
        x10 = a00;
        y10 = b00;
    }
    if (min == c10) {
        x10 = a10;
        y10 = b10;
    }
    if (min == c20) {
        x10 = a20;
        y10 = b20;
    }
    if (min == c30) {
        x10 = a30;
        y10 = b30;
    }
    if (min == c40) {
        x10 = a40;
        y10 = b40;
    }
    if (min == c50) {
        x10 = a50;
        y10 = b50;
    }
    if (min == c60) {
        x10 = a60;
        y10 = b60;
    }
    if (min == c70) {
        x10 = a70;
        y10 = b70;
    }
    if (min == c80) {
        x10 = a80;
        y10 = b80;
    }
    if (min == c90) {
        x10 = a90;
        y10 = b90;
    }

This code will go through each of the if statements. Why not replace it with if, else if statements, which should be faster, since it would terminate as soon as the condition is met.

The same thing goes later in your code, where you have a series of if statements comparing the max value.

Ved


#17

Good day Vet,

what you’ve pin-pointed is only one aspect of why I called the code a mess but there is more.
However, as far as I read between the lines of the OP, streamlining the code presently appears no real option.

Regards

Herbie


#18

Bonjour Robin,
Robert @haesleinhuepf Haase presented at the NEUBIAS conference his CLIJ solution that can speed up time consuming operations sometimes up to 10x. Have a look at the documention here: https://clij.github.io/clij-docs/
I see you have some geometrical transforms in your code, those could certainly be accelerated, if your hardware is compatible.
Best regards,
Jerome.


#19

Hi @r.morand @jerome and the others,

thanks for the flowers. :slight_smile: As CLIJ supports basically every recent built-in Intel GPU, you don’t need to buy fancy special hardware. Furthermore, the speedup you can gain is much higher than just 10x.


However, it is decisive that you implement a significant part of your workflow using CLIJ: Push your image once to the GPU, run several processing steps on it and then pull the resulting image back. Only if the workflow is long enough, it pays off. Otherwise the transfer of the image to the GPU and back costs more than you gain. You find my NEUBIAS poster here:

If you need any help in translating your workflows, please let me know. User feedback is very welcome!

Cheers,
Robert


#20

Jerome,

as mentioned before, there are a number of ways to make the OP’s code run faster but the OP tells us that code changes are presently not an option.

My impression is that this is a quite typical case where, sorry to say this, many modern niceties of informatics hit a practical wall.

Regards

Herbie