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
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)
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)
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)
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")
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
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
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)
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