import ij.*; import ij.plugin.*; import ij.process.*; import ij.gui.*; import ij.measure.*; import java.awt.*; import java.awt.image.*; import java.awt.event.*; import java.applet.*; import java.awt.geom.*; import java.awt.font.*; /** MS-SSIM index by Zhou Wang and MS-SSIM* Index by David Rouse and Sheila Hemami The equivalent of Zhou Wang's MS-SSIM (SUPRA-THRESHOLD LEVEL PROBLEM) MatLab code as a Java plugin inside ImageJ. Also, this plugin performs the equivalent of David Rouse and Sheila Hemami's MSSIM* (RECOGNITION THRESHOLD PROBLEM). This plugin works with 8, 16 and 32 bits gray levels. Main references: Zhou Wang, A. C. Bovik, H. R. Sheikh, and E. P. Simoncelli, “Image quality assessment: From error visibility to structural similarity”, IEEE Trans. Image Processing, vol. 13, pp. 600–612, Apr. 2004. David M. Rouse and Sheila S. Hemami, "Analyzing the Role of Visual Structure in the Recognition of Natural Image Content with Multi-Scale SSIM," Proc. SPIE Vol. 6806, Human Vision and Electronic Imaging 2008. ImageJ by W. Rasband, U. S. National Institutes of Health, Bethesda, Maryland, USA, http://rsb.info.nih.gov/ij/. 1997-2008. January 22th 2009. Java Code by Gabriel Prieto, Margarita Chevalier, Eduardo Guibelalde 22/01/2009.gprietor@med.ucm.es Permission to use, copy, or modify this software and its documentation for educational and research purposes only and without fee is hereby granted, provided that this copyright notice and the original authors' names appear on all copies and supporting documentation. This program shall not be used, rewritten, or adapted as the basis of a commercial software or hardware product without first obtaining permission of the authors. The authors make no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty. Please, refer to this version as: Gabriel Prieto, Margarita Chevalier, Eduardo Guibelalde. "MS_SSIM Index and MS_SSIM* Index as a Java plugin for ImageJ" Department of Radiology, Faculty of Medicine. Universidad Complutense. Madrid. SPAIN. http://www.ucm.es/info/fismed/MSSIM/MSSIM.htm */ public class MS_SSIM_index implements PlugIn { protected ImagePlus image_1_imp, image_2_imp; //THIS PLUGIN WORKS WITH TWO (AND ONLY TWO) IMAGES OPEN IN IMAGEJ protected ImageProcessor image_1_p, image_2_p; public void run (String arg) { String title_1, title_2, message_1, message_2; int pointer, filter_length, image_height, image_width, image_dimension, bits_per_pixel_1, bits_per_pixel_2, a, b, c; float filter_weights []; double luminance_exponent [] = { 1, 1, 1, 1, 1, 0.1333}; double contrast_exponent [] = { 1, 0.0448, 0.2856, 0.3001, 0.2363, 0.1333}; double structure_exponent []= { 1, 0.0448, 0.2856, 0.3001, 0.2363, 0.1333}; double luminance_comparison =1; double contrast_comparison =1; double structure_comparison =1; double ms_ssim_index; double [] ssim_map; double ssim_index; // // ERROR CONTROLS. TWO IMAGES SHOULD BE OPENED AND BOTH WITH THE SAME DIMENSIONS // int[] wList = WindowManager.getIDList(); if (wList==null) { IJ.error("There is no image open"); return; } a = WindowManager.getImageCount(); if (a!=2) { IJ.error("There must be two images open to calculate SSIM index"); return; } image_1_imp = WindowManager.getImage(wList[0]); image_2_imp = WindowManager.getImage(wList[1]); image_height = image_1_imp.getHeight(); a= image_2_imp.getHeight(); if (a!=image_height) { IJ.error("Both images must have the same height"); return; } image_width = image_1_imp.getWidth(); a= image_2_imp.getWidth(); if (a!=image_width) { IJ.error("Both images must have the same width"); return; } if ((image_height < 32) | (image_width < 32)) { IJ.error("Miminum height and width must be 32 pixels"); return; } bits_per_pixel_1=image_1_imp.getBitDepth(); bits_per_pixel_2=image_2_imp.getBitDepth(); if (bits_per_pixel_1 != bits_per_pixel_2){ IJ.error("Both images must have the same number of bits per pixel"); return; } if (bits_per_pixel_1 == 24){ IJ.error("RGB images are not supportedl"); return; } // // END OF CONTROL ERRORS // // // THIS DIALOG BOX SHOWS DIFFERENT OPTIONS TO CREATE THE WINDOW WE ARE GOING TO USE TO EVALUATE SSIM INDEX OVER THE ENTIRE IMAGES // double sigma_gauss = 1.5; int filter_width = 11; double K1 = 0.01; double K2 = 0.03; double lod [] = {0.0378, -0.0238, -0.1106, 0.3774, 0.8527, 0.3774, -0.1106, -0.0238, 0.0378}; // // LOD [] ARE LOW PASS FILTER VALUES. Impulse response of low-pass filter to use defaults to 9/7 biorthogonal wavelet filters. IT USES THE ROUSE/HEMAMI'S VALUES INSTEAD OF ZHOU WANG'S METHOD AND VALUES. // THERE IS A LITTLE DIFFERENCE (< 2%) WITH MR. WANG'S VALUES FOR THE SAME SET OF IMAGES. // double number_of_levels = 5; double downsampled=1; boolean gaussian_window = true; String[] window_type = {"Gaussian","Same weight"}; // WE CAN WEIGHTS THE WINDOW WITH A GAUSSIAN WEIGHTING FUNCTION OR GIVING THE SAME WEIGHT TO ALL THE PIXELS IN THE WINDOW String window_selection = window_type[0]; String[] kind_of_algorithm = {"Zhou Wang","Rouse/Hemami"}; // WE CAN USE THE INDEX FOR THE SUPRA-THRESHOLD LEVEL (WANG) OR THE RECOGNITION THRESHOLD (ROUSE/HEMAMI) String algorithm_selection = kind_of_algorithm[0]; boolean out=false; boolean show_ssim_map= false; String[] ssim_map_level_option = {"0", "1", "2", "3", "4", "5"}; String ssim_map_selection = ssim_map_level_option[0]; int ssim_map_level=0; boolean show_downsampled_images= false; while (!out){ out=true; GenericDialog gd = new GenericDialog ("MS-SSIM Index calculation"); gd.addNumericField ("Standard deviation:", sigma_gauss, 1); gd.addChoice("Window type:", window_type, window_selection); gd.addChoice("Algorithm:", kind_of_algorithm, algorithm_selection); gd.addNumericField ("Filter width:", filter_width, 0); gd.addNumericField ("K1:", K1, 2); gd.addNumericField ("K2:", K2, 2); gd.addNumericField ("Downsample image (prior to calculate MS-SSIM):", downsampled, 0); gd.addChoice("Show SSIM map (with exponents alfa, beta, gamma eq 1) at level: ", ssim_map_level_option, ssim_map_selection); gd.addNumericField ("lod_11_33:", lod[0], 4, 7, ""); gd.addNumericField ("lod_12_32:", lod[1], 4, 7, ""); gd.addNumericField ("lod_13_31:", lod[2], 4, 7, ""); gd.addNumericField ("lod_21_23:", lod[3], 4, 7, ""); gd.addNumericField ("lod_22:", lod[4], 4, 7, ""); gd.showDialog(); if (gd.wasCanceled()) return; sigma_gauss = gd.getNextNumber(); window_selection = gd.getNextChoice(); algorithm_selection = gd.getNextChoice(); filter_width = (int) (gd.getNextNumber()); K1 = gd.getNextNumber(); K2 = gd.getNextNumber(); downsampled = (int) gd.getNextNumber (); ssim_map_level = gd.getNextChoiceIndex(); lod[0] = lod[8]=gd.getNextNumber(); lod[1] = lod[7]=gd.getNextNumber(); lod[2] = lod[6]=gd.getNextNumber(); lod[3] = lod[5]=gd.getNextNumber(); lod[4] = gd.getNextNumber(); // // WE SHOW THE DIALOG BOX // double d; a = filter_width/2; d = filter_width -a*2; if (window_selection != "Gaussian") gaussian_window = false; if (d==0) { IJ.error("Filter width and heigth must be odd"); out = false; } if (gaussian_window & sigma_gauss <= 0) { IJ.error("Sigma must be greater than 0"); out = false; } if ((image_height/downsampled < 32) | (image_width/downsampled < 32)) { IJ.error("Miminum height must be 32 pixels (review downsample value)"); out = false; } if (downsampled < 1) { IJ.error("Minimun value of Viewing scale must be 1"); out = false; } if (downsampled != 1){ show_downsampled_images = true; } gd.dispose(); } double C1 = (Math.pow(2, bits_per_pixel_1) - 1)*K1; C1= C1*C1; double C2 = (Math.pow(2, bits_per_pixel_1) - 1)*K2; C2=C2*C2; // // NOW, WE CREATE THE FILTER, GAUSSIAN OR MEDIA FILTER, ACCORDING TO THE VALUE OF boolean "gaussian_window" // filter_length = filter_width*filter_width; float window_weights [] = new float [filter_length]; double [] array_gauss_window = new double [filter_length]; if (gaussian_window) { double value, distance = 0; int center = (filter_width/2); double total = 0; double sigma_sq=sigma_gauss*sigma_gauss; for (int y = 0; y < filter_width; y++){ for (int x = 0; x < filter_width; x++){ distance = Math.abs(x-center)*Math.abs(x-center)+Math.abs(y-center)*Math.abs(y-center); pointer = y*filter_width + x; array_gauss_window[pointer] = Math.exp(-0.5*distance/sigma_sq); total = total + array_gauss_window[pointer]; } } for (pointer=0; pointer < filter_length; pointer++) { array_gauss_window[pointer] = array_gauss_window[pointer] / total; window_weights [pointer] = (float) array_gauss_window[pointer]; } } else { // NO WEIGHTS. ALL THE PIXELS IN THE EVALUATION WINDOW HAVE THE SAME WEIGHT for (pointer=0; pointer < filter_length; pointer++) { array_gauss_window[pointer]= (double) 1.0/ filter_length; window_weights [pointer] = (float) array_gauss_window[pointer]; } } // // END OF FILTER SELECTION // // // THE VALUE OF THE LOW PASS FILTER // float [] lpf = new float [81]; int lpf_width = 9; for (a=0; a