Probe Software Users Forum

Software => CalcImage => Topic started by: Ben Buse on May 06, 2025, 04:54:49 AM

Title: imagej plotting scripts
Post by: Ben Buse on May 06, 2025, 04:54:49 AM
Apologies if posted elsewhere, digging out these old scripts

Here's a basic load all the .txt files in a folder into imagej (which assumes using calcimage maps have been exported to ascii txt)

It calibrates the distance - ready for further analysis, and selects a colour palette - for viewing

What's frustrating is I resort to a input box to ask the user for the pixel size, has anyone else found a better solution for reading in the resolution

input = getDirectory("Input directory");

suffix = ".TXT";  //you only want to apply to tiff images, no need to ask

pixelsize = getNumber("pixel size in microns", 1);

processFolder(input);

function processFolder(input) {
    list = getFileList(input);
    for (i = 0; i < list.length; i++) {
        if(File.isDirectory(input + list[i]))  //if it's a directory, go to subfolder
            processFolder("" + input + list[i]);
        if(endsWith(list[i], suffix))  //if it's a tiff image, process it
            processFile(input, list[i], pixelsize);
        //if it's neither a tiff nor a directory, do nothing
    }
}

function processFile(input, file,pixelsize) {

//here, you put the real processingprocessFolder(input);
run("Text Image... ", "open="+"'"+input+file+"'");
run("Set Scale...", "distance=1 known="+pixelsize+" pixel=1 unit=um");
w = getWidth();
h = getHeight();
w1 = w+100;
h1 = h+50;
run("16_colors");
}

Title: Re: imagej plotting scripts
Post by: Probeman on May 06, 2025, 07:48:02 AM
Quote from: Ben Buse on May 06, 2025, 04:54:49 AMHere's a basic load all the .txt files in a folder into imagej (which assumes using calcimage maps have been exported to ascii txt)
...
What's frustrating is I resort to a input box to ask the user for the pixel size, has anyone else found a better solution for reading in the resolution

Why not just read the GRD files directly instead of converting them to TXT?  Then you'll have the X and Y (and Z) min and max limits.

Here's some code Jon Fellowes posted a while back:

https://smf.probesoftware.com/index.php?topic=1510.msg11663#msg11663

It needs a bit more work to read the X, Y and Z limits (and handle the orientation), but that would not be difficult...

Note that because the GRD file must have cartesian X and Y coordinates (to be a valid GRD file for Surfer), we rely on a small file called GRDInfo.INI which contains the orientation and units:

For Cameca (cartesian) it looks like this:

[Stage]
X_Polarity=0
Y_Polarity=0
Stage_Units=um

For JEOL (anti-cartesian) it looks like this:

[Stage]
X_Polarity=-1
Y_Polarity=-1
Stage_Units=mm

Title: Re: imagej plotting scripts
Post by: Ben Buse on May 06, 2025, 08:32:41 AM
Yes that's good where you can read the .grd files directly like in R or python. I'd be interested if anyone has done this for imagej, I don't think this is possible. Interestingly my earlier script using surfer .bas scripter https://smf.probesoftware.com/index.php?topic=739.0 (https://smf.probesoftware.com/index.php?topic=739.0) to generate .img files also carried the resolution information - although labelled 'pixel' it was mm. I mean having a input box is not really a problem, I just wondered whether other people did it differently. Such as along the lines of this https://forum.image.sc/t/reading-different-text-file-formats/63125 (https://forum.image.sc/t/reading-different-text-file-formats/63125) which could read from a specific txt file containing extra information created by calcimage, or read from .prbimg file for the resolution can be calculated from the registration part of the file
Title: Re: imagej plotting scripts
Post by: Ben Buse on May 07, 2025, 11:13:28 AM
Ok so looked at reading variable from file

To read from prbimg file and calculate resolution

filestring = File.openAsString("");
List.setList(filestring);
X1Pixel = List.get("X1Pixel");
X2Pixel = List.get("X2Pixel");
X1Real = List.get("X1Real");
X2Real = List.get("X2Real");
PixelDistance = parseFloat(X1Pixel) - parseFloat(X2Pixel);
RealDistance = parseFloat(X1Real) - parseFloat(X2Real);
RealDistance2=Math.sqrt(Math.sqr(RealDistance));
PixelResolution = RealDistance2/PixelDistance;

So if put into my scrip which loads oxide.txt files - it's complicated because .txt files have different naming structure to .prbimg files with the calcimg files naming consisting of mdb name in front. So have to loop for matching files. It might be easier if calcimage as well as producing ascii txt file also produce a text file with same name and different extension containing resolution. Or imagej script prompts user to select a single prbimg file as above. Eitherway below script works importing files into imagej and reading resolution from prbimg files

input = getDirectory("Input directory");
output = getDirectory("Output directory");
ispresent = 0;
pixelsize = 0;

suffix = "Oxide.TXT";  //you only want to apply to tiff images, no need to ask

//pixelsize = getNumber("pixel size in microns", 1);

    list = getFileList(input);
    for (i = 0; i < list.length; i++) {
        //if(File.isDirectory(input + list[i]))   //if it's a directory, go to subfolder
           // processFolder("" + input + list[i]);
        if(endsWith(list[i], suffix))  { //if it's a tiff image, process it
            //print(list[i]);
            list2=getFileList(input);
            for (x = 0; x < list2.length; x++){
                 //print(list2[x]);
                 if(endsWith(list2[x], ".prbimg"))  { //if it's a tiff image, process it
                     print("list2");
                     print(list2[x]);
                     fileprbimg=list2[x];
                     filestring=list[i];
                     if(endsWith(filestring, "Oxygen_Oxide.TXT")){
                         filestring=replace(filestring,"_Oxygen_Oxide","COMPO");}
                     if(endsWith(filestring, "Total_Oxide.TXT")){
                         filestring=replace(filestring,"_Oxygen_Oxide","COMPO");}
                     fileprbimg = replace(fileprbimg,".prbimg","");
                     print("list1");
                     print(list[i]);
                     ispresent=0;
                     ispresent=indexOf(filestring,fileprbimg);
                     print(ispresent);
                     if(ispresent >= 0){
                         print("yes");
                         filestring2 = File.openAsString(input+fileprbimg+".prbimg");
                         List.setList(filestring2);
                         X1Pixel = List.get("X1Pixel");
                         X2Pixel = List.get("X2Pixel");
                         X1Real = List.get("X1Real");
                         X2Real = List.get("X2Real");
                         PixelDistance = parseFloat(X1Pixel) - parseFloat(X2Pixel);
                         RealDistance = parseFloat(X1Real) - parseFloat(X2Real);
                         RealDistance2=Math.sqrt(Math.sqr(RealDistance));
                         PixelResolution = RealDistance2/PixelDistance;
                         print(PixelResolution);
                         pixelsize=PixelResolution*1000;
                         processFile(input, output, list[i], pixelsize);}}
                     }
                 }
        //processFile(input, output, list[i], pixelsize);}
        //if it's neither a tiff nor a directory, do nothing
    }

function processFile(input, output, file,pixelsize) {
print(input);
print(file);
print(pixelsize);
//pixelsize=parseFloat(pixelsize)*1000;
print(pixelsize);
//here, you put the real processingprocessFolder(input);
run("Text Image... ", "open="+"'"+input+file+"'");
run("Set Scale...", "distance=1 known="+pixelsize+" pixel=1 unit=um");
}


StackName = getString("Enter name of Stack, no spaces", "Oxide_Stack");
run("Images to Stack", "name="+StackName+" title=[] use");
print(pixelsize);
//run("Set Scale...", "distance=1 known="+pixelsize+" pixel=1 unit=um");
print(pixelsize);
run("Stack to Hyperstack...", "order=xyczt(default) channels="+nSlices+" slices=1 frames=1 display=Color");
Stack.getDimensions(width,height,channels,slices,frames);
max2=channels+1;
for (i=1; i<max2; i++) {
Stack.setChannel(i);
run("Enhance Contrast", "saturated=0.35");
}
name = getTitle;
saveAs("Tiff", output+name);

setAutoThreshold("Default");
//run("Threshold...");
setThreshold(-1000000000000000000000000000000.0000, 150.0000);
run("NaN Background", "stack");

for (i=1; i<max2; i++) {
Stack.setChannel(i);
run("Enhance Contrast", "saturated=0.35");
}
name = getTitle;
name2 = "threshold"
saveAs("Tiff", output+name2+name);
Title: Re: imagej plotting scripts
Post by: John Donovan on May 07, 2025, 11:56:23 AM
Quote from: Ben Buse on May 07, 2025, 11:13:28 AMSo if put into my scrip which loads oxide.txt files - it's complicated because .txt files have different naming structure to .prbimg files with the calcimg files naming consisting of mdb name in front. So have to loop for matching files. It might be easier if calcimage as well as producing ascii txt file also produce a text file with same name and different extension containing resolution. Or imagej script prompts user to select a single prbimg file as above. Eitherway below script works importing files into imagej and reading resolution from prbimg files

Maybe it would be better if I added the resolution info at the top of the txt files?

If that is OK, I'll put the info from the GRD files in there (X and Y and units from GRDInfo.ini).
Title: Re: imagej plotting scripts
Post by: Ben Buse on May 07, 2025, 01:20:29 PM
Quote from: John Donovan on May 07, 2025, 11:56:23 AMMaybe it would be better if I added  the resolution info at the top of the txt files?

If that is OK, I'll put the info from the GRD files in there (X and Y and units from GRDInfo.ini).
Thanks John for suggestion.
Unfortunately that would be problematic as imagej textimage expects no header, so would be best as currently is (which is why we designed the output as it is  :) )
Title: Re: imagej plotting scripts
Post by: JonF on May 08, 2025, 02:20:09 AM
Quote from: Ben Buse on May 07, 2025, 01:20:29 PMUnfortunately that would be problematic as imagej textimage expects no header, so would be best as currently is (which is why we designed the output as it is  :) )

Very much this! Please don't alter the text image output!
Title: Re: imagej plotting scripts
Post by: Ben Buse on May 08, 2025, 03:36:50 AM
Here's a different version of above (the above finds the prbimg that corresponds to the oxide.TXT file - and for oxygen and total it uses the compo.TXT file - assuming there is one)

Here the MAP number is extracted from the .txt file - assuming it's the only 5 digit number in filename
            file1 = list[i];
            array1 = split(file1,"_.");
            MapNum=Array.filter(array1, "(^[0-9]{5}$)");
            MapNum1=MapNum[0];

And the resolution is extracted from a prbimg file with the same map number

            x = 0;
            ispresent = -1;
            do{
                if(endsWith(list2[x], ".prbimg"))  {
                    //print(x);
                    //print(list2[x]);
                    ispresent=indexOf(list2[x],MapNum1);
                    //print(ispresent);
                    if(ispresent >= 0){
                        print("yes");
                        filestring2 = File.openAsString(input+list2[x]);
                        List.setList(filestring2);
                        X1Pixel = List.get("X1Pixel");
                        X2Pixel = List.get("X2Pixel");
                        X1Real = List.get("X1Real");
                        X2Real = List.get("X2Real");
                        PixelDistance = parseFloat(X1Pixel) - parseFloat(X2Pixel);
                        RealDistance = parseFloat(X1Real) - parseFloat(X2Real);
                        RealDistance2=Math.sqrt(Math.sqr(RealDistance));
                        PixelResolution = RealDistance2/PixelDistance;
                        print(PixelResolution);
                        pixelsize=PixelResolution*1000;
                        processFile(input, output, list[i], pixelsize);
                    }
                }
                x=x+1;
            } while(x < list2.length && ispresent==-1)

To give complete

input = getDirectory("Input directory");
output = getDirectory("Output directory");
ispresent = -1;
pixelsize = 0;

suffix = "Oxide.TXT";

list = getFileList(input);
    for (i = 0; i < list.length; i++) {
        //print(list[i]);
        if(endsWith(list[i], suffix))  {
            file1 = list[i];
            array1 = split(file1,"_.");
            MapNum=Array.filter(array1, "(^[0-9]{5}$)");
            MapNum1=MapNum[0];
            list2=getFileList(input);
            x = 0;
            ispresent = -1;
            do{
                if(endsWith(list2[x], ".prbimg"))  {
                    //print(x);
                    //print(list2[x]);
                    ispresent=indexOf(list2[x],MapNum1);
                    //print(ispresent);
                    if(ispresent >= 0){
                        print("yes");
                        filestring2 = File.openAsString(input+list2[x]);
                        List.setList(filestring2);
                        X1Pixel = List.get("X1Pixel");
                        X2Pixel = List.get("X2Pixel");
                        X1Real = List.get("X1Real");
                        X2Real = List.get("X2Real");
                        PixelDistance = parseFloat(X1Pixel) - parseFloat(X2Pixel);
                        RealDistance = parseFloat(X1Real) - parseFloat(X2Real);
                        RealDistance2=Math.sqrt(Math.sqr(RealDistance));
                        PixelResolution = RealDistance2/PixelDistance;
                        print(PixelResolution);
                        pixelsize=PixelResolution*1000;
                        processFile(input, output, list[i], pixelsize);
                    }
                }
                x=x+1;
            } while(x < list2.length && ispresent==-1)
        }
    }

function processFile(input, output, file,pixelsize) {
//print(input);
//print(file);
//print(pixelsize);
//pixelsize=parseFloat(pixelsize)*1000;
print(pixelsize);
//here, you put the real processingprocessFolder(input);
run("Text Image... ", "open="+"'"+input+file+"'");
run("Set Scale...", "distance=1 known="+pixelsize+" pixel=1 unit=um");
}


StackName = getString("Enter name of Stack, no spaces", "Oxide_Stack");
run("Images to Stack", "name="+StackName+" title=[] use");
//print(pixelsize);
//run("Set Scale...", "distance=1 known="+pixelsize+" pixel=1 unit=um");
//print(pixelsize);
run("Stack to Hyperstack...", "order=xyczt(default) channels="+nSlices+" slices=1 frames=1 display=Color");
Stack.getDimensions(width,height,channels,slices,frames);
max2=channels+1;
for (i=1; i<max2; i++) {
Stack.setChannel(i);
run("Enhance Contrast", "saturated=0.35");
}
name = getTitle;
saveAs("Tiff", output+name);

setAutoThreshold("Default");
//run("Threshold...");
setThreshold(-1000000000000000000000000000000.0000, 150.0000);
run("NaN Background", "stack");

for (i=1; i<max2; i++) {
Stack.setChannel(i);
run("Enhance Contrast", "saturated=0.35");
}
name = getTitle;
name2 = "threshold"
saveAs("Tiff", output+name2+name);
Title: Re: imagej plotting scripts
Post by: John Donovan on May 08, 2025, 08:40:03 AM
Quote from: Ben Buse on May 07, 2025, 01:20:29 PM
Quote from: John Donovan on May 07, 2025, 11:56:23 AMMaybe it would be better if I added  the resolution info at the top of the txt files?

If that is OK, I'll put the info from the GRD files in there (X and Y and units from GRDInfo.ini).
Thanks John for suggestion.
Unfortunately that would be problematic as imagej textimage expects no header, so would be best as currently is (which is why we designed the output as it is  :) )

I think I might have a solution for this ImageJ TXT import issue...

After one has quantified a CalcImage mapping project, open the Image Processing | Classify Image menu as seen here:

(https://smf.probesoftware.com/gallery/1_08_05_25_8_13_31.png)

That will open the Classify Image windows as discussed here:

https://smf.probesoftware.com/index.php?topic=1071.0

Then perform a quick clustering operation on the map, using the default parameters (because we don't care about the clustering information). When the cluster calculations are finished (usually a few seconds), CalcImage will output GRD files for each cluster, and also a BMP and ACQ file for the project as seen here:

(https://smf.probesoftware.com/gallery/1_08_05_25_8_41_35.png)

It's this ACQ file that can be used by one's script to read the stage extents, stage orientation and stage units as seen here:

(https://smf.probesoftware.com/gallery/1_08_05_25_8_39_29.png)

The only reason I am suggesting this method is so you would have an ACQ file that has the same name as the CalcImage project file...

How does that sound?
Title: Re: imagej plotting scripts
Post by: John Donovan on May 09, 2025, 09:03:10 AM
OK, I think this is a better solution for importing GRD file quant images into ImageJ... we modified the convert code for GRD to TXT to also output an ACQ file for each GRD file:

(https://smf.probesoftware.com/gallery/1_09_05_25_8_55_30.png)

Now when you run this conversion you get a TXT and ACQ (and BMP) file for each GRD file:

(https://smf.probesoftware.com/gallery/1_09_05_25_8_55_51.png)

The ACQ file contains the stage calibration information along with the units (um or mm) as shown in the previous post. Now each GRD file will have the same name as the TXT and ACQ file and both are ASCII and can be easily read by a script.

Note you'll probably want to copy the GRD files you actually want to convert, to a separate sub folder, so *every* GRD file doesn't get converted. Unless of course, that is what you want!
Title: Re: imagej plotting scripts
Post by: Ben Buse on May 12, 2025, 04:17:46 AM
Thanks for this John, it's great to have parameters for each file.

Below in case it's of interest is reading the new acq file imagej macro

filestring = File.openAsString("");
filestring = replace(filestring,'"',""); // remove quotes
filestring = replace(filestring," ","_"); // remove spaces
List.setList(filestring);
XY1Twips = List.get("Screen_reference_point1_(twips)");
XY2Twips = List.get("Screen_reference_point2_(twips)");
X1Twips = split(XY1Twips,",");
X2Twips = split(XY2Twips,",");
print(X1Twips[0]);
print(X2Twips[0]);
X1Pixel = parseFloat(X1Twips[0]);
X2Pixel = parseFloat(X2Twips[0]);
X1Pixel = X1Pixel*0.0666666667;
X2Pixel = X2Pixel*0.0666666667;
print(X1Pixel);
print(X1Pixel+10);
print(X2Pixel);
print(X2Pixel+10);
XY1Real = List.get("Stage_reference_point1");
XY2Real = List.get("Stage_reference_point2");
X1Real = split(XY1Real,",");
X2Real = split(XY2Real,",");
print(X1Real[0]);
print(X2Real[0]);
PixelDistance = X1Pixel - X2Pixel;
PixelDistance2=Math.sqrt(Math.sqr(PixelDistance));
RealDistance = parseFloat(X1Real[0]) - parseFloat(X2Real[0]);
RealDistance2=Math.sqrt(Math.sqr(RealDistance));
PixelResolution = RealDistance2/PixelDistance2;
print(PixelResolution);
print(PixelResolution*1000);

Giving
input = getDirectory("Input directory");
output = getDirectory("Output directory");
ispresent = -1;
pixelsize = 0;

suffix = "Oxide.TXT";  //type of file to open Oxide or Quant

    list = getFileList(input);
    for (i = 0; i < list.length; i++) {
        if(endsWith(list[i], suffix)) {  //if it's correct image type, process it
            //open associated ACQ file
            file1=list[i];
            print(list[i]);
            print(file1);
            //file1=split(file1,".TXT");
            file1=substring(file1,0,lengthOf(file1)-4);
            print(file1);
            print(input+file1+".ACQ");
            filestring = (input+file1+".ACQ");
            //filestring = File.openAsString(input+file1+".ACQ");
            print(filestring);
            //get pixel resolution
            pixelRes = extractResolution(filestring);
            print(pixelRes);
            processFile(input, output, list[i], pixelRes);
        }
    }

function extractResolution(filestring){
filestring = File.openAsString(input+file1+".ACQ");
print(filestring);
filestring = replace(filestring,'"',"");
filestring = replace(filestring," ","_");
List.setList(filestring);
XY1Twips = List.get("Screen_reference_point1_(twips)");
XY2Twips = List.get("Screen_reference_point2_(twips)");
X1Twips = split(XY1Twips,",");
X2Twips = split(XY2Twips,",");
print(X1Twips[0]);
print(X2Twips[0]);
X1Pixel = parseFloat(X1Twips[0]);
X2Pixel = parseFloat(X2Twips[0]);
X1Pixel = X1Pixel*0.0666666667;
X2Pixel = X2Pixel*0.0666666667;
print(X1Pixel);
print(X1Pixel+10);
print(X2Pixel);
print(X2Pixel+10);
XY1Real = List.get("Stage_reference_point1");
XY2Real = List.get("Stage_reference_point2");
X1Real = split(XY1Real,",");
X2Real = split(XY2Real,",");
print(X1Real[0]);
print(X2Real[0]);
PixelDistance = X1Pixel - X2Pixel;
PixelDistance2=Math.sqrt(Math.sqr(PixelDistance));
RealDistance = parseFloat(X1Real[0]) - parseFloat(X2Real[0]);
RealDistance2=Math.sqrt(Math.sqr(RealDistance));
PixelResolution = RealDistance2/PixelDistance2;
print(PixelResolution);
print(PixelResolution*1000);
return PixelResolution*1000;
}

function processFile(input, output, file,pixelsize) {
//print(input);
//print(file);
//print(pixelsize);
//pixelsize=parseFloat(pixelsize)*1000;
print(pixelsize);
//here, you put the real processingprocessFolder(input);
run("Text Image... ", "open="+"'"+input+file+"'");
run("Set Scale...", "distance=1 known="+pixelsize+" pixel=1 unit=um");
}

imagelist = getList("image.titles");
print(imagelist.length);
if (imagelist.length > 0) {
    if (imagelist.length > 1){
    processStack();
    } else {
    print("only one image");}
} else {
print("no images loaded");}

function processStack(){
StackName = getString("Enter name of Stack, no spaces", "Oxide_Stack");
run("Images to Stack", "name="+StackName+" title=[] use");
//print(pixelsize);
//run("Set Scale...", "distance=1 known="+pixelsize+" pixel=1 unit=um");
//print(pixelsize);
run("Stack to Hyperstack...", "order=xyczt(default) channels="+nSlices+" slices=1 frames=1 display=Color");
Stack.getDimensions(width,height,channels,slices,frames);
max2=channels+1;
for (i=1; i<max2; i++) {
Stack.setChannel(i);
run("Enhance Contrast", "saturated=0.35");
}
name = getTitle;
saveAs("Tiff", output+name);

setAutoThreshold("Default");
//run("Threshold...");
setThreshold(-1000000000000000000000000000000.0000, 150.0000);
run("NaN Background", "stack");

for (i=1; i<max2; i++) {
Stack.setChannel(i);
run("Enhance Contrast", "saturated=0.35");
}
name = getTitle;
name2 = "threshold";
saveAs("Tiff", output+name2+name);
}

So now have the following in ImageJ, making it really easy
(https://smf.probesoftware.com/gallery/453_12_05_25_4_17_18.png)
Title: Re: imagej plotting scripts
Post by: John Donovan on May 12, 2025, 08:38:03 AM
Quote from: Ben Buse on May 12, 2025, 04:17:46 AMSo now have the following in ImageJ, making it really easy

This is awesome.

Could you write up a little step by step procedure for this and post it here for those without programming skills?
Title: Re: imagej plotting scripts
Post by: Ben Buse on May 13, 2025, 04:42:22 AM
Good point,

1. Copy the plugin folder to the plugin folder within ImageJ installation
2. Restart ImageJ if it is open, and should then be visible within ImageJ plugin menu.

To use line options first draw a line on the channel stack (the open window containing all the maps), if double click on line tool can specify line width.

To use the measure option first draw an area on the channel stack - either using rectangle, circle or freehand tool.

(Is it possible to attach files to forum post?; Otherwise can be downloaded from here https://github.com/Benjamin-Buse/PFE-ImageJ/blob/main/PFE_CalcImage.zip (https://github.com/Benjamin-Buse/PFE-ImageJ/blob/main/PFE_CalcImage.zip))

It is now on ImageJ wiki https://imagej.net/plugins/pfe-calcimage (https://imagej.net/plugins/pfe-calcimage)