Workshop: Automation of image analysis tasks with ImageJ and MRI Cell Image Analyzer Montpellier RIO Imaging Volker Baecker 14.03.2008 volker.baecker@mri.cnrs.fr 1/26
Table of Contents 1. Introduction...3 1.1 The ImageJ macro language Hello World...3 1.2 Integrating macros into the ImageJ interface...4 1.2.1 Startup macros...4 1.2.2 Macros as plugins...4 1.2.3 Macros as menu items...5 1.2.4 Calling macros from action bars...6 1.2.5 Running macros from toolsets...7 2. Writing an image type converter batch...9 2.1 Using the ImageJ macro language...9 2.1.1 Macro recorder and Multiple Image Processor...9 2.1.2 Programming the loop...10 2.2 Using MRI Visual Scripting...12 3. Measuring intensities inside and outside a masked region...13 3.1 Getting the basic steps from the macro recorder...14 3.2 Resetting the results table...14 3.3 Adding the loop and loading images belonging together...15 3.4 Closing images no longer needed and freeing memory...16 3.5 Saving the results...16 4. Interactive visual scripts...17 5. Measuring perpendicular segments along a line...18 6. Writing a 2d region growing segmentation tool macro...22 volker.baecker@mri.cnrs.fr 2/26
1. Introduction In this workshop you will learn how to automate image analysis tasks using ImageJ. There are three mechanisms that allow to create automated procedures in ImageJ. Maybe the most simple is the MRI Visual Scripting plugin that allows to create applications from existing operations using drag and drop. The second one is the ImageJ macro language. The third possibilty is to write and integrate custom plugins. In this workshop we will only talk about the first two possibilties, the visual scripting and the ImageJ macro language. Once a custom procedure has been created it can be integrated into ImageJ in different ways. It can appear as a menu item, or as a button and a keyboard shortcut can be associated with it. 1.1 The ImageJ macro language Hello World A short introduction to the ImageJ macro language can be found on http://rsb.info.nih.gov/ij/developer/macro/macros.html and a list of functions available in the macro language on http://rsb.info.nih.gov/ij/developer/macro/functions.html. As dictated by tradition, the first thing we will do is to write a macro that prints out «Hello World!». Use Plugins>New to create a new macro. In the dialog enter the name «Hello World» and select the type macro. Illustration 1: Creating a new macro. In the text editor enter command print(«hello World»);. You can run the macro using Macros>Run Macro or by pressing ctrl+r. Create a folder workshop-macros and save the macro using File>Save As. You can use Plugins>Macros>Run to run any saved macro. Or you can use Plugins>Macros>Install to put the macro into the Macros menu during one session (until you close ImageJ). Open the Hello World macro again, using Plugins>Edit. We will now define a keyboard shortcut to run the macro. There can be multiple macros in the same textfile. In this case each macro starts with the keyword macro, followed by the name of the macro in quotation marks. After the name but still within the quotation marks you can define a keyboard shortcut in brackets, for example [f11]. The body of the macro must then be surrounded with the brackets { and. Save the modified macro and reinstall it. For the keyboard shortcut to work, an ImageJ window must have the focus. Click on the ImageJ launcher window and press f11. volker.baecker@mri.cnrs.fr 3/26
macro "Hello World [f11]" { print("hello World!"); Text 1: The hello world macro. Let's add a second hello world macro that draws the text into an image. Edit the hello world file as shown in Text 2. macro "Hello World [f11]" { print("hello World!"); macro "Hello World 2 [f12]" { newimage("welcome", "8-bit White", 800, 150, 1); setfont("serif", 100, "antialiased"); setcolor(255,255,0); drawstring("hello world!", 150, 100); Save the new macro, install it and run it. Text 2: A graphical hello world has been added. 1.2 Integrating macros into the ImageJ interface. 1.2.1 Startup macros Macros defined in the file StartupMacros.txt in the macros folder will be automatically installed when ImageJ is started. They will appear in the Plugins>Macros menu and the keyboard shortcuts will be working as well. Copy the two Hello World macros into the StartupMacros.txt file. If StartupMacros.txt contains a macro named AutoRun, this macro will be automatically executed each time ImageJ is started. macro 'AutoRun' { run("hello World 2"); Text 3: Automatically running a command when ImageJ starts up. 1.2.2 Macros as plugins One way to add our macro command permanently to ImageJ is to add it as a plugin. Files that are in the plugin folder or in one of its subfolders and that contain an underscore in their name are regarded as plugins and appear in the Plugins menu. For this usage each macro has to be in its own file and the shortcut keys defined within the macro are not taken into account. However a shortcut for the plugin can be defined afterwards. Create a subfolder mri-workshop in the plugins folder, create a file Hello_World.txt containing the first macro and a file Hello_World_2.txt containing the second macro. After you restarted ImageJ you find a menu entry Plugins>mri-workshop containing volker.baecker@mri.cnrs.fr 4/26
the two macros. You can define the keyboard shortcuts using Plugins>Shortcuts>Create Shortcut. Select the Hello World plugin from the list and enter f11 as shortcut key. This will create an entry in the file IJ_Prefs.txt in the ImageJ base-folder and the shortcuts will remain when you restart ImageJ. 1.2.3 Macros as menu items Using the file IJProps.txt in the ij.jar in the ImageJ base-folder, you can change and add menu items. You can modify the context menu that opens when you right-click on an image and almost all submenus, like for example Process>Filters. Make a copy of IJProps.txt with a different name. If after a modification of IJProps ImageJ doesn't come up anymore you rename the copy back to IJProps.txt. Open the file IJProps.txt and try to install the Hello World 2 macro in the File>New submenu. Be careful to give it a different name from the name it has in the Plugins>MRI-workshop menu, otherwise you will create an endless loop when you call it from its new place. The command you need, to call the macro is ij.plugin.hotkeys("hello World 2"). The command names in the ImageJ menus must be unique, since they can directly be called from a macro using the run command. If you want to place the same command in different menus, you can add spaces behind the name for all but the first menu. # Plugins installed in the File/New submenu new01="image...[n]",ij.plugin.commands("new") new02="text Window[N]",ij.plugin.NewPlugin("text") new03="internal Clipboard",ij.plugin.Clipboard("show") new04="system Clipboard[V]",ij.plugin.Clipboard("showsys") new05=new06="hello World 2 ",ij.plugin.hotkeys("hello World 2") Text 4: Adding a command that calls a macro to the File>New menu. volker.baecker@mri.cnrs.fr 5/26
Illustration 2: The macro command appears in the File>New menu. 1.2.4 Calling macros from action bars The Action Bar plugin from Jerome Mutterer (http://imagejdocu.tudor.lu/members/jmutterer/action_bar/) allows to create toolbars with custom buttons for which the commands can be configured. Start it from Plugins>Action Bar>Action Bar. Create an action bar with one row and two buttons. Use Hello_ as name of the action bar. Illustration 3: The Action Bar plugin. After you pressed OK the macro editor will be opened and you can edit the action bar. label = the tooltip text that will be displayed when the mouse pointer is over the button volker.baecker@mri.cnrs.fr 6/26
Automation of image analysis tasks with ImageJ and MRI Cell Image Analyzer icon = the icon image for the button action = one of the following possibilities: run_macro_file (run a macro file from the macros folder or one of its subfolders) install_macro (install a macro file from the macros folder or one of its subfolders) run_macro_string (a macro command string) arg = the name of the macro file or the macro command string After you made changes save the macro and run it using Macros>Run Macro. The macro will be saved to plugins\actionbar. After you restarted ImageJ it will appear in the corresponding menu. Each action bar has a subfolder in the folder plugins\actionbar\icons. Put the icons you want to use into this folder. // Action Bar description file :Hello_ run("action Bar","/plugins/ActionBar/Hello_.txt"); exit(); <line> <button> 1 line 1 label=hello World icon=hello_/globe.png action=run_macro_string arg=run("hello World"); <button> 2 line 1 label=hello World 2 icon=hello_/globe.png action=run_macro_string arg=run("hello World 2") </line> // end of file Text 5: The definition of the Hello action bar. Illustration 4: The resulting action bar. Illustration 5: Another action bar. 1.2.5 Running macros from toolsets The rightmost button in the ImageJ launcher window switches between different sets of tools that can be activated or run using the buttons after the color picker tool. You can create your own toolsets. Create a file «Hello World.txt» in the folder macros/toolsets. A toolset macro must either contain Tool or Action Tool in its name. In the first case, if you click the button the current tool changes and active tool is called each time the user clicks into an image. In the second case the macro is called directly when the button is pressed. The name of the macro must be followed by a volker.baecker@mri.cnrs.fr 7/26
character and a definition of the icon image to be displayed on the button. You can find a description of the syntax for defining the icon images here: http://rsb.info.nih.gov/ij/developer/macro/macros.html You can create the icon from an image using the «Image to tool icon» macro from: http://rsb.info.nih.gov/ij/macros/tools/. The image must have a size of 16x16 pixel and it must not contain more than 16 colors. Download the macro and put it into plugins>utilities, then restart ImageJ. Load the globe.png image and convert it to an 8-bit color image with 16 colors, then start the plugin. macro "Unused Tool - C037" { macro "Hello World Action Tool - C68bD6bD6cD7bD8aD8bD9aD9bDa1C347D00D04D06D07D10D11D14D20D 21D2dD30D31D40D41D50D60D70CcddD25D29D2aD35D43D44D49D4cD52 D76D81D94Da3DabDbaDc4Dc6Dc8C712D3eDaeDcdDdbDdcDe9DeaCbbcD 26D28D33D42D5bD62D78D83D85D91D92D93D95DacDb5DbbC569D09D2c D51D6eD7eD80D8eD90Da0Db1DbdDc1Dc2DccDd2Dd3De4De5De6De7De8 CeeeD34D45D4bD53D54D55D5aD64D65D71D98Da4Da9DaaDb3Db6Db8Db 9Dc5C9bcD17D18D24D2bD3cD59D5dD69D9dDa5Db2Dc3Dc7DcaDd5Dd9C 569D05D08D12D13D15D1cD22D23D32D4eD5eD7cD8cD9cD9eCdefD36D3 bd46d47d56d57d61d63d66d67d73d74d75da2ca00d8fd9fdafdbedbfd cedcfdddddeddfdebdecdeedefdf5df6df7df8df9dfadfbdffcbcdd27 D37D38D48D58D68D6dD72D77D7dD82D84D8dDd6Dd7Dd8C78aD0aD16D1 ad1bd3dd4dd5cdaddc9dd4ddacfffd39d3ad4ad86d87d88d96d97da6d a7da8db4db7c8acd19d6ad79d7ad89d99dbcdcb" { print("hello World!"); macro "Hello World 2 Action Tool - C68bD6bD6cD7bD8aD8bD9aD9bDa1C347D00D04D06D07D10D11D14D20D 21D2dD30D31D40D41D50D60D70CcddD25D29D2aD35D43D44D49D4cD52 D76D81D94Da3DabDbaDc4Dc6Dc8C712D3eDaeDcdDdbDdcDe9DeaCbbcD 26D28D33D42D5bD62D78D83D85D91D92D93D95DacDb5DbbC569D09D2c D51D6eD7eD80D8eD90Da0Db1DbdDc1Dc2DccDd2Dd3De4De5De6De7De8 CeeeD34D45D4bD53D54D55D5aD64D65D71D98Da4Da9DaaDb3Db6Db8Db 9Dc5C9bcD17D18D24D2bD3cD59D5dD69D9dDa5Db2Dc3Dc7DcaDd5Dd9C 569D05D08D12D13D15D1cD22D23D32D4eD5eD7cD8cD9cD9eCdefD36D3 bd46d47d56d57d61d63d66d67d73d74d75da2ca00d8fd9fdafdbedbfd cedcfdddddeddfdebdecdeedefdf5df6df7df8df9dfadfbdffcbcdd27 D37D38D48D58D68D6dD72D77D7dD82D84D8dDd6Dd7Dd8C78aD0aD16D1 ad1bd3dd4dd5cdaddc9dd4ddacfffd39d3ad4ad86d87d88d96d97da6d a7da8db4db7c8acd19d6ad79d7ad89d99dbcdcb" { newimage("welcome", "8-bit White", 800, 150, 1); setfont("serif", 100, "antialiased"); setcolor(255,255,0); drawstring("hello world!", 150, 100); Text 6: Defining a toolset for the ImageJ toolbar. Restart ImageJ and click on the >> button. You find a new menu item «Hello World». Select it and two buttons with the globe icon appear on the toolbar. volker.baecker@mri.cnrs.fr 8/26
Illustration 6: The Hello World toolset appears in the ImageJ toolbar. 2. Writing an image type converter batch Our first task will be to write an automatic image type converter. The converter shall take a folder of 16-bit greyscale or RGB-color images as input and convert each image to an 8-bit greyscale image. 2.1 Using the ImageJ macro language 2.1.1 Macro recorder and Multiple Image Processor We first need to know the command to convert an image to 8bit. The macro recorder writes all actions executed by ImageJ to a text file that you can install and run as a macro afterwards. Start the recording from Plugins>Macros>Record. Open an rgb or 16-bit greyscale image and convert it to 8- bit. In the recorder window you'll find the command run("8-bit");. Using the run command you can call all ImageJ menu item commands from a macro. Remove everything except for the convertion command from the recorder window. Enter the name of the macro: «Convert to 8-bit» and press the create button. Revert the opened image (ctrl+r) and run the macro from macros>run macro. If everything works as expected save the macro into the macro folder. Illustration 7: The convert to 8-bit macro. You can now use the Multiple Image Processor plugin from http://ciar.rcm.upr.edu/projects/imageprocessor/multiples to apply the macro to all images in a folder. Run the plugin from plugins>utilities>multiple Image Processor. volker.baecker@mri.cnrs.fr 9/26
Illustration 8: The Multiple Image Processor plugin. Choose plant (manual) as input folder and work/out as output folder. Select the macro file and press ok. Each image in the folder will be opened, displayed and converted and the result will be written to the output folder. The macro can of course contain any sequence of commands. 2.1.2 Programming the loop We might need more control of the processing as is provided by the «Multiple Image Processor». We're going to write the processing loop for all images in a folder ourselves now. inputfolder = getdirectory("8-bit converter - Choose the input folder!"); outputfolder = getdirectory("8-bit converter - Choose the output folder!"); images = getfilelist(inputfolder); for (i=0; i<images.length; i++) { inputpath = inputfolder + images[i]; open(inputpath); run("8-bit"); outputpath = outputfolder + images[i]; save(outputpath); close(); Text 7: A first version of the convert to 8 bit macro. We first need to get the input and output folders from the user. This can be done with the command getdirectory(<string>). We will store the answers in the variables inputfolder and outputfolder. Now we need a list of the files in the input folder. The command getfilelist(<dir>) can be used to get the list. The for loop runs from 0 to the length of the list of images 1. In each iteration the variable i is increased by one. The operator +, if applied to strings, concatenates two strings. In this way the absolute path of the current input image is constructed from the input folder and the filename. volker.baecker@mri.cnrs.fr 10/26
The open command opens loads the image. Then the convertion is run. The absolute output path is calculated in a similar way as the input path and the converted image is saved. Save and run the macro. It works but it has some problems: each opened image is displayed, which is useless in a batch application if there is a file in the folder that is not an image the macro will stop with an error we can't see the progress in the end we don't know what has been done by the macro The first problem can be solved with the command setbatchmode(<boolean>). Call it with true before the loop to activate the batch mode and with false after the loop to deactivate the batch mode. To solve the second problem we will check if the file extension corresponds to an image file type. In order not to mess up our code we will write a function that checks the file extension and call it from our main loop. function isimage(filename) { extensions = newarray("tif", "tiff", "jpg", "bmp"); result = false; for (i=0; i<extensions.length; i++) { if (endswith(tolowercase(filename), "." + extensions[i])) result = true; return result; Text 8: The isimage function answers true if the file has tif, tiff, jpg or bmp as extension. To solve the third problem we can use the integrated showprogress(<current>,<total>) function. However we need to count the number of images among the files in the folder first. To do this we write a second function. function countimages(array) { result = 0; for (i=0; i<array.length; i++) { if (isimage(array[i])) result++; return result; Text 9: A function to count the number of images in a list of filenames. To solve the last problem we will print out the name of each written image to a log file that will be opened after the processing of all images finished. volker.baecker@mri.cnrs.fr 11/26
inputfolder = getdirectory("8-bit converter - Choose the input folder!"); outputfolder = getdirectory("8-bit converter - Choose the output folder!"); setbatchmode(true); images = getfilelist(inputfolder); imagecount = countimages(images); notice = "converted: \n"; for (i=0; i<images.length; i++) { showprogress(i, imagecount); inputpath = inputfolder + images[i]; if (isimage(inputpath)) { open(inputpath); run("8-bit"); outputpath = outputfolder + images[i]; save(outputpath); close(); notice = notice + outputpath + "\n"; setbatchmode(false); print(notice); exit(); Text 10: The modified macro using the functions defined above. 2.2 Using MRI Visual Scripting We will now create the same convert to 8-bit application using MRI Visual Scripting. Select the MRI Tools toolset and click on the Visual Scripting button. Illustration 9: The MRI Visual Scripting launcher. To create a new application run Applications>new... and enter the name of the new application. A tile representation and a box representation of the application are opened. For the moment the box is empty. Open the list of all available operations from Operations>all. Scroll down to the convert image type operation and drag it from the box. Click on the questionmark to get a help text for the operation. Drop it into the application box. Click on the O-button to see the options of the operation. The only option is the output type. Add the open image operation before the convert image operation. Click on the P-button of the convert image type operation to connect the input of the operation wilth the output of the open image operation. Add a show image operation at the end of the application and connect it, then run the application on an RGB image. To run an application click on the central button with the applications's name in the tile-representation. The application opens a file browser and asks the user for an image. It opens the image, converts it to 8-bit and displays it. Now we need to add a loop and instead of showing the result image we need to save it to disk. volker.baecker@mri.cnrs.fr 12/26
To create the loop add either the operation «for each image do» or the operation «for each image in list do» to the beginning of the application and the operation foreach image end to the end of the application. Connect the end operation with the do operation. Connect the «open image» operation with the «do» operation. The «open» operation will then get the filename from the do operation in each iteration of the loop. Both do operations let the user select a number of files by either selecting all the files or by selecting a folder. If a folder is selected all images in the folder and in all subfolders will be used. The second do operation gives the user the possibility to eliminate files from the list. This way he can add in a root folder and then remove the images that should not be treated. Run the application and select 3 or 4 RGB images. Now remove the «show image» operation from the application by clicking on the white cross in the upper right corner. Replace it with the «save image» operation and connect its input image parameter to the «convert image» operation. Connect its path parameter either to the do operation or to the «open image» operation. Open the options of the «save image» operation. We need to tell the operation where to save the result. You can either use the browse button to select an output folder or you put a name in the output folder field and select «create in source folder». Use for example «converted» as output folder and select «create in source folder». We should now save the application. Right click in the top area of the application tile and select «save as» from the context menu. Create a new folder workshop in the _applications folder and save the application there. There will now be a menu item «workshop» containing the «convert to 8-bit» application in the applications>applications menu. Illustration 10: The convert to 8-bit application as a visual script. 3. Measuring intensities inside and outside a masked region We will now create a macro that reads in two images. One image of nuclei and one image of the cytoplasm. The macro will create a mask from the nuclei, subtract the background in the cytoplasm image and measure the intensities inside and outside the nuclei in the cytoplasm image. volker.baecker@mri.cnrs.fr 13/26
You will learn how to: Automation of image analysis tasks with ImageJ and MRI Cell Image Analyzer open multiple images belonging together run visual scripting operations from a macro use the particle analyzer from a macro add entries to a results table save measurement result in a way that they can be opened with a spreadsheet program afterwards 3.1 Getting the basic steps from the macro recorder We will execute the basic steps needed to accomplish our application and record them with the macro recorder. You can fill in commands manually in the recorder window. You can create a macro from the recorder window in between and run it to test your changes. Start the macro recorder and open the image «A4 dapi 1.tif». Open the threshold adjuster and create a mask of the nuclei. For the script keep only the setautothreshold() and «convert to mask» commands. Run «fill holes» to get rid of the holes in the mask. Then run the «particle analyzer» to exclude the small object that is not a nucleus. Don't display the results, we are only interested in the new mask in which objects too small are excluded. Create a selection from the mask. Open the image «A4 Rhod 1.tif». Run the «find and subtract background» operation. Remark that this generates a «call» command in the macro recorder. The call command can be used to call any static public java method. call("operations.operation.run", "find and subtract background", "1; 1; 2; 0.05"); Text 11: Visual scripting operations are run by the call command. Transfer the selection to that image. Set the measurements, so that they include the integrated density and the area and measure the selection. Invert the selection an measure again. Now we did all the steps to get the informations we wanted. 3.2 Resetting the results table Since later on we will build a loop to treat multiple images around our code, we should put the «set measurements» command to the very top of the macro, so that it will only be run once before the loop is entered. When the macro is run it will add measurements to a opened results table. So we should clear the results table before writing measurements into it. To clear a results table manually use Edit>Clear Results in the window of the results table. run("set Measurements...", "area mean min integrated redirect=none decimal=3"); run("clear Results"); Text 12: Setting the measurements and clearing the results table. volker.baecker@mri.cnrs.fr 14/26
3.3 Adding the loop and loading images belonging together The next thing we are going to change is to add the loop to treat all images in a folder. Since now we need two images, the nuclei and the cytoplasm, we will automatically read in the cytoplasm image for each nuclei image. We define two variables containing the part of the filename that identifies an image as nuclei or cytoplasm image. Then we ask the user for the source folder. We get the file list of all files in the source folder. Using the keyword var we define the filelist as a global variable. This way it is directly accessible everywhere and we don't need to pass it as an argument to a function. Write a function that counts the number of nuclei images and a function that answers an array of all images containing a text in the name. Use the second function to get a list of all nuclei images. nucleiname = "dapi"; cytoplasmname = "Rhod"; inputfolder = getdirectory("8-bit converter - Choose the input folder!"); var allimages; allimages = getfilelist(inputfolder); var numberofimages = 0; numberofimages = countnucleiimages(); nucleiimages = getimagescontaining(nucleiname); Text 13: Getting a list of the nuclei images. function countnucleiimages() { count = 0; for(i=0; i<allimages.length; i++) { if (indexof(allimages[i], nucleiname)!=-1 ) count++; return count; Text 14: Counting the nuclei images. function getimagescontaining(text) { result = newarray(numberofimages); index = 0; for (i=0; i<allimages.length; i++) { if (indexof(allimages[i], text)!= -1) { result[index] = allimages[i]; index++; return result; Text 15: Getting all filenames containing a text from a list of filenames. Now write a loop around the processing part of the macro that opens all nuclei images and the corresponding cytoplasm images. Use the replace function to create the cytoplasm image filename from the nuclei image filename. volker.baecker@mri.cnrs.fr 15/26
for (i=0; i<nucleiimages.length;i++) { inputpath = inputfolder + nucleiimages[i]; open(inputpath); setautothreshold(); run("convert to Mask"); run("fill Holes"); run("analyze Particles...", "size=800-infinity circularity=0.00-1.00 show=masks"); run("create Selection"); open(inputfolder + replace(nucleiimages[i], nucleiname, cytoplasmname)); call("operations.operation.run", "find and subtract background", "1; 1; 2; 0.05"); run("restore Selection"); run("measure"); run("make Inverse"); run("measure"); Text 16: The main processing loop. 3.4 Closing images no longer needed and freeing memory To save memory we should close image no longer needed as soon as possible. ImageJ always works on the active image. To apply a command to another image we can get the ID of the currently active image with the command getimageid(). The command selectimage(id) makes the image with the given id the active image. Use call("java.lang.system.gc"); to initiate a garbage collection that frees memory no longer used. This call should be the first command in the loop. 3.5 Saving the results To save the results table we first ask the user for the output folder in the beginning of the macro. Then when the measurements have been made the results table can be selected with selectwindow("results");. The results can be saved as a text file using saveas("measurements", "" + outputfolder + "results.txt");. The content will be tab separated and can be loaded into a spreadsheet program. selectwindow("results"); saveas("measurements", "" + outputfolder + "results.txt"); Text 17: Saving the results table. Instead of saving the results table we can write a custom report. Let us write a function that writes a report containing: the name of the image the total intensity within the nuclei the total intensity within the cytoplasm the ratio of the two intensities volker.baecker@mri.cnrs.fr 16/26
Use the File.open(<filename>) function to create a new text file. You can write into the file using print(<file>, <text>). Separate columns with a tabulator by using «\t» and rows by a newline using «\n». function reportresults(filename) { reportfile = File.open(filename); headerline = "filename\tinside intensity\toutside intensity\tratio\n"; print(reportfile, headerline); for (i=0; i<nucleiimages.length; i++) { line = nucleiimages[i] + "\t"; int1 = getresult("intden", i+0); int2 = getresult("intden", i+1); line = line + int1 + "\t"; line = line + int2 + "\t"; line = line + (int2 / int1); line = line + "\n"; print(reportfile, line); File.close(reportFile); Text 18: A function that writes a custom report. 4. Interactive visual scripts Let us see how to put some interactivity into a visual script. We want to open a list of images one after the other. Let the user make a rectangular selection. Threshold the region within the selection and set find all objects in this region. The objects should be marked with a point selection. Then the user should have the possibility to correct the point selection and to accept or reject the result. volker.baecker@mri.cnrs.fr 17/26
Illustration 11: An interactive application. You can use «show results table» instead of «report measurements» if you want to see the result directly instead of writing it to a file. 5. Measuring perpendicular segments along a line Let the user make a line selection and enter a distance d and a length l. The macro shall create line selections in the distance d of length l along the firsth line selection and measure the intensities of these line selections. You will learn how to get user input for a macro create selections (rois) from a macro volker.baecker@mri.cnrs.fr 18/26
We will first do the work and then care about the user input. In a first version we just use variables to define the distance and length. First we have to make sure that the image has a line selection and display an error message if it has none. distance = 200; radius = 50; getline(x1, y1, x2, y2, linewidth); if (x1 == -1) { exit("this macro needs a line selection"); Text 19: If there is no line selection the macro quits and displays an error message. We will calculate a perpendicular line segment and move it along the line selection. We define the four coordinates of the line segment as global variables. Since we are working in radians when calculating angles we define the constant pi as a global variable as well. We can create a perpendicular line segment in the start point of the line selection in the following way: Create a horizontal line segment of the desired size with its middle point in 0,0. Calculate the angle alpha of the line selection with the x axis. The angle is the atan of the gradient. And the gradient is (y2-y1) / (x2-x1). Turn the new line segment around 0,0 by 90 + alpha. move the middle point of the line segment to the start point of the line selection. We implement the described algorithm in a function called newperpendicularlineseg that takes the start and end point of the line selection and the radius of the new segment as input parameters. Call the new function and try it by drawing the line segment into the image. Use drawline to draw the segment and updatedisplay to make the change visible. volker.baecker@mri.cnrs.fr 19/26
var segxs; var segys; var segxe; var segye; var pi = 3.14159265; newperpendicularlineseg(x1, y1, x2, y2, radius); drawline(segxs,segys, segxe, segye); updatedisplay() ; exit(); function newperpendicularlineseg(xs, ys, xe, ye, radius) { // create segment with middle point in 0,0, horizontal to x-axis segxs = - radius; segys = 0; segxe = radius; segye = 0; // calculate angle alpha of line selection m = (ye-ys) / (xe-xs); alpha = atan(m); // make new line segment perpendicular to line selection angle = alpha + (pi/2); segxsnew = cos(angle) * segxs - sin(angle) * segys; segysnew = sin(angle) * segxs + cos(angle) * segys; segxenew = cos(angle) * segxe - sin(angle) * segye; segyenew = sin(angle) * segxe + cos(angle) * segye; // move new line segment to start point of line selection segxs = segxsnew + xs; segys = segysnew + ys; segxe = segxenew + xs; segye = segyenew + ys; Text 20: Creating a line segment perpendicular to the line selection. Now we are going to move the line segment along the line selection in steps of the given distance until we get behind the end of the line selection. We will write a function that moves the segment by a given distance along the line selection. Then we will create a loop that runs while the segment and the line selection intersect. volker.baecker@mri.cnrs.fr 20/26
function movelineseg(x1, y1, x2, y2, distance) { dx = x2 x1; dy = y2 y1; n = sqrt((dx*dx) + (dy*dy)); xinc = (dx / n) * distance; yinc = (dy / n) * distance; segxs = segxs + xinc; segys = segys + yinc; segxe = segxe + xinc; segye = segye + yinc; Text 21: Move the line segment along the line selection by distance. function intersects(x1, y1, x2, y2) { result = false; den = (segye-segys)*(x2-x1) (segxe-segxs)*(y2-y1); ua = ((segxe-segxs)*(y1-segys) - (segye-segys)*(x1-segxe)) / den; ub = ((x2-x1)*(y1-segys)-(y2-y1)*(x1-segxs)) / den; if (ua>=-1 && ub>-1 && ua<=1 && ub<=1) result = true; return result; Text 22: Test if the segment intersects the line selection. newperpendicularlineseg(x1, y1, x2, y2, radius); while(intersects(x1, y1, x2, y2)) { drawline(segxs,segys, segxe, segye); updatedisplay() ; movelineseg(x1, y1, x2, y2, distance); exit(); Text 23: The main loop, drawing line sgments along the line selection. Before we replace the drawing with creating a selection and measuring it we create a dialog that lets the user enter the distance between the line segments and the radius of the line. Dialog.create("measure perpendicular line segments"); Dialog.addNumber("distance: ", 100); Dialog.addNumber("radius: ", 50); Dialog.show(); distance = Dialog.getNumber(); radius = Dialog.getNumber(); Text 24: A dialog to read in distance and radius. To replace the drawing with the ceation of a selection use makeline. Then instead of measuring directly we add each selection to the roi manager. The user can then decide to measure them, to draw them, to save them and so on. This is done with the command roimanager(«add»);. volker.baecker@mri.cnrs.fr 21/26
newperpendicularlineseg(x1, y1, x2, y2, radius); while(intersects(x1, y1, x2, y2)) { makeline(segxs,segys, segxe, segye); roimanager("add"); updatedisplay(); movelineseg(x1, y1, x2, y2, distance); exit(); Text 25: Creating the selections and adding them to the roi manager. 6. Writing a 2d region growing segmentation tool macro We want to create a tool that lets the user click on a point in the image and creates a selection around this point by iteratively adding points for which the intensity doesn't differ more than a threshold value from the current average intensity in the already selected region. We will create the macro as a tool macro. The threshold delta value can be set by the user as an option of the macro. We will use a data structure called a queue to manage the points for which the neighbors have still to be checked. A queue is a structure where entries are consumed from one side while new entries are added at the other side. We will mark the points added to the selection and the points already rejected in a temporary image that we call mask. We count the current iteration of the algorithm. We want use it, however a more sophisticated region growing could change the threshold delta as a function of the iteration. We declare the global variables to keep the described data. var currentregionsize = 0; var currentregionintden = 0; var threshold = 10; var queue; var mask; var inputimage; var iteration = 0; Text 26: The global variables for the region grow macro. Since the variables will keep their values after the macro ran, we have to initialize them at the beginning. In the same time we remove an existing selection from the image. function initialize() { run("select None"); currentregionsize = 0; currentregionintden = 0; iteration = 0; Text 27: Initailizing the global variables. Remark that we do not initialize the threshold in the initialize method. The threshold will per volker.baecker@mri.cnrs.fr 22/26
default have the value it has been initialized with in the beginning and the user will be able to change it by double clicking the tool icon. To achive this we have to add a macro with the same name as our tool but with the word Options added. macro "Region Growing Tool Options" { threshold = getnumber("threshold delta:", threshold); Text 28: This macro allows the user to set the threshold by double clicking the tool icon. In the region growing tool we first call the initialize method. Then we get the current image and the current cursor location, i.e. the point on which the user clicked. We switch to batch mode and create the mask that will keep track of the points already visited. Since there are no dynamic data structures available in the scripting language we will model the queue as a string. We initialize the queue with the point the user clicked on and call the function growregion. macro "Region Growing Tool - C0a0L18f8L818f" { initialize(); inputimage = getimageid(); getcursorloc(x, y, z, flags); setbatchmode(true); newimage("mask", "8-bit", getwidth(), getheight(), nslices); mask = getimageid(); selectimage(inputimage); queue = "" + x + "," + y + ";"; growregion(); Text 29: First part of the region growing macro. Grow region will run as long as there are points in the queue left. If a point is in the queue his neighbors still have to be examined. To give a sign of life while the macro is running we write the current lengths of the queue to the status bar of the ImageJ launcher window. Using the split function, we get the first point as an array of the x and y coordinates from the queue. Using further string functions the first point is removed from the queue in the function removefirst(). We add the point to the result region. To do so we mark the pixel with the value 50 in the mask. We increment the region size and update the current average. We get the intensity of the point with the function getpixel(<x>,<y>);. Then we call the function testandaddneighbors for each of the 4-connected neighbors of the pixel. volker.baecker@mri.cnrs.fr 23/26
function growregion() { while(lengthof(queue)!=0) { showstatus("queue: " + lengthof(queue)); points = split(queue, ";"); point = split(points[0], ","); removefirst(); addtoselection(point, 50); currentregionsize++; currentregionintden = currentregionintden + getpixel(parseint(point[0]),parseint(point[1])); testandaddneighbor(point, -1, 0); testandaddneighbor(point, 1, 0); testandaddneighbor(point, 0, -1); testandaddneighbor(point, 0, 1); iteration++; Text 30: Grow region runs as long as there are points in the queue. testandaddneighbors first checks if the pixel has been examined before. Pixels that have already been checked will be marked with a value 100 in the mask when they will not be part of the result region and a value 50 if they are accepted in the result region. If the ccordinates are out of the image region the function returns as well. Then the function checks if the intensity of the pixel lies within the the interval defined by the current average intensity plus/minus the threshold. If this is the case the point will be added to the result region, otherwise he will be marked as already checked. In the first case the point will be added to the queue, as well, to avoid that the same point gets into the queue multiple times. volker.baecker@mri.cnrs.fr 24/26
function testandaddneighbor(point, xoff, yoff) { neighborx = parseint(point[0]) + xoff; neighbory = parseint(point[1]) + yoff; if (alreadytested(neighborx, neighbory)) return; if (neighborx<0 neighborx>getwidth()-1) return; if (neighbory<0 neighbory>getheight()-1) return; neighborint = getpixel(neighborx, neighbory); average = currentregionintden / currentregionsize; if (neighborint>average-threshold && neighborint<average+threshold) { addtoqueue(neighborx, neighbory); point = newarray(2); point[0] = neighborx; point[1] = neighbory; addtoselection(point, 50); else { point = newarray(2); point[0] = neighborx; point[1] = neighbory; addtoselection(point, 100); function addtoqueue(x, y) { queue = queue + x + "," + y + ";"; function alreadytested(x,y) { result = false; selectimage(mask); if (getpixel(x,y)==50 getpixel(x,y)==100) result = true; selectimage(inputimage); return result; Text 31: The testandaddneighbor function. Finally we threshold the mask image to get only the pixel with intensity 50. We fill holes in the mask so that regions totally surrounded by our result region will be added to the result region. Then we create a selection from the mask and transfer it to the input image. selectimage(mask); setthreshold(50, 50); run("convert to Mask"); run("fill Holes"); run("create Selection"); close(); run("restore Selection"); setbatchmode(false); Text 32: The second half of the region growing macro. Try to run it with different threshold values on the image cells.tif. Remark that threshold values can be floating point numbers in this case. Change the macro to see what happens on the mask while it is created. The macro will run much volker.baecker@mri.cnrs.fr 25/26
slower when not in batch mode. volker.baecker@mri.cnrs.fr 26/26