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}