def test_plausible_maximisation(self, input_array, transform_coeffs, wavelet_index, wavelet_index_ho, dwt_depth, dwt_depth_ho): # Check that the test pattern does in fact appear to maximise the filter # output value within the limits of the affine arithmetic bounds value_min = -512 value_max = 255 signal_range = {"signal_min": value_min, "signal_max": value_max} for level, orients in transform_coeffs.items(): for orient, target_array in orients.items(): for tx in range(target_array.period[0]): for ty in range(target_array.period[1]): # Produce a test pattern ts = make_analysis_maximising_pattern( input_array, target_array, tx, ty, ) # Find the expected bounds for values in the targeted # transform coefficient new_tx, new_ty = ts.target target_filter = target_array[new_tx, new_ty] lower_bound, upper_bound = analysis_filter_bounds( target_filter) lower_bound = lower_bound.subs(signal_range).constant upper_bound = upper_bound.subs(signal_range).constant # Create a test picture and encode it with the VC-2 # pseudocode test_pattern_picture, _ = ts.pattern.as_picture_and_slice( value_min, value_max) height, width = test_pattern_picture.shape test_pattern_picture = test_pattern_picture.tolist() value_maximised = encode_with_vc2( test_pattern_picture, width, height, wavelet_index, wavelet_index_ho, dwt_depth, dwt_depth_ho, )[level][orient][new_ty][new_tx] # Check we get within 1% of the upper bound assert upper_bound * 0.99 <= value_maximised <= upper_bound
def test_plausible_maximisation_no_quantisation( self, analysis_input_array, analysis_transform_coeff_arrays, synthesis_output_array, synthesis_intermediate_arrays, wavelet_index, wavelet_index_ho, dwt_depth, dwt_depth_ho): # Check that the test patterns do, in fact, appear to maximise the # filter output for quantisation-free filtering value_min = -512 value_max = 255 for tx in range(synthesis_output_array.period[0]): for ty in range(synthesis_output_array.period[1]): # Produce a test pattern ts = make_synthesis_maximising_pattern( analysis_input_array, analysis_transform_coeff_arrays, synthesis_output_array, synthesis_output_array, tx, ty, ) # Create a test picture and encode/decode it with the VC-2 # pseudocode (with no quantisaton) test_pattern_picture, _ = ts.pattern.as_picture_and_slice( value_min, value_max) height, width = test_pattern_picture.shape test_pattern_picture = test_pattern_picture.tolist() new_tx, new_ty = ts.target kwargs = { "width": width, "height": height, "wavelet_index": wavelet_index, "wavelet_index_ho": wavelet_index_ho, "dwt_depth": dwt_depth, "dwt_depth_ho": dwt_depth_ho, } value_maximised = decode_with_vc2( encode_with_vc2(test_pattern_picture, **kwargs), **kwargs)[new_ty][new_tx] # Check the value was in fact maximised assert value_maximised == value_max
def test_correctness(tmpdir): # This integration test runs the generated test pictures through a VC-2 # encoder/quantiser/decoder and verifies that the (observable) signal # levels match those predicted by the bit-widths table tool (and in the # locations indicated by the provided metadata). static_analysis_filename = str(tmpdir.join("static_analysis.json")) bit_widths_table_filename = str(tmpdir.join("bit_widths_table.csv")) test_pictures_dir = str(tmpdir.join("test_pictures")) os.mkdir(test_pictures_dir) wavelet_index = WaveletFilters.le_gall_5_3 wavelet_index_ho = WaveletFilters.le_gall_5_3 dwt_depth = 1 dwt_depth_ho = 0 quantisation_matrix = QUANTISATION_MATRICES[( wavelet_index, wavelet_index_ho, dwt_depth, dwt_depth_ho, )] picture_bit_width = 8 # Assumed to be 8 in rest of test... vc2_static_filter_analysis([ "--wavelet-index", wavelet_index.name, "--wavelet-index-ho", wavelet_index_ho.name, "--dwt-depth", str(dwt_depth), "--dwt-depth-ho", str(dwt_depth_ho), "--output", static_analysis_filename, ]) vc2_bit_widths_table([ static_analysis_filename, "--picture-bit-width", str(picture_bit_width), "--show-all-filter-phases", "--output", bit_widths_table_filename, ]) # Read bit widths table (to get model answers) # {(type, level, array_name, x, y, maximise): value, ...} expected_signal_values = {} for row in csv.DictReader(open(bit_widths_table_filename)): typename = row.pop("type") level = int(row.pop("level")) array_name = row.pop("array_name") x = int(row.pop("x")) y = int(row.pop("y")) minimise = int(row.pop("test_pattern_min")) maximise = int(row.pop("test_pattern_max")) expected_signal_values[( typename, level, array_name, x, y, False, )] = minimise expected_signal_values[( typename, level, array_name, x, y, True, )] = maximise vc2_bit_width_test_pictures([ static_analysis_filename, "64", "32", # Chosen to be multiple of required power of 2 "--picture-bit-width", str(picture_bit_width), "--output-directory", test_pictures_dir, ]) test_picture_filenames = os.listdir(test_pictures_dir) # Encode the analysis test pictures # {filename: encoded_data, ...} encoded_analysis_pictures = {} for filename in test_picture_filenames: if filename.startswith("analysis_") and filename.endswith(".png"): picture = np.asarray( PIL.Image.open(os.path.join(test_pictures_dir, filename))) picture = picture.astype(int) - 128 encoded_analysis_pictures[filename] = encode_with_vc2( picture.tolist(), picture.shape[1], picture.shape[0], wavelet_index, wavelet_index_ho, dwt_depth, dwt_depth_ho, ) # Encode/quantise/decode the synthesis test pictures # {filename: encoded_data, ...} decoded_synthesis_pictures = {} for filename in test_picture_filenames: if filename.startswith("synthesis_") and filename.endswith(".png"): picture = np.asarray( PIL.Image.open(os.path.join(test_pictures_dir, filename))) picture = picture.astype(int) - 128 qi = int(filename.partition("qi")[2].partition(".")[0]) transform_coeffs = encode_with_vc2( picture.tolist(), picture.shape[1], picture.shape[0], wavelet_index, wavelet_index_ho, dwt_depth, dwt_depth_ho, ) quantised_coeffs = quantise_coeffs(transform_coeffs, qi, quantisation_matrix) decoded_synthesis_pictures[filename] = decode_with_vc2( quantised_coeffs, picture.shape[1], picture.shape[0], wavelet_index, wavelet_index_ho, dwt_depth, dwt_depth_ho, ) # Extract the target values for the outputs of the encoded/decoded pictures # {(type, level, array_name, x, y, maximise): value, ...} actual_signal_values = {} for filename in test_picture_filenames: if filename.startswith("analysis_") and filename.endswith(".png"): json_filename = filename.replace(".png", ".json") test_points = json.load( open(os.path.join(test_pictures_dir, json_filename))) for test_point in test_points: # Convert L'' and H'' coordinates into LL, LH, HL, HH # coordinates (omitted since they're just an interleaving). # This assumes we've chosen a 2D transform with 2 lifting # stages in this test... if test_point["array_name"] not in ("L''", "H''"): continue level = test_point["level"] tx = test_point["tx"] ty = test_point["ty"] // 2 if test_point["y"] % 2 == 0: subband = "LL" if test_point[ "array_name"] == "L''" else "HL" else: subband = "LH" if test_point[ "array_name"] == "L''" else "HH" if subband == "LL": level -= 1 if subband == "LL" and level != 0: continue value = encoded_analysis_pictures[filename][level][subband][ ty][tx] actual_signal_values[( "analysis", test_point["level"], test_point["array_name"], test_point["x"], test_point["y"], test_point["maximise"], )] = value for filename in test_picture_filenames: if filename.startswith("synthesis_") and filename.endswith(".png"): json_filename = filename.replace(".png", ".json") test_points = json.load( open(os.path.join(test_pictures_dir, json_filename))) for test_point in test_points: # Only consider the output picture if test_point["array_name"] != "Output" or test_point[ "level"] != dwt_depth + dwt_depth_ho: continue tx = test_point["tx"] ty = test_point["ty"] value = decoded_synthesis_pictures[filename][ty][tx] actual_signal_values[( "synthesis", test_point["level"], test_point["array_name"], test_point["x"], test_point["y"], test_point["maximise"], )] = value # Check the actual signal levels observed match the expectations for key, value in actual_signal_values.items(): assert value == expected_signal_values[key]
def test_plausible_maximisation_with_quantisation( self, analysis_input_array, analysis_transform_coeff_arrays, synthesis_output_array, synthesis_intermediate_arrays, wavelet_index, wavelet_index_ho, dwt_depth, dwt_depth_ho): # Check that the test patterns do, in fact, appear to make larger values # post-quantisation than would appear with a more straight-forward # scheme value_min = -512 value_max = 511 num_periods_made_larger_by_test_pattern = 0 for tx in range(synthesis_output_array.period[0]): for ty in range(synthesis_output_array.period[1]): # Produce a test pattern ts = make_synthesis_maximising_pattern( analysis_input_array, analysis_transform_coeff_arrays, synthesis_output_array, synthesis_output_array, tx, ty, ) test_pattern_picture, _ = ts.pattern.as_picture_and_slice( value_min, value_max) height, width = test_pattern_picture.shape test_pattern_picture = test_pattern_picture.tolist() # Create picture where just the target pixel is set new_tx, new_ty = ts.target just_target_picture = [[ int((x, y) == (new_tx, new_ty)) * value_max for x in range(width) ] for y in range(height)] kwargs = { "width": width, "height": height, "wavelet_index": wavelet_index, "wavelet_index_ho": wavelet_index_ho, "dwt_depth": dwt_depth, "dwt_depth_ho": dwt_depth_ho, } # Encode with VC-2 pseudocode test_pattern_coeffs = encode_with_vc2(test_pattern_picture, **kwargs) just_target_coeffs = encode_with_vc2(just_target_picture, **kwargs) # Try different quantisation levels and record the worst-case # effect on the target pixel test_pattern_value_worst_case = 0 just_target_value_worst_case = 0 for qi in range(64): test_pattern_value = decode_with_vc2( quantise_coeffs(test_pattern_coeffs, qi), **kwargs)[new_ty][new_tx] if abs(test_pattern_value) > abs( test_pattern_value_worst_case): test_pattern_value_worst_case = test_pattern_value just_target_value = decode_with_vc2( quantise_coeffs(just_target_coeffs, qi), **kwargs)[new_ty][new_tx] if abs(just_target_value) > abs( just_target_value_worst_case): just_target_value_worst_case = just_target_value # Check the value was in fact made bigger in the worst case by # the new test pattern if abs(test_pattern_value_worst_case) > abs( just_target_value_worst_case): num_periods_made_larger_by_test_pattern += 1 # Check that, in the common case, make the worst-case values worse with # the test pattern than a single hot pixel would. num_periods = synthesis_output_array.period[ 0] * synthesis_output_array.period[1] assert num_periods_made_larger_by_test_pattern > (num_periods / 2.0)
def test_correctness( self, wavelet_index, filter_params, dwt_depth, dwt_depth_ho, quantisation_matrix, analysis_input_linexp_array, analysis_coeff_linexp_arrays, synthesis_output_linexp_array, synthesis_output_pyexp_array, ): # This test will simply attempt to maximise a real test pattern and verify # that the procedure appears to produce an improved result. max_quantisation_index = 63 input_min = -512 input_max = 511 # Make an arbitrary choice of target to maximise synthesis_target_linexp_array = synthesis_output_linexp_array synthesis_target_pyexp_array = synthesis_output_pyexp_array # Run against all filter phases as some phases may happen to be maximised # by the test pattern anyway num_improved_phases = 0 for tx in range(synthesis_target_linexp_array.period[0]): for ty in range(synthesis_target_linexp_array.period[1]): # Produce test pattern ts = make_synthesis_maximising_pattern( analysis_input_linexp_array, analysis_coeff_linexp_arrays, synthesis_target_linexp_array, synthesis_output_linexp_array, tx, ty, ) new_tx, new_ty = ts.target synthesis_pyexp = synthesis_target_pyexp_array[new_tx, new_ty] kwargs = { "h_filter_params": filter_params, "v_filter_params": filter_params, "dwt_depth": dwt_depth, "dwt_depth_ho": dwt_depth_ho, "quantisation_matrix": quantisation_matrix, "synthesis_pyexp": synthesis_pyexp, "test_pattern_specification": ts, "input_min": input_min, "input_max": input_max, "max_quantisation_index": max_quantisation_index, "random_state": np.random.RandomState(1), "added_corruptions_per_iteration": (len(ts.pattern) + 19) // 20, # 5% "removed_corruptions_per_iteration": (len(ts.pattern) + 8) // 5, # 20% "added_iterations_per_improvement": 50, "terminate_early": None, } # Run without any greedy searches to get 'baseline' figure base_ts = optimise_synthesis_maximising_test_pattern( number_of_searches=1, base_iterations=0, **kwargs) # Verify unrelated test pattern parameters passed through assert base_ts.target == ts.target assert base_ts.pattern_translation_multiple == ts.pattern_translation_multiple assert base_ts.target_translation_multiple == ts.target_translation_multiple # Run with greedy search to verify better result imp_ts = optimise_synthesis_maximising_test_pattern( number_of_searches=3, base_iterations=100, **kwargs) assert imp_ts.num_search_iterations >= 3 * 100 # Ensure new test pattern is normalised to polarities assert np.all(np.isin(imp_ts.pattern.polarities, (-1, +1))) # Should have improved over the test pattern alone if abs(imp_ts.decoded_value) > abs(base_ts.decoded_value): num_improved_phases += 1 # Check to see if decoded value matches what the pseudocode decoder # would produce imp_test_pattern_picture, _ = imp_ts.pattern.as_picture_and_slice( input_min, input_max) height, width = imp_test_pattern_picture.shape imp_test_pattern_picture = imp_test_pattern_picture.tolist() kwargs = { "width": width, "height": height, "wavelet_index": wavelet_index, "wavelet_index_ho": wavelet_index, "dwt_depth": dwt_depth, "dwt_depth_ho": dwt_depth_ho, } actual_decoded_value = decode_with_vc2( quantise_coeffs( encode_with_vc2(imp_test_pattern_picture, **kwargs), imp_ts.quantisation_index, quantisation_matrix, ), **kwargs)[new_ty][new_tx] assert imp_ts.decoded_value == actual_decoded_value # Consider the procedure to work if some of the phases are improved assert num_improved_phases >= 1