def test_quantisation_index_bound(): wavelet_index = WaveletFilters.haar_with_shift wavelet_index_ho = WaveletFilters.haar_with_shift dwt_depth = 1 dwt_depth_ho = 0 quantisation_matrix = { 0: { "LL": 10 }, 1: { "LH": 10, "HL": 10, "HH": 10 }, } picture_bit_width = 10 ( analysis_signal_bounds, synthesis_signal_bounds, analysis_test_patterns, synthesis_test_patterns, ) = static_filter_analysis( wavelet_index, wavelet_index_ho, dwt_depth, dwt_depth_ho, ) ( concrete_analysis_signal_bounds, concrete_synthesis_signal_bounds, ) = evaluate_filter_bounds( wavelet_index, wavelet_index_ho, dwt_depth, dwt_depth_ho, analysis_signal_bounds, synthesis_signal_bounds, picture_bit_width, ) max_qi = quantisation_index_bound( concrete_analysis_signal_bounds, quantisation_matrix, ) max_coeff_magnitude = max( max(abs(lower), abs(upper)) for lower, upper in concrete_analysis_signal_bounds.values()) assert forward_quant(max_coeff_magnitude, max_qi - 10) == 0 assert forward_quant(max_coeff_magnitude, max_qi - 11) != 0
def main(args=None): args = parse_args(args) # Load precomputed signal bounds static_filter_analysis = json.load(args.static_filter_analysis) quantisation_matrix = parse_quantisation_matrix_argument( args.custom_quantisation_matrix, static_filter_analysis["wavelet_index"], static_filter_analysis["wavelet_index_ho"], static_filter_analysis["dwt_depth"], static_filter_analysis["dwt_depth_ho"], ) analysis_signal_bounds = deserialise_signal_bounds( static_filter_analysis["analysis_signal_bounds"] ) synthesis_signal_bounds = deserialise_signal_bounds( static_filter_analysis["synthesis_signal_bounds"] ) ( concrete_analysis_signal_bounds, concrete_synthesis_signal_bounds, ) = evaluate_filter_bounds( static_filter_analysis["wavelet_index"], static_filter_analysis["wavelet_index_ho"], static_filter_analysis["dwt_depth"], static_filter_analysis["dwt_depth_ho"], analysis_signal_bounds, synthesis_signal_bounds, args.picture_bit_width, ) print(quantisation_index_bound( concrete_analysis_signal_bounds, quantisation_matrix, )) return 0
# # analysis_bounds_dicts = [{(level, array_name, x, y): (lower_bound, upper_bound), ...}, ...] # synthesis_bounds_dicts = same as above concrete_analysis_bounds, concrete_synthesis_bounds = evaluate_filter_bounds( static_filter_analysis["wavelet_index"], static_filter_analysis["wavelet_index_ho"], static_filter_analysis["dwt_depth"], static_filter_analysis["dwt_depth_ho"], analysis_signal_bounds, synthesis_signal_bounds, args.picture_bit_width, ) # Find the maximum quantisation index for each bit width max_quantisation_index = quantisation_index_bound( concrete_analysis_bounds, quantisation_matrix, ) # Find test signal output values for each bit width _, synthesis_test_signal_outputs = evaluate_test_signal_outputs( static_filter_analysis["wavelet_index"], static_filter_analysis["wavelet_index_ho"], static_filter_analysis["dwt_depth"], static_filter_analysis["dwt_depth_ho"], args.picture_bit_width, quantisation_matrix, max_quantisation_index, analysis_test_signals, synthesis_test_signals, )
def load_filter_analysis( static_filter_analysis_file, optimised_synthesis_patterns_file, quantisation_matrix_argument, picture_bit_width, ): """ Load a static filter analysis and optionally a set of optimised synthesis test patterns, returning all of the loaded data. Parameters ========== static_filter_analysis_file : :py:class:`file` An open file ready to read the static filter analysis data from a JSON file. optimised_synthesis_patterns_file : :py:class:`file` or None An open file ready to read a set of optimised synthesis test patterns for a JSON file. If None, synthesis test patterns will be read from the ``static_filter_analysis_file`` instead. quantisation_matrix_argument : [str, ...] or None The --custom-quantisation-matrix argument which will be parsed (if optimised_synthesis_patterns_file is not provided) picture_bit_width : int or None The --picture-bit-width argument which will be used if no optimised_synthesis_test_patterns file is provided. Returns ======= wavelet_index : int wavelet_index_ho : int dwt_depth : int dwt_depth_ho : int quantisation_matrix : {level: {orient: value, ...}, ...} picture_bit_width : int max_quantisation_index : int concrete_analysis_bounds : {(level, array_name, x, y): (lo, hi), ...} concrete_synthesis_bounds : {(level, array_name, x, y): (lo, hi), ...} analysis_test_patterns : {(level, array_name, x, y): :py:class:`~vc2_bit_widths.patterns.TestPatternSpecification`, ...} synthesis_test_patterns : {(level, array_name, x, y): :py:class:`~vc2_bit_widths.patterns.TestPatternSpecification`, ...} """ # Load precomputed signal bounds static_filter_analysis = json.load(static_filter_analysis_file) analysis_signal_bounds = deserialise_signal_bounds( static_filter_analysis["analysis_signal_bounds"] ) synthesis_signal_bounds = deserialise_signal_bounds( static_filter_analysis["synthesis_signal_bounds"] ) # Load precomputed test patterns analysis_test_patterns = deserialise_test_pattern_specifications( TestPatternSpecification, static_filter_analysis["analysis_test_patterns"] ) synthesis_test_patterns = deserialise_test_pattern_specifications( TestPatternSpecification, static_filter_analysis["synthesis_test_patterns"] ) # Load optimised synthesis signal if optimised_synthesis_patterns_file is not None: optimised_json = json.load(optimised_synthesis_patterns_file) assert static_filter_analysis["wavelet_index"] == optimised_json["wavelet_index"] assert static_filter_analysis["wavelet_index_ho"] == optimised_json["wavelet_index_ho"] assert static_filter_analysis["dwt_depth"] == optimised_json["dwt_depth"] assert static_filter_analysis["dwt_depth_ho"] == optimised_json["dwt_depth_ho"] picture_bit_width = optimised_json["picture_bit_width"] quantisation_matrix = deserialise_quantisation_matrix( optimised_json["quantisation_matrix"] ) synthesis_test_patterns = deserialise_test_pattern_specifications( OptimisedTestPatternSpecification, optimised_json["optimised_synthesis_test_patterns"] ) else: quantisation_matrix = parse_quantisation_matrix_argument( quantisation_matrix_argument, static_filter_analysis["wavelet_index"], static_filter_analysis["wavelet_index_ho"], static_filter_analysis["dwt_depth"], static_filter_analysis["dwt_depth_ho"], ) # Compute signal bounds for all specified bit widths # # analysis_bounds_dicts = [{(level, array_name, x, y): (lower_bound, upper_bound), ...}, ...] # synthesis_bounds_dicts = same as above concrete_analysis_bounds, concrete_synthesis_bounds = evaluate_filter_bounds( static_filter_analysis["wavelet_index"], static_filter_analysis["wavelet_index_ho"], static_filter_analysis["dwt_depth"], static_filter_analysis["dwt_depth_ho"], analysis_signal_bounds, synthesis_signal_bounds, picture_bit_width, ) # Find the maximum quantisation index for each bit width max_quantisation_index = quantisation_index_bound( concrete_analysis_bounds, quantisation_matrix, ) return ( static_filter_analysis["wavelet_index"], static_filter_analysis["wavelet_index_ho"], static_filter_analysis["dwt_depth"], static_filter_analysis["dwt_depth_ho"], quantisation_matrix, picture_bit_width, max_quantisation_index, concrete_analysis_bounds, concrete_synthesis_bounds, analysis_test_patterns, synthesis_test_patterns, )
def get_test_pictures(codec_features, bundle_filename=get_bundle_filename()): """ Gets a set of test pictures for a codec with the specified codec configuration. Raises a :py:exc:`MissingStaticAnalysisError` if no static filter analysis is available for the specified codec. Parameters ========== bundle_filename : str Filename of the VC-2 bit widths bundle file to read test patterns from. codec_features : :py:class:`~vc2_conformance.codec_features.CodecFeatures` Returns ======= analysis_luma_pictures : [:py:class:`AnalysisPicture`, ...] synthesis_luma_pictures : [:py:class:`SynthesisPicture`, ...] analysis_color_diff_pictures : [:py:class:`AnalysisPicture`, ...] synthesis_color_diff_pictures : [:py:class:`SynthesisPicture`, ...] Test pictures for luma and color difference components respectively. (See :py:func:`vc2_bit_widths.helpers.generate_test_pictures`.) """ if codec_features["quantization_matrix"] is not None: quantisation_matrix = codec_features["quantization_matrix"] else: quantisation_matrix = QUANTISATION_MATRICES[( codec_features["wavelet_index"], codec_features["wavelet_index_ho"], codec_features["dwt_depth"], codec_features["dwt_depth_ho"], )] dimensions_and_depths = compute_dimensions_and_depths( codec_features["video_parameters"], codec_features["picture_coding_mode"], ) # Load the test pattern specifications try: ( analysis_signal_bounds, synthesis_signal_bounds, analysis_test_patterns, synthesis_test_patterns, ) = bundle_get_static_filter_analysis( bundle_filename, codec_features["wavelet_index"], codec_features["wavelet_index_ho"], codec_features["dwt_depth"], codec_features["dwt_depth_ho"], ) except KeyError: raise MissingStaticAnalysisError() all_analysis_pictures = [] all_synthesis_pictures = [] for component in ["Y", "C1"]: picture_bit_width = dimensions_and_depths[component].depth_bits picture_width = dimensions_and_depths[component].width picture_height = dimensions_and_depths[component].height # ...using optimised synthesis test patterns if available... try: synthesis_test_patterns = bundle_get_optimised_synthesis_test_patterns( bundle_filename, codec_features["wavelet_index"], codec_features["wavelet_index_ho"], codec_features["dwt_depth"], codec_features["dwt_depth_ho"], quantisation_matrix, picture_bit_width, ) except KeyError: pass # Determine the maximum quantisation index which may be usefully used for # the codec configuration specified concrete_analysis_bounds, concrete_synthesis_bounds = evaluate_filter_bounds( codec_features["wavelet_index"], codec_features["wavelet_index_ho"], codec_features["dwt_depth"], codec_features["dwt_depth_ho"], analysis_signal_bounds, synthesis_signal_bounds, picture_bit_width, ) max_quantisation_index = quantisation_index_bound( concrete_analysis_bounds, quantisation_matrix, ) # Find the worst-case quantisation index for the synthesis test patterns _, synthesis_test_pattern_outputs = evaluate_test_pattern_outputs( codec_features["wavelet_index"], codec_features["wavelet_index_ho"], codec_features["dwt_depth"], codec_features["dwt_depth_ho"], picture_bit_width, quantisation_matrix, max_quantisation_index, analysis_test_patterns, synthesis_test_patterns, ) # Generate packed test pictures analysis_pictures, synthesis_pictures = generate_test_pictures( picture_width, picture_height, picture_bit_width, analysis_test_patterns, synthesis_test_patterns, synthesis_test_pattern_outputs, ) all_analysis_pictures.append(analysis_pictures) all_synthesis_pictures.append(synthesis_pictures) return ( all_analysis_pictures[0], all_synthesis_pictures[0], all_analysis_pictures[1], all_synthesis_pictures[1], )
def main(args=None): args = parse_args(args) if args.verbose: logging.basicConfig(level=logging.INFO) static_filter_analysis = json.load(args.static_filter_analysis) quantisation_matrix = parse_quantisation_matrix_argument( args.custom_quantisation_matrix, static_filter_analysis["wavelet_index"], static_filter_analysis["wavelet_index_ho"], static_filter_analysis["dwt_depth"], static_filter_analysis["dwt_depth_ho"], ) analysis_signal_bounds = deserialise_signal_bounds( static_filter_analysis["analysis_signal_bounds"]) synthesis_signal_bounds = deserialise_signal_bounds( static_filter_analysis["synthesis_signal_bounds"]) synthesis_test_patterns = deserialise_test_pattern_specifications( TestPatternSpecification, static_filter_analysis["synthesis_test_patterns"]) ( concrete_analysis_signal_bounds, concrete_synthesis_signal_bounds, ) = evaluate_filter_bounds( static_filter_analysis["wavelet_index"], static_filter_analysis["wavelet_index_ho"], static_filter_analysis["dwt_depth"], static_filter_analysis["dwt_depth_ho"], analysis_signal_bounds, synthesis_signal_bounds, args.picture_bit_width, ) max_quantisation_index = quantisation_index_bound( concrete_analysis_signal_bounds, quantisation_matrix, ) random_state = np.random.RandomState(args.seed) optimised_synthesis_test_patterns = optimise_synthesis_test_patterns( wavelet_index=static_filter_analysis["wavelet_index"], wavelet_index_ho=static_filter_analysis["wavelet_index_ho"], dwt_depth=static_filter_analysis["dwt_depth"], dwt_depth_ho=static_filter_analysis["dwt_depth_ho"], quantisation_matrix=quantisation_matrix, picture_bit_width=args.picture_bit_width, synthesis_test_patterns=synthesis_test_patterns, max_quantisation_index=max_quantisation_index, random_state=random_state, number_of_searches=args.number_of_searches, terminate_early=args.terminate_early, added_corruption_rate=args.added_corruption_rate, removed_corruption_rate=args.removed_corruption_rate, base_iterations=args.base_iterations, added_iterations_per_improvement=args.added_iterations_per_improvement, ) out = { "wavelet_index": static_filter_analysis["wavelet_index"], "wavelet_index_ho": static_filter_analysis["wavelet_index_ho"], "dwt_depth": static_filter_analysis["dwt_depth"], "dwt_depth_ho": static_filter_analysis["dwt_depth_ho"], "picture_bit_width": args.picture_bit_width, "quantisation_matrix": serialise_quantisation_matrix(quantisation_matrix, ), "optimised_synthesis_test_patterns": serialise_test_pattern_specifications( OptimisedTestPatternSpecification, optimised_synthesis_test_patterns, ) } json.dump(out, args.output) args.output.write("\n") return 0
def test_generate_test_pictures(): wavelet_index = WaveletFilters.haar_with_shift wavelet_index_ho = WaveletFilters.le_gall_5_3 dwt_depth = 1 dwt_depth_ho = 0 h_filter_params = LIFTING_FILTERS[wavelet_index_ho] v_filter_params = LIFTING_FILTERS[wavelet_index] quantisation_matrix = { 0: { "LL": 0 }, 1: { "LH": 1, "HL": 2, "HH": 3 }, } picture_width = 16 picture_height = 8 picture_bit_width = 10 value_offset = 1 << (picture_bit_width - 1) ( analysis_signal_bounds, synthesis_signal_bounds, analysis_test_patterns, synthesis_test_patterns, ) = static_filter_analysis( wavelet_index, wavelet_index_ho, dwt_depth, dwt_depth_ho, ) ( concrete_analysis_signal_bounds, concrete_synthesis_signal_bounds, ) = evaluate_filter_bounds( wavelet_index, wavelet_index_ho, dwt_depth, dwt_depth_ho, analysis_signal_bounds, synthesis_signal_bounds, picture_bit_width, ) max_quantisation_index = quantisation_index_bound( concrete_analysis_signal_bounds, quantisation_matrix, ) ( analysis_test_pattern_outputs, synthesis_test_pattern_outputs, ) = evaluate_test_pattern_outputs( wavelet_index, wavelet_index_ho, dwt_depth, dwt_depth_ho, picture_bit_width, quantisation_matrix, max_quantisation_index, analysis_test_patterns, synthesis_test_patterns, ) ( analysis_pictures, synthesis_pictures, ) = generate_test_pictures( picture_width, picture_height, picture_bit_width, analysis_test_patterns, synthesis_test_patterns, synthesis_test_pattern_outputs, ) # Check all test patterns and maximise/minimise options were included assert set((tp.level, tp.array_name, tp.x, tp.y, tp.maximise) for p in analysis_pictures for tp in p.test_points) == set( (level, array_name, x, y, maximise) for (level, array_name, x, y) in analysis_test_patterns for maximise in [True, False]) assert set((tp.level, tp.array_name, tp.x, tp.y, tp.maximise) for p in synthesis_pictures for tp in p.test_points) == set( (level, array_name, x, y, maximise) for (level, array_name, x, y) in synthesis_test_patterns for maximise in [True, False]) # Test analysis pictures do what they claim for analysis_picture in analysis_pictures: for test_point in analysis_picture.test_points: # Perform analysis on the whole test picture, capturing the target # value along the way target_value = fast_partial_analysis_transform( h_filter_params, v_filter_params, dwt_depth, dwt_depth_ho, analysis_picture.picture.copy() - value_offset, # NB: Argument is mutated ( test_point.level, test_point.array_name, test_point.tx, test_point.ty, ), ) # Compare with expected output level for that test pattern expected_outputs = analysis_test_pattern_outputs[( test_point.level, test_point.array_name, test_point.x, test_point.y, )] if test_point.maximise: expected_value = expected_outputs[1] else: expected_value = expected_outputs[0] assert target_value == expected_value # PyExps required for synthesis implementation _, synthesis_pyexps = synthesis_transform( h_filter_params, v_filter_params, dwt_depth, dwt_depth_ho, make_variable_coeff_arrays(dwt_depth, dwt_depth_ho), ) # Test synthesis pictures do what they claim for synthesis_picture in synthesis_pictures: for test_point in synthesis_picture.test_points: # Perform analysis, quantisation and synthesis on the whole test # picture, capturing just the target synthesis value codec = FastPartialAnalyseQuantiseSynthesise( h_filter_params, v_filter_params, dwt_depth, dwt_depth_ho, quantisation_matrix, [synthesis_picture.quantisation_index], synthesis_pyexps[( test_point.level, test_point.array_name, )][test_point.tx, test_point.ty], ) # NB: Argument is mutated target_value = codec.analyse_quantise_synthesise( synthesis_picture.picture.copy() - value_offset, )[0] # Compare with expected output level for that test pattern expected_outputs = synthesis_test_pattern_outputs[( test_point.level, test_point.array_name, test_point.x, test_point.y, )] if test_point.maximise: expected_value = expected_outputs[1][0] else: expected_value = expected_outputs[0][0] assert target_value == expected_value
def test_evaluate_test_pattern_outputs(): wavelet_index = WaveletFilters.haar_with_shift wavelet_index_ho = WaveletFilters.le_gall_5_3 dwt_depth = 1 dwt_depth_ho = 0 quantisation_matrix = { 0: { "LL": 0 }, 1: { "LH": 1, "HL": 2, "HH": 3 }, } picture_bit_width = 10 ( analysis_signal_bounds, synthesis_signal_bounds, analysis_test_patterns, synthesis_test_patterns, ) = static_filter_analysis( wavelet_index, wavelet_index_ho, dwt_depth, dwt_depth_ho, ) ( concrete_analysis_signal_bounds, concrete_synthesis_signal_bounds, ) = evaluate_filter_bounds( wavelet_index, wavelet_index_ho, dwt_depth, dwt_depth_ho, analysis_signal_bounds, synthesis_signal_bounds, picture_bit_width, ) max_quantisation_index = quantisation_index_bound( concrete_analysis_signal_bounds, quantisation_matrix, ) # Run a null-search to determine the actual decoded output of the synthesis # test patterns generated... (bodge) random_state = np.random.RandomState(1) number_of_searches = 1 terminate_early = None added_corruption_rate = 0 removed_corruption_rate = 0.0 base_iterations = 0 added_iterations_per_improvement = 0 optimised_synthesis_test_patterns = optimise_synthesis_test_patterns( wavelet_index, wavelet_index_ho, dwt_depth, dwt_depth_ho, quantisation_matrix, picture_bit_width, synthesis_test_patterns, max_quantisation_index, random_state, number_of_searches, terminate_early, added_corruption_rate, removed_corruption_rate, base_iterations, added_iterations_per_improvement, ) ( analysis_test_pattern_outputs, synthesis_test_pattern_outputs, ) = evaluate_test_pattern_outputs( wavelet_index, wavelet_index_ho, dwt_depth, dwt_depth_ho, picture_bit_width, quantisation_matrix, max_quantisation_index, analysis_test_patterns, optimised_synthesis_test_patterns, ) # Analysis values should be similar to the bounds for (level, array_name, x, y), (minimum, maximum) in analysis_test_pattern_outputs.items(): lower_bound, upper_bound = concrete_analysis_signal_bounds[(level, array_name, x, y)] assert np.isclose(minimum, lower_bound, 0.01) assert lower_bound <= minimum assert np.isclose(maximum, upper_bound, 0.01) assert maximum <= upper_bound # Synthesis values should be equal to the optimiser's values (where # present) for ( (level, array_name, x, y), ((minimum, min_qi), (maximum, max_qi)), ) in synthesis_test_pattern_outputs.items(): if (level, array_name, x, y) in optimised_synthesis_test_patterns: ts = optimised_synthesis_test_patterns[(level, array_name, x, y)] assert ts.decoded_value == maximum assert ts.quantisation_index == max_qi
def test_optimise_synthesis_test_patterns(): wavelet_index = WaveletFilters.haar_with_shift wavelet_index_ho = WaveletFilters.le_gall_5_3 dwt_depth = 1 dwt_depth_ho = 0 quantisation_matrix = { 0: { "LL": 0 }, 1: { "LH": 0, "HL": 0, "HH": 0 }, } picture_bit_width = 10 input_min = -512 input_max = 511 ( analysis_signal_bounds, synthesis_signal_bounds, analysis_test_patterns, synthesis_test_patterns, ) = static_filter_analysis( wavelet_index, wavelet_index_ho, dwt_depth, dwt_depth_ho, ) ( concrete_analysis_signal_bounds, concrete_synthesis_signal_bounds, ) = evaluate_filter_bounds( wavelet_index, wavelet_index_ho, dwt_depth, dwt_depth_ho, analysis_signal_bounds, synthesis_signal_bounds, picture_bit_width, ) max_quantisation_index = quantisation_index_bound( concrete_analysis_signal_bounds, quantisation_matrix, ) random_state = np.random.RandomState(1) number_of_searches = 2 terminate_early = None added_corruption_rate = 0.5 removed_corruption_rate = 0.0 base_iterations = 10 added_iterations_per_improvement = 1 optimised_synthesis_test_patterns = optimise_synthesis_test_patterns( wavelet_index, wavelet_index_ho, dwt_depth, dwt_depth_ho, quantisation_matrix, picture_bit_width, synthesis_test_patterns, max_quantisation_index, random_state, number_of_searches, terminate_early, added_corruption_rate, removed_corruption_rate, base_iterations, added_iterations_per_improvement, ) # As a sanity check, ensure that the test patterns provided do produce the # values they say they do at the quantisation indices claimed state = State( wavelet_index=wavelet_index, wavelet_index_ho=wavelet_index_ho, dwt_depth=dwt_depth, dwt_depth_ho=dwt_depth_ho, ) for level, array_name, x, y in optimised_synthesis_test_patterns: # Only check the final decoder output (as the pseudocode doesn't # provide access to other arrays) if level != dwt_depth + dwt_depth_ho or array_name != "Output": continue ts = optimised_synthesis_test_patterns[(level, array_name, x, y)] picture, _ = convert_test_pattern_to_padded_picture_and_slice( ts.pattern, input_min, input_max, dwt_depth, dwt_depth_ho, ) tx, ty = ts.target coeffs = dwt(state, picture.tolist()) qi = ts.quantisation_index quantised_coeffs = { level: { orient: [[ inverse_quant(forward_quant(value, qi), qi) for value in row ] for row in rows] for orient, rows in orients.items() } for level, orients in coeffs.items() } decoded_picture = idwt(state, quantised_coeffs) assert decoded_picture[ty][tx] == ts.decoded_value