Ejemplo n.º 1
0
class TestLDSlice(object):
    @pytest.mark.parametrize(
        "slice_y_length,exp_fail",
        [
            # In range
            (0, False),
            (40, False),
            (80 - 7 - intlog2(80 - 7), False),
            # Too long
            (80 - 7 - intlog2(80 - 7) + 1, True),
            (127, True),
        ],
    )
    def test_slice_y_length_must_be_valid(self, slice_y_length, exp_fail):
        state = single_sample_transform_base_state.copy()
        state.update({
            "slice_bytes_numerator": 10,
            "slice_bytes_denominator": 1
        })
        ld_slice_bytes = serialise_to_bytes(
            bitstream.LDSlice(slice_y_length=slice_y_length),
            state,
            0,
            0,
        )
        state.update(bytes_to_state(ld_slice_bytes))

        if exp_fail:
            with pytest.raises(decoder.InvalidSliceYLength) as exc_info:
                decoder.ld_slice(state, 0, 0)
            assert exc_info.value.slice_y_length == slice_y_length
            assert exc_info.value.slice_bytes == 10
            assert exc_info.value.sx == 0
            assert exc_info.value.sy == 0
        else:
            decoder.ld_slice(state, 0, 0)

    def test_qindex_constrained_by_level(self):
        state = single_sample_transform_base_state.copy()
        state.update({
            "slice_bytes_numerator": 1,
            "slice_bytes_denominator": 1
        })
        state.update(
            bytes_to_state(
                serialise_to_bytes(
                    bitstream.LDSlice(qindex=0, ),
                    state,
                    0,
                    0,
                )))

        decoder.ld_slice(state, 0, 0)

        # Just check that assert_level_constraint has added the index to the
        # dictionary.
        assert state["_level_constrained_values"]["qindex"] == 0
Ejemplo n.º 2
0
def float_to_int_clipped(a, offset, excursion):
    """
    Convert (an array of) float sample values in the nominal range 0 to +1 or
    -0.5 to +0.5 to integers (with the specified offset and excursion).

    Values which fall outside the range of the integer representation are
    clipped.
    """
    a = float_to_int(a, offset, excursion)
    return np.clip(a, 0, (2**(intlog2(excursion + 1))) - 1)
Ejemplo n.º 3
0
def test_to_xyz_and_from_xyz_roundtrip(
    luma_offset,
    luma_excursion,
    color_diff_offset,
    color_diff_excursion,
    primaries,
    matrix,
    transfer_function,
):
    video_parameters = VideoParameters(
        color_diff_format_index=ColorDifferenceSamplingFormats.color_4_4_4,
        luma_offset=luma_offset,
        luma_excursion=luma_excursion,
        color_diff_offset=color_diff_offset,
        color_diff_excursion=color_diff_excursion,
        color_primaries_index=primaries,
        color_matrix_index=matrix,
        transfer_function_index=transfer_function,
    )

    luma_bits = intlog2(luma_excursion + 1)
    color_diff_bits = intlog2(color_diff_excursion + 1)

    w, h = 6, 4

    rand = np.random.RandomState(0)
    y = rand.randint(0, 2 ** luma_bits, (h, w))
    c1 = rand.randint(0, 2 ** color_diff_bits, (h, w))
    c2 = rand.randint(0, 2 ** color_diff_bits, (h, w))

    xyz = to_xyz(y, c1, c2, video_parameters)

    assert xyz.shape == (h, w, 3)

    new_y, new_c1, new_c2 = from_xyz(xyz, video_parameters)

    assert np.array_equal(y, new_y)
    assert np.array_equal(c1, new_c1)
    assert np.array_equal(c2, new_c2)
Ejemplo n.º 4
0
def test_intlog2_equal_to_intlog2_float():
    # Ensure the integer implementation produces identical results to the
    # as-described-in-spec float version.
    for n in range(1, 100000):
        assert vc2_math.intlog2(n) == vc2_math.intlog2_float(n)
Ejemplo n.º 5
0
def ld_slice(serdes, state, sx, sy):
    """(13.5.3.1)"""
    slice_bits_left = 8 * slice_bytes(state, sx, sy)

    qindex = serdes.nbits("qindex", 7)  # noqa: F841
    slice_bits_left -= 7

    # Not required for bitstream unpacking
    ### slice_quantizers(state, qindex)

    length_bits = intlog2((8 * slice_bytes(state, sx, sy)) - 7)
    slice_y_length = serdes.nbits("slice_y_length", length_bits)
    slice_bits_left -= length_bits

    # Ensure robustness in the presence of invalid bitstreams
    ## Begin not in spec
    if slice_y_length > slice_bits_left:
        slice_y_length = slice_bits_left
    ## End not in spec

    # Initialise context dictionary
    serdes.computed_value("_sx", sx)
    serdes.computed_value("_sy", sy)
    serdes.declare_list("y_transform")
    serdes.declare_list("c_transform")

    serdes.bounded_block_begin(slice_y_length)

    if state["dwt_depth_ho"] == 0:
        slice_band(serdes, state, "y_transform", 0, "LL", sx, sy)
        for level in range(1, state["dwt_depth"] + 1):
            for orient in ["HL", "LH", "HH"]:
                slice_band(serdes, state, "y_transform", level, orient, sx, sy)
    else:
        slice_band(serdes, state, "y_transform", 0, "L", sx, sy)
        for level in range(1, state["dwt_depth_ho"] + 1):
            slice_band(serdes, state, "y_transform", level, "H", sx, sy)
        for level in range(state["dwt_depth_ho"] + 1,
                           state["dwt_depth_ho"] + state["dwt_depth"] + 1):
            for orient in ["HL", "LH", "HH"]:
                slice_band(serdes, state, "y_transform", level, orient, sx, sy)

    serdes.bounded_block_end("y_block_padding")

    slice_bits_left -= slice_y_length

    serdes.bounded_block_begin(slice_bits_left)

    if state["dwt_depth_ho"] == 0:
        color_diff_slice_band(serdes, state, 0, "LL", sx, sy)
        for level in range(1, state["dwt_depth"] + 1):
            for orient in ["HL", "LH", "HH"]:
                color_diff_slice_band(serdes, state, level, orient, sx, sy)
    else:
        color_diff_slice_band(serdes, state, 0, "L", sx, sy)
        for level in range(1, state["dwt_depth_ho"] + 1):
            color_diff_slice_band(serdes, state, level, "H", sx, sy)
        for level in range(state["dwt_depth_ho"] + 1,
                           state["dwt_depth_ho"] + state["dwt_depth"] + 1):
            for orient in ["HL", "LH", "HH"]:
                color_diff_slice_band(serdes, state, level, orient, sx, sy)

    serdes.bounded_block_end("c_block_padding")
Ejemplo n.º 6
0
def cut_off_value_at_end_of_ld_slice(
    state,
    sx,
    sy,
    ld_slice,
    component,
    dangle_type,
    magnitude,
):
    """
    Given a :py:class:`~vc2_conformance.bitstream.LDSlice`, mutate it in-place
    such that the slice size remains the same, but the specified component's
    bounded block contains a value whose last bits are off the end of the
    bounded block.

    Parameters
    ==========
    state : :py:class:`~vc2_conformance.pseudocode.state.State`
        A state dictionary containing at least:

        * ``slices_x``
        * ``slices_y``
        * ``slice_bytes_numerator``
        * ``slice_bytes_denominator``
    sx, sy : int
        The slice coordinates. (Not used but present for consistency with
        :py:func:`cut_off_value_at_end_of_hq_slice`.
    ld_slice : :py:class:`~vc2_conformance.bitstream.LDSlice`
        The slice to modify.
    component : "Y" or "C"
        The component in which to place the dangling value.
    dangle_type : :py:class:`DanglingTransformValueType`
        The type of dangling value to produce.
    magnitude : int
        The magnitude of the dangling value, in bits. Ignored when dangle_type
        is :py:attr:`DanglingTransformValueType.lsb_stop_and_sign_dangling`.

    Raises
    ======
    UnsatisfiableBlockSizeError
        If unable to create the specified type of dangling values in the space
        and transform components available. Should only occur in degenerate
        cases.
    """
    # Make the designated slice have at least 16 bits (this should allow for
    # plenty of flexibility in fitting sensible magnitudes
    slice_data_bits = 8 * slice_bytes(state, sx, sy)
    slice_data_bits -= 7
    length_field_bits = intlog2(slice_data_bits)
    slice_data_bits -= length_field_bits

    block_bits = 16

    if slice_data_bits < block_bits:
        raise UnsatisfiableBlockSizeError("Slices too small")

    if component == "Y":
        ld_slice["slice_y_length"] = block_bits
    elif component == "C":
        ld_slice["slice_y_length"] = slice_data_bits - block_bits

    # Work out how many transform coeffs are in this component
    num_values = len(ld_slice["{}_transform".format(component.lower())])

    # Force picture content to zeros
    for c in ["y_transform", "c_transform"]:
        for i in range(len(ld_slice[c])):
            ld_slice[c][i] = 0

    # Fill requested component with dangling values
    values = generate_dangling_transform_values(
        block_bits,
        num_values,
        dangle_type,
        magnitude,
    )
    ld_slice["{}_transform".format(component.lower())] = values
Ejemplo n.º 7
0
def fill_ld_slice_padding(
    state,
    sx,
    sy,
    ld_slice,
    component,
    filler,
    byte_align=False,
):
    """
    Given a :py:class:`~vc2_conformance.bitstream.LDSlice`, mutate it in-place
    such that the slice size remains the same, but the
    specified component's bounded block padding data is set to repeated copies
    of ``filler``. All transform data will be forced to zeros.

    Parameters
    ==========
    state : :py:class:`~vc2_conformance.pseudocode.state.State`
        A state dictionary containing at least:

        * ``slices_x``
        * ``slices_y``
        * ``slice_bytes_numerator``
        * ``slice_bytes_denominator``
    sx, sy : int
        The slice coordinates
    ld_slice : :py:class:`~vc2_conformance.bitstream.LDSlice`
        The slice to modify.
    component : "Y", "C"
        The component to stuff with filler data.
    filler : bytes
        A byte string to use as filler. Will be repeated as many times as
        necessary to fill the available space.
    byte_align : bool
        If True, insert the filler bytes on a byte-aligned boundary.
    """
    assert len(filler) > 0

    # Force picture content to zeros
    for c in ["y_transform", "c_transform"]:
        for i in range(len(ld_slice[c])):
            ld_slice[c][i] = 0

    # Work out the slice's size
    slice_data_bits = 8 * slice_bytes(state, sx, sy)
    slice_data_bits -= 7
    length_field_bits = intlog2(slice_data_bits)
    slice_data_bits -= length_field_bits

    # Force the specified component to the full length
    if component == "Y":
        ld_slice["slice_y_length"] = slice_data_bits
    else:
        ld_slice["slice_y_length"] = 0

    # Work out the size of the padding bits
    bits_used_by_zeros = len(ld_slice["{}_transform".format(
        component.lower())])
    padding_bits = slice_data_bits - bits_used_by_zeros

    data_start_offset_bits = 7 + length_field_bits

    if padding_bits > 0:
        padding = generate_filled_padding(
            padding_bits,
            filler,
            data_start_offset_bits if byte_align else 0,
        )
    else:
        padding = bitarray()

    ld_slice["{}_block_padding".format(component.lower())] = padding
Ejemplo n.º 8
0
def video_depth(state, video_parameters):
    """(11.6.3) Compute the bits-per-sample for the decoded video."""
    state["luma_depth"] = intlog2(video_parameters["luma_excursion"] + 1)
    state["color_diff_depth"] = intlog2(
        video_parameters["color_diff_excursion"] + 1)
Ejemplo n.º 9
0
def make_transform_data_ld_lossy(picture_bytes,
                                 transform_coeffs,
                                 minimum_qindex=0):
    """
    Quantize and pack transform coefficients into LD picture slices in a
    :py:class:`TransformData`.

    Raises :py:exc:`InsufficientLDPictureBytesError` if ``picture_bytes`` is too
    small.

    Parameters
    ==========
    picture_bytes : int
        The total number of bytes the picture slices should take up (i.e.
        including qindex and slice_y_length fields). Must be at least 1 byte
        per slice.
    transform_coeffs : [[:py:class:`SliceCoeffs`, ...], ...]
    minimum_qindex : int
        If provided, gives the quantization index to start with when trying to
        find a suitable quantization index.

    Returns
    =======
    transform_data : :py:class:`vc2_conformance.bitstream.TransformData`
    """
    # Used by slice_bytes
    state = State(
        slices_x=width(transform_coeffs),
        slices_y=height(transform_coeffs),
        slice_bytes_numerator=picture_bytes,
        slice_bytes_denominator=width(transform_coeffs) *
        height(transform_coeffs),
    )

    transform_data = TransformData(ld_slices=[])
    for sy, transform_coeffs_row in enumerate(transform_coeffs):
        for sx, transform_coeffs_slice in enumerate(transform_coeffs_row):
            target_size = 8 * slice_bytes(state, sx, sy)
            target_size -= 7  # qindex field
            target_size -= intlog2(target_size)  # slice_y_length field

            if target_size < 0:
                raise InsufficientLDPictureBytesError()

            # Interleave color components
            y_coeffs = transform_coeffs_slice.Y
            c_coeffs = ComponentCoeffs(
                coeff_values=interleave(
                    transform_coeffs_slice.C1.coeff_values,
                    transform_coeffs_slice.C2.coeff_values,
                ),
                quant_matrix_values=interleave(
                    transform_coeffs_slice.C1.quant_matrix_values,
                    transform_coeffs_slice.C2.quant_matrix_values,
                ),
            )

            # Quantize each slice to fit
            qindex, (y_transform, c_transform) = quantize_to_fit(
                target_size,
                [y_coeffs, c_coeffs],
                minimum_qindex=minimum_qindex,
            )
            transform_data["ld_slices"].append(
                make_ld_slice(
                    y_transform,
                    c_transform,
                    qindex,
                ))

    return transform_data