1

I wrote an imageJ script to color and merge a series of black and white images. The script saves both the unmerged colored images and merged colored images. Everything works beautifully when I'm running in debug mode and step through the script. When I run it for real, however, it occasionally saves a couple of the original black and whites instead of the resulting colored image. All the merged images appear to be fine.

Why would everything work fine in debug mode but fail during regular usage?

Below is my code :

// Choose the directory with the images
dir = getDirectory("Choose a Directory ");

// Get a list of everything in the directory
list = getFileList(dir);

// Determine if a composite directory exists.  If not create one.
if (File.exists(dir+"/composite") == 0) {
    File.makeDirectory(dir+"/composite")
}

// Determine if a colored directory exists.  If not create one.
if (File.exists(dir+"/colored") == 0) {
    File.makeDirectory(dir+"/colored")
}

// Close all files currently open to be safe
run("Close All");

// Setup options
setOption("display labels", true);
setBatchMode(false);

// Counter 1 keeps track of if you're on the first or second image of the tumor/vessel pair
count = 1;
// Counter 2 keeps track of the number of pairs in the folder
count2 = 1;
// Default Radio Button State
RadioButtonDefault = "Vessel";
// Set Default SatLevel for Contrast Adjustment
// The contrast adjustment does a histogram equalization.  The Sat Level is a percentage of pixels that are allowed to saturate.  A larger number means more pixels can saturate making the image appear brighter.
satLevelDefault = 2.0;

// For each image in the list
for (i=0; i<list.length; i++) {
    // As long as the name doesn't end with / or .jpg 
    if (endsWith(list[i], ".tif")) {
        // Define the full path to the filename
        fn = list[i];
        path = dir+list[i];
        // Open the file    
            open(path);
        // Create a dialog box but don't show it yet
        Dialog.create("Image Type");
        Dialog.addRadioButtonGroup("Type:", newArray("Vessel", "Tumor"), 1, 2, RadioButtonDefault)
        Dialog.addNumber("Image Brightness Adjustment", satLevelDefault, 2, 4, "(applied only to vessel images)")
        // If it's the first image of the pair ...
        if (count == 1) {
            // Show the dialog box
            Dialog.show();
            // Get the result and put it into a new variable and change the Default Radio Button State for the next time through
            if (Dialog.getRadioButton=="Vessel") {
                imgType = "Vessel";
                RadioButtonDefault = "Tumor";
            } else {
                imgType = "Tumor";
                RadioButtonDefault = "Vessel";
            }
        // If it's the second image of the pair
        } else {
            // And the first image was a vessel assume the next image is a tumor
            if (imgType=="Vessel") {
                imgType="Tumor";
            // otherwise assume the next image is a vessel
            } else {
                imgType="Vessel";
            }
        }
        // Check to see the result of the dialog box input
        // If vessel do this
        if (imgType=="Vessel") {
            // Make image Red
            run("Red");
            // Adjust Brightness
            run("Enhance Contrast...", "saturated="+Dialog.getNumber+" normalize");
            // Strip the .tif off the existing filename to use for the new filename
            fnNewVessel = replace(fn,"\\.tif","");
            // Save as jpg
            saveAs("Jpeg", dir+"/colored/"+ fnNewVessel+"_colored");
            // Get the title of the image for the merge
            vesselTitle = getTitle();
        // Othersie do this ... 
        } else {
            // Make green
            run("Green");
            // Strip the .tif off the existing filename to use for the new filename
            fnNewTumor = replace(fn,"\\.tif","");
            // Save as jpg
            saveAs("Jpeg", dir+"/colored/"+ fnNewTumor+"_colored");
            // Get the title of the image for the merge
            tumorTitle = getTitle();
        }
    // If it's the second in the pair ...
    if (count == 2) {
        // Merge the two images
        run("Merge Channels...", "c1="+vesselTitle+" c2="+tumorTitle+" create");
        // Save as Jpg
        saveAs("Jpeg", dir+"/composite/composite_"+count2);
        // Reset the number within the pair counter
        count = count-1;
        // Increment the number of pairs counter
        count2 = count2+1;
   // Otherwise
    } else {
    // Increment the number within the pair counter
    count += 1;
    }
    }
}
agf1997
  • 2,668
  • 4
  • 21
  • 36

2 Answers2

0

Not sure why I'd need to do this but adding wait(100) immediately before saveAs() seems to do the trick

agf1997
  • 2,668
  • 4
  • 21
  • 36
  • This is due to a [race condition](http://stackoverflow.com/questions/34510/what-is-a-race-condition) with running `Merge Channels`. It must be running on its own thread, so sometimes it finishes before `saveAs` and sometimes it doesn't. When you step through the code you're always giving it time to complete so you never see the race condition come up. – hineroptera Jun 05 '15 at 14:46
  • `wait(100)` is not guaranteed to always work (if the `Merge Channels` call takes less than 100ms you could hit this race condition again). Off the top of my head the easiest thing to do would be to poll `IJ.macroRunning()`. So after your `run("Merge Channels..." ..)` line, put `while(IJ.macroRunning()) { /* Wait for macro to end */ }`. That should be safer, I believe. – hineroptera Jun 05 '15 at 14:53
  • @hinerm thanks. If you want to make this its own answer I'll remove mine about wait(100). One last question, what's the syntax to wait for a macro to end? Would it just be 'wait(run("Merge Channels ..."))' ? – agf1997 Jun 05 '15 at 15:58
  • There's really no way to use [wait()](https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#wait%28%29) in this way. You would have to know the run time of "Merge Channels" before actually running it, and pass that value to `wait()`. So you're stuck polling - I put example syntax in my answer. Let me know if it's not clear though. – hineroptera Jun 05 '15 at 16:28
  • Is there a way to apply this within the basic imageJ macro language? – agf1997 Aug 09 '17 at 16:46
0

The best practice in this scenario would be to poll IJ.macroRunning(). This method will return true if a macro is running. I would suggest using helper methods that can eventually time out, like:

/** Run with default timeout of 30 seconds */
public boolean waitForMacro() {
  return waitForMacro(30000);
}

/**
 * @return True if no macro was running. False if a macro runs for longer than
 *         the specified timeOut value.
 */
public boolean waitForMacro(final long timeOut) {
    final long time = System.currentTimeMillis();

    while (IJ.macroRunning()) {
        // Time out after 30 seconds.
        if (System.currentTimeMillis() - time > timeOut) return false;
    }

    return true;
}

Then call one of these helper methods whenever you use run(), open(), or newImage().

Another direction that may require more work, but provide a more robust solution, is using ImageJ2. Then you can run things with a ThreadService, which gives you back a Java Future which can then guarantee execution completion.

hineroptera
  • 769
  • 3
  • 10