示例#1
0
    def test_any_value_substitution(self):
        allowed_values = [
            {
                "foo": AnyValue(),
                "bar": ValueSet(123)
            },
            {
                "foo": ValueSet(2),
                "bar": ValueSet(321)
            },
            {
                "foo": ValueSet(3),
                "bar": ValueSet(123)
            },
        ]

        # No substitution
        assert allowed_values_for(allowed_values, "foo",
                                  {"bar": 123}) == AnyValue()

        # With substitution
        assert (allowed_values_for(
            allowed_values,
            "foo",
            {"bar": 123},
            ValueSet(1, 2, 3, 4),
        ) == ValueSet(1, 2, 3, 4))
示例#2
0
def rank_allowed_base_video_format_similarity(codec_features):
    """
    Produce a ranked list of base video format IDs for the provided codec
    features. Video formats with the wrong 'top field first' value and which
    aren't allowed by the current level are omitted. The most similar base
    format is returned first.

    Raises
    :py:exc:`~vc2_conformance.encoder.exceptions.IncompatibleLevelAndVideoFormatError`
    if no suitable base video format is available.
    """

    constrained_values = codec_features_to_trivial_level_constraints(
        codec_features)

    return rank_base_video_format_similarity(
        codec_features["video_parameters"],
        list(
            allowed_values_for(
                LEVEL_CONSTRAINTS,
                "base_video_format",
                constrained_values,
                LEVEL_CONSTRAINT_ANY_VALUES["base_video_format"],
            ).iter_values()),
    )
示例#3
0
def assert_level_constraint(state, key, value):
    """
    Check that the given key and value is allowed according to the
    :py:data:`vc2_conformance.level_constraints.LEVEL_CONSTRAINTS`. Throws a
    :py;exc:`vc2_conformance.decoder.exceptions.ValueNotAllowedInLevel`
    exception on failure.

    Takes the current :py:class:`~vc2_conformance.pseudocode.state.State` instance from
    which the current
    :py:attr:`~vc2_conformance.pseudocode.state.State._level_constrained_values` will be
    created/updated.
    """
    state.setdefault("_level_constrained_values", OrderedDict())

    allowed_values = allowed_values_for(
        LEVEL_CONSTRAINTS,
        key,
        state["_level_constrained_values"],
    )

    if value not in allowed_values:
        raise ValueNotAllowedInLevel(
            state["_level_constrained_values"], key, value, allowed_values
        )
    else:
        state["_level_constrained_values"][key] = value
示例#4
0
def slice_size_scaler(codec_features):
    """
    **Tests that the 'slice_size_scaler' field is correctly handled.**

    This test case generates a sequence which sets slice_size_scaler value
    (13.5.4) 1 larger than it otherwise would be.

    This test case is only generated for the high quality profile, and levels
    which permit a slice size scaler value greater than 1.
    """
    # Skip if not high quality profile
    if codec_features["profile"] != Profiles.high_quality:
        return None

    # Pick a minimum slice size scaler which is larger than the slice size
    # scaler which would otherwise be used
    if codec_features["lossless"]:
        # We're just going to code mid-gray frames which compress to 0 bytes so
        # slice size scaler = 1 is always sufficient.
        minimum_slice_size_scaler = 2
    else:
        minimum_slice_size_scaler = (
            get_safe_lossy_hq_slice_size_scaler(
                codec_features["picture_bytes"],
                codec_features["slices_x"] * codec_features["slices_y"],
            )
            + 1
        )

    # Skip if level prohibits non-1 slice size scaler
    if minimum_slice_size_scaler not in allowed_values_for(
        LEVEL_CONSTRAINTS,
        "slice_size_scaler",
        codec_features_to_trivial_level_constraints(codec_features),
    ):
        return None

    sequence = make_sequence(
        codec_features,
        mid_gray(
            codec_features["video_parameters"],
            codec_features["picture_coding_mode"],
        ),
        minimum_slice_size_scaler=minimum_slice_size_scaler,
    )

    # Force lossless coding modes to use a non-zero number of bytes for each
    # slice's coefficients (so that slice_size_scaler actually has to be used).
    if codec_features["lossless"]:
        for _state, _sx, _sy, hq_slice in iter_slices_in_sequence(
            codec_features, sequence
        ):
            assert hq_slice["slice_c2_length"] == 0
            hq_slice["slice_c2_length"] = 1

    return Stream(sequences=[sequence])
示例#5
0
def decide_extended_transform_flag(codec_features, flag_name, required):
    """
    Decide what asym_transform*_flag setting to use, accounting for level
    restrictions.

    Parameters
    ==========
    codec_features : :py:class:`~vc2_conformance.codec_features.CodecFeatures`
    flag_name : str
        The name of the flag to be decided (e.g. "asym_transform_index_flag").
    required : str
        If True, require this flag to be True, if False, the flag may be set to
        True or False, as allowed by the level, preferring False.

    Returns
    =======
    flag : bool

    Raises
    ======
    vc2_conformance.encoder.exceptions.IncompatibleLevelAndExtendedTransformParametersError
        If ``required`` is True but the level prohibits the flag.
    """
    # The flag states which we could use to encode the required value
    usable_flags = [True] if required else [False, True]

    # The allowed flag values according to the current level
    constrained_values = codec_features_to_trivial_level_constraints(
        codec_features)
    permitted_flags = allowed_values_for(LEVEL_CONSTRAINTS, flag_name,
                                         constrained_values)

    # Warning: Slight bodge/special case handled here...
    #
    # The make_extended_transform_parameters function (and by extension, this
    # function) is required to return an ExtendedTransformParameters object
    # even for streams with major_version < 3 which don't contain that field.
    #
    # To handle this case, we treat an empty constraint table entry as allowing
    # 'False' as a valid flag option when in reality for < v3 streams no value
    # is permitted.
    #
    # Note that we will still raise
    # IncompatibleLevelAndExtendedTransformParametersError if required is True
    # in this case. We also don't allow False if the level specifically
    # requires the flag to be True (i.e. we only allow it when the level gives
    # no values).
    if permitted_flags == ValueSet():
        permitted_flags.add_value(False)

    try:
        return next(flag for flag in usable_flags if flag in permitted_flags)
    except StopIteration:
        raise IncompatibleLevelAndExtendedTransformParametersError(
            codec_features)
示例#6
0
 def test_unfiltered(self):
     assert (allowed_values_for(
         [{
             "foo": ValueSet(1)
         }, {
             "foo": ValueSet(2)
         }, {
             "foo": ValueSet(3)
         }],
         "foo",
     ) == ValueSet(1, 2, 3))
示例#7
0
def default_quantization_matrix(codec_features):
    """
    **Tests that the default quantization matrix can be used.**

    This test case is only generated when a non ``default`` value is specified
    for the ``quantization_matrix`` codec features CSV entry but when a default
    quantization matrix is defined.

    .. note::

        This is the only test case which sets the ``custom_quant_matrix`` flag
        (12.4.5.3) to 0 when a ``quantization_matrix`` is supplied in the codec
        features CSV.

    .. note::

        For lossy coding modes, the encoded picture will contain a noise signal
        (see the :decoder-test-case:`static_noise` test case).

        For lossless coding modes, the encoded picture will be the test pattern
        used by the :decoder-test-case:`lossless_quantization` test case. This
        test pattern is designed to be losslessly encodable when some
        quantization is applied.
    """
    # Skip if already using the default quantisation matrix
    if codec_features["quantization_matrix"] is None:
        return None

    # Skip if no default quantization matrix is defined for the codec in use
    if (
        codec_features["wavelet_index"],
        codec_features["wavelet_index_ho"],
        codec_features["dwt_depth"],
        codec_features["dwt_depth_ho"],
    ) not in QUANTISATION_MATRICES:
        return None

    # Skip if the level requires a custom quantisation matrix to be specified
    constrained_values = codec_features_to_trivial_level_constraints(codec_features)
    del constrained_values["custom_quant_matrix"]
    if False not in allowed_values_for(
        LEVEL_CONSTRAINTS, "custom_quant_matrix", constrained_values
    ):
        return None

    # Override the quantization_matrix setting in the codec features to force
    # the default quantization matrix to be used
    codec_features = CodecFeatures(
        codec_features,
        quantization_matrix=None,
    )

    return generate_test_stream(codec_features)
示例#8
0
 def test_filtered(self):
     assert (allowed_values_for(
         [
             {
                 "foo": ValueSet(1),
                 "bar": ValueSet(123)
             },
             {
                 "foo": ValueSet(2),
                 "bar": ValueSet(321)
             },
             {
                 "foo": ValueSet(3),
                 "bar": ValueSet(123)
             },
         ],
         "foo",
         {"bar": 123},
     ) == ValueSet(1, 3))
示例#9
0
 def test_empty(self):
     assert allowed_values_for([], "foo") == ValueSet()
     assert allowed_values_for([], "foo", {"bar": 123}) == ValueSet()
示例#10
0
def custom_quantization_matrix(codec_features):
    """
    **Tests that a custom quantization matrix can be specified.**

    A series of bitstreams with different custom quantisation matrices are
    generated as follows:

    ``custom_quantization_matrix[zeros]``
        Specifies a custom quantisation matrix with all matrix values set to zero.

    ``custom_quantization_matrix[arbitrary]``
        Specifies a custom quantisation matrix with all matrix values set to
        different, though arbitrary, values.

    ``custom_quantization_matrix[default]``
        Specifies a custom quantisation matrix containing the same values as
        the default quantisation matrix. This test case is only generated when
        a default quantization matrix is defined for the codec.

    These test cases are only generated when permitted by the VC-2 level in
    use.

    .. note::

        For lossy coding modes, the encoded picture will contain a noise signal
        (see the :decoder-test-case:`static_noise` test case).

        For lossless coding modes, the encoded picture will be the test pattern
        used by the :decoder-test-case:`lossless_quantization` test case. This
        test pattern is designed to be losslessly encodable when some
        quantization is applied.
    """
    # Skip if the level disallows custom quantisation matrices
    constrained_values = codec_features_to_trivial_level_constraints(codec_features)
    constrained_values["custom_quant_matrix"] = True
    allowed_quant_matrix_values = allowed_values_for(
        LEVEL_CONSTRAINTS, "quant_matrix_values", constrained_values
    )
    if allowed_quant_matrix_values == ValueSet():
        return

    # A set of alternative quantisation matrices to test
    # [(description, quantization_matrix), ...]
    candidate_matrices = []

    # A special case: a flat (all zeros) quantisation matrix
    candidate_matrices.append(
        ("zeros", quantization_matrix_from_generator(codec_features, repeat(0)))
    )

    # An arbitrary quantisation matrix with different values in each entry (if
    # possible)
    if allowed_quant_matrix_values == AnyValue():
        values = count()
    else:
        values = cycle(sorted(allowed_quant_matrix_values.iter_values()))
    candidate_matrices.append(
        ("arbitrary", quantization_matrix_from_generator(codec_features, values))
    )

    # If a default quantisation matrix is available, try explicitly giving the
    # values from the default quant matrix
    wavelet_specification = (
        codec_features["wavelet_index"],
        codec_features["wavelet_index_ho"],
        codec_features["dwt_depth"],
        codec_features["dwt_depth_ho"],
    )
    if wavelet_specification in QUANTISATION_MATRICES:
        candidate_matrices.append(
            (
                "default",
                QUANTISATION_MATRICES[wavelet_specification],
            )
        )

    # Generate test cases for each custom quantisation matrix.
    for description, quantization_matrix in candidate_matrices:
        # If we happen to have generated the quantization matrix specified in
        # the codec features, skip that case...
        if quantization_matrix == codec_features["quantization_matrix"]:
            continue

        # If we the level restricts the quantisation matrix values such that
        # the generated cases is not allowed, skip it.
        if not all(
            value in allowed_quant_matrix_values
            for orients in quantization_matrix.values()
            for value in orients.values()
        ):
            continue

        stream = generate_test_stream(
            CodecFeatures(
                codec_features,
                quantization_matrix=quantization_matrix,
            )
        )
        if stream is not None:
            yield TestCase(stream, description)
示例#11
0
def slice_prefix_bytes(codec_features):
    """
    **Tests the decoder can handle a non-zero number of slice prefix bytes.**

    Produces test cases with a non-zero number of slice prefix bytes
    containing the following values:

    ``slice_prefix_bytes[zeros]``
        All slice prefix bytes are 0x00.

    ``slice_prefix_bytes[ones]``
        All slice prefix bytes are 0xFF.

    ``slice_prefix_bytes[end_of_sequence]``
        All slice prefix bytes contain bits which encode an end of sequence
        data unit (10.4).

    These test cases apply only to the high quality profile and are omitted
    when the low delay profile is used.
    """
    # This test only applies to high quality codecs
    if codec_features["profile"] != Profiles.high_quality:
        return

    constrained_values = codec_features_to_trivial_level_constraints(codec_features)
    allowed_slice_prefix_bytes = allowed_values_for(
        LEVEL_CONSTRAINTS, "slice_prefix_bytes", constrained_values
    )

    mid_gray_pictures = list(
        mid_gray(
            codec_features["video_parameters"],
            codec_features["picture_coding_mode"],
        )
    )

    test_cases = [
        ("zeros", b"\x00"),
        ("ones", b"\xFF"),
        ("end_of_sequence", make_dummy_end_of_sequence()),
    ]

    for description, filler in test_cases:
        sequence = make_sequence(codec_features, mid_gray_pictures)

        # Determine how many slice prefix bytes we can fit in our slices
        if codec_features["lossless"]:
            # Lossless slices can be as large as we like; assign enough slice
            # bytes for the full set of filler bytes
            slice_prefix_bytes = len(filler)
        else:
            # Find the space assigned for coefficients in the smallest slice in
            # a fixed-bit-rate stream; we'll replace all slice coefficients
            # with slice prefix bytes.
            slice_prefix_bytes = min(
                (
                    hq_slice["slice_y_length"]
                    + hq_slice["slice_c1_length"]
                    + hq_slice["slice_c2_length"]
                )
                * state["slice_size_scaler"]
                for state, sx, sy, hq_slice in iter_slices_in_sequence(
                    codec_features, sequence
                )
            )

        # Check level constraints allow this slice_prefix_bytes
        #
        # NB: This implementation assumes that either the slice_prefix_bytes
        # field is required to be zero or it is free to be any value. This
        # assumption is verified for all existing VC-2 levels in the tests for
        # this module. Should this assumption be violated, more sophisticated
        # behaviour will be required here...
        if slice_prefix_bytes not in allowed_slice_prefix_bytes:
            continue

        if slice_prefix_bytes < len(filler):
            logging.warning(
                (
                    "Codec '%s' has a very small picture_bytes value "
                    "meaning the slice_prefix_bytes[%s] test case might not "
                    "be as useful as intended."
                ),
                codec_features["name"],
                description,
            )

        # Set the number of slice prefix bytes in all slice parameter headers
        for slice_parameters in iter_slice_parameters_in_sequence(sequence):
            assert slice_parameters["slice_prefix_bytes"] == 0
            slice_parameters["slice_prefix_bytes"] = slice_prefix_bytes

        # Add prefix bytes to all slices
        prefix_bytes = (filler * slice_prefix_bytes)[:slice_prefix_bytes]
        for state, sx, sy, hq_slice in iter_slices_in_sequence(
            codec_features, sequence
        ):
            hq_slice["prefix_bytes"] = prefix_bytes

            # Keep overall slice size the same for lossy (constant bit rate)
            # modes
            if not codec_features["lossless"]:
                total_length = (
                    hq_slice["slice_y_length"]
                    + hq_slice["slice_c1_length"]
                    + hq_slice["slice_c2_length"]
                )

                total_length -= slice_prefix_bytes // state["slice_size_scaler"]

                hq_slice["slice_y_length"] = 0
                hq_slice["slice_c1_length"] = 0
                hq_slice["slice_c2_length"] = total_length

        yield TestCase(Stream(sequences=[sequence]), description)
def test_all_levels_have_constraint_table_entries():
    assert set(Levels) == set(allowed_values_for(LEVEL_CONSTRAINTS, "level"))
示例#13
0
def extended_transform_parameters(codec_features):
    """
    **Tests that extended transform parameter flags are handled correctly.**

    Ensures that extended transform parameters fields (12.4.4) are correctly
    handled by decoders for symmetric transform modes.

    ``extended_transform_parameters[asym_transform_index_flag]``
        Verifies that ``asym_transform_index_flag`` can be set to ``1``.

    ``extended_transform_parameters[asym_transform_flag]``
        Verifies that ``asym_transform_flag`` can be set to ``1``.

    These test cases are skipped for streams whose major version is less than 3
    (which do not support the extended transform parameters header).
    Additionally, these test cases are skipped for asymmetric transforms when
    the flag being tested must already be ``1``.
    """
    # Generate a base sequence in which we'll replace the extended transform
    # parameters later
    base_sequence = make_sequence(
        codec_features,
        static_sprite(
            codec_features["video_parameters"],
            codec_features["picture_coding_mode"],
        ),
    )

    # Skip this test if the generated sequence does not use major_version 3
    # since no extended transform parameters field will be present.
    autofill_major_version(Stream(sequences=[base_sequence]))
    major_versions = [
        data_unit["sequence_header"]["parse_parameters"]["major_version"]
        for data_unit in base_sequence["data_units"]
        if data_unit["parse_info"]["parse_code"] == ParseCodes.sequence_header
    ]
    if major_versions[0] != 3:
        return

    # Try enabling the asym_transform_index_flag and asym_transform_flag, if
    # not already enabled and if the current level permits it
    constrained_values = codec_features_to_trivial_level_constraints(
        codec_features)
    if True in allowed_values_for(LEVEL_CONSTRAINTS,
                                  "asym_transform_index_flag",
                                  constrained_values):
        sequence, changed = update_extended_transform_parameters(
            base_sequence,
            asym_transform_index_flag=True,
            wavelet_index_ho=codec_features["wavelet_index_ho"],
        )
        if changed:
            yield TestCase(Stream(sequences=[sequence]),
                           "asym_transform_index_flag")

    if True in allowed_values_for(LEVEL_CONSTRAINTS, "asym_transform_flag",
                                  constrained_values):
        sequence, changed = update_extended_transform_parameters(
            base_sequence,
            asym_transform_flag=True,
            dwt_depth_ho=codec_features["dwt_depth_ho"],
        )
        if changed:
            yield TestCase(Stream(sequences=[sequence]), "asym_transform_flag")
示例#14
0
def quant_matrix(state):
    """(12.4.5.3)"""
    custom_quant_matrix = read_bool(state)
    # (C.3) Check level allows use of custom quantisation matrices
    ## Begin not in spec
    assert_level_constraint(state, "custom_quant_matrix", custom_quant_matrix)
    ## End not in spec

    if custom_quant_matrix:
        # (C.3) Check that quantisation matrix values are in the level-defined
        # range
        ## Begin not in spec
        allowed_values = allowed_values_for(
            LEVEL_CONSTRAINTS,
            "quant_matrix_values",
            state["_level_constrained_values"],
        )

        def check(quant_matrix_value):
            assert_in(
                quant_matrix_value,
                allowed_values,
                QuantisationMatrixValueNotAllowedInLevel,
                state["_level_constrained_values"],
            )

        ## End not in spec

        # NB: For historical reasons, we use a dict not an array in this
        # implementation.
        ### state["quant_matrix"] = new_array(state["dwt_depth_ho"] + state["dwt_depth"] + 1)
        state["quant_matrix"] = {}  ## Not in spec
        if state["dwt_depth_ho"] == 0:
            state["quant_matrix"][0] = {}
            state["quant_matrix"][0]["LL"] = read_uint(state)
            check(state["quant_matrix"][0]["LL"])  ## Not in spec
        else:
            state["quant_matrix"][0] = {}
            state["quant_matrix"][0]["L"] = read_uint(state)
            check(state["quant_matrix"][0]["L"])  ## Not in spec
            for level in range(1, state["dwt_depth_ho"] + 1):
                state["quant_matrix"][level] = {}
                state["quant_matrix"][level]["H"] = read_uint(state)
                check(state["quant_matrix"][level]["H"])  ## Not in spec
        for level in range(state["dwt_depth_ho"] + 1,
                           state["dwt_depth_ho"] + state["dwt_depth"] + 1):
            state["quant_matrix"][level] = {}
            state["quant_matrix"][level]["HL"] = read_uint(state)
            check(state["quant_matrix"][level]["HL"])  ## Not in spec
            state["quant_matrix"][level]["LH"] = read_uint(state)
            check(state["quant_matrix"][level]["LH"])  ## Not in spec
            state["quant_matrix"][level]["HH"] = read_uint(state)
            check(state["quant_matrix"][level]["HH"])  ## Not in spec
    else:
        # (12.4.5.3) If no custom quantisation matrix has been specified, a
        # default table must be available
        ## Begin not in spec
        configuration = (
            state["wavelet_index"],
            state["wavelet_index_ho"],
            state["dwt_depth"],
            state["dwt_depth_ho"],
        )
        if configuration not in QUANTISATION_MATRICES:
            raise NoQuantisationMatrixAvailable(*configuration)
        ## End not in spec

        set_quant_matrix(state)