/* 3D_HaT This plugin measures Haralick textures in 3 dimensions. The calculation of GLCM is performed in 13-directions of each pixel, then symmetrized and normalized according Löfstedt et al. The calculation of Haralick features is performed according to according Löfstedt et al. The plugin has been developped to work on any images read by bioformat. Update : -Add of a ROI possibility Julien Dumont November 2020 */ #@ ImagePlus (required=false) singleimg #@ OpService ops #@ ResultsTable rt import net.imagej.ops.image.cooccurrenceMatrix.MatrixOrientation3D import net.imagej.ops.image.cooccurrenceMatrix.MatrixOrientation2D import net.imglib2.img.array.ArrayImgs import net.imglib2.img.display.imagej.ImageJFunctions import net.imglib2.type.numeric.real.DoubleType import net.imglib2.Cursor import net.imglib2.IterableInterval import ij.plugin.ImageCalculator import ij.IJ import loci.plugins.BF import loci.formats.ImageReader import loci.formats.MetadataTools import loci.plugins.in.ImporterOptions import net.imglib2.type.numeric.integer.LongType import groovy.time.TimeDuration import groovy.time.TimeCategory import ij.gui.GenericDialog import fiji.util.gui.GenericDialogPlus import java.awt.Button import java.awt.Panel import java.awt.event.ActionEvent import java.awt.event.* import groovy.json.JsonSlurper import ij.gui.WaitForUserDialog import ij.plugin.frame.RoiManager import org.scijava.plugin.Parameter import org.scijava.plugin.Plugin import org.scijava.command.Command @Plugin(type = Command.class, menuPath = "Plugins>PreProcessing") def main(){ GUI() if(gdp.wasCanceled() == true){ return } //Verification of the radii index and retrieve of the distance if (gdp.wasOKed()){ if(radii==0){ IJ.log("Error : A radius must be selected.") GUI() } //Extraction of the radius if(radii==1){ flop = radiusdiag.getNextNumber() flip = "["+ flop +"]" radius = new JsonSlurper().parseText(flip) } else{ radius = radiusdiag.getNextString() flip = "["+ radius +"]" radius = new JsonSlurper().parseText(flip) } } if (singleimg == null){ srcFile = new File(gdp.getNextString()) } dstFile = new File(gdp.getNextString()) filename = gdp.getNextString() quant = gdp.getNextNumber() roi = gdp.getCheckboxes().get(0).getState() //Argument checking //Quantization factor check if(quant==0 || quant%2!=0 && quant!=1){ IJ.log("ERROR : Quantization should be even or equal to 1, and can't be 0.\nScript has been stopped.") return } //Single image analysis if (singleimg != null) { //Folder to save datas imp = singleimg title = imp.getTitle() if(imp.getClass().toString() != "class ij.ImagePlus"){ IJ.log("Snapshot " + title + " not compatible with 2D-analysis : Multicolor image detected") return -1 } if(filename != null){ filepath = dstFile.toString()+ File.separator + filename+ File.separator feature = filepath + File.separator + title + "_GLCM_feature.xls" } else{ filepath = dstFile.toString()+ File.separator + title + "_d=" + radius+ "_q=" + quant + File.separator feature = filepath + File.separator + "GLCM_feature.xls" } File outfolder = new File(filepath) if( !outfolder.exists()) { outfolder.mkdirs() } shorttitle = imp.getShortTitle() nGLCalc(imp, quant) //Quantization of the image if(roi == true){ rm = new RoiManager() rm.reset() rm.getRoiManager() wfud = new WaitForUserDialog("ROI", "Draw ROI and add them to the ROIManager.\nClick \"OK\" when you're done, or \"Esc\" to quit.") wfud.show() if(wfud.escPressed){ return } Roisave = outfolder.toString() + title +".zip" rm.runCommand("Save", Roisave) for(r=0; r"+ "Contrast :

"+ "Contrast measures the quantity of local changes in an image.
Large contrast means coarse texture, while low contrast reflects acute texture.

"+ "Energy :

"+ "Energy measures the homogeneity of the image.

"+ "Entropy :

"+ "Entropy measure of randomness of intensity image.

"+ "Homogeneity :

"+ "Homogeneity measures the similarity of pixels.

"+ "Correlation :

"+ "Correlation measures how correlated a pixel is to its neighborhood.

"+ "

This work is based on :
  • Löfstedt et al.,Gray-level invariant Haralick texture features. PLOS ONE, 2019, 14. e0212110. 10.1371/journal.pone.0212110." + "
  • R. M. Haralick et al., Textural Features for Image Classification, IEEE Transactions on Systems, Man, and Cybernetics, Nov. 1973
  • " + "



    Plugin designed by Julien Dumont" + "") gdp.showDialog() } def openimage (file, Nserie) { options = new ImporterOptions() options.setId(file) options.setSeriesOn(Nserie, true) imps = BF.openImagePlus(options) return imps[0] } def nGLCalc(img, quant){ bit = img.getBitDepth() nGL = (2**bit)/quant nGL = nGL.intValue() return nGL } def GLCMprocess(img, nGL, distance) { zslice = img.getNSlices() //Quantization of the image IJ.run(img, "Divide...", "value=" + quant +" stack") //Calculus of the co-occurence matrix in the 4(2D) or 13(3D) directions. Return stack of GLCM and number of grey-level if (zslice != 1) { orientation = [MatrixOrientation3D.HORIZONTAL, MatrixOrientation3D.VERTICAL, MatrixOrientation3D.DIAGONAL, MatrixOrientation3D.ANTIDIAGONAL, MatrixOrientation3D.HORIZONTAL_VERTICAL, MatrixOrientation3D.HORIZONTAL_DIAGONAL, MatrixOrientation3D.VERTICAL_VERTICAL, MatrixOrientation3D.VERTICAL_DIAGONAL, MatrixOrientation3D.DIAGONAL_VERTICAL, MatrixOrientation3D.DIAGONAL_DIAGONAL, MatrixOrientation3D.ANTIDIAGONAL_VERTICAL, MatrixOrientation3D.ANTIDIAGONAL_DIAGONAL, MatrixOrientation3D.DEPTH] } else{ orientation = [MatrixOrientation2D.HORIZONTAL, MatrixOrientation2D.VERTICAL, MatrixOrientation2D.DIAGONAL, MatrixOrientation2D.ANTIDIAGONAL] } long[] dimensions = [nGL, nGL, orientation.size()] GLCM = ArrayImgs.doubles(dimensions) cursor = GLCM.randomAccess() for (z = 0; z < orientation.size(); z++){ result = ops.run("image.cooccurrenceMatrix", img, nGL, distance, orientation[z]) for(y = 0; y < nGL; y++){ for(x = 0; x < nGL; x++){ cursor.setPosition(x, 0) cursor.setPosition(y, 1) cursor.setPosition(z, 2) value = result[x][y] cursor.get().set(value) } } } //img.close() if (zslice != 1) { GLCM = ops.run("math.divide", GLCM, 13) } else{ GLCM = ops.run("math.divide", GLCM, 4) } return [GLCM, nGL] } def calculus(GLCM, nGL, distance, folder){ //Differentials dA = 1/(Math.pow(nGL, 2)) dU = 1/nGL //Projection along Z dimension to sum different orientation projectOp = ops.op("stats.sum", GLCM) GLCMproj = ArrayImgs.doubles(nGL, nGL) GLCMproj = ops.run("transform.project", GLCMproj, GLCM, projectOp, 2) prout = ImageJFunctions.wrap(GLCMproj, "prout") IJ.save(prout, folder.toString() + File.separator + shorttitle + "_GLCM_original"+".tif" ) //Invariant normalization of the symmetrical GLCM try{ GLCMfin = ops.run("math.divide", GLCMproj, dA) } catch(Exception ex){ GLCMproj = ImageJFunctions.wrap(GLCMproj) GLCMfin = ops.run("math.divide", GLCMproj, dA) } GLCM = ImageJFunctions.wrap(GLCMfin, "GLCM") IJ.save(GLCM, folder.toString() + File.separator + shorttitle + "_GLCM_d=" + distance + ".tif" ) //Calcul de Pxi(i) => Somme de la GLCM dans la dimension Y rand = GLCMfin.randomAccess() Pxi = [nGL] for(x=0; x Somme de la GLCM dans la dimension X rand = GLCMfin.randomAccess() Pyi = [nGL] for(y=0; y