add support for interpolation before calculating convex hull

add support for generating interpolated sampling RD points for RD
curves for each resolution before calculating convex hull. save the RD
point for convex hull generated based on the original RD points and
interpolated RD points. update BD rate calculaiton script to also
calculate bdrate based on two convex hulls

Change-Id: I9631c1e4f7c6a20ab59817b893aa7ccad8e87695
diff --git a/tools/convexhull_framework/src/CalcBDRate.py b/tools/convexhull_framework/src/CalcBDRate.py
index 9e7646d..aca1997 100644
--- a/tools/convexhull_framework/src/CalcBDRate.py
+++ b/tools/convexhull_framework/src/CalcBDRate.py
@@ -10,7 +10,7 @@
 ##
 __author__ = "maggie.sun@intel.com, ryan.lei@intel.com"
 
-import numpy
+import numpy as np
 import math
 import scipy.interpolate
 import logging
@@ -68,15 +68,14 @@
 # BJONTEGAARD    Bjontegaard metric
 # Calculation is adapted from Google implementation
 # PCHIP method - Piecewise Cubic Hermite Interpolating Polynomial interpolation
-def BD_RATE(br1, qtyMtrc1, br2, qtyMtrc2, qp1=None, qp2=None, EnablePreInterpolation=False):
-    brqtypairs1 = []; brqtypairs2=[]
-    if (EnablePreInterpolation):
-        assert(qp1 is not None and qp2 is not None)
-        brqtypairs1 = [(br1[i], qtyMtrc1[i], qp1[i]) for i in range(min(len(qp1), len(qtyMtrc1), len(br1)))]
-        brqtypairs2 = [(br2[i], qtyMtrc2[i], qp2[i]) for i in range(min(len(qp2), len(qtyMtrc2), len(br2)))]
-    else:
-        brqtypairs1 = [(br1[i], qtyMtrc1[i]) for i in range(min(len(qtyMtrc1), len(br1)))]
-        brqtypairs2 = [(br2[i], qtyMtrc2[i]) for i in range(min(len(qtyMtrc2), len(br2)))]
+def BD_RATE(br1, qtyMtrc1, br2, qtyMtrc2):
+    brqtypairs1 = []; brqtypairs2 = []
+    for i in range(min(len(qtyMtrc1), len(br1))):
+        if (br1[i] != '' and qtyMtrc1[i] != ''):
+            brqtypairs1.append((br1[i], qtyMtrc1[i]))
+    for i in range(min(len(qtyMtrc2), len(br2))):
+        if (br2[i] != '' and qtyMtrc2[i] != ''):
+            brqtypairs2.append((br2[i], qtyMtrc2[i]))
 
     # sort the pair based on quality metric values in increasing order
     # if quality metric values are the same, then sort the bit rate in increasing order
@@ -87,19 +86,11 @@
     qmetrics1 = [100.0 if x[1] == float('inf') else x[1] for x in brqtypairs1]
     logbr2 = [math.log(x[0]) for x in brqtypairs2]
     qmetrics2 = [100.0 if x[1] == float('inf') else x[1] for x in brqtypairs2]
-    if (EnablePreInterpolation):
-        qp1 = [x[2] for x in brqtypairs1]
-        qp2 = [x[2] for x in brqtypairs2]
 
     if not brqtypairs1 or not brqtypairs2:
         logger.info("one of input lists is empty!")
         return 0.0
 
-    # perform an bi-linear interpolation based on QP
-    if EnablePreInterpolation:
-        qp1, logbr1, qmetrics1 = Interpolate(qp1, logbr1, qmetrics1)
-        qp2, logbr2, qmetrics2 = Interpolate(qp2, logbr2, qmetrics2)
-
     # remove duplicated quality metric value, the RD point with higher bit rate is removed
     dup_idx = [i for i in range(1, len(qmetrics1)) if qmetrics1[i - 1] == qmetrics1[i]]
     for idx in sorted(dup_idx, reverse=True):
@@ -118,7 +109,7 @@
         return 0.0
 
     # generate samples between max and min of quality metrics
-    lin = numpy.linspace(min_int, max_int, num=100, retstep=True)
+    lin = np.linspace(min_int, max_int, num=100, retstep=True)
     interval = lin[1]
     samples = lin[0]
 
@@ -127,8 +118,8 @@
     v2 = scipy.interpolate.pchip_interpolate(qmetrics2, logbr2, samples)
 
     # Calculate the integral using the trapezoid method on the samples.
-    int1 = numpy.trapz(v1, dx=interval)
-    int2 = numpy.trapz(v2, dx=interval)
+    int1 = np.trapz(v1, dx=interval)
+    int2 = np.trapz(v2, dx=interval)
 
     # find avg diff
     avg_exp_diff = (int2 - int1) / (max_int - min_int)
diff --git a/tools/convexhull_framework/src/Config.py b/tools/convexhull_framework/src/Config.py
index 53281f3..2fc154c 100644
--- a/tools/convexhull_framework/src/Config.py
+++ b/tools/convexhull_framework/src/Config.py
@@ -117,12 +117,14 @@
 VMAF = os.path.join(BinPath, 'vmafossexec.exe')
 HDRTool = os.path.join(BinPath, 'HDRMetrics.exe')
 CalcBDRateInExcel = False
+EnablePreInterpolation = True
 
 ######################## config for exporting data to excel  #################
 #https://xlsxwriter.readthedocs.io/working_with_colors.html#colors
 # line color used, number of colors >= len(DnScaledRes)
 LineColors = ['blue', 'red', 'green', 'orange', 'pink', 'yellow']
 ConvexHullColor = 'white'
+Int_ConvexHullColor = 'cyan'
 
 # find out QP/Resolution with specified qty metrics
 TargetQtyMetrics = {'VMAF_Y': [60, 70, 80, 90],
@@ -140,7 +142,7 @@
 
 # format for writing convexhull curve data
 CvxHDataStartRow = CvxH_WtRows[-1] + 2; CvxHDataStartCol = 0
-CvxHDataNum = 5  # qty, bitrate, qp, resolution, 1 empty row as internal
+CvxHDataNum = 7  # qty, bitrate, qp, resolution, int_qty, int_bitrate, 1 empty row as internal
 CvxHDataRows = [CvxHDataStartRow + 1 + CvxHDataNum * i for i in range(len(QualityList))]
 
 ######################## post analysis #########################################
diff --git a/tools/convexhull_framework/src/ConvexHullBDRate.py b/tools/convexhull_framework/src/ConvexHullBDRate.py
index 311fe91..b7a8b40 100644
--- a/tools/convexhull_framework/src/ConvexHullBDRate.py
+++ b/tools/convexhull_framework/src/ConvexHullBDRate.py
@@ -79,12 +79,16 @@
         return int(cell_val)
 
 
-def ParseConvexHullRD(xls):
+def ParseConvexHullRD(xls, EnablePreInterpolation = False):
     wb = xlrd.open_workbook(xls)
     shts = wb.sheet_names()   #list of sheet names
     data = {}   #dict of data, key is the sheet name
 
-    cols = [3 + i * 4 for i in range(len(QualityList))]
+    cvx_cols = 4
+    if EnablePreInterpolation:
+        cvx_cols = 6
+
+    cols = [3 + i * cvx_cols for i in range(len(QualityList))]
     for sht_name in shts:
         sht = wb.sheet_by_name(sht_name)
         #skip the title row
@@ -107,11 +111,20 @@
                     qp  = read_cell_as_int(sht, start_row+row, col + 1)     #QP
                     br  = read_cell_as_float(sht, start_row+row, col + 2)   #Bitrate
                     q   = read_cell_as_float(sht, start_row+row, col + 3)   #Quality
-                    if br != '' and q != '':
-                        if qty in rd_data:
-                            rd_data[qty].append((res, qp, br, q))
-                        else:
-                            rd_data.update({qty: [(res, qp, br, q)]})
+                    if EnablePreInterpolation:
+                        int_br = read_cell_as_float(sht, start_row+row, col + 4)   #Int_Bitrate
+                        int_q = read_cell_as_float(sht, start_row+row, col + 5)  # Int_Quality
+                        if int_br != '' and int_q != '':
+                            if qty in rd_data:
+                                rd_data[qty].append((br, q, int_br, int_q))
+                            else:
+                                rd_data.update({qty: [(br, q, int_br, int_q)]})
+                    else:
+                        if br != '' and q != '':
+                            if qty in rd_data:
+                                rd_data[qty].append((br, q))
+                            else:
+                                rd_data.update({qty: [(br, q)]})
 
             start_row += num
             point.RDPoints = rd_data
@@ -123,93 +136,147 @@
     return shts, data
 
 
-def WriteOutputHeaderRow(sht):
+def WriteOutputHeaderRow(sht, EnablePreInterpolation = False):
     sht.write(0, 0, 'Content Class')
     sht.write(0, 1, 'Content Name')
     sht.write(0, 2, 'Num RD Points')
     col = 3
     for qty in QualityList:
-        sht.write(0, col, 'Resolution')
-        sht.write(0, col + 1, 'QP')
-        sht.write(0, col + 2, 'Bitrate(kbps)')
-        sht.write(0, col + 3, qty)
-        col += 4
+        sht.write(0, col, 'Bitrate(kbps)')
+        sht.write(0, col + 1, qty)
+        if EnablePreInterpolation:
+            sht.write(0, col + 2, 'Int_Bitrate(kbps)')
+            sht.write(0, col + 3, 'Int_' + qty)
+            col += 4
+        else:
+            col += 2
+
     col += 1
     for qty in QualityList:
-        sht.write(0, col, 'Resolution')
-        sht.write(0, col + 1, 'QP')
-        sht.write(0, col + 2, 'Bitrate(kbps)')
-        sht.write(0, col + 3, qty)
-        col += 4
+        sht.write(0, col, 'Bitrate(kbps)')
+        sht.write(0, col + 1, qty)
+        if EnablePreInterpolation:
+            sht.write(0, col + 2, 'Int_Bitrate(kbps)')
+            sht.write(0, col + 3, 'Int_' + qty)
+            col += 4
+        else:
+            col += 2
     col += 1
     for (idx, qty) in zip(range(len(QualityList)), QualityList):
         sht.write(0, col + idx, "BDRATE-%s" % qty)
 
 
-def WriteRDData(sht, rd_data, start_row, start_col, format):
+def WriteRDData(sht, rd_data, start_row, start_col, format,
+                EnablePreInterpolation = False):
     col = start_col
     max_rows = 0
     for qty in QualityList:
         row = start_row
         for (line, point) in zip(range(len(rd_data.RDPoints[qty])),
                                  rd_data.RDPoints[qty]):
-            sht.write_string(row + line, col, point[0])                #Resolution
-            sht.write_number(row + line, col + 1, point[1])            #QP
-            sht.write_number(row + line, col + 2, point[2], format)    #Bitrate
-            sht.write_number(row + line, col + 3, point[3], format)    #Quality
-        col += 4
+            if point[0] != '':
+                sht.write_number(row + line, col, point[0], format)        #Bitrate
+            if point[1] != '':
+                sht.write_number(row + line, col + 1, point[1], format)    #Quality
+            if EnablePreInterpolation:
+                if point[2] != '':
+                    sht.write_number(row + line, col + 2, point[2], format)  # Int_Bitrate
+                if point[3] != '':
+                    sht.write_number(row + line, col + 3, point[3], format)  # Int_Quality
+        if EnablePreInterpolation:
+            col += 4
+        else:
+            col += 2
         max_rows = max(max_rows, len(rd_data.RDPoints[qty]))
     return max_rows
 
 
-def WriteRDRecord(sht, base_data, target_data, start_row, bdrate_fmt, float_fmt):
+def WriteRDRecord(sht, base_data, target_data, start_row, bdrate_fmt, float_fmt,
+                  EnablePreInterpolation = False):
     sht.write(start_row, 0, base_data.ContentClass)
     sht.write(start_row, 1, base_data.ContentName)
 
     #write base data
     base_start_col = 3
+    cvx_cols = 2
+    if EnablePreInterpolation:
+        cvx_cols = 4
+
     base_max_rows = WriteRDData(sht, base_data, start_row, base_start_col,
-                                float_fmt)
+                                float_fmt, EnablePreInterpolation)
 
     #write target data
-    target_start_col = base_start_col + 4 * len(QualityList) + 1
+    target_start_col = base_start_col + cvx_cols * len(QualityList) + 1
     target_max_rows = WriteRDData(sht, target_data, start_row, target_start_col,
-                                  float_fmt)
+                                  float_fmt, EnablePreInterpolation)
 
     #write bdrate formula
-    bdrate_start_col = target_start_col + 4 * len(QualityList) + 1
+    bdrate_start_col = target_start_col + cvx_cols * len(QualityList) + 1
     total_rows = max(base_max_rows, target_max_rows)
     sht.write(start_row, 2, total_rows)
     for (qty, col) in zip(QualityList, range(len(QualityList))):
         if CalcBDRateInExcel:
-            refbr_b = xlrd.cellnameabs(start_row, base_start_col + col * 4 + 2)
+            refbr_b = xlrd.cellnameabs(start_row,
+                                       base_start_col + col * cvx_cols)
             refbr_e = xlrd.cellnameabs(start_row + total_rows - 1,
-                                       base_start_col + col * 4 + 2)
-            refq_b = xlrd.cellnameabs(start_row, base_start_col + col * 4 + 3)
+                                       base_start_col + col * cvx_cols)
+            refq_b = xlrd.cellnameabs(start_row,
+                                      base_start_col + col * cvx_cols + 1)
             refq_e = xlrd.cellnameabs(start_row + total_rows - 1,
-                                      base_start_col + col * 4 + 3)
+                                      base_start_col + col * cvx_cols + 1)
 
-            testbr_b = xlrd.cellnameabs(start_row, target_start_col + col * 4 + 2)
+            testbr_b = xlrd.cellnameabs(start_row,
+                                        target_start_col + col * cvx_cols)
             testbr_e = xlrd.cellnameabs(start_row + total_rows - 1,
-                                        target_start_col + col * 4 + 2)
-            testq_b = xlrd.cellnameabs(start_row, target_start_col + col * 4 + 3)
+                                        target_start_col + col * cvx_cols)
+            testq_b = xlrd.cellnameabs(start_row,
+                                       target_start_col + col * cvx_cols + 1)
             testq_e = xlrd.cellnameabs(start_row + total_rows - 1,
-                                       target_start_col + col * 4 + 3)
+                                       target_start_col + col * cvx_cols + 1)
 
             # formula = '=-bdrate(%s:%s,%s:%s,%s:%s,%s:%s)' % (
             # refbr_b, refbr_e, refq_b, refq_e, testbr_b, testbr_e, testq_b, testq_e)
             formula = '=bdRateExtend(%s:%s,%s:%s,%s:%s,%s:%s)'\
                 % (refbr_b, refbr_e, refq_b, refq_e, testbr_b, testbr_e, testq_b, testq_e)
             sht.write_formula(start_row, bdrate_start_col + col, formula, bdrate_fmt)
+
+            if EnablePreInterpolation:
+                refbr_b = xlrd.cellnameabs(start_row,
+                                           base_start_col + col * cvx_cols + 2)
+                refbr_e = xlrd.cellnameabs(start_row + total_rows - 1,
+                                           base_start_col + col * cvx_cols + 2)
+                refq_b = xlrd.cellnameabs(start_row,
+                                          base_start_col + col * cvx_cols + 3)
+                refq_e = xlrd.cellnameabs(start_row + total_rows - 1,
+                                          base_start_col + col * cvx_cols + 3)
+
+                testbr_b = xlrd.cellnameabs(start_row,
+                                            target_start_col + col * cvx_cols + 2)
+                testbr_e = xlrd.cellnameabs(start_row + total_rows - 1,
+                                            target_start_col + col * cvx_cols + 2)
+                testq_b = xlrd.cellnameabs(start_row,
+                                           target_start_col + col * cvx_cols + 3)
+                testq_e = xlrd.cellnameabs(start_row + total_rows - 1,
+                                           target_start_col + col * cvx_cols + 3)
+                formula = '=bdRateExtend(%s:%s,%s:%s,%s:%s,%s:%s)' \
+                    % (refbr_b, refbr_e, refq_b, refq_e, testbr_b, testbr_e, testq_b,
+                       testq_e)
+
+                sht.write_formula(start_row + 1, bdrate_start_col + col, formula, bdrate_fmt)
         else:
-            refqps   = [base_data.RDPoints[qty][i][1] for i in range(len(base_data.RDPoints[qty]))]
-            refbrs   = [base_data.RDPoints[qty][i][2] for i in range(len(base_data.RDPoints[qty]))]
-            refqtys  = [base_data.RDPoints[qty][i][3] for i in range(len(base_data.RDPoints[qty]))]
-            testqps  = [target_data.RDPoints[qty][i][1] for i in range(len(target_data.RDPoints[qty]))]
-            testbrs  = [target_data.RDPoints[qty][i][2] for i in range(len(target_data.RDPoints[qty]))]
-            testqtys = [target_data.RDPoints[qty][i][3] for i in range(len(target_data.RDPoints[qty]))]
-            bdrate = BD_RATE(refbrs, refqtys, testbrs, testqtys, refqps, testqps, False) / 100.0
+            refbrs   = [base_data.RDPoints[qty][i][0] for i in range(len(base_data.RDPoints[qty]))]
+            refqtys  = [base_data.RDPoints[qty][i][1] for i in range(len(base_data.RDPoints[qty]))]
+            testbrs  = [target_data.RDPoints[qty][i][0] for i in range(len(target_data.RDPoints[qty]))]
+            testqtys = [target_data.RDPoints[qty][i][1] for i in range(len(target_data.RDPoints[qty]))]
+            bdrate = BD_RATE(refbrs, refqtys, testbrs, testqtys) / 100.0
             sht.write_number(start_row, bdrate_start_col + col, bdrate, bdrate_fmt)
+            if EnablePreInterpolation:
+                refbrs = [base_data.RDPoints[qty][i][2] for i in range(len(base_data.RDPoints[qty]))]
+                refqtys = [base_data.RDPoints[qty][i][3] for i in range(len(base_data.RDPoints[qty]))]
+                testbrs = [target_data.RDPoints[qty][i][2] for i in range(len(target_data.RDPoints[qty]))]
+                testqtys = [target_data.RDPoints[qty][i][3] for i in range(len(target_data.RDPoints[qty]))]
+                bdrate = BD_RATE(refbrs, refqtys, testbrs, testqtys) / 100.0
+                sht.write_number(start_row + 1, bdrate_start_col + col, bdrate, bdrate_fmt)
     return total_rows
 
 
@@ -228,8 +295,8 @@
     #"-o","ConvexHullBDRate.xlsm"]
     ParseArguments(sys.argv)
 
-    base_shts, base_rd_data = ParseConvexHullRD(InputBase)
-    target_shts, target_rd_data = ParseConvexHullRD(InputTarget)
+    base_shts, base_rd_data = ParseConvexHullRD(InputBase, True)
+    target_shts, target_rd_data = ParseConvexHullRD(InputTarget, True)
 
     output_wb = xlsxwriter.Workbook(Output)
     # vba file needed when to calculate bdrate
@@ -242,14 +309,14 @@
     for sht_name in base_shts:
         if sht_name in target_shts:
             sht = output_wb.add_worksheet(sht_name)
-            WriteOutputHeaderRow(sht)
+            WriteOutputHeaderRow(sht, True)
             start_row = 1
             for base_data in base_rd_data[sht_name]:
                 ContentName = base_data.ContentName
                 target_data = FindContent(ContentName, target_rd_data[sht_name])
                 if target_data != '':
                     total_rows = WriteRDRecord(sht, base_data, target_data,
-                                               start_row, bdrate_fmt, float_fmt)
+                                               start_row, bdrate_fmt, float_fmt, True)
                     start_row += total_rows
 
     output_wb.close()
diff --git a/tools/convexhull_framework/src/ConvexHullTest.py b/tools/convexhull_framework/src/ConvexHullTest.py
index 3c3e4c3..8493db1 100644
--- a/tools/convexhull_framework/src/ConvexHullTest.py
+++ b/tools/convexhull_framework/src/ConvexHullTest.py
@@ -14,6 +14,9 @@
 import sys
 import xlsxwriter
 import argparse
+import numpy as np
+import scipy.interpolate
+
 from EncDecUpscale import Run_EncDec_Upscale, GetBsReconFileName
 from VideoScaler import GetDownScaledOutFile, DownScaling
 from CalculateQualityMetrics import CalculateQualityMetric, GatherQualityMetrics
@@ -29,7 +32,9 @@
      CvxH_WtRows, QualityList, LineColors, DnScalingAlgos, UpScalingAlgos,\
      ContentPath, SummaryOutPath, WorkPath, Path_RDResults, Clips, \
      ConvexHullColor, EncodeMethods, CodecNames, LoggerName, LogCmdOnly, \
-     TargetQtyMetrics, CvxHDataRows, CvxHDataStartRow, CvxHDataStartCol, CvxHDataNum
+     TargetQtyMetrics, CvxHDataRows, CvxHDataStartRow, CvxHDataStartCol, \
+     CvxHDataNum, Int_ConvexHullColor, EnablePreInterpolation
+from operator import itemgetter
 
 ###############################################################################
 ##### Helper Functions ########################################################
@@ -121,11 +126,15 @@
     return qtyQPs, qtyRes
 
 
-def AddConvexHullCurveToCharts(sht, charts, rdPoints, dnScaledRes, tgtqmetrics):
+def AddConvexHullCurveToCharts(sht, charts, rdPoints, dnScaledRes, tgtqmetrics,
+                               EnablePreInterpolation = False, int_rdPoints = None):
+    if EnablePreInterpolation:
+        assert int_rdPoints is not None
+
     shtname = sht.get_name()
     sht.write(CvxHDataStartRow, CvxHDataStartCol, "ConvexHull Data")
 
-    hull = {}; cvh_QPs = {}; cvh_Res_txt = {}
+    hull = {}; cvh_QPs = {}; cvh_Res_txt = {}; int_hull = {}
     max_len = 0
 
     for qty, idx, row in zip(QualityList, range(len(QualityList)), CvxHDataRows):
@@ -136,6 +145,11 @@
         sht.write(row + 1, CvxHDataStartCol, "Bitrate(kbps)")
         sht.write(row + 2, CvxHDataStartCol, "QP")
         sht.write(row + 3, CvxHDataStartCol, 'Resolution')
+        if EnablePreInterpolation:
+            lower, upper = convex_hull(int_rdPoints[idx])
+            int_hull[qty] = upper
+            sht.write(row + 4, CvxHDataStartCol, "Int_" + qty)
+            sht.write(row + 5, CvxHDataStartCol, "Int_Bitrate(kbps)")
 
         brts = [h[0] for h in hull[qty]]
         qtys = [h[1] for h in hull[qty]]
@@ -148,10 +162,20 @@
         cvh_Res_txt[qty] = ["%sx%s" % (x, y) for (x, y) in cvh_Res]
         sht.write_row(row + 2, CvxHDataStartCol + 1, cvh_QPs[qty])
         sht.write_row(row + 3, CvxHDataStartCol + 1, cvh_Res_txt[qty])
+        if EnablePreInterpolation:
+            int_brts = [h[0] for h in int_hull[qty]]
+            int_qtys = [h[1] for h in int_hull[qty]]
+            sht.write_row(row + 4, CvxHDataStartCol + 1, int_qtys)
+            sht.write_row(row + 5, CvxHDataStartCol + 1, int_brts)
 
         cols = [CvxHDataStartCol + 1 + i for i in range(len(hull[qty]))]
         AddSeriesToChart_Scatter_Rows(shtname, cols, row, row + 1, charts[idx],
                                       'ConvexHull', ConvexHullColor)
+        if EnablePreInterpolation:
+            int_cols = [CvxHDataStartCol + 1 + i for i in range(len(int_hull[qty]))]
+            AddSeriesToChart_Scatter_Rows(shtname, int_cols, row + 4, row + 5,
+                                          charts[idx], 'Int_ConvexHull',
+                                          Int_ConvexHullColor)
     endrow = CvxHDataRows[-1] + CvxHDataNum
 
     # find out QP/resolution for given qty metric and qty value
@@ -233,7 +257,40 @@
 
     Utils.Logger.info("finish running encode test.")
 
-def SaveConvexHullResultsToExcel(content, dnScAlgos, upScAlgos):
+def Interpolate(RDPoints):
+    '''
+    generate interpolated points on a RD curve.
+    input is list of existing RD points as (bitrate, quality) tuple
+    total number of interpolated points depends on the min and max QP
+    '''
+    # sort the pair based on bitrate in increasing order
+    # if bitrate is the same, then sort based on quality in increasing order
+    RDPoints.sort(key = itemgetter(0, 1))
+    br = [RDPoints[i][0] for i in range(len(RDPoints))]
+    qty = [RDPoints[i][1] for i in range(len(RDPoints))]
+
+    # generate samples between max and min of quality metrics
+    min_br = min(br); max_br = max(br)
+    min_qp = min(QPs); max_qp = max(QPs)
+    lin = np.linspace(min_br, max_br, num = (max_qp - min_qp + 1), retstep = True)
+    int_br = lin[0]
+
+    # interpolation using pchip
+    int_qty = scipy.interpolate.pchip_interpolate(br, qty, int_br)
+
+    '''
+    print("before interpolation:")
+    for i in range(len(br)):
+        print("%f, %f"%(br[i], qty[i]))
+    print("after interpolation:")
+    for i in range(len(int_br)):
+        print("%f, %f"%(int_br[i], int_qty[i]))
+    '''
+    int_points = [(int_br[i], int_qty[i]) for i in range(len(int_br))]
+    return int_points
+
+def SaveConvexHullResultsToExcel(content, dnScAlgos, upScAlgos,
+                                 EnablePreInterpolation=False):
     Utils.Logger.info("start saving RD results to excel file.......")
     if not os.path.exists(Path_RDResults):
         os.makedirs(Path_RDResults)
@@ -253,13 +310,13 @@
         sht.write_column(CvxH_WtRows[0], 0, QPs)
         shtname = sht.get_name()
 
-        charts = [];  y_mins = {}; y_maxs = {}; RDPoints = {}
+        charts = [];  y_mins = {}; y_maxs = {}; RDPoints = {}; Int_RDPoints = {}
         for qty, x in zip(QualityList, range(len(QualityList))):
             chart_title = 'RD Curves - %s with %s' % (contentname, shtname)
             xaxis_name = 'Bitrate - Kbps'
             chart = CreateChart_Scatter(wb, chart_title, xaxis_name, qty)
             charts.append(chart)
-            y_mins[x] = []; y_maxs[x] = []; RDPoints[x] = []
+            y_mins[x] = []; y_maxs[x] = []; RDPoints[x] = []; Int_RDPoints[x] = []
 
         # write RD data
         for col, i in zip(CvxH_WtCols, range(len(DnScaledRes))):
@@ -297,10 +354,14 @@
                 # get RD points - (bitrate, quality) for each quality metrics
                 rdpnts = [(brt, qty) for brt, qty in zip(bitratesKbps, qs)]
                 RDPoints[x] = RDPoints[x] + rdpnts
+                if EnablePreInterpolation:
+                    int_rdpnts = Interpolate(rdpnts)
+                    Int_RDPoints[x] = Int_RDPoints[x] + int_rdpnts
 
         # add convexhull curve to charts
         endrow = AddConvexHullCurveToCharts(sht, charts, RDPoints, DnScaledRes,
-                                            TargetQtyMetrics)
+                                            TargetQtyMetrics, EnablePreInterpolation,
+                                            Int_RDPoints)
 
         #update RD chart with approprate y axis range
         for qty, x in zip(QualityList, range(len(QualityList))):
@@ -399,8 +460,8 @@
                 Run_ConvexHull_Test(content, dnScalAlgo, upScalAlgo)
     elif Function == 'convexhull':
         for content in Contents:
-            SaveConvexHullResultsToExcel(content, DnScalingAlgos, UpScalingAlgos)
-
+            SaveConvexHullResultsToExcel(content, DnScalingAlgos, UpScalingAlgos,
+                                         EnablePreInterpolation)
     elif Function == 'summary':
         RDResultFilesGenerated = []
         for content in Contents:
@@ -411,8 +472,10 @@
                                                   ContentPath, Clips)
         Utils.Logger.info("RD data summary file generated: %s" % RDsmfile)
 
-        CvxHsmfile = GenerateSummaryConvexHullExcelFile(EncodeMethod, CodecName, EncodePreset,
-                                                       SummaryOutPath, RDResultFilesGenerated)
+        CvxHsmfile = GenerateSummaryConvexHullExcelFile(EncodeMethod, CodecName,
+                                                        EncodePreset, SummaryOutPath,
+                                                        RDResultFilesGenerated,
+                                                        EnablePreInterpolation)
         Utils.Logger.info("Convel hull summary file generated: %s" % CvxHsmfile)
     else:
         Utils.Logger.error("invalid parameter value of Function")
diff --git a/tools/convexhull_framework/src/PostAnalysis_Summary.py b/tools/convexhull_framework/src/PostAnalysis_Summary.py
index c2b78a9..290c8c9 100644
--- a/tools/convexhull_framework/src/PostAnalysis_Summary.py
+++ b/tools/convexhull_framework/src/PostAnalysis_Summary.py
@@ -414,16 +414,19 @@
     return smfile
 
 def GenerateSummaryConvexHullExcelFile(encMethod, codecName, preset,
-                                       summary_outpath, resultfiles):
+                                       summary_outpath, resultfiles,
+                                       EnablePreInterpolation = False):
     if not os.path.exists(summary_outpath):
         os.makedirs(summary_outpath)
     smfile = GetConvexHullDataSummaryFileName(encMethod, codecName, preset,
                                      summary_outpath)
     wb = xlsxwriter.Workbook(smfile)
-
+    cvx_cols = 4
+    if EnablePreInterpolation:
+        cvx_cols = 6
     # shts is for all scaling algorithms' convex hull test results
     shts = []
-    cols = [3 + i * 4 for i in range(len(QualityList))]
+    cols = [3 + i * cvx_cols for i in range(len(QualityList))]
     for dnsc, upsc in zip(dnScalAlgos, upScalAlgos):
         shtname = dnsc + '--' + upsc
         sht = wb.add_worksheet(shtname)
@@ -438,6 +441,9 @@
             sht.write(0, col + 1, 'QP')
             sht.write(0, col + 2, 'Bitrate(kbps)')
             sht.write(0, col + 3,  qty)
+            if EnablePreInterpolation:
+                sht.write(0, col + 4, 'Int_Bitrate(kbps)')
+                sht.write(0, col + 5, 'Int_' + qty)
 
         # copy convexhull data from each content's result file to corresponding
         # location in summary excel file
@@ -452,9 +458,10 @@
                     if key in resfile:
                         rdwb = xlrd.open_workbook(resfile)
                         rdsht = rdwb.sheet_by_name(shtname)
-                        maxNumQty = 0
+                        maxNumQty = 0; maxNumIntQty = 0
                         for rdrow, col in zip(CvxHDataRows, cols):
                             qtys = []; brs = []; qps = []; ress = []
+                            int_qtys = []; int_brs = []
                             numQty = 0
                             for qty in rdsht.row_values(rdrow)[rdcolstart:]:
                                 if qty == '':
@@ -479,14 +486,32 @@
                                     break
                                 else:
                                     ress.append(res)
+                            if EnablePreInterpolation:
+                                numQty = 0
+                                for qty in rdsht.row_values(rdrow + 4)[
+                                           rdcolstart:]:
+                                    if qty == '':
+                                        break
+                                    else:
+                                        int_qtys.append(qty)
+                                        numQty = numQty + 1
+                                maxNumIntQty = max(maxNumIntQty, numQty)
+                                for br in rdsht.row_values(rdrow + 5)[rdcolstart:]:
+                                    if br == '':
+                                        break
+                                    else:
+                                        int_brs.append(br)
 
                             sht.write_column(row, col, ress)
                             sht.write_column(row, col + 1, qps)
                             sht.write_column(row, col + 2, brs)
                             sht.write_column(row, col + 3, qtys)
+                            if EnablePreInterpolation:
+                                sht.write_column(row, col + 4, int_brs)
+                                sht.write_column(row, col + 5, int_qtys)
 
-                        sht.write(row, 2, maxNumQty)
-                        row = row + maxNumQty
+                        sht.write(row, 2, max(maxNumQty, maxNumIntQty))
+                        row = row + max(maxNumQty, maxNumIntQty)
                         break
 
     wb.close()