├── .gitignore ├── src └── main │ ├── resources │ └── plugins.config │ └── java │ ├── AFI_.java │ └── PHANTAST_.java ├── LICENSE ├── pom.xml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | target/ 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | *.settings 11 | *.target 12 | *.project 13 | *.classpath 14 | 15 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 16 | hs_err_pid* 17 | -------------------------------------------------------------------------------- /src/main/resources/plugins.config: -------------------------------------------------------------------------------- 1 | # Name: Phase Contrast Segmentation Toolbox (PHANTAST) 2 | # Author:Nicolas Jaccard 3 | # Version: 0.1-SNAPSHOT 4 | 5 | # A single .jar file can contain multiple plugins, specified in separate lines. 6 | # 7 | # The format is: , "", 8 | # 9 | # If something like ("") is appended to the class name, the setup() method 10 | # will get that as arg parameter; otherwise arg is simply the empty string. 11 | 12 | Plugins>Segmentation, "PHANTAST", PHANTAST_ 13 | #Plugins>Analyze, "Augmented Fluorescence Image", AFI_ 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Nicolas Jaccard 2 | 3 | Department of Biochemical Engineering, UCL 4 | Centre for Mathematics and Physics in the Life Sciences and Experimental Biology, UCL 5 | 6 | All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without 9 | modification, are permitted provided that the following conditions are met: 10 | 11 | * Redistributions of source code must retain the above copyright notice, this 12 | list of conditions and the following disclaimer. 13 | 14 | * Redistributions in binary form must reproduce the above copyright notice, 15 | this list of conditions and the following disclaimer in the documentation 16 | and/or other materials provided with the distribution. 17 | 18 | * Neither the name of PHANTAST nor the names of its 19 | contributors may be used to endorse or promote products derived from 20 | this software without specific prior written permission. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 23 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 25 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 26 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 28 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 29 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 30 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 4.0.0 7 | 8 | 9 | org.scijava 10 | pom-scijava 11 | 26.0.0 12 | 13 | 14 | 15 | PHANTAST 16 | PHANTAST_ 17 | 0.3 18 | 19 | Phase Contrast Segmentation Toolbox (PHANTAST) 20 | The phae contrast segmentation toolbox (PHANTAST) enables high performance segmentation of phase contrast microscopy images 21 | http://phantast.googlecode.com 22 | 23 | University College London 24 | http://www.ucl.ac.uk 25 | 26 | 27 | 28 | 29 | nicjac 30 | Nicolas Jaccard 31 | nicolas.jaccard@gmail.com 32 | http://www.nicjac.net 33 | University College London 34 | http://www.ucl.ac.uk 35 | 36 | architect 37 | developer 38 | 39 | -6 40 | 41 | 42 | oburri 43 | Olivier Burri 44 | olivier.burri@gmail.com 45 | https://biop.epfl.ch/INFO_Facility.html 46 | Ecole Polytechnique Fédérale de Lausanne 47 | https://epfl.ch 48 | 49 | architect 50 | developer 51 | 52 | +1 53 | 54 | 55 | 56 | 57 | scm:git:git://github.com/nicjac/PHANTAST-FIJI 58 | scm:git:git@github.com:nicjac/PHANTAST-FIJI 59 | HEAD 60 | https://github.com/nicjac/PHANTAST-FIJI 61 | 62 | 63 | 64 | 65 | scijava.public 66 | https://maven.scijava.org/content/groups/public 67 | 68 | 69 | 70 | 71 | 72 | net.imagej 73 | ij 74 | 75 | 76 | sc.fiji 77 | fiji-lib 78 | 79 | 80 | net.imglib2 81 | imglib2 82 | 83 | 84 | net.imglib2 85 | imglib2-ij 86 | 87 | 88 | net.imglib2 89 | imglib2-algorithm 90 | 91 | 92 | 93 | 94 | true 95 | 96 | 97 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Looking for maintainer(s)! 2 | 3 | Hi everyone 👋 4 | 5 | Unfortunately, I haven't had time to look after this repository for a (long) while. However, based on citations and traffic to repositories, I see there is still interest in PHANTAST. If you would like to become a maintainer, please create an issue here on Github, or email me: hello [AT] nicjac [DOT] dev. 6 | 7 | I hope you find this code useful. Thanks! 8 | 9 | ---------- 10 | 11 | 12 | PHANTAST for FIJI 13 | ======== 14 | 15 | The phase contrast microscopy segmentation toolbox (PHANTAST) is a collection of open-source algorithms and tools for the processing of phase contrast microscopy (PCM) images. It was developed at [University College London](http://www.ucl.ac.uk)'s [department of Biochemical Engineering](http://www.ucl.ac.uk/biochemeng) and [CoMPLEX](http://www.ucl.ac.uk/complex). 16 | 17 | With the ultimate goal of being plateform agnostic, PHANTAST is currently available as a standalone GUI, MATLAB code, and FIJI plugin. 18 | 19 | **IMPORTANT: You are currently browsing the PHANTAST for FIJI plugin. If you are interested in PHANTAST for MATLAB or the standalone GUI application (offering similar functionality), please naviguate to the [corresponding repository](https://github.com/nicjac/PHANTAST-MATLAB).** 20 | 21 | PHANTAST was first described in an [open-access paper](http://onlinelibrary.wiley.com/doi/10.1002/bit.25115/abstract) where it was used to non-invasively monitor proliferation, cell death, growth arrest and morphologyical changes in adherent cell cultures based on phase contrast microscopy images. 22 | 23 | * **Microscope users / researchers**: get the most of your phase contrast microscopy images and start generating quantitative data right away using our graphical user interface tool that does not require any image-processing knowledge 24 | * **Developers**: study, re-use and/or improve on our segmentation algorithm. We are keen on seeing what you can do with it! 25 | 26 | Getting started 27 | --------------- 28 | * Head to the [release page](https://github.com/nicjac/PHANTAST-FIJI/releases) and download the latest release of the plugin. Alternatively, you can checkout the code and build it yourself. 29 | * The wiki contains a [tutorial to get you started with PHANTAST for FIJI](https://github.com/nicjac/PHANTAST-FIJI/wiki/PHANTAST-FIJI-plugin-tutorial) 30 | * You can get in touch with us through various means: 31 | * Use the [issue page](https://github.com/nicjac/PHANTAST-FIJI/issues) to report problems with the plugin 32 | * Head to the [getting in touch page](https://github.com/nicjac/PHANTAST-FIJI/wiki/Getting-in-touch) for more traditional contact means 33 | * If MATLAB is more your thing (or if you are after a standalone GUI version of PHANTAST), you can have a look at [PHANTAST for MATLAB](https://github.com/nicjac/PHANTAST-MATLAB), which offers similar functionality to that of the FIJI plugin 34 | * If you use PHANTAST for your research, please consider [citing our papers](https://github.com/nicjac/PHANTAST-FIJI/wiki/How-to-cite-PHANTAST) 35 | 36 | More about PHANTAST 37 | ------------------- 38 | ![alt text](https://github.com/nicjac/phantast/blob/gh-pages/images/Example.png "Example of PCM image segmentation using PHANTAST") 39 | 40 | Generating quantitative data from PCM images is generally a tedious experience. Manual processing is time-consuming and error-prone while the use of general purpose image processing software package can be frustrating due to their complexity and the need to tweak parameters. 41 | 42 | Clearly, PCM image processing is currently much more difficult than it should be. PHANTAST was developed by researchers who are in the laboratory and understand the need for a convenient tool that delivers uncompromising performance. 43 | 44 | Key features of PHANTAST: 45 | * PHANTAST enables culture confluency determination and cell density estimation (even for colony-forming cell lines) based on unlabelled PCM images. 46 | * High performance. PHANTAST is based on image processing algorithm specifically developed with PCM images features in mind, more specifically the low contrast between the cells and the background, and halo artefacts surrouding cellular objects. 47 | * It is free. Do not spend a large portion of your research budget on software license. 48 | * It is open. Unlike most commercial products that adopt a black box model, PHANTAST is open and you are encouraged to delve into the code. See something wrong? Think a feature should be implemented? Let us know and join our effort! 49 | * Consistency. PHANTAST has been thoroughly validated using a variety of cell lines, imaging protocols and microscope models. 50 | * Choice. PHANTAST is available as MATLAB code, as a standalone GUI tool and as a FIJI plugin. 51 | -------------------------------------------------------------------------------- /src/main/java/AFI_.java: -------------------------------------------------------------------------------- 1 | import ij.ImagePlus; 2 | import ij.gui.Roi; 3 | import ij.gui.ImageRoi; 4 | import ij.gui.Overlay; 5 | import java.awt.image.IndexColorModel; 6 | import ij.plugin.filter.PlugInFilterRunner; 7 | import ij.plugin.PlugIn; 8 | import ij.process.ImageProcessor; 9 | import ij.gui.GenericDialog; 10 | import ij.gui.DialogListener; 11 | import ij.WindowManager; 12 | import ij.io.FileInfo; 13 | import ij.plugin.ImageCalculator; 14 | import ij.plugin.filter.GaussianBlur; 15 | import ij.process.FloatProcessor; 16 | import java.awt.*; 17 | import ij.*; 18 | 19 | public class AFI_ implements PlugIn,DialogListener { 20 | protected ImagePlus image; 21 | 22 | private double overlayOpacity; 23 | private boolean previewing; 24 | private ImagePlus rawImage; 25 | private ImagePlus UVImage; 26 | private ImagePlus maskImage; 27 | 28 | private ImagePlus resultImage; 29 | 30 | 31 | /** 32 | * As the class implements DialogListener, this method is called whenever a setting is changed 33 | */ 34 | public boolean dialogItemChanged(GenericDialog gd, AWTEvent e) { 35 | rawImage = (WindowManager.getImage(gd.getNextChoiceIndex() + 1)).duplicate(); 36 | int maskChoice = gd.getNextChoiceIndex(); 37 | 38 | if(maskChoice == 0) { 39 | //Make mask from ROI 40 | } 41 | else 42 | { 43 | maskImage = (WindowManager.getImage(maskChoice).duplicate()); 44 | } 45 | UVImage = (WindowManager.getImage(gd.getNextChoiceIndex() + 1)).duplicate(); 46 | overlayOpacity = gd.getNextNumber(); 47 | previewing = gd.getNextBoolean(); 48 | 49 | if(previewing) run(""); 50 | 51 | return true; 52 | } 53 | 54 | /** 55 | * Automatically called by ExtendedPlugInFilter, displays the setting dialog 56 | * When done, run(ImageProcessor) will be automatically called 57 | * The ROI/image select stuff was adapted from / src-plugins / Colocalisation_Analysis / src / main / java / Coloc_2.java 58 | */ 59 | public boolean showDialog() { 60 | 61 | GenericDialog gd = new GenericDialog("PHANTAST Settings", IJ.getInstance()); 62 | 63 | // get IDs of open windows 64 | int[] windowList = WindowManager.getIDList(); 65 | // if there are less than 2 windows open, cancel 66 | if (windowList == null || windowList.length < 2) { 67 | IJ.showMessage("At least 2 images must be open!"); 68 | return false; 69 | } 70 | String[] titles = new String[windowList.length]; 71 | /* the masks and ROIs array needs three more entries than 72 | * windows to contain "none", "ROI ch 1" and "ROI ch 2" 73 | */ 74 | String[] roisAndMasks= new String[windowList.length + 1]; 75 | 76 | roisAndMasks[0]="ROI(s) in raw image"; 77 | 78 | 79 | // go through all open images and add them to GUI 80 | for (int i=0; i < windowList.length; i++) { 81 | ImagePlus imp= WindowManager.getImage(windowList[i]); 82 | if (imp != null) { 83 | titles[i] = imp.getTitle(); 84 | roisAndMasks[i + 1] =imp.getTitle(); 85 | } else { 86 | titles[i] = ""; 87 | } 88 | } 89 | 90 | int index1 = 0; 91 | int index2 = 0; 92 | int indexMask = 0; 93 | 94 | gd.addChoice("Raw image", titles, titles[index1]); 95 | gd.addChoice("Cells segmentation mask", roisAndMasks, roisAndMasks[indexMask]); 96 | gd.addChoice("Fluorescence image", titles, titles[index2]); 97 | gd.addSlider("Overlay opacity",0,1,0.6); 98 | gd.addCheckbox("Preview", false); 99 | gd.addDialogListener(this); 100 | 101 | gd.showDialog(); 102 | 103 | if(gd.wasOKed()){ 104 | return true; 105 | }else{ 106 | return false; 107 | } 108 | } 109 | 110 | FileInfo getLUTvalues(int color) 111 | { 112 | FileInfo fi = new FileInfo(); 113 | 114 | fi.reds = new byte[256]; 115 | fi.greens = new byte[256]; 116 | fi.blues = new byte[256]; 117 | 118 | for (int i=0; i<256; i++) { 119 | if ((color&4)!=0) 120 | fi.reds[i] = (byte)i; 121 | if ((color&2)!=0) 122 | fi.greens[i] = (byte)i; 123 | if ((color&1)!=0) 124 | fi.blues[i] = (byte)i; 125 | if(color==0) 126 | { 127 | fi.reds[i] = (byte)0; 128 | fi.greens[i] = (byte)0; 129 | fi.blues[i] = (byte)0; 130 | } 131 | } 132 | 133 | return fi; 134 | } 135 | 136 | public void run(String arg) { 137 | boolean dialogState = false; 138 | if(!previewing) 139 | { 140 | dialogState = showDialog(); 141 | } 142 | else generateAFI(""); 143 | 144 | if(dialogState) generateAFI(""); 145 | } 146 | 147 | public void generateAFI(String arg) { 148 | if(resultImage == null) 149 | { 150 | resultImage = rawImage.duplicate(); 151 | resultImage.show(); 152 | } else // If result window already up we refresh it 153 | { 154 | resultImage.setProcessor(rawImage.getProcessor()); 155 | } 156 | 157 | resultImage.setOverlay(null); 158 | 159 | FloatProcessor green = UVImage.getProcessor().toFloat(1, null); 160 | 161 | ImagePlus greenIP = new ImagePlus("Fluorescent image",green); 162 | 163 | GaussianBlur gb = new GaussianBlur(); 164 | gb.blurGaussian(greenIP.getProcessor(),2,2,0.02); 165 | 166 | ImagePlus greenMask = greenIP.duplicate(); 167 | greenMask.getProcessor().setThreshold(24, 24, ImageProcessor.NO_LUT_UPDATE); 168 | IJ.run(greenMask, "Convert to Mask", ""); 169 | 170 | //greenMask.show(); 171 | //LCThresholdedIP.show(); 172 | ImageCalculator ic = new ImageCalculator(); 173 | ImagePlus positiveMask = ic.run("AND create", maskImage, greenMask); 174 | 175 | ImagePlus negativeMask = ic.run("OR create", maskImage, greenMask); 176 | 177 | ImagePlus backgroundMask = maskImage.duplicate(); 178 | backgroundMask.getProcessor().invert(); 179 | 180 | // Generate the LUT 181 | // Stolen from / src / main / java / ij / plugin / LutLoader.java 182 | // Red = 4, green = 2, blue = 1, black = 0; 183 | // Dumb object just to get LUT values 184 | FileInfo fi = new FileInfo(); 185 | 186 | fi=getLUTvalues(2); 187 | IndexColorModel cm = new IndexColorModel(8, 256, fi.reds, fi.greens, fi.blues); 188 | positiveMask.getProcessor().setColorModel(cm); 189 | 190 | fi=getLUTvalues(4); 191 | cm = new IndexColorModel(8, 256, fi.reds, fi.greens, fi.blues); 192 | negativeMask.getProcessor().setColorModel(cm); 193 | 194 | //fi=getLUTvalues(0); 195 | fi.reds = new byte[256]; 196 | fi.greens = new byte[256]; 197 | fi.blues = new byte[256]; 198 | 199 | fi.reds[0] = (byte)0; 200 | fi.blues[0] = (byte)0; 201 | fi.greens[0] = (byte)0; 202 | 203 | fi.reds[255] = (byte)5; 204 | fi.blues[255] = (byte)5; 205 | fi.greens[255] = (byte)5; 206 | cm = new IndexColorModel(8, 256, fi.reds, fi.greens, fi.blues); 207 | backgroundMask.getProcessor().setColorModel(cm); 208 | 209 | // Userful ROI source http://fiji.sc/cgi-bin/gitweb.cgi?p=ImageJA.git;a=blob;f=src/main/java/ij/plugin/OverlayCommands.java;h=2e560ced45035d86ef7a06a0b2cb154164a758bc;hb=refs/heads/master 210 | Overlay overlayList = resultImage.getOverlay(); 211 | if (overlayList==null) overlayList = new Overlay(); 212 | 213 | ImageRoi roi = new ImageRoi(0, 0, negativeMask.getProcessor()); 214 | roi.setZeroTransparent(true); 215 | roi.setOpacity(overlayOpacity); 216 | overlayList.add(roi); 217 | 218 | roi = new ImageRoi(0, 0, positiveMask.getProcessor()); 219 | roi.setZeroTransparent(true); 220 | roi.setOpacity(overlayOpacity); 221 | overlayList.add(roi); 222 | 223 | roi = new ImageRoi(0, 0, backgroundMask.getProcessor()); 224 | roi.setZeroTransparent(true); 225 | roi.setOpacity(overlayOpacity); 226 | overlayList.add(roi); 227 | 228 | resultImage.setOverlay(overlayList); 229 | 230 | } 231 | } -------------------------------------------------------------------------------- /src/main/java/PHANTAST_.java: -------------------------------------------------------------------------------- 1 | /* 2 | * PHANTAST - Plugin for FIJI 3 | * 4 | * December 2017: Modifications by Olivier Burri 5 | * BioImaging And Optics Platform (BIOP) 6 | * Ecole Polytechnique Fédérale 7 | * 8 | * Adds corrections to bugs from previous versions 9 | * 1. Input images were required to be 32-bit 10 | * Mended code to handle 32-conversion internally 11 | * 2. When processing stacks, output would produce either a single mask image or a single selection 12 | * Added code to add selections to ROI manager and to create mask stack when needed 13 | * 3. Slice numbers were not appended to the results when working on stacks 14 | * Adds a new "Slice" column to the results table 15 | * 4. With stacks, it is useful to test parameters on multiple slices 16 | * Adds a slider to the dialog box so that the user can choose the slice for preview 17 | * 5. Last run preferences were not saved 18 | * Uses Prefs class to store preferences of the last time the user clicked OK 19 | * 20 | * 21 | Copyright (c) 2013, Nicolas Jaccard 22 | 23 | Department of Biochemical Engineering, UCL 24 | Centre for Mathematics and Physics in the Life Sciences and Experimental Biology, UCL 25 | The British Heart Foundation 26 | 27 | All rights reserved. 28 | 29 | Redistribution and use in source and binary forms, with or without 30 | modification, are permitted provided that the following conditions are 31 | met: 32 | 33 | * Redistributions of source code must retain the above copyright 34 | notice, this list of conditions and the following disclaimer. 35 | * Redistributions in binary form must reproduce the above copyright 36 | notice, this list of conditions and the following disclaimer in 37 | the documentation and/or other materials provided with the distribution 38 | * Neither the names of the University College London or British Heart Foundation nor the names 39 | of its contributors may be used to endorse or promote products derived 40 | from this software without specific prior written permission. 41 | 42 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 43 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 44 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 45 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 46 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 47 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 48 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 49 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 50 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 51 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 52 | POSSIBILITY OF SUCH DAMAGE. 53 | 54 | */ 55 | import ij.ImagePlus; 56 | import ij.IJ; 57 | import ij.ImageJ; 58 | 59 | import java.awt.*; 60 | 61 | import ij.gui.GenericDialog; 62 | import ij.gui.Overlay; 63 | import ij.plugin.filter.ExtendedPlugInFilter; 64 | import ij.plugin.filter.PlugInFilterRunner; 65 | import ij.plugin.frame.RoiManager; 66 | import ij.gui.DialogListener; 67 | import ij.Prefs; 68 | import ij.process.ImageProcessor; 69 | import ij.process.BinaryProcessor; 70 | import ij.process.ByteProcessor; 71 | import net.imglib2.Cursor; 72 | import ij.gui.Roi; 73 | import net.imglib2.img.Img; 74 | import net.imglib2.img.display.imagej.ImageJFunctions; 75 | import net.imglib2.img.ImagePlusAdapter; 76 | import net.imglib2.type.NativeType; 77 | import net.imglib2.type.numeric.RealType; 78 | import net.imglib2.type.numeric.IntegerType; 79 | import net.imglib2.type.numeric.integer.UnsignedByteType; 80 | import net.imglib2.type.logic.BitType; 81 | import net.imglib2.algorithm.gauss.Gauss; 82 | import net.imglib2.img.ImgFactory; 83 | import net.imglib2.img.array.ArrayImgFactory; 84 | import net.imglib2.type.Type; 85 | 86 | import java.util.Iterator; 87 | 88 | import net.imglib2.RandomAccess; 89 | import ij.plugin.filter.ParticleAnalyzer; 90 | import ij.measure.ResultsTable; 91 | import ij.measure.Measurements; 92 | 93 | import java.util.*; 94 | //import net.imglib2.script.math.fn.BinaryOperation; 95 | 96 | 97 | 98 | 99 | 100 | @SuppressWarnings("deprecation") 101 | public class PHANTAST_ & NativeType> implements ExtendedPlugInFilter,DialogListener { 102 | protected ImagePlus inputImage; 103 | 104 | private ImagePlus maskImage; 105 | 106 | private double sigma = 1.2; 107 | private double epsilon = 0.03; 108 | private boolean doHaloCorrection = true;; 109 | private boolean previewing = false; 110 | private boolean computeConfluency = false; 111 | private int slider=1; 112 | private String prefix = "phantast.plugin."; 113 | 114 | private int nPasses; // Total number of passes 115 | private int pass; // Current pass 116 | private boolean outputSelection; 117 | private boolean outputMask; 118 | 119 | 120 | int flags = DOES_8G+DOES_16+DOES_32+FINAL_PROCESSING; // Add possibility to work on 8 and 16-bit images, we do the conversion ourselves 121 | 122 | int[][] projectionCones = new int[][] 123 | {{1, 2, 8}, 124 | {2, 1, 3}, 125 | {3, 2, 4}, 126 | {4, 3, 5}, 127 | {5, 4, 6}, 128 | {6, 5, 7}, 129 | {7, 6, 8}, 130 | {8, 1, 7}}; 131 | 132 | int[][] directionOffsets2= new int[][] 133 | {{0,1}, //EAST 1 134 | {-1,1}, //NORTH EAST 2 135 | {-1,0}, //NORTH 3 136 | {-1,-1}, //NORTH WEST 4 137 | {0,-1}, //WEST 5 138 | {1,-1}, //SOUTH WEST 6 139 | {1,0}, //SOUTH 7 140 | {1,1}}; //SOUTH EAST 8 141 | 142 | int[][] directionOffsets= new int[][] 143 | {{1,0}, //EAST 1 144 | {1,-1}, //NORTH EAST 2 145 | {0,-1}, //NORTH 3 146 | {-1,-1}, //NORTH WEST 4 147 | {-1,0}, //WEST 5 148 | {-1,1}, //SOUTH WEST 6 149 | {0,1}, //SOUTH 7 150 | {1,1}}; //SOUTH EAST 8 151 | 152 | private Overlay ov; 153 | 154 | /** 155 | * @see ij.plugin.filter.PlugInFilter#setup(java.lang.String, ij.ImagePlus) 156 | */ 157 | @Override 158 | public int setup(String arg, ImagePlus imp) { 159 | //IJ.run("PHANTAST ", "sigma=1.20 epsilon=0.03 output=[Binary mask]"); 160 | // does not handle RGB, since the wrapped type is ARGBType (not a RealType) 161 | if(arg.equals("final")) { 162 | if (ov.size() > 1) { 163 | // If we asked for the output selection, add it to the ROI manager 164 | IJ.run("To ROI Manager", ""); 165 | } else if (ov.size()==1){ 166 | imp.setRoi(ov.get(0)); 167 | } 168 | 169 | // If we ask for the mask, we should output the ImagePlus 170 | if(outputMask) { 171 | maskImage.show(); 172 | } 173 | imp.setOverlay(null); 174 | } 175 | if (imp == null) { 176 | IJ.error("No image open"); 177 | return DONE; 178 | } 179 | 180 | // Store the image during setup rather than during dialog display... 181 | this.inputImage= imp; 182 | 183 | // Read from the stored preferences. Avoids tedious work of remembering the last settings. 184 | readSettings(); 185 | 186 | // Create an overlay to hold the results 187 | ov = new Overlay(); 188 | 189 | return flags; 190 | } 191 | 192 | /** 193 | * Read in the settings from a previous run. 194 | */ 195 | private void readSettings() { 196 | sigma = Prefs.get(prefix+"sigma", sigma); 197 | epsilon = Prefs.get(prefix+"epsilon", epsilon); 198 | doHaloCorrection = Prefs.get(prefix+"do.halo", doHaloCorrection); 199 | computeConfluency= Prefs.get(prefix+"do.confluency", computeConfluency); 200 | outputSelection = Prefs.get(prefix+"do.selection", outputSelection); 201 | outputMask = Prefs.get(prefix+"do.mask", outputMask); 202 | } 203 | 204 | /** 205 | * Save in the settings from a the current run. 206 | */ 207 | private void saveSettings() { 208 | // Save new values to IJ.Prefs 209 | Prefs.set(prefix+"sigma", sigma); 210 | Prefs.set(prefix+"epsilon", epsilon); 211 | Prefs.set(prefix+"do.halo", doHaloCorrection); 212 | Prefs.set(prefix+"do.confluency", computeConfluency); 213 | Prefs.set(prefix+"do.selection", outputSelection); 214 | Prefs.set(prefix+"do.mask", outputMask); 215 | } 216 | 217 | // Used to set number of calls(progress bar) 218 | public void setNPasses(int nPasses) { 219 | this.nPasses = nPasses; 220 | pass = 0; 221 | } 222 | 223 | /** 224 | * As the class implements DialogListener, this method is called whenever a setting is changed 225 | */ 226 | public boolean dialogItemChanged(GenericDialog gd, AWTEvent e) { 227 | sigma = (double)gd.getNextNumber(); 228 | epsilon = (double)gd.getNextNumber(); 229 | doHaloCorrection = gd.getNextBoolean(); 230 | computeConfluency = gd.getNextBoolean(); 231 | outputSelection = gd.getNextBoolean(); 232 | outputMask = gd.getNextBoolean(); 233 | if(inputImage.getStackSize() > 1) { 234 | slider = (int) gd.getNextNumber(); 235 | } 236 | previewing = gd.getPreviewCheckbox().getState(); 237 | 238 | if( sigma == 0.0 || epsilon == 0.0 || Double.isNaN(sigma) || Double.isNaN(epsilon) ) return false; 239 | return true; 240 | } 241 | 242 | /** 243 | * Automatically called by ExtendedPlugInFilter, displays the setting dialog 244 | * When done, run(ImageProcessor) will be automatically called 245 | */ 246 | public int showDialog(ImagePlus imp, String command, PlugInFilterRunner pfr) { 247 | 248 | GenericDialog gd = new GenericDialog("PHANTAST Settings", IJ.getInstance()); 249 | gd.addMessage("SEGMENTATION PARAMETERS",new Font("",Font.BOLD,12)); 250 | gd.addMessage("Local contrast thresholding",new Font("",Font.ITALIC,12)); 251 | gd.addNumericField("Sigma", sigma, 2); 252 | gd.addNumericField("Epsilon", epsilon, 2); 253 | gd.addMessage("Halo correction",new Font("",Font.ITALIC,12)); 254 | gd.addCheckbox("Do halo correction",doHaloCorrection); 255 | gd.addMessage("OUTPUT OPTIONS",new Font("",Font.BOLD,12)); 256 | gd.addMessage("Measurements",new Font("",Font.ITALIC,12)); 257 | gd.addCheckbox("Compute confluency", computeConfluency); 258 | gd.addMessage("Image output",new Font("",Font.ITALIC,12)); 259 | gd.addCheckbox("Selection overlay on original image", outputSelection); 260 | gd.addCheckbox("New mask image", outputMask); 261 | gd.addMessage(""); 262 | if(imp.getStackSize() >1) { 263 | gd.addSlider("Preview Slice", 0, imp.getStackSize(), 1); 264 | } 265 | gd.addPreviewCheckbox(pfr); 266 | gd.addDialogListener(this); 267 | 268 | gd.showDialog(); 269 | 270 | 271 | if(gd.wasCanceled()){ 272 | // Check if we need to do some cleanup, on the overlay 273 | imp.setOverlay(null); 274 | return DONE; 275 | }else{ 276 | flags = IJ.setupDialog(imp, flags); //ask whether to process all slices of stack (if a stack) 277 | 278 | sigma = (double)gd.getNextNumber(); 279 | epsilon = (double)gd.getNextNumber(); 280 | doHaloCorrection = gd.getNextBoolean(); 281 | computeConfluency = gd.getNextBoolean(); 282 | outputSelection = gd.getNextBoolean(); 283 | outputMask = gd.getNextBoolean(); 284 | previewing = false; // This avoids issues with the checkbox still being selected... 285 | saveSettings(); 286 | return flags; 287 | 288 | } 289 | } 290 | 291 | 292 | /** 293 | * @see ij.plugin.filter.PlugInFilter#run(ij.process.ImageProcessor) 294 | */ 295 | @Override 296 | public void run(ImageProcessor ip) { 297 | 298 | // This should do the work on the given ImageProcessor directly, because when you convert it to imagePlus, everything gets overwritten when working with stacks. 299 | // if it's a stack, the PlugInFilter should work to assemble an image in the end 300 | int slice = slider; //inputImage.getSlice(); 301 | inputImage.setSlice(slice); 302 | if(!previewing || nPasses > 1) { 303 | pass++; 304 | slice = pass; 305 | } 306 | 307 | String imageTitle; 308 | 309 | // Decide on the name of the image based on the number of passes (meaning, is it a stack) 310 | 311 | if(inputImage.getStack().getSize() >1 && nPasses==1) { 312 | imageTitle = inputImage.getStack().getSliceLabel(slice); 313 | 314 | if(imageTitle == null) { imageTitle = inputImage.getTitle()+" Slice "+slice; } // Avoid having "null" as a name in case there is no slice label 315 | 316 | } else { 317 | imageTitle = inputImage.getTitle(); 318 | } 319 | ImagePlus image = new ImagePlus(imageTitle, ip.convertToFloat()); 320 | 321 | // Convert the image to imglib2 Img format 322 | Img img = ImageJFunctions.wrap(image); 323 | 324 | // Apply local contrast filter 325 | final Img localContrast = getLocalContrastImage(img, sigma); 326 | 327 | // Threshold the resulting image 328 | Img LCThresholded = thresholdImage(localContrast,epsilon); 329 | LCThresholded = removeSmallObjectsAndFillHoles(LCThresholded); 330 | 331 | if(doHaloCorrection) haloCorrection(LCThresholded,image); 332 | 333 | 334 | ImagePlus LCThresholdedIP = ImageJFunctions.wrap(LCThresholded,"ResultImage"); 335 | 336 | 337 | // Create the selection 338 | IJ.run(LCThresholdedIP,"Convert to Mask", "method=Li background=Dark"); 339 | IJ.runPlugIn(LCThresholdedIP,"ij.plugin.filter.ThresholdToSelection", ""); 340 | Roi resultRoi = LCThresholdedIP.getRoi(); 341 | resultRoi.setPosition(slice); 342 | resultRoi.setName("Slice "+IJ.pad(slice,3)); 343 | 344 | // Prepare outputs 345 | 346 | if(outputMask) { 347 | // This is the output mask for the current slice so we should save it somewhere and display it later 348 | // for a preview, we could show the current one as an overlay, and avoid having images. 349 | // The issue is that when the preview 350 | if(previewing) { 351 | inputImage.setOverlay(null); 352 | Overlay ov = new Overlay(); 353 | resultRoi.setFillColor(new Color(255,255,255)); 354 | ov.add(resultRoi); 355 | inputImage.setOverlay(ov); 356 | } 357 | 358 | if(nPasses > 1) { 359 | if(maskImage == null || maskImage.getStackSize() != nPasses) { 360 | maskImage = inputImage.createHyperStack(imageTitle+"- Output Mask", 1, nPasses, 1, 8); 361 | maskImage.setTitle(imageTitle+"- Output Mask"); 362 | } 363 | maskImage.getStack().setProcessor(LCThresholdedIP.getProcessor().duplicate(), slice); 364 | } else { 365 | maskImage = new ImagePlus(imageTitle+"- Output Mask", LCThresholdedIP.duplicate().getProcessor()); 366 | } 367 | } 368 | 369 | if(outputSelection) 370 | { 371 | if(previewing || slice==1) {// Do it as an Overlay 372 | inputImage.setOverlay(null); 373 | ov = new Overlay(); 374 | } 375 | 376 | ov.add(resultRoi); 377 | inputImage.setOverlay(ov); 378 | inputImage.show(); 379 | } 380 | 381 | if(!previewing || nPasses > 1) { 382 | if(computeConfluency) 383 | { 384 | ResultsTable rt = ResultsTable.getResultsTable(); 385 | 386 | rt.incrementCounter(); 387 | rt.addLabel("Image Name", imageTitle); 388 | if(inputImage.getImageStackSize() > 1) rt.addValue("Slice", slice); 389 | rt.addValue("Confluency",computeConfluency(LCThresholded)); 390 | 391 | rt.show("Results"); 392 | } 393 | } 394 | 395 | showProgress(); 396 | 397 | } 398 | 399 | // Let IJ know about our progress 400 | void showProgress() 401 | { 402 | double percent = (double)pass/nPasses; 403 | IJ.showProgress(percent); 404 | } 405 | 406 | /** 407 | * Correct halo artifacts 408 | */ 409 | void haloCorrection(Img imgToShrink, ImagePlus image) 410 | { 411 | Img< UnsignedByteType> LCThresholdedOutline = getOutlineImage(imgToShrink,false); 412 | 413 | //ImageJFunctions.show(LCThresholded); 414 | 415 | // Store the positions of outline pixels 416 | ArrayList outlinePixels = getOutlinePixels(LCThresholdedOutline); 417 | 418 | // Get the direction image 419 | Img< UnsignedByteType > directionImage = getDirectionImage(image); 420 | //ImageJFunctions.show(directionImage,"Gradient Image"); 421 | 422 | final RandomAccess< UnsignedByteType > r = directionImage.randomAccess(); 423 | int[] currentPosition = new int[2]; 424 | 425 | long[] dimDirectionImage = new long[2]; 426 | directionImage.dimensions(dimDirectionImage); 427 | 428 | // Create an instance of an image factory 429 | ImgFactory< BitType > imgFactoryBit = new ArrayImgFactory< BitType >(); 430 | 431 | // Create an Img with the same dimension as the input image 432 | final Img< BitType > consideredAsStartingPoint = imgFactoryBit.create(dimDirectionImage, new BitType() ); 433 | 434 | boolean go = true; 435 | final RandomAccess< UnsignedByteType > rBinaryImage = imgToShrink.randomAccess(); 436 | 437 | ArrayList pixelsToProcess = new ArrayList(outlinePixels); 438 | int count = 0; 439 | while(go) 440 | { 441 | ++count; 442 | ArrayList toAddToQueue = new ArrayList(); 443 | ArrayList toBeRemoved = new ArrayList(); 444 | 445 | for(int i=0;i rStartPoint = consideredAsStartingPoint.randomAccess(); 463 | rStartPoint.setPosition( (int)currentPosition[0], 0 ); 464 | rStartPoint.setPosition( (int)currentPosition[1], 1 ); 465 | int startFlag = (int) rStartPoint.get().getInteger(); 466 | if(startFlag==0) 467 | { 468 | // Need to add 'consideredStartingPoint' stuff 469 | if((currentPosition[0] >0) & (currentPosition[1] >0) & (currentPosition[0] < dimDirectionImage[0]-1) & (currentPosition[1] < dimDirectionImage[1]-1)) 470 | { 471 | // Everything is an object apparently! 472 | // Set this position as already visisted 473 | final BitType t = rStartPoint.get(); 474 | t.setOne(); 475 | 476 | for(int k=0;k<3;k++) { 477 | int[] directionOffset = directionOffsets[currentDirectionCone[k]-1]; 478 | int[] nextPosition = new int[2]; 479 | 480 | nextPosition[0] = currentPosition[0] + directionOffset[0]; 481 | nextPosition[1] = currentPosition[1] + directionOffset[1]; 482 | 483 | rBinaryImage.setPosition((int)nextPosition[0],0); 484 | rBinaryImage.setPosition((int)nextPosition[1],1); 485 | //IJ.log("" + (int)nextPosition[0]); 486 | //IJ.log("" + (int)nextPosition[1]); 487 | if(rBinaryImage.get().getInteger()==255) 488 | { 489 | if((nextPosition[0] >=1) & (nextPosition[1] >=1) & (nextPosition[0] <= dimDirectionImage[0]) & (nextPosition[1] <= dimDirectionImage[1])) 490 | { 491 | validPath = true; 492 | toAddToQueue.add(nextPosition); 493 | } 494 | } 495 | } 496 | } 497 | } 498 | 499 | if(validPath) { 500 | toBeRemoved.add((int[])currentPosition); 501 | } 502 | 503 | } 504 | 505 | for(int j=1; j(toAddToQueue); 514 | 515 | if(pixelsToProcess.size()==0) 516 | { 517 | go = false; 518 | } 519 | } 520 | 521 | } 522 | 523 | 524 | 525 | double computeConfluency (Img img) 526 | { 527 | Cursor c = img.cursor(); 528 | 529 | double onPixels = 0; 530 | double offPixels = 0; 531 | 532 | while (c.hasNext()) 533 | { 534 | IntegerType t = c.next(); 535 | 536 | if(t.getInteger()==255) 537 | { 538 | onPixels++; 539 | } 540 | else offPixels++; 541 | } 542 | 543 | //IJ.log("" + ((int[])outlinePixelsIndices.get(12))[0]); 544 | return (double)onPixels/(onPixels+offPixels); 545 | } 546 | 547 | Img removeSmallObjectsAndFillHoles(Img img){ 548 | ResultsTable table = new ResultsTable(); 549 | //# Create a ParticleAnalyzer, with arguments: 550 | //# 1. options (could be SHOW_ROI_MASKS, SHOW_OUTLINES, SHOW_MASKS, SHOW_NONE, ADD_TO_MANAGER, and others; combined with bitwise-or) 551 | //# 2. measurement options (see [http://rsb.info.nih.gov/ij/developer/api/ij/measure/Measurements.html Measurements]) 552 | //# 3. a ResultsTable to store the measurements 553 | //# 4. The minimum size of a particle to consider for measurement 554 | //# 5. The maximum size (idem) 555 | //# 6. The minimum circularity of a particle 556 | //# 7. The maximum circularity 557 | 558 | // Remove small particles 559 | ParticleAnalyzer pa = new ParticleAnalyzer(ParticleAnalyzer.SHOW_MASKS, Measurements.AREA, table, 100, Double.POSITIVE_INFINITY, 0.0, 1.0); 560 | pa.setHideOutputImage(true); 561 | ImagePlus tmp = ImageJFunctions.wrap(img,"Tmp"); 562 | pa.analyze(tmp); 563 | 564 | // Remove holes 565 | ImagePlus tmp2 = pa.getOutputImage(); 566 | pa = new ParticleAnalyzer(ParticleAnalyzer.SHOW_MASKS, Measurements.AREA, table, 25, Double.POSITIVE_INFINITY, 0.0, 1.0); 567 | pa.setHideOutputImage(true); 568 | pa.analyze(tmp2); 569 | 570 | BinaryProcessor binaryProc = new BinaryProcessor(new ByteProcessor(pa.getOutputImage().getChannelProcessor(),false)); 571 | binaryProc.invert(); 572 | ImagePlus tmp3 = new ImagePlus("NoHolesorSmallObjects", binaryProc); 573 | return ImageJFunctions.wrap(tmp3); 574 | 575 | } 576 | 577 | Img getOutlineImage(Img img, boolean fillImage) 578 | { 579 | // Create an ImagePlus version of the local contrast image 580 | // This is done as I don't know yet how to do outline calculation using imglib2 directly 581 | ImagePlus Img_IP = ImageJFunctions.wrap(img,"Thresholded"); 582 | //LCThresholded_IP.show(); 583 | 584 | if(fillImage) IJ.runPlugIn(Img_IP,"ij.plugin.filter.ThresholdToSelection", ""); 585 | 586 | // Create a binary processor which allows to call outline() directly 587 | // Reminder: ImagePlus contain processor (not the other way around!) 588 | BinaryProcessor binaryProc = new BinaryProcessor(new ByteProcessor(Img_IP.getChannelProcessor(),false)); 589 | 590 | // Weird but need for some reason 591 | binaryProc.invert(); 592 | binaryProc.outline(); 593 | binaryProc.invert(); 594 | 595 | // Create a new ImagePlus object from the processor 596 | ImagePlus outlineImage_IP = new ImagePlus("outline", binaryProc); 597 | 598 | //LCThresholdedOutline_IP.show(); 599 | 600 | // Convert back to imglib2 Img format 601 | Img< UnsignedByteType > outlineImage = ImageJFunctions.wrap(outlineImage_IP); 602 | 603 | return outlineImage; 604 | } 605 | 606 | Img getImageWithOverlay(Img img, ArrayList maskPixels, double outlineValue) 607 | { 608 | Img tmpImage = img.copy(); 609 | 610 | final RandomAccess< T > r = tmpImage.randomAccess(); 611 | 612 | int[] currentPosition = new int[2]; 613 | 614 | for(int i=0;i t = r.get(); 623 | t.setReal(outlineValue); 624 | } 625 | 626 | return tmpImage; 627 | } 628 | 629 | public Img getLocalContrastImage(Img img, double sigma_) 630 | { 631 | final double[] sigma = new double[ img.numDimensions() ]; 632 | 633 | for ( int d = 0; d < sigma.length; ++d ) 634 | sigma[ d ] = sigma_; 635 | 636 | final Img< T > imageRooted = squareImage(img); 637 | 638 | final Img< T > imageRootedConvolved = Gauss.inFloat( sigma, imageRooted ); 639 | 640 | final Img< T > imageConvolved = Gauss.inFloat( sigma, img ); 641 | 642 | final Img< T > imageConvolvedRooted = squareImage(imageConvolved); 643 | 644 | final Img< T > localContrast = divideImages(squareRootImage(subtractImages(imageRootedConvolved,imageConvolvedRooted)),imageConvolved); 645 | 646 | return localContrast; 647 | } 648 | 649 | public Img getDirectionImage(ImagePlus img) 650 | { 651 | float[][] kirsch = new float[][] { {-3,-3,5,-3,0,5,-3,-3,5}, 652 | {-3,5,5,-3,0,5,-3,-3,-3}, 653 | {5,5,5,-3,0,-3,-3,-3,-3}, 654 | {5,5,-3,5,0,-3,-3,-3,-3}, 655 | {5,-3,-3,5,0,-3,5,-3,-3}, 656 | {-3,-3,-3,5,0,-3,5,5,-3}, 657 | {-3,-3,-3,-3,0,-3,5,5,5}, 658 | {-3,-3,-3,-3,0,5,-3,5,5}}; 659 | 660 | 661 | Img[] responseImages = new Img[8]; 662 | 663 | for(int i = 0; i < 8; i++) 664 | { 665 | ImagePlus tmp = img.duplicate(); 666 | tmp.getChannelProcessor().convolve(kirsch[i],3,3); 667 | responseImages[i] = ImagePlusAdapter.wrap(tmp); 668 | } 669 | 670 | Cursor c1 = responseImages[0].cursor(); 671 | Cursor c2 = responseImages[1].cursor(); 672 | Cursor c3 = responseImages[2].cursor(); 673 | Cursor c4 = responseImages[3].cursor(); 674 | Cursor c5 = responseImages[4].cursor(); 675 | Cursor c6 = responseImages[5].cursor(); 676 | Cursor c7 = responseImages[6].cursor(); 677 | Cursor c8 = responseImages[7].cursor(); 678 | 679 | 680 | 681 | long[] dim = new long[2]; 682 | responseImages[0].dimensions(dim); 683 | 684 | //final Img< T > directionImage = imgFactory.create( responseImages[0], responseImages[0].firstElement() ); 685 | 686 | ImgFactory< UnsignedByteType > imgFactory = new ArrayImgFactory< UnsignedByteType >(); 687 | final Img directionImage = imgFactory.create(dim,new UnsignedByteType()); 688 | //final Img< T > directionImage = imgFactory.create(dim,new RealType()); 689 | 690 | Cursor cDirection = directionImage.cursor(); 691 | 692 | while (c1.hasNext()) 693 | { 694 | RealType[] t = new RealType[8]; 695 | t[0] = (RealType)c1.next(); 696 | t[1] = (RealType)c2.next(); 697 | t[2] = (RealType)c3.next(); 698 | t[3] = (RealType)c4.next(); 699 | t[4] = (RealType)c5.next(); 700 | t[5] = (RealType)c6.next(); 701 | t[6] = (RealType)c7.next(); 702 | t[7] = (RealType)c8.next(); 703 | 704 | RealType tDirection = cDirection.next(); 705 | 706 | float max = 0f; 707 | int kernelId = 0; 708 | for(int i = 0; i < 8; i++) 709 | { 710 | float currentValue = t[i].getRealFloat(); 711 | if(i==0) 712 | { 713 | max = currentValue; 714 | kernelId = 0; 715 | } 716 | else if(currentValue>max) 717 | { 718 | max = currentValue; 719 | kernelId = i; 720 | } 721 | } 722 | 723 | tDirection.setReal(kernelId); 724 | 725 | } 726 | 727 | return directionImage; 728 | } 729 | 730 | public < T extends RealType< T > & NativeType< T > > ArrayList getOutlinePixels(Img img) 731 | { 732 | ArrayList outlinePixelsIndices = new ArrayList(); 733 | 734 | Cursor c = img.cursor(); 735 | 736 | while (c.hasNext()) 737 | { 738 | IntegerType t = (IntegerType)c.next(); 739 | int[] position = new int[2]; 740 | 741 | if(t.getInteger()==255) 742 | { 743 | c.localize(position); 744 | outlinePixelsIndices.add(position); 745 | } 746 | } 747 | 748 | //IJ.log("" + ((int[])outlinePixelsIndices.get(12))[0]); 749 | return outlinePixelsIndices; 750 | } 751 | 752 | public Img thresholdImage(Img img, double thresholdValue) 753 | { 754 | // Create an instance of an image factory 755 | ImgFactory< UnsignedByteType > imgFactory = new ArrayImgFactory< UnsignedByteType >(); 756 | 757 | long[] dim = new long[2]; 758 | img.dimensions(dim); 759 | 760 | // Create an Img with the same dimension as the input image 761 | final Img< UnsignedByteType > tmpImage = imgFactory.create( dim, new UnsignedByteType() ); 762 | 763 | // Create a cursor for both images 764 | Cursor c1 = img.cursor(); 765 | Cursor c2 = tmpImage.cursor(); 766 | 767 | // do for all pixels 768 | while ( c1.hasNext() ) 769 | { 770 | // get value of both imgs 771 | RealType t1 = c1.next(); 772 | RealType t2 = c2.next(); 773 | 774 | // overwrite img1 with the result 775 | 776 | if(t1.getRealFloat() > thresholdValue) 777 | { 778 | t2.setReal(255); 779 | } 780 | else 781 | { 782 | t2.setReal(0); 783 | } 784 | } 785 | 786 | return tmpImage; 787 | } 788 | 789 | public < T extends RealType< T > & NativeType< T > > void scaleImage(Img img) 790 | { 791 | // create two empty variables 792 | T min = img.firstElement().createVariable(); 793 | T max = img.firstElement().createVariable(); 794 | 795 | // compute min and max of the Image 796 | computeMinMax( img, min, max ); 797 | 798 | //IJ.log( "minimum Value (img): " + min ); 799 | //IJ.log( "maximum Value (img): " + max ); 800 | //write("lol"); 801 | 802 | // Create a cursor for both images 803 | Cursor c1 = img.cursor(); 804 | 805 | // do for all pixels 806 | while ( c1.hasNext() ) 807 | { 808 | // get value of both imgs 809 | RealType t1 = c1.next(); 810 | 811 | // overwrite img1 with the result 812 | t1.setReal( t1.getRealFloat() / max.getRealFloat() ); 813 | } 814 | } 815 | 816 | 817 | public < T extends Comparable< T > & Type< T > > void computeMinMax(final Iterable< T > input, final T min, final T max ) 818 | { 819 | // create a cursor for the image (the order does not matter) 820 | final Iterator< T > iterator = input.iterator(); 821 | 822 | // initialize min and max with the first image value 823 | T type = iterator.next(); 824 | 825 | min.set( type ); 826 | max.set( type ); 827 | 828 | // loop over the rest of the data and determine min and max value 829 | while ( iterator.hasNext() ) 830 | { 831 | // we need this type more than once 832 | type = iterator.next(); 833 | 834 | if ( type.compareTo( min ) < 0 ) 835 | min.set( type ); 836 | 837 | if ( type.compareTo( max ) > 0 ) 838 | max.set( type ); 839 | } 840 | } 841 | 842 | 843 | public static< T extends RealType< T > & NativeType< T > > Img squareImage(Img img) 844 | { 845 | return multiplyImages(img,img); 846 | } 847 | 848 | public static< T extends RealType< T > & NativeType< T > > Img multiplyImages(Img img1,Img img2) 849 | { 850 | // Create an instance of an image factory 851 | ImgFactory< T > imgFactory = new ArrayImgFactory< T >(); 852 | 853 | // Create an Img with the same dimension as the input image 854 | final Img< T > tmpImage = imgFactory.create( img1, img1.firstElement() ); 855 | 856 | // Create a cursor for both images 857 | Cursor c1 = img1.cursor(); 858 | Cursor c2 = img2.cursor(); 859 | Cursor c3 = tmpImage.cursor(); 860 | 861 | // do for all pixels 862 | while ( c1.hasNext() ) 863 | { 864 | // get value of both imgs 865 | RealType t1 = c1.next(); 866 | RealType t2 = c2.next(); 867 | RealType t3 = c3.next(); 868 | 869 | // overwrite img1 with the result 870 | t3.setReal( t1.getRealFloat() * t2.getRealFloat() ); 871 | } 872 | 873 | return tmpImage; 874 | } 875 | 876 | public static< T extends RealType< T > & NativeType< T > > Img divideImages(Img img1,Img img2) 877 | { 878 | // Create an instance of an image factory 879 | ImgFactory< T > imgFactory = new ArrayImgFactory< T >(); 880 | 881 | // Create an Img with the same dimension as the input image 882 | final Img< T > tmpImage = imgFactory.create( img1, img1.firstElement() ); 883 | 884 | // Create a cblurGaussianblurGaussianursor for both images 885 | Cursor c1 = img1.cursor(); 886 | Cursor c2 = img2.cursor(); 887 | Cursor c3 = tmpImage.cursor(); 888 | 889 | // do for all pixels 890 | while ( c1.hasNext() ) 891 | { 892 | // get value of both imgs 893 | RealType t1 = c1.next(); 894 | RealType t2 = c2.next(); 895 | RealType t3 = c3.next(); 896 | 897 | if(t2.getRealFloat()>0) 898 | { 899 | // overwrite img1 with the result 900 | t3.setReal( t1.getRealFloat() / t2.getRealFloat() ); 901 | } else { 902 | t3.setReal(0); 903 | } 904 | 905 | } 906 | 907 | return tmpImage; 908 | } 909 | 910 | public static< T extends RealType< T > & NativeType< T > > Img subtractImages(Img img1,Img img2) 911 | { 912 | // Create an instance of an image factory 913 | ImgFactory< T > imgFactory = new ArrayImgFactory< T >(); 914 | 915 | // Create an Img with the same dimension as the input image 916 | final Img< T > tmpImage = imgFactory.create( img1, img1.firstElement() ); 917 | 918 | // Create a cursor for both images 919 | Cursor c1 = img1.cursor(); 920 | Cursor c2 = img2.cursor(); 921 | Cursor c3 = tmpImage.cursor(); 922 | 923 | // do for all pixels 924 | while ( c1.hasNext() ) 925 | { 926 | // get value of both imgs 927 | RealType t1 = c1.next(); 928 | RealType t2 = c2.next(); 929 | RealType t3 = c3.next(); 930 | 931 | if(t2.getRealFloat()>0) 932 | { 933 | // overwrite img1 with the result 934 | t3.setReal( t1.getRealFloat() - t2.getRealFloat() ); 935 | } else { 936 | t3.setReal(0); 937 | } 938 | 939 | } 940 | 941 | return tmpImage; 942 | } 943 | 944 | public static< T extends RealType< T > & NativeType< T > > Img squareRootImage(Img img) 945 | { 946 | // Create an instance of an image factory 947 | ImgFactory< T > imgFactory = new ArrayImgFactory< T >(); 948 | 949 | // Create an Img with the same dimension as the input image 950 | final Img< T > tmpImage = imgFactory.create( img, img.firstElement() ); 951 | 952 | // Create a cursor for both images 953 | Cursor c1 = img.cursor(); 954 | Cursor c2 = tmpImage.cursor(); 955 | 956 | // do for all pixels 957 | while ( c1.hasNext() ) 958 | { 959 | // get value of both imgs 960 | RealType t1 = c1.next(); 961 | RealType t2 = c2.next(); 962 | 963 | // overwrite img1 with the result 964 | //t2.setReal( Math.sqrt(t1.getRealFloat())); 965 | t2.setReal( Math.sqrt(t1.getRealFloat())); 966 | //t2.setReal( t1.getRealFloat()); 967 | } 968 | 969 | return tmpImage; 970 | } 971 | 972 | /** 973 | * Quick test method to run directly in IDE 974 | */ 975 | public static void main(String[] args) throws Exception { 976 | // set the plugins.dir property to make the plugin appear in the Plugins menu 977 | // see: https://stackoverflow.com/a/7060464/1207769 978 | Class clazz = PHANTAST_.class; 979 | java.net.URL url = clazz.getProtectionDomain().getCodeSource().getLocation(); 980 | java.io.File file = new java.io.File(url.toURI()); 981 | System.setProperty("plugins.dir", file.getAbsolutePath()); 982 | 983 | ImageJ ij = new ImageJ(); 984 | ij.exitWhenQuitting(true); 985 | 986 | ImagePlus imp = IJ.openImage("D:\\Phase_Tests\\003 TRANS-5.tif"); 987 | imp.show(); 988 | RoiManager rm = new RoiManager(); 989 | 990 | IJ.run("PHANTAST ", ""); 991 | } 992 | 993 | 994 | } 995 | 996 | --------------------------------------------------------------------------------