InterpolatingMicrosphere.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      https://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. /*
  18.  * This is not the original file distributed by the Apache Software Foundation
  19.  * It has been modified by the Hipparchus project
  20.  */
  21. package org.hipparchus.analysis.interpolation;

  22. import java.util.ArrayList;
  23. import java.util.List;

  24. import org.hipparchus.exception.LocalizedCoreFormats;
  25. import org.hipparchus.exception.MathIllegalArgumentException;
  26. import org.hipparchus.exception.MathIllegalStateException;
  27. import org.hipparchus.random.UnitSphereRandomVectorGenerator;
  28. import org.hipparchus.util.FastMath;
  29. import org.hipparchus.util.MathArrays;
  30. import org.hipparchus.util.MathUtils;

  31. /**
  32.  * Utility class for the {@link MicrosphereProjectionInterpolator} algorithm.
  33.  *
  34.  */
  35. public class InterpolatingMicrosphere {
  36.     /** Microsphere. */
  37.     private final List<Facet> microsphere;
  38.     /** Microsphere data. */
  39.     private final List<FacetData> microsphereData;
  40.     /** Space dimension. */
  41.     private final int dimension;
  42.     /** Number of surface elements. */
  43.     private final int size;
  44.     /** Maximum fraction of the facets that can be dark. */
  45.     private final double maxDarkFraction;
  46.     /** Lowest non-zero illumination. */
  47.     private final double darkThreshold;
  48.     /** Background value. */
  49.     private final double background;

  50.     /**
  51.      * Create an unitialiazed sphere.
  52.      * Sub-classes are responsible for calling the {@code add(double[]) add}
  53.      * method in order to initialize all the sphere's facets.
  54.      *
  55.      * @param dimension Dimension of the data space.
  56.      * @param size Number of surface elements of the sphere.
  57.      * @param maxDarkFraction Maximum fraction of the facets that can be dark.
  58.      * If the fraction of "non-illuminated" facets is larger, no estimation
  59.      * of the value will be performed, and the {@code background} value will
  60.      * be returned instead.
  61.      * @param darkThreshold Value of the illumination below which a facet is
  62.      * considered dark.
  63.      * @param background Value returned when the {@code maxDarkFraction}
  64.      * threshold is exceeded.
  65.      * @throws MathIllegalArgumentException if {@code dimension <= 0}
  66.      * or {@code size <= 0}.
  67.      * @throws MathIllegalArgumentException if {@code darkThreshold < 0}.
  68.      * @throws MathIllegalArgumentException if {@code maxDarkFraction} does not
  69.      * belong to the interval {@code [0, 1]}.
  70.      */
  71.     protected InterpolatingMicrosphere(int dimension,
  72.                                        int size,
  73.                                        double maxDarkFraction,
  74.                                        double darkThreshold,
  75.                                        double background) {
  76.         if (dimension <= 0) {
  77.             throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_SMALL_BOUND_EXCLUDED,
  78.                                                    dimension, 0);
  79.         }
  80.         if (size <= 0) {
  81.             throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_SMALL_BOUND_EXCLUDED,
  82.                                                    size, 0);
  83.         }
  84.         MathUtils.checkRangeInclusive(maxDarkFraction, 0, 1);
  85.         if (darkThreshold < 0) {
  86.             throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_SMALL, darkThreshold, 0);
  87.         }

  88.         this.dimension = dimension;
  89.         this.size = size;
  90.         this.maxDarkFraction = maxDarkFraction;
  91.         this.darkThreshold = darkThreshold;
  92.         this.background = background;
  93.         microsphere = new ArrayList<>(size);
  94.         microsphereData = new ArrayList<>(size);
  95.     }

  96.     /**
  97.      * Create a sphere from randomly sampled vectors.
  98.      *
  99.      * @param dimension Dimension of the data space.
  100.      * @param size Number of surface elements of the sphere.
  101.      * @param rand Unit vector generator for creating the microsphere.
  102.      * @param maxDarkFraction Maximum fraction of the facets that can be dark.
  103.      * If the fraction of "non-illuminated" facets is larger, no estimation
  104.      * of the value will be performed, and the {@code background} value will
  105.      * be returned instead.
  106.      * @param darkThreshold Value of the illumination below which a facet
  107.      * is considered dark.
  108.      * @param background Value returned when the {@code maxDarkFraction}
  109.      * threshold is exceeded.
  110.      * @throws MathIllegalArgumentException if the size of the generated
  111.      * vectors does not match the dimension set in the constructor.
  112.      * @throws MathIllegalArgumentException if {@code dimension <= 0}
  113.      * or {@code size <= 0}.
  114.      * @throws MathIllegalArgumentException if {@code darkThreshold < 0}.
  115.      * @throws MathIllegalArgumentException if {@code maxDarkFraction} does not
  116.      * belong to the interval {@code [0, 1]}.
  117.      */
  118.     public InterpolatingMicrosphere(int dimension,
  119.                                     int size,
  120.                                     double maxDarkFraction,
  121.                                     double darkThreshold,
  122.                                     double background,
  123.                                     UnitSphereRandomVectorGenerator rand) {
  124.         this(dimension, size, maxDarkFraction, darkThreshold, background);

  125.         // Generate the microsphere normals, assuming that a number of
  126.         // randomly generated normals will represent a sphere.
  127.         for (int i = 0; i < size; i++) {
  128.             add(rand.nextVector(), false);
  129.         }
  130.     }

  131.     /**
  132.      * Copy constructor.
  133.      *
  134.      * @param other Instance to copy.
  135.      */
  136.     protected InterpolatingMicrosphere(InterpolatingMicrosphere other) {
  137.         dimension = other.dimension;
  138.         size = other.size;
  139.         maxDarkFraction = other.maxDarkFraction;
  140.         darkThreshold = other.darkThreshold;
  141.         background = other.background;

  142.         // Field can be shared.
  143.         microsphere = other.microsphere;

  144.         // Field must be copied.
  145.         microsphereData = new ArrayList<>(size);
  146.         for (FacetData fd : other.microsphereData) {
  147.             microsphereData.add(new FacetData(fd.illumination(), fd.sample()));
  148.         }
  149.     }

  150.     /**
  151.      * Perform a copy.
  152.      *
  153.      * @return a copy of this instance.
  154.      */
  155.     public InterpolatingMicrosphere copy() {
  156.         return new InterpolatingMicrosphere(this);
  157.     }

  158.     /**
  159.      * Get the space dimensionality.
  160.      *
  161.      * @return the number of space dimensions.
  162.      */
  163.     public int getDimension() {
  164.         return dimension;
  165.     }

  166.     /**
  167.      * Get the size of the sphere.
  168.      *
  169.      * @return the number of surface elements of the microspshere.
  170.      */
  171.     public int getSize() {
  172.         return size;
  173.     }

  174.     /**
  175.      * Estimate the value at the requested location.
  176.      * This microsphere is placed at the given {@code point}, contribution
  177.      * of the given {@code samplePoints} to each sphere facet is computed
  178.      * (illumination) and the interpolation is performed (integration of
  179.      * the illumination).
  180.      *
  181.      * @param point Interpolation point.
  182.      * @param samplePoints Sampling data points.
  183.      * @param sampleValues Sampling data values at the corresponding
  184.      * {@code samplePoints}.
  185.      * @param exponent Exponent used in the power law that computes
  186.      * the weights (distance dimming factor) of the sample data.
  187.      * @param noInterpolationTolerance When the distance between the
  188.      * {@code point} and one of the {@code samplePoints} is less than
  189.      * this value, no interpolation will be performed, and the value
  190.      * of the sample will just be returned.
  191.      * @return the estimated value at the given {@code point}.
  192.      * @throws MathIllegalArgumentException if {@code exponent < 0}.
  193.      */
  194.     public double value(double[] point,
  195.                         double[][] samplePoints,
  196.                         double[] sampleValues,
  197.                         double exponent,
  198.                         double noInterpolationTolerance) {
  199.         if (exponent < 0) {
  200.             throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_SMALL, exponent, 0);
  201.         }

  202.         clear();

  203.         // Contribution of each sample point to the illumination of the
  204.         // microsphere's facets.
  205.         final int numSamples = samplePoints.length;
  206.         for (int i = 0; i < numSamples; i++) {
  207.             // Vector between interpolation point and current sample point.
  208.             final double[] diff = MathArrays.ebeSubtract(samplePoints[i], point);
  209.             final double diffNorm = MathArrays.safeNorm(diff);

  210.             if (FastMath.abs(diffNorm) < noInterpolationTolerance) {
  211.                 // No need to interpolate, as the interpolation point is
  212.                 // actually (very close to) one of the sampled points.
  213.                 return sampleValues[i];
  214.             }

  215.             final double weight = FastMath.pow(diffNorm, -exponent);
  216.             illuminate(diff, sampleValues[i], weight);
  217.         }

  218.         return interpolate();
  219.     }

  220.     /**
  221.      * Replace {@code i}-th facet of the microsphere.
  222.      * Method for initializing the microsphere facets.
  223.      *
  224.      * @param normal Facet's normal vector.
  225.      * @param copy Whether to copy the given array.
  226.      * @throws MathIllegalArgumentException if the length of {@code n}
  227.      * does not match the space dimension.
  228.      * @throws MathIllegalStateException if the method has been called
  229.      * more times than the size of the sphere.
  230.      */
  231.     protected void add(double[] normal,
  232.                        boolean copy) {
  233.         if (microsphere.size() >= size) {
  234.             throw new MathIllegalStateException(LocalizedCoreFormats.MAX_COUNT_EXCEEDED, size);
  235.         }
  236.         if (normal.length > dimension) {
  237.             throw new MathIllegalArgumentException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
  238.                                                    normal.length, dimension);
  239.         }

  240.         microsphere.add(new Facet(copy ? normal.clone() : normal));
  241.         microsphereData.add(new FacetData(0d, 0d));
  242.     }

  243.     /**
  244.      * Interpolation.
  245.      *
  246.      * @return the value estimated from the current illumination of the
  247.      * microsphere.
  248.      */
  249.     private double interpolate() {
  250.         // Number of non-illuminated facets.
  251.         int darkCount = 0;

  252.         double value = 0;
  253.         double totalWeight = 0;
  254.         for (FacetData fd : microsphereData) {
  255.             final double iV = fd.illumination();
  256.             if (iV != 0d) {
  257.                 value += iV * fd.sample();
  258.                 totalWeight += iV;
  259.             } else {
  260.                 ++darkCount;
  261.             }
  262.         }

  263.         final double darkFraction = darkCount / (double) size;

  264.         return darkFraction <= maxDarkFraction ?
  265.             value / totalWeight :
  266.             background;
  267.     }

  268.     /**
  269.      * Illumination.
  270.      *
  271.      * @param sampleDirection Vector whose origin is at the interpolation
  272.      * point and tail is at the sample location.
  273.      * @param sampleValue Data value of the sample.
  274.      * @param weight Weight.
  275.      */
  276.     private void illuminate(double[] sampleDirection,
  277.                             double sampleValue,
  278.                             double weight) {
  279.         for (int i = 0; i < size; i++) {
  280.             final double[] n = microsphere.get(i).getNormal();
  281.             final double cos = MathArrays.cosAngle(n, sampleDirection);

  282.             if (cos > 0) {
  283.                 final double illumination = cos * weight;

  284.                 if (illumination > darkThreshold &&
  285.                     illumination > microsphereData.get(i).illumination()) {
  286.                     microsphereData.set(i, new FacetData(illumination, sampleValue));
  287.                 }
  288.             }
  289.         }
  290.     }

  291.     /**
  292.      * Reset the all the {@link Facet facets} data to zero.
  293.      */
  294.     private void clear() {
  295.         for (int i = 0; i < size; i++) {
  296.             microsphereData.set(i, new FacetData(0d, 0d));
  297.         }
  298.     }

  299.     /**
  300.      * Microsphere "facet" (surface element).
  301.      */
  302.     private static class Facet {
  303.         /** Normal vector characterizing a surface element. */
  304.         private final double[] normal;

  305.         /**
  306.          * @param n Normal vector characterizing a surface element
  307.          * of the microsphere. No copy is made.
  308.          */
  309.         Facet(double[] n) {
  310.             normal = n; // NOPMD - array cloning is taken care of at call site
  311.         }

  312.         /**
  313.          * Return a reference to the vector normal to this facet.
  314.          *
  315.          * @return the normal vector.
  316.          */
  317.         public double[] getNormal() {
  318.             return normal; // NOPMD - returning an internal array is intentional and documented here
  319.         }
  320.     }

  321.     /**
  322.      * Data associated with each {@link Facet}.
  323.      */
  324.     private static class FacetData {
  325.         /** Illumination received from the sample. */
  326.         private final double illumination;
  327.         /** Data value of the sample. */
  328.         private final double sample;

  329.         /**
  330.          * @param illumination Illumination.
  331.          * @param sample Data value.
  332.          */
  333.         FacetData(double illumination, double sample) {
  334.             this.illumination = illumination;
  335.             this.sample = sample;
  336.         }

  337.         /**
  338.          * Get the illumination.
  339.          * @return the illumination.
  340.          */
  341.         public double illumination() {
  342.             return illumination;
  343.         }

  344.         /**
  345.          * Get the data value.
  346.          * @return the data value.
  347.          */
  348.         public double sample() {
  349.             return sample;
  350.         }
  351.     }
  352. }