| #!/usr/bin/env python |
| ## Copyright (c) 2019, Alliance for Open Media. All rights reserved |
| ## |
| ## This source code is subject to the terms of the BSD 2 Clause License and |
| ## the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License |
| ## was not distributed with this source code in the LICENSE file, you can |
| ## obtain it at www.aomedia.org/license/software. If the Alliance for Open |
| ## Media Patent License 1.0 was not distributed with this source code in the |
| ## PATENTS file, you can obtain it at www.aomedia.org/license/patent. |
| ## |
| __author__ = "maggie.sun@intel.com, ryan.lei@intel.com" |
| |
| import os |
| import xlsxwriter |
| import xlrd |
| import re |
| from Config import QPs, DnScaleRatio, QualityList, VbaBinFile, CvxH_WtRows,\ |
| CvxH_WtLastCol, LoggerName, CalcBDRateInExcel, CvxH_WtCols, CvxHDataRows, CvxHDataStartCol |
| from Utils import GetShortContentName, CalcRowsClassAndContentDict |
| from CalcBDRate import BD_RATE |
| import logging |
| |
| subloggername = "PostAnalysisSummary" |
| loggername = LoggerName + '.' + '%s' % subloggername |
| logger = logging.getLogger(loggername) |
| |
| # give all paths including convex hull result file (only one file for each |
| # content) to generate summary file for all contents in Input path |
| # assume all content's result has same test settings |
| |
| ################################################################################ |
| ### Helper Functions ########################################################### |
| def GetRDSummaryFileName(encMethod, codecName, preset, path): |
| filetype = 'xlsm' if CalcBDRateInExcel else 'xlsx' |
| name = 'ConvexHullRDSummary_ScaleAlgosNum_%d_%s_%s_%s.%s'\ |
| % (len(DnScaleRatio), encMethod, codecName, preset, filetype) |
| return os.path.join(path, name) |
| |
| def GetConvexHullDataSummaryFileName(encMethod, codecName, preset, path): |
| name = 'ConvexHullData_ScaleAlgosNum_%d_%s_%s_%s.xlsx'\ |
| % (len(DnScaleRatio), encMethod, codecName, preset) |
| return os.path.join(path, name) |
| |
| def SweepScalingAlgosInOneResultFile(resultfiles): |
| dnscls = [] |
| upscls = [] |
| |
| # here assume all result files includes same combinations of dn and up scaling algos |
| # that is, same number of sheet and sheet names |
| file = resultfiles[0] |
| if os.path.isfile(file): |
| rdwb = xlrd.open_workbook(file) |
| else: |
| return dnscls, upscls |
| if rdwb is not None: |
| shtnms = rdwb.sheet_names() |
| for shtname in shtnms: |
| item = re.findall(r"(.+)\-\-(.+)", shtname) |
| dnsl = item[0][0] |
| upsl = item[0][1] |
| dnscls.append(dnsl) |
| upscls.append(upsl) |
| |
| return dnscls, upscls |
| |
| def CopyResultDataToSummaryFile_Onesheet(sht, wt_cols, resultfiles): |
| rdrows = CvxH_WtRows |
| rd_endcol = CvxH_WtLastCol |
| |
| shtname = sht.get_name() |
| sht.write(1, 0, 'Content Class') |
| sht.write(1, 1, 'Content Name') |
| sht.write(1, 2, 'QP') |
| for residx, col in zip(range(len(DnScaleRatio)), wt_cols): |
| sht.write(0, col, 'Scaling Ratio = %.2f' % (DnScaleRatio[residx])) |
| sht.write(1, col, 'Bitrate(kbps)') |
| qtynames = ['%s' % qty for qty in QualityList] |
| sht.write_row(1, col + 1, qtynames) |
| |
| # copy the results data from each content's result file to corresponding |
| # location in summary excel file |
| for (cls, clip_list), row_class in zip(ClipDict.items(), Rows_Class): |
| sht.write(row_class, 0, cls) |
| rows_content = [i * len(QPs['AS']) for i in range(len(clip_list))] |
| for clip, row_cont in zip(clip_list, rows_content): |
| key = GetShortContentName(clip.file_name) |
| sht.write(row_class + row_cont, 1, key) |
| rdwb = None |
| for resfile in resultfiles: |
| if key in resfile: |
| rdwb = xlrd.open_workbook(resfile) |
| rdsht = rdwb.sheet_by_name(shtname) |
| for i, rdrow in zip(range(len(QPs['AS'])), rdrows): |
| data = rdsht.row_values(rdrow, 0, rd_endcol + 1) |
| sht.write_row(row_class + row_cont + i, 2, data) |
| break |
| assert rdwb is not None |
| if rdwb is None: |
| logger.warning("not find convex hull result file for content:%s" |
| % clip.file_name) |
| |
| def CalBDRateWithExcel_OneSheet(sht, cols, cols_bdmtrs, cellformat): |
| row_refst = 0 |
| bdstep = len(QPs['AS']) - 1 |
| for cols_bd, residx in zip(cols_bdmtrs, range(1, len(DnScaleRatio))): |
| sht.write(0, cols_bd, 'BD-Rate %.2f vs. %.2f' % (DnScaleRatio[residx], |
| DnScaleRatio[0])) |
| sht.write_row(1, cols_bd, QualityList) |
| for (cls, clip_list), row_class in zip(ClipDict.items(), Rows_Class): |
| rows_content = [i * len(QPs['AS']) for i in range(len(clip_list))] |
| for row_cont in rows_content: |
| for y in range(len(QualityList)): |
| refbr_b = xlrd.cellnameabs(row_class + row_cont + row_refst, |
| cols[0]) |
| refbr_e = xlrd.cellnameabs(row_class + row_cont + row_refst |
| + bdstep, cols[0]) |
| refq_b = xlrd.cellnameabs(row_class + row_cont + row_refst, |
| cols[0] + 1 + y) |
| refq_e = xlrd.cellnameabs(row_class + row_cont + row_refst |
| + bdstep, cols[0] + 1 + y) |
| |
| testbr_b = xlrd.cellnameabs(row_class + row_cont + row_refst, |
| cols[residx]) |
| testbr_e = xlrd.cellnameabs(row_class + row_cont + row_refst |
| + bdstep, cols[residx]) |
| testq_b = xlrd.cellnameabs(row_class + row_cont + row_refst, |
| cols[residx] + 1 + y) |
| testq_e = xlrd.cellnameabs(row_class + row_cont + row_refst |
| + bdstep, cols[residx] + 1 + y) |
| |
| #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(row_class + row_cont, cols_bd + y, formula, |
| cellformat) |
| |
| |
| def CalBDRateWithPython_OneSheet(sht, cols_bdmtrs, resultfiles, cellformat): |
| row_refst = 0 |
| bdstep = len(QPs['AS']) - 1 |
| assert row_refst + bdstep < len(CvxH_WtRows) |
| |
| shtname = sht.get_name() |
| rdrows = CvxH_WtRows |
| rdcols = CvxH_WtCols |
| for cols_bd, residx in zip(cols_bdmtrs, range(1, len(DnScaleRatio))): |
| sht.write(0, cols_bd, 'BD-Rate %.2f vs. %.2f' % (DnScaleRatio[residx], |
| DnScaleRatio[0])) |
| sht.write_row(1, cols_bd, QualityList) |
| for (cls, clip_list), row_class in zip(ClipDict.items(), Rows_Class): |
| rows_content = [i * len(QPs['AS']) for i in range(len(clip_list))] |
| for row_cont, clip in zip(rows_content, clip_list): |
| key = GetShortContentName(clip.file_name) |
| for resfile in resultfiles: |
| if key in resfile: |
| rdwb = xlrd.open_workbook(resfile) |
| rdsht = rdwb.sheet_by_name(shtname) |
| break |
| for y in range(len(QualityList)): |
| refbrs = rdsht.col_values(rdcols[0], rdrows[row_refst], rdrows[row_refst + bdstep] + 1) |
| refqtys = rdsht.col_values(rdcols[0] + 1 + y, rdrows[row_refst], rdrows[row_refst + bdstep] + 1) |
| testbrs = rdsht.col_values(rdcols[residx], rdrows[row_refst], rdrows[row_refst + bdstep] + 1) |
| testqtys = rdsht.col_values(rdcols[residx] + 1 + y, rdrows[row_refst], rdrows[row_refst + bdstep] + 1) |
| bdrate = BD_RATE(refbrs, refqtys, testbrs, testqtys) |
| if (bdrate != 'Error'): |
| bdrate /= 100.0 |
| sht.write(row_class + row_cont, cols_bd + y, bdrate, cellformat) |
| else: |
| sht.write(row_class + row_cont, cols_bd + y, bdrate) |
| |
| def GenerateFormula_SumRows(shtname, rows, col): |
| cells = '' |
| for row in rows: |
| location = xlrd.cellnameabs(row, col) |
| cells = cells + '\'%s\'!%s,' % (shtname, location) |
| cells = cells[:-1] # remove the last , |
| formula = '=SUM(%s)/%d' % (cells, len(rows)) |
| return formula |
| |
| def GenerateFormula_SumRows_Weighted(rows, col, weight_rows, weight_col, num): |
| cells = '' |
| for row, wtrow in zip(rows, weight_rows): |
| location = xlrd.cellnameabs(row, col) |
| weight = xlrd.cellnameabs(wtrow, weight_col) |
| cells = cells + '%s * %s,' % (location, weight) |
| cells = cells[:-1] # remove the last , |
| formula = '=SUM(%s)/%d' % (cells, num) |
| return formula |
| |
| def WriteBitrateQtyAverageSheet(wb, rdshts, rdcols): |
| avg_sht = wb.add_worksheet('Average') |
| avg_sht.write(2, 0, 'Content Class') |
| avg_sht.write(2, 1, 'Content Number') |
| avg_sht.write(2, 2, 'QP') |
| |
| colstart = 3 |
| cols_res = [colstart] |
| step = len(QualityList) + 1 + 1 # 1 for bitrate, 1 for interval |
| colres_2nd_start = colstart + step |
| step = len(upScalAlgos) * (len(QualityList) + 1) + 1 # + 1 for interval |
| cols_res += [step * i + colres_2nd_start for i in range(len(DnScaleRatio) - 1)] |
| step = len(QualityList) + 1 # + 1 for bitrate |
| cols_upscl = [step * i for i in range(len(upScalAlgos))] |
| for residx, col_res in zip(range(len(DnScaleRatio)), cols_res): |
| avg_sht.write(0, col_res, 'ScalingRatio = %.2f' % (DnScaleRatio[residx])) |
| if residx == 0: |
| avg_sht.write(1, col_res + 1, 'None') |
| avg_sht.write(2, col_res, 'Bitrate(kbps)') |
| avg_sht.write_row(2, col_res + 1, QualityList) |
| else: |
| for dnsc, upsc, col_upscl in zip(dnScalAlgos, upScalAlgos, cols_upscl): |
| avg_sht.write(1, col_res + col_upscl + 1, '%s--%s' % (dnsc, upsc)) |
| avg_sht.write(2, col_res + col_upscl, 'Bitrate(kbps)') |
| avg_sht.write_row(2, col_res + col_upscl + 1, QualityList) |
| |
| startrow = 3 |
| step = len(QPs['AS']) |
| rows_class_avg = [startrow + step * i for i in range(len(ClipDict))] |
| totalnum_content = 0 |
| for (cls, clip_list), row_class, rdclassrow in zip(ClipDict.items(), |
| rows_class_avg, |
| Rows_Class): |
| avg_sht.write(row_class, 0, cls) |
| totalnum_content = totalnum_content + len(clip_list) |
| avg_sht.write(row_class, 1, len(clip_list)) |
| avg_sht.write_column(row_class, 2, QPs['AS']) |
| rows_content = [i * len(QPs['AS']) for i in range(len(clip_list))] |
| |
| for rdcol, col_res, residx in zip(rdcols, cols_res, range(len(DnScaleRatio))): |
| for i in range(len(QPs['AS'])): |
| sum_rows = [rdclassrow + row_cont + i for row_cont in rows_content] |
| for col_upscl, sht in zip(cols_upscl, rdshts): |
| shtname = sht.get_name() |
| # write bitrate average formula. |
| formula = GenerateFormula_SumRows(shtname, sum_rows, rdcol) |
| avg_sht.write_formula(row_class + i, col_res + col_upscl, |
| formula) |
| # write quality average formula |
| for j in range(len(QualityList)): |
| formula = GenerateFormula_SumRows(shtname, sum_rows, |
| rdcol + 1 + j) |
| avg_sht.write_formula(row_class + i, |
| col_res + col_upscl + 1 + j, |
| formula) |
| |
| # for first resolution, no down and up scaling. only need |
| # one set of bitrate/quality data |
| if residx == 0: |
| break |
| |
| # write total average |
| last_class_row = rows_class_avg[-1] + len(QPs['AS']) + 1 # 1 for 1 row of interval |
| avg_sht.write(last_class_row, 0, 'Total') |
| avg_sht.write(last_class_row, 1, totalnum_content) |
| avg_sht.write_column(last_class_row, 2, QPs['AS']) |
| weight_rows = [row_class for row_class in rows_class_avg] |
| for col_res, residx in zip(cols_res, range(len(DnScaleRatio))): |
| for i in range(len(QPs['AS'])): |
| sum_rows = [row_class + i for row_class in rows_class_avg] |
| for col_upscl in cols_upscl: |
| # bitrate average |
| formula = GenerateFormula_SumRows_Weighted(sum_rows, |
| col_res + col_upscl, |
| weight_rows, 1, |
| totalnum_content) |
| avg_sht.write_formula(last_class_row + i, col_res + col_upscl, |
| formula) |
| # quality average |
| for j in range(len(QualityList)): |
| formula = GenerateFormula_SumRows_Weighted(sum_rows, |
| col_res + |
| col_upscl + 1 + j, |
| weight_rows, 1, |
| totalnum_content) |
| avg_sht.write_formula(last_class_row + i, |
| col_res + col_upscl + 1 + j, formula) |
| # for first resolution, no down and up scaling. only need one |
| # set of bitrate/quality data |
| if residx == 0: |
| break |
| |
| def WriteBDRateAverageSheet(wb, rdshts, rd_cols_bdmtrs, cellformat): |
| # write bdrate average sheet |
| bdavg_sht = wb.add_worksheet('Average_BDRate') |
| bdavg_sht.write(2, 0, 'Content Class') |
| bdavg_sht.write(2, 1, 'Content Number') |
| |
| startcol = 2 |
| startrow = 3 |
| colintval_scalalgo = 1 |
| colintval_dnscalres = 1 |
| |
| step_upscl = len(QualityList) + colintval_scalalgo |
| cols_upscl_bd = [step_upscl * i for i in range(len(upScalAlgos))] |
| step_res = len(upScalAlgos) * step_upscl + colintval_dnscalres |
| cols_res_bd = [step_res * i + startcol for i in range(len(DnScaleRatio) - 1)] |
| rows_class_rdavg = [startrow + i for i in range(len(ClipDict))] |
| |
| for residx, col_res_bd in zip(range(1, len(DnScaleRatio)), cols_res_bd): |
| bdavg_sht.write(0, col_res_bd, 'BD-Rate %.2f vs. %.2f' |
| % (DnScaleRatio[residx], DnScaleRatio[0])) |
| for dnsc, upsc, col_upscl_bd in zip(dnScalAlgos, upScalAlgos, cols_upscl_bd): |
| bdavg_sht.write(1, col_res_bd + col_upscl_bd, '%s--%s' % (dnsc, upsc)) |
| bdavg_sht.write_row(2, col_res_bd + col_upscl_bd, QualityList) |
| |
| totalnum_content = 0 |
| for (cls, clip_list), row_class, rdclassrow in zip(ClipDict.items(), |
| rows_class_rdavg, |
| Rows_Class): |
| bdavg_sht.write(row_class, 0, cls) |
| totalnum_content = totalnum_content + len(clip_list) |
| bdavg_sht.write(row_class, 1, len(clip_list)) |
| rows_content = [i * len(QPs['AS']) for i in range(len(clip_list))] |
| sum_rows = [rdclassrow + row_cont for row_cont in rows_content] |
| for rdcol, col_res in zip(rd_cols_bdmtrs, cols_res_bd): |
| # write average bd rate |
| for col_upscl, sht in zip(cols_upscl_bd, rdshts): |
| shtname = sht.get_name() |
| for j in range(len(QualityList)): |
| formula = GenerateFormula_SumRows(shtname, sum_rows, rdcol + j) |
| bdavg_sht.write_formula(row_class, col_res + col_upscl + j, |
| formula, cellformat) |
| |
| # write total average |
| last_row = rows_class_rdavg[-1] + 1 |
| bdavg_sht.write(last_row, 0, 'Total') |
| bdavg_sht.write(last_row, 1, totalnum_content) |
| sum_rows = [row_class for row_class in rows_class_rdavg] |
| for col_res in cols_res_bd: |
| for col_upscl in cols_upscl_bd: |
| for j in range(len(QualityList)): |
| formula = GenerateFormula_SumRows_Weighted(sum_rows, |
| col_res + col_upscl + j, |
| sum_rows, 1, |
| totalnum_content) |
| bdavg_sht.write_formula(last_row, col_res + col_upscl + j, |
| formula, cellformat) |
| |
| |
| ####################################################################### |
| ####################################################################### |
| # GenerateSummaryExcelFile is to |
| # 1. summarize all contents convexhull results into one file |
| # 2. calculate average of bitrate and quality metrics for each content class |
| # 3. calculate BD rate across different scaling ratios for all scaling |
| # algorithms in convex hull test |
| # 4. calcualte average BD rate for each content class |
| # Arguments description: |
| # content_paths is where test contents located, which used for generating convex |
| # hull results. |
| # resultfiles is a list of all convex hull RD result files generated by |
| # runninging '-f convexhull' |
| # summary_outpath is the folder where output summary file will be |
| def GenerateSumRDExcelFile(encMethod, codecName, preset, summary_outpath, |
| resultfiles, clip_list): |
| |
| global dnScalAlgos, upScalAlgos |
| # find all scaling algos tested in results file, |
| # IMPORTANT: expect up and down scaling algos are the same for every content |
| dnScalAlgos, upScalAlgos = SweepScalingAlgosInOneResultFile(resultfiles) |
| |
| if not os.path.exists(summary_outpath): |
| os.makedirs(summary_outpath) |
| smfile = GetRDSummaryFileName(encMethod, codecName, preset, summary_outpath) |
| wb = xlsxwriter.Workbook(smfile) |
| |
| # shts is for all scaling algorithms' convex hull test results |
| shts = [] |
| for dnsc, upsc in zip(dnScalAlgos, upScalAlgos): |
| shtname = dnsc + '--' + upsc |
| sht = wb.add_worksheet(shtname) |
| shts.append(sht) |
| |
| # below variables define summary file data layout format. |
| # if to change them, modify CopyResultsDataToSummaryFile_Onesheet() and |
| # CalcRowsCategAndContentDict() accordingly |
| colstart = 3 |
| colInterval = 2 |
| rowstart = 2 |
| # to generate rows number of starting of each class: Rows_Class |
| global ClipDict, Rows_Class |
| ClipDict, Rows_Class = CalcRowsClassAndContentDict(rowstart, clip_list, |
| len(QPs['AS'])) |
| # cols is column number of results files |
| step = colInterval + 1 + len(QualityList) # 1 is for bitrate |
| sum_wtcols = [step * i + colstart for i in range(len(DnScaleRatio))] |
| |
| if CalcBDRateInExcel: |
| wb.add_vba_project(VbaBinFile) |
| cellformat = wb.add_format() |
| cellformat.set_num_format('0.00%') |
| #cols_bdmtrs is the column number to write the bdrate data |
| step = len(QualityList) + 1 |
| start_col_bd = sum_wtcols[-1] + step + 1 |
| cols_bdmtrs = [start_col_bd + i * step for i in range(len(DnScaleRatio) - 1)] |
| # -1 because first resolution is used as reference |
| |
| for sht in shts: |
| CopyResultDataToSummaryFile_Onesheet(sht, sum_wtcols, resultfiles) |
| # calculate bd rate in each scaling sheet |
| if CalcBDRateInExcel: |
| CalBDRateWithExcel_OneSheet(sht, sum_wtcols, cols_bdmtrs, cellformat) |
| else: |
| CalBDRateWithPython_OneSheet(sht, cols_bdmtrs, resultfiles, cellformat) |
| |
| # calculate average bitrate and quality metrics for each category and |
| # write to "average" sheet |
| WriteBitrateQtyAverageSheet(wb, shts, sum_wtcols) |
| |
| # calculate average bd metrics and write to a new sheet |
| WriteBDRateAverageSheet(wb, shts, cols_bdmtrs, cellformat) |
| |
| wb.close() |
| return smfile |
| |
| def GenerateSumCvxHullExcelFile(encMethod, codecName, preset, 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 * cvx_cols for i in range(len(QualityList))] |
| for dnsc, upsc in zip(dnScalAlgos, upScalAlgos): |
| shtname = dnsc + '--' + upsc |
| sht = wb.add_worksheet(shtname) |
| shts.append(sht) |
| # write headers |
| sht.write(0, 0, 'Content Class') |
| sht.write(0, 1, 'Content Name') |
| sht.write(0, 2, 'Num RD Points') |
| |
| for qty, col in zip(QualityList, cols): |
| sht.write(0, col, 'Resolution') |
| 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 |
| row = 1 |
| rdcolstart = CvxHDataStartCol + 1 |
| for (cls, clip_list) in ClipDict.items(): |
| sht.write(row, 0, cls) |
| for clip in clip_list: |
| key = GetShortContentName(clip.file_name) |
| sht.write(row, 1, key) |
| for resfile in resultfiles: |
| if key in resfile: |
| rdwb = xlrd.open_workbook(resfile) |
| rdsht = rdwb.sheet_by_name(shtname) |
| 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 == '': |
| break |
| else: |
| qtys.append(qty) |
| numQty = numQty + 1 |
| maxNumQty = max(maxNumQty, numQty) |
| |
| for br in rdsht.row_values(rdrow + 1)[rdcolstart:]: |
| if br == '': |
| break |
| else: |
| brs.append(br) |
| for qp in rdsht.row_values(rdrow + 2)[rdcolstart:]: |
| if qp == '': |
| break |
| else: |
| qps.append(qp) |
| for res in rdsht.row_values(rdrow + 3)[rdcolstart:]: |
| if res == '': |
| 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, max(maxNumQty, maxNumIntQty)) |
| row = row + max(maxNumQty, maxNumIntQty) |
| break |
| |
| wb.close() |
| return smfile |