1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.media.videoquality.bdrate;
18 
19 import org.apache.commons.math3.analysis.polynomials.PolynomialFunction;
20 import org.apache.commons.math3.analysis.polynomials.PolynomialSplineFunction;
21 
22 /** Utilities for performing the calculus involved for BD-RATE and BD-QUALITY calculations. */
23 public final class BdCalculus {
24 
BdCalculus()25     private BdCalculus() {}
26 
27     /**
28      * Calculates the area under the curve for the provided {@link PolynomialSplineFunction} between
29      * the min and max values.
30      */
calculateAuc(PolynomialSplineFunction func, double min, double max)31     public static double calculateAuc(PolynomialSplineFunction func, double min, double max) {
32 
33         // Create the integral functions for each of the segments of the spline.
34         PolynomialFunction[] segmentFuncs = func.getPolynomials();
35         PolynomialFunction[] integralFuncs = new PolynomialFunction[segmentFuncs.length];
36         for (int funcIdx = 0; funcIdx < segmentFuncs.length; funcIdx++) {
37             integralFuncs[funcIdx] = integratePolynomial(segmentFuncs[funcIdx]);
38         }
39 
40         // Calculate the integral for each segment, summing up the results
41         // which is the value of the spline's integral.
42         double result = 0;
43         double[] knots = func.getKnots();
44         for (int leftKnotIdx = 0; leftKnotIdx < knots.length - 1; leftKnotIdx++) {
45             double leftKnot = knots[leftKnotIdx];
46             double rightKnot = knots[leftKnotIdx + 1];
47 
48             if (rightKnot < min) {
49                 continue;
50             }
51 
52             if (leftKnot > max) {
53                 break;
54             }
55 
56             double integrationLeft = Math.max(0, min - leftKnot);
57             double integrationRight = Math.min(rightKnot - leftKnot, max - leftKnot);
58 
59             PolynomialFunction integralFunc = integralFuncs[leftKnotIdx];
60             result += integralFunc.value(integrationRight) - integralFunc.value(integrationLeft);
61         }
62 
63         return result;
64     }
65 
66     /**
67      * Perform a standard polynomial integration by parts on the provided {@link
68      * PolynomialFunction}, returning a new {@link PolynomialFunction} representing the integrated
69      * function.
70      */
integratePolynomial(PolynomialFunction function)71     private static PolynomialFunction integratePolynomial(PolynomialFunction function) {
72         double[] newCoeffs = new double[function.getCoefficients().length + 1];
73         for (int i = 1; i <= function.getCoefficients().length; i++) {
74             newCoeffs[i] = function.getCoefficients()[i - 1] / i;
75         }
76         newCoeffs[0] = 0;
77         return new PolynomialFunction(newCoeffs);
78     }
79 }
80