Edit page History

Multithreaded Image Processing in JavaScript

    == Purpose ==

    An example Javascript ImageJ script illustrating how to create java Threads for concurrent execution.

    The example illustrates as well the use of functions with variable arguments.

    Code

    // Albert Cardona 20081109
    // This code is released under the public domain
    //
    // A multithreading framework for ImageJ in javascript
    //
    // First a function named doItMultithreaded shows how to run a process in
    // parallel, in as many threads as CPU cores. The key idea is that of iterating
    // over a list of numbers from start to end, where each index in the list means
    // something: for example, a line of pixels in an image.
    //
    // The example simply prints a list of numbers in a multithreaded way.
    //
    // Then, the multithreading part and the printing part are separated into the
    // "multithreader" function and the "printer" function. The "printer" is
    // invoked by passing it to the "multithreader" function as an argument.
    //
    // Finally, a more real-world example is show, in which lines of an image, 10
    // lines at a time, are processed independelty in separate threads and filled
    // with random pixel values, using the "multithreader" framework function.
    //
    // Please note that generating random values has so little overhead that a
    // multithreaded setup does not pay off for small images, no matter how many
    // lines at a time are processed together. This is intended as an example of
    // what could be done, for example, for very computationally expensive filters
    // like a large median filter or a gaussian with a large standard deviation.
    //
    // Have fun! 
    
    importClass(Packages.ij.IJ);
     
    // Print all numbers from start to end (inclusive), multithreaded
    function doItMultithreaded(start, end) {
        var threads = java.lang.reflect.Array.newInstance(java.lang.Thread.class, java.lang.Runtime.getRuntime().availableProcessors());
        var ai = new java.util.concurrent.atomic.AtomicInteger(start);
        var body = {
            run: function() {
                for (var i = ai.getAndIncrement(); i <= end; i = ai.getAndIncrement()) {
                    IJ.log("i is " + i);
                    java.lang.Thread.sleep(100); // NOT NEEDED, just to pretend we are doing something!
                }
            }
        }
        // start all threads
        for (var i = 0; i < threads.length; i++) {
            threads[i] = new Thread(new Runnable(body)); // automatically as Runnable
            threads[i].start();
        }
        // wait until all threads finish
        for (var i = 0; i < threads.length; i++) {
            threads[i].join();
        }
    }
     
    // execute like:
    // doItMultithreaded(0, 10);
     
    // Now, abstract away the multithreading framework into a function
    // that takes another function as argument:
    function multithreader(fun, start, end) {
        var threads = java.lang.reflect.Array.newInstance(java.lang.Thread.class, java.lang.Runtime.getRuntime().availableProcessors());
        var ai = new java.util.concurrent.atomic.AtomicInteger(start);
        // Prepare arguments: all other arguments passed to this function
        // beyond the mandatory arguments fun, start and end:
        var args = new Array();
        var b = 0;
        IJ.log("Multithreading function \"" + fun.name + "\" with arguments:\n  argument 0 is index from " + start + " to " + end);
        for (var a = 3; a < arguments.length; a++) {
            args[b] = arguments[a];
            IJ.log("  argument " + (b+1) + " is " + args[b]);
            b++;
        }
        var body = {
            run: function() {
                for (var i = ai.getAndIncrement(); i <= end; i = ai.getAndIncrement()) {
                    // Execute the function given as argument,
                    // passing to it all optional arguments:
                    fun(i, args);
                }
            }
        }
        // start all threads
        for (var i = 0; i < threads.length; i++) {
            threads[i] = new java.lang.Thread(new java.lang.Runnable(body)); // automatically as Runnable
            threads[i].start();
        }
        // wait until all threads finish
        for (var i = 0; i < threads.length; i++) {
            threads[i].join();
        }
    }
     
    // The actual desired effect: the printer
    function printer(i) {
        IJ.log("i is " + i);
    }
     
    // Execute like (uncomment!):
    // multithreader(printer, 0, 10);
     
     
    // Above, notice how we are passing the printer function as an argument to the
    // multithreader function, which is executed simply by putting parenthesis to
    // its name. Simple!
     
     
    // Now, armed with the multithreader, we can parallelize any function we want:
    // for example, filling each pixel of an image with a random value.
    //
    // The key for best performance is to break down the task in significant
    // chunks. Multithreading for each pixel makes little sense--to much overhead
    // wipes away the gain. Multithreading for one line, same thing: a random value
    // is not so costly to compute, still too much overhead. So we are going to
    // multithread the processing of for example 100 lines at a time:
     
     
    // Takes a starting line and a number of lines to process,
    // and sets their pixels to a random value
    
    function randomizer(line, args) {
        // Obtain and check the arguments:
        if (args.length < 5) {
            IJ.log("randomizer needs at least 5 arguments: line, pix, width, height, n_lines and rand");
            return;
        }
        var pix = args[0];
        var width = args[1];
        var height = args[2];
        var n_lines = args[3];
        var rand = args[4];
        for (var y = line; y < height && y < height + n_lines; y++) {
            var offset = y * width;
            for (var x = 0; x < width; x++) {
                pix[offset + x] = rand.nextFloat();
            }
        }
    }
     
    // Test: create a new image, fill it with random values, and show it
    width = 512;
    height = 512;
    imp = new Packages.ij.ImagePlus("Random", new Packages.ij.process.FloatProcessor(width, height));
    pix = imp.getProcessor().getPixels();
    importClass(java.util.Random);
    rand = new Random(java.lang.System.currentTimeMillis());
    block_size = 100; // number of lines to be processed together
    n_blocks = ((height / block_size)|0) + 1; // casting to int with bitwise or to zero
     
    // Execute the randomizer in multithreaded fashion:
    //   - At the top row, the three arguments for the multithreading framework
    //   - At the bottom row, the N arguments for the function to parallelize
    multithreader(randomizer, 0, n_blocks,
                  pix, width, height, block_size, rand);
     
    // Show the image:
    imp.getProcessor().setMinAndMax(0, 1); // random values between 0 and 1
    imp.show();
    

    See also