org.specvis.logic.Functions.java Source code

Java tutorial

Introduction

Here is the source code for org.specvis.logic.Functions.java

Source

package org.specvis.logic;

import javafx.scene.control.Alert;
import javafx.scene.paint.Color;
import org.apache.commons.math3.fitting.PolynomialCurveFitter;
import org.apache.commons.math3.fitting.WeightedObservedPoints;
import org.specvis.StartApplication;
import org.specvis.datastructures.luminancescale.LuminanceScale;
import org.specvis.datastructures.procedures.basic.ProcedureBasicData;
import org.specvis.datastructures.procedures.basic.ProcedureBasicStimulus;
import org.specvis.view.miscellaneous.ViewExceptionDialog;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

/**
 * Created by Piotr Dzwiniel on 2016-02-12.
 */

/*
 * Copyright from 2014 till now - Piotr Dzwiniel
 *
 * This file is part of Specvis.
 *
 * Specvis is free software; you can redistribute it
 * and/or modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 3 of the License,
 * or (at your option) any later version.
 *
 * Specvis is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Specvis; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

public class Functions {

    public Functions() {

    }

    /**
     * Create individual identificator consisted of current date and N number
     * of unspecified symbols (lower- and upper-case letters and 0-9 digits). Format
     * of the identificator is "yyyyMMdd_nsymbols".
     * @param currentDate
     * @param numberOfUnspecifiedSymbols
     * @return Unique identificator consisted of current date and N symbols in a format "yyyyMMdd_symbols".
     */
    public String createIndividualID(String currentDate, int numberOfUnspecifiedSymbols) {
        char[] chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".toCharArray();
        StringBuilder sb = new StringBuilder();
        Random random = new Random();
        for (int l = 0; l < numberOfUnspecifiedSymbols; l++) {
            char c = chars[random.nextInt(chars.length)];
            sb.append(c);
        }
        String date = currentDate.replaceAll("-", "");
        return date + "_" + sb.toString();
    }

    /**
     * Get current date in "yyyyMMdd" format.
     * @return Current date in "yyyyMMdd" format.
     */
    public String getCurrentDateYYYYmmDD() {
        Calendar date = new GregorianCalendar();
        String day = Integer.toString(date.get(Calendar.DAY_OF_MONTH));
        String month = Integer.toString(date.get(Calendar.MONTH) + 1);
        String year = Integer.toString(date.get(Calendar.YEAR));
        if (month.length() < 2) {
            month = "0" + month;
        }
        if (day.length() < 2) {
            day = "0" + day;
        }
        return year + "-" + month + "-" + day;
    }

    /**
     * Round given double value to N decimal places.
     * @param value
     * @param places
     * @return Rounded double value.
     */
    public double round(double value, int places) {
        if (places < 0)
            throw new IllegalArgumentException();
        BigDecimal bd = new BigDecimal(value);
        bd = bd.setScale(places, RoundingMode.HALF_UP);
        return bd.doubleValue();
    }

    /**
     * Convert javafx.scene.paint.Color to its HEX value.
     * @param color
     * @return Color in HEX value in "#RRGGBB" format.
     */
    public String toHexCode(Color color) {
        return String.format("#%02X%02X%02X", (int) (color.getRed() * 255), (int) (color.getGreen() * 255),
                (int) (color.getBlue() * 255));
    }

    /**
     * Fit 2nd degree polynomial to some X vector based on given coefficients.
     * @param coeff
     * @param x
     * @return Fitted 2nd degree polynomial in a form of a vector.
     * @throws Exception
     */
    public double[] poly2D(double[] coeff, double[] x) throws Exception {
        if (coeff.length != 3) {
            throw new Exception("poly2D method can handle only 3 coefficients.");
        } else {
            double[] y = new double[x.length];
            for (int i = 0; i < y.length; i++) {
                y[i] = coeff[0] * Math.pow(x[i], 2) + coeff[1] * x[i] + coeff[2];
            }
            return y;
        }
    }

    /**
     * Create brightness vector based on provided vector length.
     * @param vectorLength
     * @return Brightness vector.
     */
    public double[] createBrightnessVector(int vectorLength) {
        double step = 101 / (double) vectorLength;
        double[] brightnessVector = new double[vectorLength];
        for (int i = 0; i < vectorLength; i++) {
            brightnessVector[i] = (double) i * step;
        }
        return brightnessVector;
    }

    /**
     * Get fitting coefficients for N degree polynomial.
     * @param x
     * @param y
     * @param degree
     * @param reverseCoefficients
     * @return Fitting coefficients.
     */
    public double[] fitPolynomialToData(double[] x, double[] y, int degree, boolean reverseCoefficients) {
        WeightedObservedPoints obs = new WeightedObservedPoints();
        for (int i = 0; i < x.length; i++) {
            obs.add(1, x[i], y[i]);
        }
        PolynomialCurveFitter fitter = PolynomialCurveFitter.create(degree);
        double[] coefficients = fitter.fit(obs.toList());
        if (reverseCoefficients) {
            return reverseDoublePrimitiveArray(coefficients);
        } else {
            return coefficients;
        }
    }

    /**
     * Reverse double vector.
     * @param array
     * @return Reversed double vector.
     */
    public double[] reverseDoublePrimitiveArray(double[] array) {
        for (int i = 0; i < array.length / 2; i++) {
            double temp = array[i];
            array[i] = array[array.length - i - 1];
            array[array.length - i - 1] = temp;
        }
        return array;
    }

    /**
     * Check whether given double vector contains some specific X value, ie. "target value".
     * @param arr
     * @param targetValue
     * @return True if given vector contains provided target value; False if given vector do not contains target value.
     */
    public boolean isThisPrimitiveArrayContainsValue(double[] arr, double targetValue) {
        for (double i : arr) {
            if (i == targetValue) {
                return true;
            }
        }
        return false;
    }

    /**
     * Calculate opposite of the angle.
     * @param a: angle in degrees;
     * @param r:
     * @return: Opposite of the angle;
     */
    public double calculateOppositeAngle(double a, double r) {
        /**
         *      Calculation note:
         *
         *      |\
         *      |a\
         *      |  \
         *     r|   \
         *      |    \
         *      |     \
         *      |______\
         *         x
         *
         *      In order to calculate "x" (opposite of the angle "a") one must do:
         *
         *      x = r / cot(a),
         *
         *      where "a" is in radians. Function "cot" can be also
         *      described as "1/tan".
         */

        double x;
        a = Math.toRadians(a);
        x = r / (1 / Math.tan(a));
        return x;
    }

    /**
     * Convert millimiters to pixels.
     * @param m: millimiters;
     * @param ssp: screen size in pixels;
     * @param ssm: screen size in millimiters;
     * @return: Pixels;
     */
    public double millimitersToPixels(double m, double ssp, double ssm) {
        double pixelsForOneMillimiter = ssp / ssm;
        return m * pixelsForOneMillimiter;
    }

    /**
     * If some given "resolution" expressed in pixels (px) can be expressed in degrees (dg) of the visual field,
     * say 1920px can be expressed as 50dg of the visual field, that it is possible to calculate
     * how many pixels is for one degree of the visual field. In this example 1dg = 38.4px.
     * @param resolution
     * @param field
     * @return Pixels per one degree.
     */
    public double pxForOneDg(int resolution, double field) {
        return resolution / field;
    }

    /**
     * Generate double linspace vector.
     * @param start
     * @param stop
     * @param n
     * @param roundToInt
     * @return Double linspace vector.
     */
    public ArrayList<Double> linspace(double start, double stop, int n, boolean roundToInt) {
        ArrayList<Double> result = new ArrayList();
        double step = (stop - start) / (n - 1);
        for (int i = 0; i <= n - 2; i++) {
            if (roundToInt) {
                BigDecimal bd = new BigDecimal(start + (i * step));
                bd = bd.setScale(0, RoundingMode.HALF_UP);
                result.add(bd.doubleValue());
            } else {
                result.add(start + (i * step));
            }
        }
        result.add(stop);
        return result;
    }

    /**
     * Generate random int value from some given min-max range.
     * @param min
     * @param max
     * @return Random int value.
     */
    public int randomInt(int min, int max) {
        Random random = new Random();
        return random.nextInt(max - min + 1) + min;
    }

    /**
     * Generate random int value from some given "(0 to N) + M" range.
     * @param constantPart
     * @param randomPart
     * @return Random int value.
     */
    public int randomInterval(int constantPart, int randomPart) {
        Random randomGenerator = new Random();
        return constantPart + randomGenerator.nextInt(randomPart + 1);
    }

    /**
     * Get rounded decibel (dB) double value based on provided luminance parameters.
     * @param maxLuminance
     * @param stimulusLuminance
     * @param backgroundLuminance
     * @param round
     * @return Decibel (dB) double value.
     */
    public double decibelsValue(double maxLuminance, double stimulusLuminance, double backgroundLuminance,
            int round) {
        double decibels = 10 * Math.log10(maxLuminance / stimulusLuminance);
        return round(decibels, round);
    }

    /**
     * Get time interval between some start and end value in "hh:mm:ss" format.
     * @param start
     * @param end
     * @return Time in "hh:mm:ss" format.
     */
    public String totalTime(long start, long end) {
        long difference = end - start;
        return String.format("%02d:%02d:%02d", TimeUnit.MILLISECONDS.toHours(difference),
                TimeUnit.MILLISECONDS.toMinutes(difference)
                        - TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(difference)),
                TimeUnit.MILLISECONDS.toSeconds(difference)
                        - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(difference)));
    }

    /**
     * Create arange vector.
     * @param start
     * @param end
     * @param step
     * @return Arange vector.
     */
    public double[] arange(double start, double end, double step) {
        return IntStream.rangeClosed(0, (int) ((end - start) / step)).mapToDouble(x -> x * step + start).toArray();
    }

    /**
     * Convert ternary value to decimal value;
     * @param ternaryNumber: ternary number splitted to single trits, where ternaryNumber.get(0)
     *                     is the least-significant-trit and ternaryNumber.get(ternaryNumber.size()-1)
     *                     is the most-significant-trit;
     * @return: decimal representation of ternary value;
     */
    public int ternaryToDecimalConverter(ArrayList<Integer> ternaryNumber) {

        int decimalValue = 0;

        for (int i = 0; i < ternaryNumber.size(); i++) {
            if (ternaryNumber.get(i) >= 0 && ternaryNumber.get(i) <= 2) {
                decimalValue += ternaryNumber.get(i) * Math.pow(3, i);
            } else {
                throw new IllegalArgumentException();
            }
        }

        return decimalValue;
    }

    /**
     * Check if a given value is below, within, or above given range.
     * @param startOfRange
     * @param endOfRange
     * @param value
     * @return: 0 - below range, 1 - within range, 2 - above range;
     */
    public int checkIfValueIsInRange(double startOfRange, double endOfRange, double value) {
        if (value < startOfRange) {
            return 0;
        } else if (value >= startOfRange && value <= endOfRange) {
            return 1;
        } else {
            return 2;
        }
    }

    /**
     * Find existing Luminance Scale by its ID.
     * @param scaleID
     * @return Found Luminance Scale.
     */
    public LuminanceScale findExistingLuminanceScaleByID(String scaleID) {
        LuminanceScale luminanceScale = new LuminanceScale();
        try {
            // 1.
            ArrayList<String> array = new ArrayList<>();
            File file = new File("screenLuminanceScales.s");
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                array.add(line);
            }
            bufferedReader.close();

            // 2.
            for (String anArray : array) {
                String[] str = anArray.split("\t");
                if (str[0].equals(scaleID)) {
                    luminanceScale.setId(str[0]);
                    luminanceScale.setName(str[1]);
                    luminanceScale.setHue(Double.valueOf(str[2]));
                    luminanceScale.setSaturation(Double.valueOf(str[3]));
                    luminanceScale.setLuminanceForBrightness0(Double.valueOf(str[4]));
                    luminanceScale.setLuminanceForBrightness20(Double.valueOf(str[5]));
                    luminanceScale.setLuminanceForBrightness40(Double.valueOf(str[6]));
                    luminanceScale.setLuminanceForBrightness60(Double.valueOf(str[7]));
                    luminanceScale.setLuminanceForBrightness80(Double.valueOf(str[8]));
                    luminanceScale.setLuminanceForBrightness100(Double.valueOf(str[9]));
                    luminanceScale.setAdditionalInformation(str[10].replaceAll("#n%", "\n"));
                }
            }

            // 3.
            double[] brightnessValues = new double[] { 0, 20, 40, 60, 80, 100 };
            double[] measuredLuminances = new double[] { luminanceScale.getLuminanceForBrightness0(),
                    luminanceScale.getLuminanceForBrightness20(), luminanceScale.getLuminanceForBrightness40(),
                    luminanceScale.getLuminanceForBrightness60(), luminanceScale.getLuminanceForBrightness80(),
                    luminanceScale.getLuminanceForBrightness100() };
            double[] polynomialFitCoefficients = fitPolynomialToData(brightnessValues, measuredLuminances, 2, true);
            double[] brightnessVector = createBrightnessVector(101);
            double[] fittedLuminanceForEachBrightnessValue = new double[brightnessVector.length];
            try {
                fittedLuminanceForEachBrightnessValue = poly2D(polynomialFitCoefficients, brightnessVector);
            } catch (Exception ex) {
                ViewExceptionDialog ed = new ViewExceptionDialog(Alert.AlertType.ERROR, ex);
                ed.setTitle("Exception");
                ed.setHeaderText(ex.getClass().getName());
                ed.showAndWait();
            }
            luminanceScale.setFittedLuminanceForEachBrightnessValue(fittedLuminanceForEachBrightnessValue);

        } catch (Exception ex) {
            ViewExceptionDialog ed = new ViewExceptionDialog(Alert.AlertType.ERROR, ex);
            ed.setTitle("Exception");
            ed.setHeaderText(ex.getClass().getName());
            ed.showAndWait();
        }
        return luminanceScale;
    }

    /**
     * Create logspace vector.
     * @param start
     * @param stop
     * @param n
     * @param roundToInt
     * @return Logspace vector.
     */
    public ArrayList<Double> logspace(double start, double stop, int n, boolean roundToInt) {
        ArrayList<Double> arrayList = new ArrayList<>();

        n -= 1;

        for (int i = 0; i <= n; i++) {
            double d = start * Math.pow(stop / start, (double) i / n);
            if (roundToInt) {
                round(d, 0);
            }
            arrayList.add(d);
        }

        return arrayList;
    }

    /**
     * Delete given directory.
     * @param dir
     * @return True if directory was deleted succesfully.
     */
    public boolean deleteDir(File dir) {
        if (dir.isDirectory()) {
            String[] children = dir.list();
            for (int i = 0; i < children.length; i++) {
                boolean success = deleteDir(new File(dir, children[i]));
                if (!success) {
                    return false;
                }
            }
        }

        return dir.delete(); // The directory is empty now and can be deleted.
    }

    /**
     * Get sorted data from Basic procedure.
     * @return Sorted data from Basic procedure.
     */
    public ArrayList<ArrayList<ProcedureBasicStimulus>> getSortedDataFromFromBasicProcedure() {

        ArrayList<ArrayList<ProcedureBasicStimulus>> sortedData = new ArrayList<>();

        // Get ProcedureBasicData.
        ProcedureBasicData procedureBasicData = StartApplication.getSpecvisData().getProcedureBasicData();
        ArrayList<ProcedureBasicStimulus> data = procedureBasicData.getArrayListProcedureBasicStimulus();

        // Sort data by positionOnTheScreenInPixelsY.
        Collections.sort(data, Comparator.comparingDouble(ProcedureBasicStimulus::getPositionOnTheScreenInPixelsY));

        // Sort data by positionOnTheScreenInPixelsX.
        double temp = 0;
        for (int i = 0; i < data.size(); i++) {

            if (temp != data.get(i).getPositionOnTheScreenInPixelsY()) {
                ArrayList<ProcedureBasicStimulus> arrayList = new ArrayList<>(); // getAllStimuliWithSpecifiYPosition(data.get(i).getPositionOnTheScreenInPixelsY(), data);

                for (int j = 0; j < data.size(); j++) {
                    if (data.get(i).getPositionOnTheScreenInPixelsY() == data.get(j)
                            .getPositionOnTheScreenInPixelsY()) {
                        arrayList.add(data.get(j));
                    }
                }

                Collections.sort(arrayList,
                        Comparator.comparingDouble(ProcedureBasicStimulus::getPositionOnTheScreenInPixelsX));
                sortedData.add(arrayList);
            }

            temp = data.get(i).getPositionOnTheScreenInPixelsY();
        }

        return sortedData;
    }

    /**
     * Find real involved visual field during the visual field examination. Real visual field
     * is a visual field counted from the locations of most distant visual stimuli presented
     * to the patient.
     * @param visualField
     * @param centerPosition
     * @param distanceBetweenStimuli
     * @return Real visual field value.
     */
    public double findRealInvolvedVisualFieldNormal(double visualField, double centerPosition,
            double distanceBetweenStimuli) {

        double realInvolvedVisualField;

        // 1.
        double leftHalf = (visualField / 2) + centerPosition;
        double rightHalf = visualField - leftHalf;

        // 2. Find real visual field.
        double tempLeftVF = distanceBetweenStimuli / 2;
        while (true) {
            if (tempLeftVF + distanceBetweenStimuli > leftHalf) {
                break;
            } else {
                tempLeftVF += distanceBetweenStimuli;
            }
        }

        double tempRightVF = distanceBetweenStimuli / 2;
        while (true) {
            if (tempRightVF + distanceBetweenStimuli > rightHalf) {
                break;
            } else {
                tempRightVF += distanceBetweenStimuli;
            }
        }

        realInvolvedVisualField = tempLeftVF + tempRightVF;

        return realInvolvedVisualField;
    }

    /**
     * Find real involved visual field during the visual field examination. Real visual field
     * is a visual field counted from the locations of most distant visual stimuli presented
     * to the patient. Here, the correction for sphericity of the field of view is used.
     * @param visualField
     * @param centerPosition
     * @param patientDistance
     * @param screenSize
     * @param distanceBetweenStimuli
     * @return Real visual field value.
     */
    public double findRealInvolvedVisualFieldWithCorrectionForSphericity(double visualField, double centerPosition,
            double patientDistance, double screenSize, double distanceBetweenStimuli) {

        double realInvolvedVisualField;

        // Define fields.
        double alpha = distanceBetweenStimuli;
        double radius = patientDistance;
        double base = screenSize;

        // 1.
        double leftHalfVF = (visualField / 2) + centerPosition;
        double rightHalfVF = visualField - leftHalfVF;

        // 2.
        double leftHalfBaseInMM = calculateOppositeAngle(leftHalfVF, radius);
        double rightHalfBaseInMM = calculateOppositeAngle(rightHalfVF, radius);

        // 3.
        double tempLeftVFinMM = calculateOppositeAngle(alpha / 2, radius);
        double tempAlphaLeft = alpha / 2;
        while (true) {
            double tempCondition = calculateOppositeAngle(tempAlphaLeft + distanceBetweenStimuli, radius);
            if (tempCondition > leftHalfBaseInMM) {
                break;
            } else {
                tempLeftVFinMM = tempCondition;
                tempAlphaLeft += distanceBetweenStimuli;
            }
        }

        // 4.
        double tempRightVFinMM = calculateOppositeAngle(alpha / 2, radius);
        double tempAlphaRight = alpha / 2;
        while (true) {
            double tempCondition = calculateOppositeAngle(tempAlphaRight + distanceBetweenStimuli, radius);
            if (tempCondition > rightHalfBaseInMM) {
                break;
            } else {
                tempRightVFinMM = tempCondition;
                tempAlphaRight += distanceBetweenStimuli;
            }
        }

        // 5.
        double totalUsedVFinMM = tempLeftVFinMM + tempRightVFinMM;

        // 6.
        realInvolvedVisualField = 2 * Math.atan2(totalUsedVFinMM / 10, 2 * (radius / 10)) * (180 / Math.PI);

        return realInvolvedVisualField;
    }
}