Commit 4dc06ef8 authored by Archit Tamarapu's avatar Archit Tamarapu
Browse files
Merge branch 'main' of ssh://forge.3gpp.org:29419/ivas-codec-pc/ivas-codec into 1347-ci-testcase-missing-for-the-ambisonics-mono-detector
parents 9638e77a edc2f442
Loading
Loading
Loading
Loading
Loading
+49 −51
Original line number Diff line number Diff line
@@ -544,81 +544,69 @@ renderer-smoke-test:
      junit:
        - report-junit.xml

# test renderer executable with cmake + asan
renderer-asan:
.renderer-sanitizer-job:
  extends:
    - .test-job-linux
    - .rules-merge-request-to-main
  needs: ["build-codec-linux-cmake"]
  stage: test
  script:
    - cmake -B cmake-build -G "Unix Makefiles" -DCLANG=asan -DCOPY_EXECUTABLES_FROM_BUILD_DIR=true
    - cmake --build cmake-build -- -j
    - testcase_timeout=180
    - python3 -m pytest -q -n auto -rA --junit-xml=report-junit.xml tests/renderer/test_renderer.py --testcase_timeout=$testcase_timeout

  timeout: "30 minutes"
  artifacts:
    name: "mr-$CI_MERGE_REQUEST_IID--sha-$CI_COMMIT_SHORT_SHA--job-$CI_JOB_NAME--results"
    expire_in: 1 week
    when: always
    paths:
      - report-junit.xml
    expose_as: "renderer asan pytest results"
      - report.html
    reports:
      junit:
        - report-junit.xml
  before_script:
    - cmake -B cmake-build -G "Unix Makefiles" -DCLANG=$SANITIZER_BUILD_STRING -DCOPY_EXECUTABLES_FROM_BUILD_DIR=true
    - cmake --build cmake-build -- -j
    # rename files to fit naming convention
    # en- and decoder needed because of split rendering testcases
    - mv IVAS_cod IVAS_cod_ref
    - mv IVAS_dec IVAS_dec_ref
    - mv IVAS_rend IVAS_rend_ref
    - mv ISAR_post_rend ISAR_post_rend_ref
    - testcase_timeout=180

# test renderer executable with cmake + asan
renderer-asan:
  extends:
    - .renderer-sanitizer-job
  variables:
    SANITIZER_BUILD_STRING: "asan"
  script:
    - python3 -m pytest -q --tb=no -n auto --html=report.html --self-contained-html --junit-xml=report-junit.xml tests/renderer/test_renderer.py tests/split_rendering/test_split_rendering.py --create_ref --testcase_timeout=$testcase_timeout
  artifacts:
    expose_as: "renderer asan result"


# test renderer executable with cmake + msan
renderer-msan:
  extends:
    - .test-job-linux
    - .rules-merge-request-to-main
  needs: ["build-codec-linux-cmake"]
  stage: test
    - .renderer-sanitizer-job
  variables:
    SANITIZER_BUILD_STRING: "msan"
  script:
    - cmake -B cmake-build -G "Unix Makefiles" -DCLANG=msan  -DCOPY_EXECUTABLES_FROM_BUILD_DIR=true
    - cmake --build cmake-build -- -j
    - testcase_timeout=180
    - python3 -m pytest -q -n auto -rA --junit-xml=report-junit.xml tests/renderer/test_renderer.py --testcase_timeout=$testcase_timeout

    - python3 -m pytest -q --tb=no -n auto --html=report.html --self-contained-html --junit-xml=report-junit.xml tests/renderer/test_renderer.py tests/split_rendering/test_split_rendering.py --create_ref --testcase_timeout=$testcase_timeout
  artifacts:
    name: "mr-$CI_MERGE_REQUEST_IID--sha-$CI_COMMIT_SHORT_SHA--job-$CI_JOB_NAME--results"
    expire_in: 1 week
    when: always
    paths:
      - report-junit.xml
    expose_as: "renderer msan pytest results"
    reports:
      junit:
        - report-junit.xml
    expose_as: "renderer msan result"


# test renderer executable with cmake + usan
renderer-usan:
  extends:
    - .test-job-linux
    - .rules-merge-request-to-main
  needs: ["build-codec-linux-cmake"]
  stage: test
    - .renderer-sanitizer-job
  variables:
    SANITIZER_BUILD_STRING: "usan"
  script:
    - cmake -B cmake-build -G "Unix Makefiles" -DCLANG=usan  -DCOPY_EXECUTABLES_FROM_BUILD_DIR=true
    - cmake --build cmake-build -- -j
    - testcase_timeout=180
    - UBSAN_OPTIONS=suppressions=scripts/ubsan.supp,report_error_type=1,log_path=usan_log_catchall python3 -m pytest -q -n auto -rA --junit-xml=report-junit.xml tests/renderer/test_renderer.py --testcase_timeout=$testcase_timeout
    - grep_exit_code=0
    - touch usan_log_empty # Creates an empty file, this is to avoid "grep: usan_log_*: No such file or directory" in case no USAN failures are reported from pytest
    - grep UndefinedBehaviorSanitizer usan_log_* || grep_exit_code=$?
    - if [ $grep_exit_code != 1 ] ; then echo "Run errors in test_renderer.py with Clang undefined-behavior-sanitizer"; exit 1; fi

    - UBSAN_OPTIONS=suppressions=scripts/ubsan.supp,report_error_type=1 python3 -m pytest -q --tb=no -n auto --html=report.html --self-contained-html --junit-xml=report-junit.xml tests/renderer/test_renderer.py tests/split_rendering/test_split_rendering.py --create_ref --testcase_timeout=$testcase_timeout
  artifacts:
    name: "mr-$CI_MERGE_REQUEST_IID--sha-$CI_COMMIT_SHORT_SHA--job-$CI_JOB_NAME--results"
    expire_in: 1 week
    when: always
    paths:
      - report-junit.xml
    expose_as: "renderer usan pytest results"
    reports:
      junit:
        - report-junit.xml
    expose_as: "renderer usan result"


# compare renderer bitexactness between target and source branch
renderer-pytest-on-merge-request:
@@ -737,6 +725,7 @@ split-rendering-pytest-on-merge-request:
    - mv IVAS_cod IVAS_cod_ref
    - mv IVAS_dec IVAS_dec_ref
    - mv IVAS_rend IVAS_rend_ref
    - mv ISAR_post_rend ISAR_post_rend_ref

    ### If ref_using_main is not set, checkout the source branch to use scripts and input from there
    - if [ $ref_using_main == 0 ]; then git restore lib_com/options.h; fi # Revert changes back before checking out another branch to avoid conflicts
@@ -784,6 +773,12 @@ ivas-pytest-on-merge-request:
  stage: compare
  needs: ["build-codec-linux-cmake", "codec-smoke-test"]
  timeout: "14 minutes"
  parallel:
    matrix:
      # note: keep in sync with list in $TESTS_DIR_CODEC_BE_ON_MR
      - PYTEST_SCRIPT:
          - test_param_file.py
          - test_sba.py
  script:
    - bash "${CI_PROJECT_DIR}"/ivas-codec-ci/snippets/print-common-info.sh
    - commits_behind_count="$(bash "${CI_PROJECT_DIR}"/ivas-codec-ci/snippets/get-commits-behind-count.sh)"
@@ -801,7 +796,7 @@ ivas-pytest-on-merge-request:

    ### prepare pytest
    # create references
    - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR -v --update_ref 1
    - python3 -m pytest "$TESTS_DIR_CODEC_BE_ON_MR"/"$PYTEST_SCRIPT" -v --update_ref 1

    ### Run test using branch scripts and input
    - if [ $ref_using_main == 1 ]; then git checkout $source_branch_commit_sha; fi
@@ -809,7 +804,7 @@ ivas-pytest-on-merge-request:
    ### run pytest
    - exit_code=0
    - testcase_timeout=60
    - python3 -m pytest $TESTS_DIR_CODEC_BE_ON_MR -v --html=report.html --self-contained-html --junit-xml=report-junit.xml --testcase_timeout=$testcase_timeout || exit_code=$?
    - python3 -m pytest "$TESTS_DIR_CODEC_BE_ON_MR"/"$PYTEST_SCRIPT" -v --html=report.html --self-contained-html --junit-xml=report-junit.xml --testcase_timeout=$testcase_timeout || exit_code=$?
    - zero_errors=$(cat report-junit.xml | grep -c 'errors="0"') || true

    - *merge-request-comparison-check
@@ -1262,6 +1257,7 @@ ivas-conformance:
    - cp -force IVAS_cod.exe IVAS_cod_ref.exe
    - cp -force IVAS_dec.exe IVAS_dec_ref.exe
    - cp -force IVAS_rend.exe IVAS_rend_ref.exe 
    - cp -force ISAR_post_rend.exe ISAR_post_rend_ref.exe
    
    # Reference creation
    - python scripts/prepare_combined_format_inputs.py
@@ -1327,6 +1323,7 @@ ivas-conformance:
    expire_in: 1 week
    when: always
    paths:
      - report_cmd.html
      - report-junit.xml
      - report.html
      - Readme_IVAS_dec.txt  
@@ -1355,6 +1352,7 @@ ivas-conformance-linux:
    - cp IVAS_cod IVAS_cod_ref
    - cp IVAS_dec IVAS_dec_ref
    - cp IVAS_rend IVAS_rend_ref
    - cp ISAR_post_rend ISAR_post_rend_ref
    
    # Reference creation
    - python3 scripts/prepare_combined_format_inputs.py
+2 −3
Original line number Diff line number Diff line
@@ -1858,13 +1858,13 @@ static ivas_error initOnFirstGoodFrame(
        return error;
    }

    int16_t *zeroBuf = malloc( pcmFrameSize * sizeof( int16_t ) );
    /* Write zeros to the output audio buffer */
    int16_t *zeroBuf = calloc( pcmFrameSize, sizeof( int16_t ) );
    if ( zeroBuf == NULL )
    {
        fprintf( stdout, "Error: Unable to allocate memory for output buffer.\n" );
        return IVAS_ERR_FAILED_ALLOC;
    }
    memset( zeroBuf, 0, pcmFrameSize * sizeof( int16_t ) );

    for ( int16_t i = 0; i < numInitialBadFrames; ++i )
    {
@@ -1888,7 +1888,6 @@ static ivas_error initOnFirstGoodFrame(
        }
        else
        {

            if ( *pRemainingDelayNumSamples < *numOutSamples )
            {
                if ( ( error = AudioFileWriter_write( *ppAfWriter, zeroBuf, *numOutSamples * *pNumOutChannels - ( *pRemainingDelayNumSamples * *pNumOutChannels ) ) ) != IVAS_ERR_OK )
+1 −0
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@
   the software. This notice grants no license of any kind, including but not limited to patent
   license, nor is any license granted by implication, estoppel or otherwise.

   Contributors are required to enter into the IVAS codec Public Collaboration agreement before making
   Contributors are required to enter into the IVAS codec Public Collaboration agreement before making
   contributions.

+27 −9
Original line number Diff line number Diff line
@@ -68,6 +68,10 @@
#define IVAS_MAX16B_FLT 32767.0f
#define IVAS_MIN16B_FLT ( -32768.0f )

#ifdef NONBE_1359_FIX_IVASREND_OMASA_BINAURAL_LOUDNESS
#define OMASA_TDREND_MATCHING_GAIN_DB ( -2.0f )
#endif

#if !defined( DEBUGGING ) && !defined( WMOPS )
static
#endif
@@ -1081,18 +1085,19 @@ int main(
            fprintf( stderr, "\nFailed to read renderer configuration from file %s\n", args.renderConfigFilePath );
            goto cleanup;
        }
#ifdef NONBE_FIX_1337_MISSING_DIRECTIVITY_DISTATT_EXTREND

        if ( ( error = RenderConfigReader_getDirectivity( renderConfigReader, args.directivityPatternId, renderConfig.directivity ) ) != IVAS_ERR_OK )
        {
            fprintf( stderr, "Failed to get directivity patterns for one or more of IDs: %d %d %d %d\n\n", args.directivityPatternId[0], args.directivityPatternId[1], args.directivityPatternId[2], args.directivityPatternId[3] );
            goto cleanup;
        }

        if ( ( error = RenderConfigReader_getDistanceAttenuation( renderConfigReader, renderConfig.distAtt ) ) != IVAS_ERR_OK )
        {
            fprintf( stderr, "Failed to get Distance Attenuation \n\n" );
            goto cleanup;
        }
#endif

        if ( args.outConfig.audioConfig == IVAS_AUDIO_CONFIG_BINAURAL_ROOM_REVERB )
        {
            aeID = args.aeSequence.count > 0 ? args.aeSequence.pID[0] : 65535;
@@ -1152,20 +1157,39 @@ int main(
        }
    }

#ifndef NONBE_1359_FIX_IVASREND_OMASA_BINAURAL_LOUDNESS
    /* Set the total number of objects */
#endif
    if ( args.inConfig.numAudioObjects > 0 )
    {
#ifdef NONBE_1359_FIX_IVASREND_OMASA_BINAURAL_LOUDNESS
        /* Set the total number of objects */
#endif
        if ( ( error = IVAS_REND_SetTotalNumberOfObjects( hIvasRend, args.inConfig.numAudioObjects ) ) != IVAS_ERR_OK )
        {
            fprintf( stderr, "\nError in IVAS_REND_SetTotalNumberOfObjects(): %s\n", ivas_error_to_string( error ) );
            goto cleanup;
        }

#ifdef NONBE_1359_FIX_IVASREND_OMASA_BINAURAL_LOUDNESS
        /* Set the metadata delay for objects */
#endif
        if ( ( error = IVAS_REND_SetIsmMetadataDelay( hIvasRend, args.syncMdDelay ) ) != IVAS_ERR_OK )
        {
            fprintf( stderr, "\nError in IVAS_REND_SetIsmMetadataDelay(): %s\n", ivas_error_to_string( error ) );
            goto cleanup;
        }
#ifdef NONBE_1359_FIX_IVASREND_OMASA_BINAURAL_LOUDNESS

        /* For OMASA input and BINAURAL output, apply a gain to objects to match the loudness with MASA part */
        if ( args.inConfig.numMasaBuses > 0 && args.outConfig.audioConfig == IVAS_AUDIO_CONFIG_BINAURAL )
        {
            for ( i = 0; i < args.inConfig.numAudioObjects; ++i )
            {
                args.inConfig.audioObjects[i].gain_dB += OMASA_TDREND_MATCHING_GAIN_DB;
            }
        }
#endif
    }

    IVAS_REND_LfePanMtx lfePanMatrix;
@@ -3490,14 +3514,8 @@ static void parseCombinedFormatInput(
        inConfig->numAmbisonicsBuses = 1;
        inConfig->ambisonicsBuses[0].audioConfig = audioConfig;
        inConfig->ambisonicsBuses[0].inputChannelIndex = inConfig->numAudioObjects;
        inConfig->ambisonicsBuses[0].gain_dB = -6.f;
        inConfig->ambisonicsBuses[0].gain_dB = 0.f;
        *configString += 4;

        /* Modify input gain for objects too */
        for ( int16_t i = 0; i < inConfig->numAudioObjects; ++i )
        {
            inConfig->audioObjects[i].gain_dB = -6.f;
        }
    }
    else if ( audioConfig == IVAS_AUDIO_CONFIG_MASA1 || audioConfig == IVAS_AUDIO_CONFIG_MASA2 )
    {
+180 −0
Original line number Diff line number Diff line
import os
import pandas as pd
import argparse
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots


def read_csv_files(root_dir):
    """Read csv files as dictionary of panda dataframes."""
    csv_data = {}
    for subdir, dirs, files in os.walk(root_dir):
        for file in files:
            if file.endswith(".csv"):
                file_path = os.path.join(subdir, file)
                try:
                    df = pd.read_csv(file_path)
                    csv_data[file_path] = df
                except Exception as e:
                    print(f"Failed to read {file_path}: {e}")
                    exit(-1)
    return csv_data


def parse_csv_data(csv_data):
    """keep 'testcase', 'format', 'MLD', 'MAX_ABS_DIFF'  and add
    'date' column."""
    cols_to_keep = ["testcase", "format", "MLD", "MAX_ABS_DIFF"]
    parsed_data = {}
    for key, df in csv_data.items():
        cols = [col for col in cols_to_keep if col in df.columns]
        date = os.path.basename(os.path.dirname(key))
        new_df = df[cols].copy()
        new_df["date"] = date
        parsed_data[key] = new_df

    # concatenate all dataframe in the dictionary
    concat_df = pd.concat(parsed_data.values(), ignore_index=True)
    return concat_df


def plot_data(df, output_filename):
    """plot max values for 'MLD' and 'MAX_ABS_DIFF' data and save
    to html file."""
    # Convert 'date' to datetime
    df["date"] = pd.to_datetime(df["date"], errors="coerce")
    df["MLD"] = pd.to_numeric(df["MLD"], errors="coerce")
    df["MAX_ABS_DIFF"] = pd.to_numeric(df["MAX_ABS_DIFF"], errors="coerce")

    # Drop rows with NaT and NaN
    clean_df = df.dropna(subset=["date", "MLD", "MAX_ABS_DIFF"])

    # Group by 'format' and 'date' to get rows with max 'MLD' per group
    max_mld = (
        clean_df.groupby(["format", "date"])
        .apply(lambda x: x.loc[x["MLD"].idxmax()])
        .reset_index(drop=True)
    )

    # Group by 'format' and 'date' to get rows with max 'MAX_ABS_DIFF' per
    # group
    max_abs_diff = (
        clean_df.groupby(["format", "date"])
        .apply(lambda x: x.loc[x["MAX_ABS_DIFF"].idxmax()])
        .reset_index(drop=True)
    )

    formats = sorted(clean_df["format"].unique())

    fig = make_subplots(
        rows=5,
        cols=2,
        specs=[[{"secondary_y": True}] * 2] * 5,
        subplot_titles=[f"{i}" for i in formats],
        shared_xaxes="columns",
    )

    for i, fmt in enumerate(formats):
        row = i // 2 + 1
        col = i % 2 + 1

        data_mld = max_mld[max_mld["format"] == fmt].sort_values("date")
        data_diff = max_abs_diff[max_abs_diff["format"]
                                 == fmt].sort_values("date")

        # Add max 'MLD' to primary y-axis
        fig.add_trace(
            go.Scatter(
                x=data_mld["date"],
                y=data_mld["MLD"],
                mode="lines+markers",
                name=f" {fmt} - Max MLD",
                hovertext=[
                    f"Testcase: {tc}<br>MLD: {mld:.4f}<br>MAX_ABS_DIFF:"
                    f"{abs_diff}<br>Format:"
                    f" {format}<br>Date: {date.date()}"
                    for tc, mld, abs_diff, format, date in zip(
                        data_mld["testcase"],
                        data_mld["MLD"],
                        data_mld["MAX_ABS_DIFF"],
                        data_mld["format"],
                        data_mld["date"],
                    )
                ],
                hoverinfo="text",
            ),
            row=row,
            col=col,
            secondary_y=False,
        )

        # Add max 'MAX_ABS_DIFF' to secondary y-axis
        fig.add_trace(
            go.Scatter(
                x=data_diff["date"],
                y=data_diff["MAX_ABS_DIFF"],
                mode="lines+markers",
                name=f"{fmt} - Max MAX_ABS_DIFF",
                hovertext=[
                    f"Testcase: {tc}<br>MLD: {mld:.4f}<br>MAX_ABS_DIFF:"
                    f" {abs_diff:.4f}<br>Format:"
                    f" {format}<br>Date: {date.date()}"
                    for tc, mld, abs_diff, format, date in zip(
                        data_diff["testcase"],
                        data_diff["MLD"],
                        data_diff["MAX_ABS_DIFF"],
                        data_diff["format"],
                        data_diff["date"],
                    )
                ],
                hoverinfo="text",
            ),
            row=row,
            col=col,
            secondary_y=True,
        )

    fig.update_layout(
        title_text="Long-term regression: max MLD and max MAX_ABS_DIFF",
        legend=dict(x=1, y=1, orientation="v"),
        hovermode="x unified",
    )

    fig.update_xaxes(automargin=True)
    fig.update_yaxes(automargin=True)

    # Update y-axes titles per subplot
    for i in range(10):
        yaxis_num = i * 2 + 1
        yaxis2_num = yaxis_num + 1
        fig["layout"][f"yaxis{yaxis_num}"].update(
            title="Max MLD", titlefont=dict(color="blue"), tickfont=dict(color="blue")
        )
        fig["layout"][f"yaxis{yaxis2_num}"].update(
            title="Max MAX_ABS_DIFF",
            titlefont=dict(color="green"),
            tickfont=dict(color="green"),
        )

    # Save to html
    fig.write_html(output_filename)


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Plot long term logs")
    parser.add_argument(
        "root_dir",
        type=str,
        help="Root directory containing subdirectories" " with CSV log files",
    )
    parser.add_argument(
        "output_filename",
        type=str,
        help="Filename of the generated plot. e.g" ". long_term_regression.html",
    )
    args = parser.parse_args()

    csv_data = read_csv_files(args.root_dir)
    data = parse_csv_data(csv_data)
    plot_data(data, args.output_filename)
Loading