Search in sources :

Example 21 with Statistics

use of gdsc.core.utils.Statistics in project GDSC-SMLM by aherbert.

the class CreateData method showSummary.

private double showSummary(List<? extends FluorophoreSequenceModel> fluorophores, List<LocalisationModel> localisations) {
    IJ.showStatus("Calculating statistics ...");
    createSummaryTable();
    Statistics[] stats = new Statistics[NAMES.length];
    for (int i = 0; i < stats.length; i++) {
        stats[i] = (settings.showHistograms || alwaysRemoveOutliers[i]) ? new StoredDataStatistics() : new Statistics();
    }
    // Find the largest timepoint
    ImagePlus outputImp = WindowManager.getImage(benchmarkImageId);
    int nFrames;
    if (outputImp == null) {
        sortLocalisationsByTime(localisations);
        nFrames = localisations.get(localisations.size() - 1).getTime();
    } else {
        nFrames = outputImp.getStackSize();
    }
    int[] countHistogram = new int[nFrames + 1];
    // Use the localisations that were drawn to create the sampled on/off times
    rebuildNeighbours(localisations);
    // Assume that there is at least one localisation
    LocalisationModel first = localisations.get(0);
    // The current localisation
    int currentId = first.getId();
    // The last time this localisation was on
    int lastT = first.getTime();
    // Number of blinks
    int blinks = 0;
    // On-time of current pulse
    int currentT = 0;
    double signal = 0;
    final double centreOffset = settings.size * 0.5;
    // Used to convert the sampled times in frames into seconds
    final double framesPerSecond = 1000.0 / settings.exposureTime;
    final double gain = (settings.getTotalGain() > 0) ? settings.getTotalGain() : 1;
    for (LocalisationModel l : localisations) {
        if (l.getData() == null)
            System.out.println("No localisation data. This should not happen!");
        final double noise = (l.getData() != null) ? l.getData()[1] : 1;
        final double intensity = (l.getData() != null) ? l.getData()[4] : l.getIntensity();
        final double intensityInPhotons = intensity / gain;
        // Q. What if the noise is zero, i.e. no background photon / read noise?
        // Just ignore it at current.
        final double snr = intensity / noise;
        stats[SIGNAL].add(intensityInPhotons);
        stats[NOISE].add(noise / gain);
        if (noise != 0)
            stats[SNR].add(snr);
        //if (l.isContinuous())
        if (l.getNext() != null && l.getPrevious() != null) {
            stats[SIGNAL_CONTINUOUS].add(intensityInPhotons);
            if (noise != 0)
                stats[SNR_CONTINUOUS].add(snr);
        }
        int id = l.getId();
        // Check if this a new fluorophore
        if (currentId != id) {
            // Add previous fluorophore
            stats[SAMPLED_BLINKS].add(blinks);
            stats[SAMPLED_T_ON].add(currentT / framesPerSecond);
            stats[TOTAL_SIGNAL].add(signal);
            // Reset
            blinks = 0;
            currentT = 1;
            currentId = id;
            signal = intensityInPhotons;
        } else {
            signal += intensityInPhotons;
            // Check if the current fluorophore pulse is broken (i.e. a blink)
            if (l.getTime() - 1 > lastT) {
                blinks++;
                stats[SAMPLED_T_ON].add(currentT / framesPerSecond);
                currentT = 1;
                stats[SAMPLED_T_OFF].add(((l.getTime() - 1) - lastT) / framesPerSecond);
            } else {
                // Continuous on-time
                currentT++;
            }
        }
        lastT = l.getTime();
        countHistogram[lastT]++;
        stats[X].add((l.getX() - centreOffset) * settings.pixelPitch);
        stats[Y].add((l.getY() - centreOffset) * settings.pixelPitch);
        stats[Z].add(l.getZ() * settings.pixelPitch);
    }
    // Final fluorophore
    stats[SAMPLED_BLINKS].add(blinks);
    stats[SAMPLED_T_ON].add(currentT / framesPerSecond);
    stats[TOTAL_SIGNAL].add(signal);
    // Samples per frame
    for (int t = 1; t < countHistogram.length; t++) stats[SAMPLES].add(countHistogram[t]);
    if (fluorophores != null) {
        for (FluorophoreSequenceModel f : fluorophores) {
            stats[BLINKS].add(f.getNumberOfBlinks());
            // On-time
            for (double t : f.getOnTimes()) stats[T_ON].add(t);
            // Off-time
            for (double t : f.getOffTimes()) stats[T_OFF].add(t);
        }
    } else {
        // show no blinks
        stats[BLINKS].add(0);
        stats[T_ON].add(1);
    //stats[T_OFF].add(0);
    }
    if (results != null) {
        final boolean emCCD = (settings.getEmGain() > 1);
        // Convert depth-of-field to pixels
        final double depth = settings.depthOfField / settings.pixelPitch;
        for (PeakResult r : results.getResults()) {
            final double precision = r.getPrecision(settings.pixelPitch, gain, emCCD);
            stats[PRECISION].add(precision);
            // The error stores the z-depth in pixels
            if (Math.abs(r.error) < depth)
                stats[PRECISION_IN_FOCUS].add(precision);
            stats[WIDTH].add(r.getSD());
        }
        // Compute density per frame. Multithread for speed
        if (settings.densityRadius > 0) {
            IJ.showStatus("Calculating density ...");
            ExecutorService threadPool = Executors.newFixedThreadPool(Prefs.getThreads());
            List<Future<?>> futures = new LinkedList<Future<?>>();
            final ArrayList<float[]> coords = new ArrayList<float[]>();
            int t = results.getHead().getFrame();
            final Statistics densityStats = stats[DENSITY];
            final float radius = (float) (settings.densityRadius * getHWHM());
            final Rectangle bounds = results.getBounds();
            currentIndex = 0;
            finalIndex = results.getTail().getFrame();
            // Store the density for each result.
            int[] allDensity = new int[results.size()];
            int allIndex = 0;
            for (PeakResult r : results.getResults()) {
                if (t != r.getFrame()) {
                    allIndex += runDensityCalculation(threadPool, futures, coords, densityStats, radius, bounds, allDensity, allIndex);
                }
                coords.add(new float[] { r.getXPosition(), r.getYPosition() });
                t = r.getFrame();
            }
            runDensityCalculation(threadPool, futures, coords, densityStats, radius, bounds, allDensity, allIndex);
            Utils.waitForCompletion(futures);
            threadPool.shutdownNow();
            threadPool = null;
            IJ.showProgress(1);
            // Split results into singles (density = 0) and clustered (density > 0)
            MemoryPeakResults singles = copyMemoryPeakResults("No Density");
            MemoryPeakResults clustered = copyMemoryPeakResults("Density");
            int i = 0;
            for (PeakResult r : results.getResults()) {
                // Store density in the original value field
                r.origValue = allDensity[i];
                if (allDensity[i++] == 0)
                    singles.add(r);
                else
                    clustered.add(r);
            }
        }
    }
    StringBuilder sb = new StringBuilder();
    sb.append(datasetNumber).append("\t");
    sb.append((fluorophores == null) ? localisations.size() : fluorophores.size()).append("\t");
    sb.append(stats[SAMPLED_BLINKS].getN() + (int) stats[SAMPLED_BLINKS].getSum()).append("\t");
    sb.append(localisations.size()).append("\t");
    sb.append(nFrames).append("\t");
    sb.append(Utils.rounded(areaInUm)).append("\t");
    sb.append(Utils.rounded(localisations.size() / (areaInUm * nFrames), 4)).append("\t");
    sb.append(Utils.rounded(getHWHM(), 4)).append("\t");
    double s = getPsfSD();
    sb.append(Utils.rounded(s, 4)).append("\t");
    s *= settings.pixelPitch;
    final double sa = PSFCalculator.squarePixelAdjustment(s, settings.pixelPitch) / settings.pixelPitch;
    sb.append(Utils.rounded(sa, 4)).append("\t");
    // Width not valid for the Image PSF
    int nStats = (imagePSF) ? stats.length - 1 : stats.length;
    for (int i = 0; i < nStats; i++) {
        double centre = (alwaysRemoveOutliers[i]) ? ((StoredDataStatistics) stats[i]).getStatistics().getPercentile(50) : stats[i].getMean();
        sb.append(Utils.rounded(centre, 4)).append("\t");
    }
    if (java.awt.GraphicsEnvironment.isHeadless()) {
        IJ.log(sb.toString());
        return stats[SIGNAL].getMean();
    } else {
        summaryTable.append(sb.toString());
    }
    // Show histograms
    if (settings.showHistograms) {
        IJ.showStatus("Calculating histograms ...");
        boolean[] chosenHistograms = getChoosenHistograms();
        WindowOrganiser wo = new WindowOrganiser();
        boolean requireRetile = false;
        for (int i = 0; i < NAMES.length; i++) {
            if (chosenHistograms[i]) {
                wo.add(Utils.showHistogram(TITLE, (StoredDataStatistics) stats[i], NAMES[i], (integerDisplay[i]) ? 1 : 0, (settings.removeOutliers || alwaysRemoveOutliers[i]) ? 2 : 0, settings.histogramBins * ((integerDisplay[i]) ? 100 : 1)));
                requireRetile = requireRetile || Utils.isNewWindow();
            }
        }
        wo.tile();
    }
    IJ.showStatus("");
    return stats[SIGNAL].getMean();
}
Also used : StoredDataStatistics(gdsc.core.utils.StoredDataStatistics) ArrayList(java.util.ArrayList) Rectangle(java.awt.Rectangle) WindowOrganiser(ij.plugin.WindowOrganiser) Statistics(gdsc.core.utils.Statistics) SummaryStatistics(org.apache.commons.math3.stat.descriptive.SummaryStatistics) StoredDataStatistics(gdsc.core.utils.StoredDataStatistics) ImagePlus(ij.ImagePlus) PeakResult(gdsc.smlm.results.PeakResult) IdPeakResult(gdsc.smlm.results.IdPeakResult) ExtendedPeakResult(gdsc.smlm.results.ExtendedPeakResult) LinkedList(java.util.LinkedList) LocalisationModel(gdsc.smlm.model.LocalisationModel) FluorophoreSequenceModel(gdsc.smlm.model.FluorophoreSequenceModel) ExecutorService(java.util.concurrent.ExecutorService) Future(java.util.concurrent.Future) MemoryPeakResults(gdsc.smlm.results.MemoryPeakResults)

Example 22 with Statistics

use of gdsc.core.utils.Statistics in project GDSC-SMLM by aherbert.

the class CMOSAnalysis method run.

private void run() {
    long start = System.currentTimeMillis();
    // Avoid all the file saves from updating the progress bar and status line
    Utils.setShowProgress(false);
    Utils.setShowStatus(false);
    JLabel statusLine = Utils.getStatusLine();
    progressBar = Utils.getProgressBar();
    // Create thread pool and workers
    ExecutorService executor = Executors.newFixedThreadPool(getThreads());
    TurboList<Future<?>> futures = new TurboList<Future<?>>(nThreads);
    TurboList<ImageWorker> workers = new TurboList<ImageWorker>(nThreads);
    double[][][] data = new double[subDirs.size()][2][];
    double[] pixelOffset = null, pixelVariance = null;
    Statistics statsOffset = null, statsVariance = null;
    // For each sub-directory compute the mean and variance
    final int nSubDirs = subDirs.size();
    boolean error = false;
    for (int n = 0; n < nSubDirs; n++) {
        SubDir sd = subDirs.getf(n);
        statusLine.setText("Analysing " + sd.name);
        // Open the series
        SeriesImageSource source = new SeriesImageSource(sd.name, sd.path.getPath());
        //source.setLogProgress(true);
        if (!source.open()) {
            error = true;
            IJ.error(TITLE, "Failed to open image series: " + sd.path.getPath());
            break;
        }
        // So the bar remains at 99% when workers have finished
        totalProgress = source.getFrames() + 1;
        stepProgress = Utils.getProgressInterval(totalProgress);
        progress = 0;
        progressBar.show(0);
        ArrayMoment moment = (rollingAlgorithm) ? new RollingArrayMoment() : new SimpleArrayMoment();
        final BlockingQueue<ImageJob> jobs = new ArrayBlockingQueue<ImageJob>(nThreads * 2);
        for (int i = 0; i < nThreads; i++) {
            final ImageWorker worker = new ImageWorker(jobs, moment);
            workers.add(worker);
            futures.add(executor.submit(worker));
        }
        // Process the data
        for (float[] pixels = source.next(); pixels != null; pixels = source.next()) {
            put(jobs, new ImageJob(pixels));
        }
        // Finish all the worker threads by passing in a null job
        for (int i = 0; i < nThreads; i++) {
            put(jobs, new ImageJob(null));
        }
        // Wait for all to finish
        for (int t = futures.size(); t-- > 0; ) {
            try {
                // The future .get() method will block until completed
                futures.get(t).get();
            } catch (Exception e) {
                // This should not happen. 
                e.printStackTrace();
            }
        }
        // Create the final aggregate statistics
        for (ImageWorker w : workers) moment.add(w.moment);
        data[n][0] = moment.getFirstMoment();
        data[n][1] = moment.getVariance();
        // Reset
        futures.clear();
        workers.clear();
        Statistics s = new Statistics(data[n][0]);
        if (n != 0) {
            // Compute mean ADU
            Statistics signal = new Statistics();
            double[] mean = data[n][0];
            for (int i = 0; i < pixelOffset.length; i++) signal.add(mean[i] - pixelOffset[i]);
            Utils.log("%s Mean = %s +/- %s. Signal = %s +/- %s ADU", sd.name, Utils.rounded(s.getMean()), Utils.rounded(s.getStandardDeviation()), Utils.rounded(signal.getMean()), Utils.rounded(signal.getStandardDeviation()));
            // TODO - optionally save
            ImageStack stack = new ImageStack(source.getWidth(), source.getHeight());
            stack.addSlice("Mean", Utils.toFloat(data[n][0]));
            stack.addSlice("Variance", Utils.toFloat(data[n][1]));
            IJ.save(new ImagePlus("PerPixel", stack), new File(directory, "perPixel" + sd.name + ".tif").getPath());
        } else {
            pixelOffset = data[0][0];
            pixelVariance = data[0][1];
            statsOffset = s;
            statsVariance = new Statistics(pixelVariance);
            Utils.log("%s Offset = %s +/- %s. Variance = %s +/- %s", sd.name, Utils.rounded(s.getMean()), Utils.rounded(s.getStandardDeviation()), Utils.rounded(statsVariance.getMean()), Utils.rounded(statsVariance.getStandardDeviation()));
        }
    }
    Utils.setShowStatus(true);
    Utils.setShowProgress(true);
    IJ.showProgress(1);
    executor.shutdown();
    if (error)
        return;
    // Compute the gain
    statusLine.setText("Computing gain");
    double[] pixelGain = new double[pixelOffset.length];
    // Ignore first as this is the 0 exposure image
    for (int i = 0; i < pixelGain.length; i++) {
        // Use equation 2.5 from the Huang et al paper.
        double bibiT = 0;
        double biaiT = 0;
        for (int n = 1; n < nSubDirs; n++) {
            double bi = data[n][0][i] - pixelOffset[i];
            double ai = data[n][1][i] - pixelVariance[i];
            bibiT += bi * bi;
            biaiT += bi * ai;
        }
        pixelGain[i] = biaiT / bibiT;
    }
    Statistics statsGain = new Statistics(pixelGain);
    Utils.log("Gain Mean = %s +/- %s", Utils.rounded(statsGain.getMean()), Utils.rounded(statsGain.getStandardDeviation()));
    // Histogram of offset, variance and gain
    int bins = Utils.getBinsSturges(pixelGain.length);
    WindowOrganiser wo = new WindowOrganiser();
    showHistogram("Offset", pixelOffset, bins, statsOffset, wo);
    showHistogram("Variance", pixelVariance, bins, statsVariance, wo);
    showHistogram("Gain", pixelGain, bins, statsGain, wo);
    wo.tile();
    // Save
    measuredStack = new ImageStack(size, size);
    measuredStack.addSlice("Offset", Utils.toFloat(pixelOffset));
    measuredStack.addSlice("Variance", Utils.toFloat(pixelVariance));
    measuredStack.addSlice("Gain", Utils.toFloat(pixelGain));
    IJ.save(new ImagePlus("PerPixel", measuredStack), new File(directory, "perPixel.tif").getPath());
    // Remove the status from the ij.io.ImageWriter class
    IJ.showStatus("");
    Utils.log("Analysis time = " + Utils.timeToString(System.currentTimeMillis() - start));
}
Also used : RollingArrayMoment(gdsc.core.math.RollingArrayMoment) ArrayMoment(gdsc.core.math.ArrayMoment) RollingArrayMoment(gdsc.core.math.RollingArrayMoment) SimpleArrayMoment(gdsc.core.math.SimpleArrayMoment) ArrayBlockingQueue(java.util.concurrent.ArrayBlockingQueue) SimpleArrayMoment(gdsc.core.math.SimpleArrayMoment) TurboList(gdsc.core.utils.TurboList) ImageStack(ij.ImageStack) SeriesImageSource(gdsc.smlm.ij.SeriesImageSource) JLabel(javax.swing.JLabel) WindowOrganiser(ij.plugin.WindowOrganiser) Statistics(gdsc.core.utils.Statistics) ImagePlus(ij.ImagePlus) ExecutorService(java.util.concurrent.ExecutorService) Future(java.util.concurrent.Future) File(java.io.File)

Example 23 with Statistics

use of gdsc.core.utils.Statistics in project GDSC-SMLM by aherbert.

the class CreateData method setNoise.

/**
	 * Sets the noise in the results if missing.
	 *
	 * @param results
	 *            the results
	 */
private void setNoise(MemoryPeakResults results, ImagePlus imp) {
    // Loaded results do not have noise
    for (PeakResult r : results.getResults()) if (r.noise != 0)
        return;
    // Compute noise per frame
    ImageStack stack = imp.getImageStack();
    final int width = stack.getWidth();
    final int height = stack.getHeight();
    final IJImageSource source = new IJImageSource(imp);
    final float[] noise = new float[source.getFrames() + 1];
    for (int slice = 1; slice < noise.length; slice++) {
        stack.getPixels(slice);
        float[] data = source.next();
        // Use the trimmed method as there may be a lot of spots in the frame
        noise[slice] = (float) FitWorker.estimateNoise(data, width, height, NoiseEstimator.Method.QUICK_RESIDUALS_LEAST_TRIMMED_OF_SQUARES);
    }
    Statistics stats = new Statistics(Arrays.copyOfRange(noise, 1, noise.length));
    System.out.printf("Noise = %.3f +/- %.3f (%d)\n", stats.getMean(), stats.getStandardDeviation(), stats.getN());
    for (PeakResult p : results.getResults()) {
        if (p.getFrame() < noise.length)
            p.noise = noise[p.getFrame()];
    }
}
Also used : IJImageSource(gdsc.smlm.ij.IJImageSource) ImageStack(ij.ImageStack) Statistics(gdsc.core.utils.Statistics) SummaryStatistics(org.apache.commons.math3.stat.descriptive.SummaryStatistics) StoredDataStatistics(gdsc.core.utils.StoredDataStatistics) PeakResult(gdsc.smlm.results.PeakResult) IdPeakResult(gdsc.smlm.results.IdPeakResult) ExtendedPeakResult(gdsc.smlm.results.ExtendedPeakResult)

Example 24 with Statistics

use of gdsc.core.utils.Statistics in project GDSC-SMLM by aherbert.

the class TraceDiffusion method summarise.

private void summarise(Trace[] traces, double[] fitMSDResult, int n, double[][] jdParams) {
    IJ.showStatus("Calculating summary ...");
    // Create summary table
    createSummaryTable();
    Statistics[] stats = new Statistics[NAMES.length];
    for (int i = 0; i < stats.length; i++) {
        stats[i] = (settings.showHistograms) ? new StoredDataStatistics() : new Statistics();
    }
    for (Trace trace : traces) {
        stats[T_ON].add(trace.getOnTime() * exposureTime);
        final double signal = trace.getSignal() / results.getGain();
        stats[TOTAL_SIGNAL].add(signal);
        stats[SIGNAL_PER_FRAME].add(signal / trace.size());
    }
    // Add to the summary table
    StringBuilder sb = new StringBuilder(title);
    sb.append('\t').append(createCombinedName());
    sb.append("\t");
    sb.append(Utils.rounded(exposureTime * 1000, 3)).append("\t");
    sb.append(Utils.rounded(settings.distanceThreshold, 3)).append("\t");
    sb.append(Utils.rounded(settings.distanceExclusion, 3)).append("\t");
    sb.append(settings.minimumTraceLength).append("\t");
    sb.append(settings.ignoreEnds).append("\t");
    sb.append(settings.truncate).append("\t");
    sb.append(settings.internalDistances).append("\t");
    sb.append(settings.fitLength).append("\t");
    sb.append(settings.msdCorrection).append("\t");
    sb.append(settings.precisionCorrection).append("\t");
    sb.append(settings.mle).append("\t");
    sb.append(traces.length).append("\t");
    sb.append(Utils.rounded(precision, 4)).append("\t");
    double D = 0, s = 0;
    if (fitMSDResult != null) {
        D = fitMSDResult[0];
        s = fitMSDResult[1];
    }
    sb.append(Utils.rounded(D, 4)).append("\t");
    sb.append(Utils.rounded(s * 1000, 4)).append("\t");
    sb.append(Utils.rounded(settings.jumpDistance * exposureTime)).append("\t");
    sb.append(n).append("\t");
    sb.append(Utils.rounded(beta, 4)).append("\t");
    if (jdParams == null) {
        sb.append("\t\t\t");
    } else {
        sb.append(format(jdParams[0])).append("\t");
        sb.append(format(jdParams[1])).append("\t");
        sb.append(Utils.rounded(ic)).append("\t");
    }
    for (int i = 0; i < stats.length; i++) {
        sb.append(Utils.rounded(stats[i].getMean(), 3)).append("\t");
    }
    if (java.awt.GraphicsEnvironment.isHeadless()) {
        IJ.log(sb.toString());
        return;
    } else {
        summaryTable.append(sb.toString());
    }
    if (settings.showHistograms) {
        IJ.showStatus("Calculating histograms ...");
        for (int i = 0; i < NAMES.length; i++) {
            if (displayHistograms[i]) {
                showHistogram((StoredDataStatistics) stats[i], NAMES[i], alwaysRemoveOutliers[i], ROUNDED[i], false);
            }
        }
    }
    tileNewWindows();
    IJ.showStatus("Finished " + TITLE);
}
Also used : Trace(gdsc.smlm.results.Trace) StoredDataStatistics(gdsc.core.utils.StoredDataStatistics) StoredDataStatistics(gdsc.core.utils.StoredDataStatistics) Statistics(gdsc.core.utils.Statistics)

Example 25 with Statistics

use of gdsc.core.utils.Statistics in project GDSC-SMLM by aherbert.

the class SpotAnalysis method extractSpotProfile.

private double[][] extractSpotProfile(ImagePlus imp, Rectangle bounds, ImageStack rawSpot) {
    final int nSlices = imp.getStackSize();
    IJImageSource rawSource = new IJImageSource(imp);
    double[][] profile = new double[2][nSlices];
    for (int n = 0; n < nSlices; n++) {
        IJ.showProgress(n, nSlices);
        float[] data = rawSource.next(bounds);
        rawSpot.setPixels(data, n + 1);
        Statistics stats = new Statistics(data);
        profile[0][n] = stats.getMean() / gain;
        profile[1][n] = stats.getStandardDeviation() / gain;
    }
    return profile;
}
Also used : IJImageSource(gdsc.smlm.ij.IJImageSource) Statistics(gdsc.core.utils.Statistics) Point(java.awt.Point)

Aggregations

Statistics (gdsc.core.utils.Statistics)32 StoredDataStatistics (gdsc.core.utils.StoredDataStatistics)14 ArrayList (java.util.ArrayList)10 MemoryPeakResults (gdsc.smlm.results.MemoryPeakResults)7 WindowOrganiser (ij.plugin.WindowOrganiser)7 Plot2 (ij.gui.Plot2)6 PeakResult (gdsc.smlm.results.PeakResult)5 ImageStack (ij.ImageStack)5 Point (java.awt.Point)5 DescriptiveStatistics (org.apache.commons.math3.stat.descriptive.DescriptiveStatistics)5 BasePoint (gdsc.core.match.BasePoint)4 Trace (gdsc.smlm.results.Trace)4 Well19937c (org.apache.commons.math3.random.Well19937c)4 Gaussian2DFunction (gdsc.smlm.function.gaussian.Gaussian2DFunction)3 ImagePlus (ij.ImagePlus)3 Rectangle (java.awt.Rectangle)3 ExecutorService (java.util.concurrent.ExecutorService)3 Future (java.util.concurrent.Future)3 RandomDataGenerator (org.apache.commons.math3.random.RandomDataGenerator)3 SummaryStatistics (org.apache.commons.math3.stat.descriptive.SummaryStatistics)3