001/* 002 * Copyright 2010-2015 Institut Pasteur. 003 * 004 * This file is part of Icy. 005 * 006 * Icy is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * Icy is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with Icy. If not, see <http://www.gnu.org/licenses/>. 018 */ 019package icy.math; 020 021import icy.file.FileUtil; 022import icy.gui.dialog.MessageDialog; 023import icy.gui.dialog.SaveDialog; 024import icy.type.TypeUtil; 025import icy.type.collection.array.ArrayUtil; 026import icy.util.StringUtil; 027import icy.util.XLSUtil; 028 029import java.io.IOException; 030import java.io.PrintWriter; 031import java.util.Arrays; 032 033import jxl.write.WritableSheet; 034import jxl.write.WritableWorkbook; 035import jxl.write.WriteException; 036 037/** 038 * @author Stephane 039 */ 040public class Histogram 041{ 042 protected final int[] bins; 043 044 protected final double minValue; 045 protected final double maxValue; 046 protected final boolean integer; 047 048 protected final double dataToBin; 049 protected final double binWidth; 050 051 /** 052 * Create a histogram for the specified value range and the desired number of bins. 053 * 054 * @param minValue 055 * minimum value 056 * @param maxValue 057 * maximum value 058 * @param nbBin 059 * number of desired bins (should be > 0). 060 * @param integer 061 * If true the input value are considered as integer values.<br> 062 * Bins number is then clamped to values range to not bias the histogram.<br> 063 * In this case the effective number of bins can differ from the desired one. 064 */ 065 public Histogram(double minValue, double maxValue, int nbBin, boolean integer) 066 { 067 super(); 068 069 this.minValue = minValue; 070 this.maxValue = maxValue; 071 this.integer = integer; 072 073 final double range = (maxValue - minValue); 074 double bw; 075 076 if (integer) 077 { 078 if (nbBin > range) 079 bw = 1d; 080 else 081 { 082 bw = (range + 1d) / nbBin; 083 bw = Math.max(1d, Math.floor(bw)); 084 } 085 bins = new int[(int) Math.ceil((range + 1d) / bw)]; 086 } 087 else 088 { 089 bw = range / nbBin; 090 bins = new int[nbBin]; 091 } 092 093 binWidth = bw; 094 095 // data to bin index conversion ratio 096 if (range > 0) 097 dataToBin = (bins.length - 1) / range; 098 else 099 dataToBin = 0d; 100 } 101 102 /** 103 * Returns histogram data in CSV format (tab separated). 104 */ 105 public String getCSVFormattedData() 106 { 107 final StringBuffer sbf = new StringBuffer(); 108 final double bw = getBinWidth(); 109 110 // column title 111 sbf.append("Bin range min (inclusive)"); 112 sbf.append("\t"); 113 sbf.append("Bin range max (exclusive)"); 114 sbf.append("\t"); 115 sbf.append("Pixel number"); 116 sbf.append("\t"); 117 sbf.append("\r\n"); 118 119 double range = getMinValue(); 120 121 for (int bin : getBins()) 122 { 123 sbf.append(StringUtil.toString(range)); 124 sbf.append("\t"); 125 range += bw; 126 sbf.append(StringUtil.toString(range)); 127 sbf.append("\t"); 128 129 sbf.append(StringUtil.toString(bin)); 130 sbf.append("\r\n"); 131 } 132 133 return sbf.toString(); 134 } 135 136 /** 137 * Do the XLS export (display the save dialog) 138 * 139 * @throws IOException 140 * @throws WriteException 141 */ 142 public void doXLSExport() throws IOException, WriteException 143 { 144 exportToXLS(SaveDialog.chooseFileForResult("Export histogram...", "histo", ".xls")); 145 } 146 147 /** 148 * Export the content of the histogram data inside an excel file (XLS format if file path extension is XLS, CSV 149 * otherwise) 150 * 151 * @throws IOException 152 * @throws WriteException 153 */ 154 public void exportToXLS(String path) throws IOException, WriteException 155 { 156 if (StringUtil.isEmpty(path)) 157 return; 158 159 final String csvContent = getCSVFormattedData(); 160 161 // CSV format wanted ? 162 if (!FileUtil.getFileExtension(path, false).toLowerCase().startsWith("xls")) 163 { 164 // just write CSV content 165 final PrintWriter out = new PrintWriter(path); 166 out.println(csvContent); 167 out.close(); 168 } 169 // XLS export 170 else 171 { 172 final WritableWorkbook workbook = XLSUtil.createWorkbook(path); 173 final WritableSheet sheet = XLSUtil.createNewPage(workbook, "ROIS"); 174 175 if (XLSUtil.setFromCSV(sheet, csvContent)) 176 XLSUtil.saveAndClose(workbook); 177 else 178 { 179 MessageDialog.showDialog("Error", "Error while exporting ROIs table content to XLS file.", 180 MessageDialog.ERROR_MESSAGE); 181 } 182 } 183 } 184 185 /** 186 * Reset histogram 187 */ 188 public void reset() 189 { 190 Arrays.fill(bins, 0); 191 } 192 193 /** 194 * Add the value to the histogram 195 */ 196 public void addValue(double value) 197 { 198 final int index = (int) ((value - minValue) * dataToBin); 199 200 if ((index >= 0) && (index < bins.length)) 201 bins[index]++; 202 } 203 204 /** 205 * Add the specified array of values to the histogram 206 * 207 * @param signed 208 * false if the input array should be interpreted as unsigned values<br> 209 * (integer type only) 210 */ 211 public void addValues(Object array, boolean signed) 212 { 213 switch (ArrayUtil.getDataType(array)) 214 { 215 case BYTE: 216 addValues((byte[]) array, signed); 217 break; 218 219 case SHORT: 220 addValues((short[]) array, signed); 221 break; 222 223 case INT: 224 addValues((int[]) array, signed); 225 break; 226 227 case LONG: 228 addValues((long[]) array, signed); 229 break; 230 231 case FLOAT: 232 addValues((float[]) array); 233 break; 234 235 case DOUBLE: 236 addValues((double[]) array); 237 break; 238 } 239 } 240 241 /** 242 * Add the specified byte array to the histogram 243 */ 244 public void addValues(byte[] array, boolean signed) 245 { 246 if (signed) 247 { 248 for (byte value : array) 249 bins[(int) ((value - minValue) * dataToBin)]++; 250 } 251 else 252 { 253 for (byte value : array) 254 bins[(int) ((TypeUtil.unsign(value) - minValue) * dataToBin)]++; 255 } 256 } 257 258 /** 259 * Add the specified short array to the histogram 260 */ 261 public void addValues(short[] array, boolean signed) 262 { 263 if (signed) 264 { 265 for (short value : array) 266 bins[(int) ((value - minValue) * dataToBin)]++; 267 } 268 else 269 { 270 for (short value : array) 271 bins[(int) ((TypeUtil.unsign(value) - minValue) * dataToBin)]++; 272 } 273 } 274 275 /** 276 * Add the specified int array to the histogram 277 */ 278 public void addValues(int[] array, boolean signed) 279 { 280 if (signed) 281 { 282 for (int value : array) 283 bins[(int) ((value - minValue) * dataToBin)]++; 284 } 285 else 286 { 287 for (int value : array) 288 bins[(int) ((TypeUtil.unsign(value) - minValue) * dataToBin)]++; 289 } 290 } 291 292 /** 293 * Add the specified long array to the histogram 294 */ 295 public void addValues(long[] array, boolean signed) 296 { 297 if (signed) 298 { 299 for (long value : array) 300 bins[(int) ((value - minValue) * dataToBin)]++; 301 } 302 else 303 { 304 for (long value : array) 305 bins[(int) ((TypeUtil.unsign(value) - minValue) * dataToBin)]++; 306 } 307 } 308 309 /** 310 * Add the specified float array to the histogram 311 */ 312 public void addValues(float[] array) 313 { 314 for (float value : array) 315 bins[(int) ((value - minValue) * dataToBin)]++; 316 } 317 318 /** 319 * Add the specified double array to the histogram 320 */ 321 public void addValues(double[] array) 322 { 323 for (double value : array) 324 bins[(int) ((value - minValue) * dataToBin)]++; 325 } 326 327 /** 328 * Get bin index from data value 329 */ 330 protected int toBinIndex(double value) 331 { 332 return (int) Math.round((value - minValue) * dataToBin); 333 } 334 335 /** 336 * Returns the minimum allowed value of the histogram. 337 */ 338 public double getMinValue() 339 { 340 return minValue; 341 } 342 343 /** 344 * Returns the maximum allowed value of the histogram. 345 */ 346 public double getMaxValue() 347 { 348 return maxValue; 349 } 350 351 /** 352 * Returns true if the input value are integer values only.<br> 353 * This is used to adapt the bin number. 354 */ 355 public boolean isIntegerType() 356 { 357 return integer; 358 } 359 360 /** 361 * Returns the number of bins of the histogram. 362 */ 363 public int getBinNumber() 364 { 365 return bins.length; 366 } 367 368 /** 369 * Return the width of a bin 370 */ 371 public double getBinWidth() 372 { 373 return binWidth; 374 } 375 376 /** 377 * Returns the size of the specified bin (number of element in the bin) 378 */ 379 public int getBinSize(int index) 380 { 381 if ((index < 0) || (index >= bins.length)) 382 return 0; 383 384 return bins[index]; 385 } 386 387 /** 388 * Returns bins of histogram 389 */ 390 public int[] getBins() 391 { 392 return bins; 393 } 394 395}