def test_period_one_complex(self): # A A A B B B # a = A A A b = B B B # A A A B B B a = SymbolArray(2, "A") b = SymbolArray(2, "B") # A B A # ab = A B A # A B A ab = InterleavedArray(a, b, 0) # A*2 B*2 A*2 # ab2 = A*2 B*2 A*2 # A*2 B*2 A*2 ab2 = LeftShiftedArray(ab, 1) # B*2 B*2 B*2 # b2 = B*2 B*2 B*2 # B*2 B*2 B*2 b2 = SubsampledArray(ab2, (2, 1), (1, 0)) spca = SymbolicPeriodicCachingArray(b2, a, b) model_answers = {(x, y): b2[x, y] for x in range(3) for y in range(3)} ab2._cache.clear() # Should produce equivalent results to wrapped array for (x, y), exp_answer in model_answers.items(): assert spca[x, y] == exp_answer # Shouldn't have requested anything but the 0th phase of the wrapped # array assert list(ab2._cache) == [(1, 0)]
def test_interleave(self): a = SymbolArray(2, "a") b = SymbolArray(2, "b") # 'Horizontal' i = InterleavedArray(a, b, 0) assert i[-2, 0] == ("a", -1, 0) assert i[-1, 0] == ("b", -1, 0) assert i[0, 0] == ("a", 0, 0) assert i[1, 0] == ("b", 0, 0) assert i[2, 0] == ("a", 1, 0) assert i[3, 0] == ("b", 1, 0) assert i[0, 1] == ("a", 0, 1) assert i[1, 1] == ("b", 0, 1) assert i[2, 1] == ("a", 1, 1) assert i[3, 1] == ("b", 1, 1) # 'Vertical' i = InterleavedArray(a, b, 1) assert i[0, -2] == ("a", 0, -1) assert i[0, -1] == ("b", 0, -1) assert i[0, 0] == ("a", 0, 0) assert i[0, 1] == ("b", 0, 0) assert i[0, 2] == ("a", 0, 1) assert i[0, 3] == ("b", 0, 1) assert i[1, 0] == ("a", 1, 0) assert i[1, 1] == ("b", 1, 0) assert i[1, 2] == ("a", 1, 1) assert i[1, 3] == ("b", 1, 1)
def test_relative_step_size_to(self): a = SymbolArray(2) s = SubsampledArray(a, (2, 3), (0, 0)) l = LeftShiftedArray(s, 123) assert l.relative_step_size_to(a) == (2, 3) assert l.relative_step_size_to(l) == (1, 1) assert l.relative_step_size_to(SymbolArray(2, "nope")) is None
def test_relative_step_size_to(self): a = SymbolArray(2) s = SubsampledArray(a, (2, 3), (0, 0)) spca = SymbolicPeriodicCachingArray(s, a) assert spca.relative_step_size_to(a) == (2, 3) assert spca.relative_step_size_to(spca) == (1, 1) assert spca.relative_step_size_to(SymbolArray(2, "nope")) is None
def test_symbol_array(): a = SymbolArray(3, "foo") assert a.period == (1, 1, 1) assert a.nop is False assert a.relative_step_size_to(a) == (1, 1, 1) assert a.relative_step_size_to(SymbolArray(2, "bar")) is None assert a[0, 0, 0] == LinExp(("foo", 0, 0, 0)) assert a[1, 2, 3] == LinExp(("foo", 1, 2, 3))
def test_relative_step_size_to(self): a = SymbolArray(3) s1 = SubsampledArray(a, (1, 2, 3), (4, 5, 6)) assert s1.relative_step_size_to(a) == (1, 2, 3) assert s1.relative_step_size_to(s1) == (1, 1, 1) assert s1.relative_step_size_to(SymbolArray(3, "nope")) is None s2 = SubsampledArray(s1, (11, 22, 33), (4, 5, 6)) assert s2.relative_step_size_to(a) == (11 * 1, 22 * 2, 33 * 3) assert s2.relative_step_size_to(s1) == (11, 22, 33) assert s2.relative_step_size_to(s2) == (1, 1, 1)
def test_left_shifted_array(self): a = SymbolArray(3, "foo") sa = LeftShiftedArray(a, 3) v = a[1, 2, 3] assert sa[1, 2, 3] == v * 8
def test_correctness(self, stage): # This test checks that the filter implemented is equivalent to what # the VC-2 pseudocode would do a = SymbolArray(2, "a") l = LiftedArray(a, stage, 0) # Run the pseudocode against a random input rand = random.Random(0) input_array = [rand.randint(0, 10000) for _ in range(20)] pseudocode_output_array = input_array[:] lift = SYNTHESIS_LIFTING_FUNCTION_TYPES[stage.lift_type] lift(pseudocode_output_array, stage.L, stage.D, stage.taps, stage.S) # Check that the symbolic version gets the same answers (modulo # rounding errors). Check at output positions which are not affected by # rounding errors. for index in [10, 11]: pseudocode_output = pseudocode_output_array[index] # Substitute in the random inputs into symbolic answer output = l[index, 123].subs({("a", i, 123): value for i, value in enumerate(input_array)}) lower_bound = affine_lower_bound(output) upper_bound = affine_upper_bound(output) assert (lower_bound <= pseudocode_output <= upper_bound)
def test_evaluate_analysis_test_pattern_output(): # In this test we check that the decoded values are plausible based on them # being close to the predicted signal range wavelet_index = WaveletFilters.haar_with_shift wavelet_index_ho = WaveletFilters.le_gall_5_3 dwt_depth = 1 dwt_depth_ho = 0 picture_bit_width = 10 h_filter_params = LIFTING_FILTERS[wavelet_index_ho] v_filter_params = LIFTING_FILTERS[wavelet_index] input_min, input_max = signed_integer_range(picture_bit_width) input_array = SymbolArray(2) _, intermediate_arrays = analysis_transform( h_filter_params, v_filter_params, dwt_depth, dwt_depth_ho, input_array, ) for (level, array_name), target_array in intermediate_arrays.items(): for x in range(target_array.period[0]): for y in range(target_array.period[1]): # Compute the expected bounds for this value lower_bound, upper_bound = evaluate_analysis_filter_bounds( *analysis_filter_bounds(target_array[x, y]), num_bits=picture_bit_width) # Create a test pattern test_pattern = make_analysis_maximising_pattern( input_array, target_array, x, y, ) # Find the actual values lower_value, upper_value = evaluate_analysis_test_pattern_output( h_filter_params, v_filter_params, dwt_depth, dwt_depth_ho, level, array_name, test_pattern, input_min, input_max, ) assert np.isclose(lower_value, lower_bound, rtol=0.01) assert np.isclose(upper_value, upper_bound, rtol=0.01)
def test_period_n(self): # A A A B B B C C C D D D # a = A A A b = B B B c = C C C d = D D D # A A A B B B C C C D D D a = SymbolArray(2, "A") b = SymbolArray(2, "B") c = SymbolArray(2, "C") d = SymbolArray(2, "D") # A B A B # ab = A B A B # A B A B ab = InterleavedArray(a, b, 0) # A C B C A C # abc = A C B C A C # A C B C A C abc = InterleavedArray(ab, c, 0) # A B C A B C # abcd = D D D D D D # A B C A B C abcd = InterleavedArray(abc, d, 1) # A*2 B*2 C*2 A*2 B*2 C*2 # abcd2 = D*2 D*2 D*2 D*2 D*2 D*2 # A*2 B*2 C*2 A*2 B*2 C*2 abcd2 = LeftShiftedArray(abcd, 1) spca = SymbolicPeriodicCachingArray(abcd2, a, b, c, d) model_answers = {(x, y): abcd2[x, y] for x in range(10) for y in range(10)} abcd2._cache.clear() # Should produce equivalent results to wrapped array for (x, y), exp_answer in model_answers.items(): assert spca[x, y] == exp_answer # Shouldn't have requested anything but the 0th phase of the wrapped # array assert set(abcd2._cache) == set([(x, y) for x in range(4) for y in range(2)])
def test_shifting(self): a = SymbolArray(3, "foo") sa = RightShiftedArray(a, 3) v = a[1, 2, 3] sv = sa[1, 2, 3] assert affine_lower_bound(sv) == v / 8 - Fraction(1, 2) assert affine_upper_bound(sv) == v / 8 + Fraction(1, 2)
def test_subsampling(self): a = SymbolArray(3, "v") s = SubsampledArray(a, (1, 2, 3), (0, 10, 20)) assert s[0, 0, 0] == LinExp(("v", 0, 10, 20)) assert s[1, 0, 0] == LinExp(("v", 1, 10, 20)) assert s[0, 1, 0] == LinExp(("v", 0, 12, 20)) assert s[0, 0, 1] == LinExp(("v", 0, 10, 23)) assert s[2, 2, 2] == LinExp(("v", 2, 14, 26))
def test_relative_step_size_to(self): a1 = SymbolArray(2, "a1") a2 = SymbolArray(2, "a2") i1 = InterleavedArray(a1, a2, 0) assert i1.relative_step_size_to(a1) == (0.5, 1) assert i1.relative_step_size_to(a2) == (0.5, 1) assert i1.relative_step_size_to(i1) == (1, 1) # Other dimensions work a3 = SymbolArray(2, "a3") a4 = SymbolArray(2, "a4") i2 = InterleavedArray(a3, a4, 1) assert i2.relative_step_size_to(a3) == (1, 0.5) assert i2.relative_step_size_to(a4) == (1, 0.5) assert i2.relative_step_size_to(i2) == (1, 1) # Non-matching arrays work assert i2.relative_step_size_to(i1) is None # Deep nesting i3 = InterleavedArray(i1, i2, 0) assert i3.relative_step_size_to(a1) == (0.25, 1) assert i3.relative_step_size_to(a2) == (0.25, 1) assert i3.relative_step_size_to(a3) == (0.5, 0.5) assert i3.relative_step_size_to(a4) == (0.5, 0.5) assert i3.relative_step_size_to(i1) == (0.5, 1) assert i3.relative_step_size_to(i2) == (0.5, 1) assert i3.relative_step_size_to(i3) == (1, 1) # Check partial support when the same values appear on both sides of an # interleaving i4 = InterleavedArray(i1, a1, 1) assert i4.relative_step_size_to(SymbolArray(2, "nope")) is None assert i4.relative_step_size_to(i4) == (1, 1) assert i4.relative_step_size_to(i1) == (1, 0.5) assert i4.relative_step_size_to(a2) == (0.5, 0.5) with pytest.raises(ValueError): i4.relative_step_size_to(a1)
def test_analysis_intermediate_steps_as_expected(self, dwt_depth, dwt_depth_ho): filter_params = tables.LIFTING_FILTERS[ tables.WaveletFilters.haar_with_shift] input_picture = SymbolArray(2, "p") _, intermediate_values = analysis_transform( filter_params, filter_params, dwt_depth, dwt_depth_ho, input_picture, ) # 2D stages have all expected values for level in range(dwt_depth_ho + 1, dwt_depth + dwt_depth_ho + 1): names = set(n for l, n in intermediate_values if l == level) assert names == set([ "Input", "DC", "DC'", "DC''", "L", "L'", "L''", "H", "H'", "H''", "LL", "LH", "HL", "HH", ]) # HO stages have all expected values for level in range(1, dwt_depth_ho + 1): names = set(n for l, n in intermediate_values if l == level) assert names == set([ "Input", "DC", "DC'", "DC''", "L", "H", ])
def test_period_one_simple(self): # Check that given a simple period-one array, the appropriate values # should have been computed. a = SymbolArray(2) sa = LeftShiftedArray(a, 1) spca = SymbolicPeriodicCachingArray(sa, a) model_answers = {(x, y): sa[x, y] for x in range(3) for y in range(3)} sa._cache.clear() # Should produce equivalent results to wrapped array for (x, y), exp_answer in model_answers.items(): assert spca[x, y] == exp_answer # Shouldn't have requested anything but the 0th phase of the wrapped # array assert list(sa._cache) == [(0, 0)]
def test_add_missing_analysis_values( wavelet_index, wavelet_index_ho, dwt_depth, dwt_depth_ho, ): h_filter_params = tables.LIFTING_FILTERS[wavelet_index_ho] v_filter_params = tables.LIFTING_FILTERS[wavelet_index] _, intermediate_values = analysis_transform( h_filter_params, v_filter_params, dwt_depth, dwt_depth_ho, SymbolArray(2), ) all_expressions = { (level, array_name, x, y): array[x, y] for (level, array_name), array in intermediate_values.items() for x in range(array.period[0]) for y in range(array.period[1]) } non_nop_expressions = { (level, array_name, x, y): array[x, y] for (level, array_name), array in intermediate_values.items() for x in range(array.period[0]) for y in range(array.period[1]) if not array.nop } # Sanity check assert all_expressions != non_nop_expressions refilled_expressions = add_missing_analysis_values( h_filter_params, v_filter_params, dwt_depth, dwt_depth_ho, non_nop_expressions, ) assert set(refilled_expressions) == set(all_expressions) assert refilled_expressions == all_expressions
def test_filters_invert_eachother(self, wavelet_index, wavelet_index_ho, dwt_depth, dwt_depth_ho): # Test that the analysis and synthesis filters invert each-other as a # check of consistency (and, indirectly, the correctness of the # analysis implementation and convert_between_synthesis_and_analysis) h_filter_params = tables.LIFTING_FILTERS[wavelet_index_ho] v_filter_params = tables.LIFTING_FILTERS[wavelet_index] input_picture = SymbolArray(2, "p") transform_coeffs, _ = analysis_transform( h_filter_params, v_filter_params, dwt_depth, dwt_depth_ho, input_picture, ) output_picture, _ = synthesis_transform( h_filter_params, v_filter_params, dwt_depth, dwt_depth_ho, transform_coeffs, ) # In this example, no quantisation is applied between the two filters. # As a consequence the only error terms arise from rounding errors in # the analysis and synthesis filters. Since this implementation does # not account for divisions of the same numbers producing the same # rounding errors, these rounding errors do not cancel out here. # However, aside from these terms, the input and output of the filters # should be identical. rounding_errors = output_picture[0, 0] - input_picture[0, 0] assert all( isinstance(sym, AAError) for sym in rounding_errors.symbols())
def test_integration(self): h_filter_params = tables.LIFTING_FILTERS[ tables.WaveletFilters.le_gall_5_3] v_filter_params = tables.LIFTING_FILTERS[ tables.WaveletFilters.haar_with_shift] # Run against a real-world, more complex set of expressions symbol_array = SymbolArray(2) coeff_arrays, intermediate_arrays = analysis_transform( h_filter_params=h_filter_params, v_filter_params=v_filter_params, dwt_depth=1, dwt_depth_ho=2, array=symbol_array, ) for array in intermediate_arrays.values(): cached_array = SymbolicPeriodicCachingArray(array, symbol_array) for x in range(array.period[0] * 2): for y in range(array.period[1] * 2): assert (strip_affine_errors( cached_array[x, y]) == strip_affine_errors(array[x, y]))
def test_mismatched_array_dimensions(self): a = SymbolArray(2, "a") b = SymbolArray(3, "b") with pytest.raises(TypeError): InterleavedArray(a, b, 0)
def make_array(level, orient): return SymbolArray(2, (prefix, level, orient))
def test_evaluate_synthesis_test_pattern_output(): # In this test we simply check that the decoded values match those # computed by the optimise_synthesis_maximising_test_pattern function wavelet_index = WaveletFilters.haar_with_shift wavelet_index_ho = WaveletFilters.le_gall_5_3 dwt_depth = 1 dwt_depth_ho = 0 picture_bit_width = 10 max_quantisation_index = 64 quantisation_matrix = { 0: { "LL": 0 }, 1: { "LH": 1, "HL": 2, "HH": 3 }, } h_filter_params = LIFTING_FILTERS[wavelet_index_ho] v_filter_params = LIFTING_FILTERS[wavelet_index] input_min, input_max = signed_integer_range(picture_bit_width) input_array = SymbolArray(2) analysis_transform_coeff_arrays, _ = analysis_transform( h_filter_params, v_filter_params, dwt_depth, dwt_depth_ho, input_array, ) symbolic_coeff_arrays = make_symbol_coeff_arrays(dwt_depth, dwt_depth_ho) symbolic_output_array, symbolic_intermediate_arrays = synthesis_transform( h_filter_params, v_filter_params, dwt_depth, dwt_depth_ho, symbolic_coeff_arrays, ) pyexp_coeff_arrays = make_variable_coeff_arrays(dwt_depth, dwt_depth_ho) _, pyexp_intermediate_arrays = synthesis_transform( h_filter_params, v_filter_params, dwt_depth, dwt_depth_ho, pyexp_coeff_arrays, ) for (level, array_name), target_array in symbolic_intermediate_arrays.items(): for x in range(target_array.period[0]): for y in range(target_array.period[1]): # Create a test pattern test_pattern = make_synthesis_maximising_pattern( input_array, analysis_transform_coeff_arrays, target_array, symbolic_output_array, x, y, ) synthesis_pyexp = pyexp_intermediate_arrays[(level, array_name)][x, y] # Run with no-optimisation iterations but, as a side effect, # compute the actual decoded value to compare with test_pattern = optimise_synthesis_maximising_test_pattern( h_filter_params, v_filter_params, dwt_depth, dwt_depth_ho, quantisation_matrix, synthesis_pyexp, test_pattern, input_min, input_max, max_quantisation_index, None, 1, None, 0.0, 0.0, 0, 0, ) # Find the actual values lower_value, upper_value = evaluate_synthesis_test_pattern_output( h_filter_params, v_filter_params, dwt_depth, dwt_depth_ho, quantisation_matrix, synthesis_pyexp, test_pattern, input_min, input_max, max_quantisation_index, ) assert upper_value[0] == test_pattern.decoded_value assert upper_value[1] == test_pattern.quantisation_index
def test_filter_dimension_out_of_range(self, stage): a = SymbolArray(2, "a") with pytest.raises(TypeError): LiftedArray(a, stage, 2)
def add_missing_analysis_values( h_filter_params, v_filter_params, dwt_depth, dwt_depth_ho, analysis_values, ): """ Fill in results for omitted (duplicate) filter arrays and phases. Parameters ========== h_filter_params, v_filter_params : :py:class:`vc2_data_tables.LiftingFilterParameters` dwt_depth, dwt_depth_ho: int The filter parameters. analysis_values : {(level, array_name, x, y): value, ...} A dictionary of values associated with individual intermediate analysis filter phases with entries omitted where arrays are just interleavings/subsamplings/renamings. Returns ======= full_analysis_values : {(level, array_name, x, y): value, ...} A new dictionary of values with missing filters and phases filled in. """ # NB: Used only to enumerate the complete set of arrays and # get array periods _, intermediate_arrays = analysis_transform( h_filter_params, v_filter_params, dwt_depth, dwt_depth_ho, SymbolArray(2), ) out = OrderedDict() for (level, array_name), array in intermediate_arrays.items(): for x in range(array.period[0]): for y in range(array.period[1]): # Below we work out which source array to use to populate the # current array/phase src_level = level src_array_name = array_name src_x = x src_y = y if (level, array_name, x, y) not in analysis_values: if array_name == "Input": src_level = level + 1 src_array_name = ("LL" if (src_level, "LL") in intermediate_arrays else "L") elif array_name == "DC": src_array_name = "Input" elif array_name in ("L", "H"): src_array_name = [ int_array_name for int_level, int_array_name in intermediate_arrays if int_level == src_level and int_array_name.startswith("DC'") ][-1] if array_name == "L": src_x = x * 2 elif array_name == "H": src_x = (x * 2) + 1 elif array_name in ("LL", "LH"): src_array_name = [ int_array_name for int_level, int_array_name in intermediate_arrays if int_level == src_level and int_array_name.startswith("L'") ][-1] if array_name == "LL": src_y = y * 2 elif array_name == "LH": src_y = (y * 2) + 1 elif array_name in ("HL", "HH"): src_array_name = [ int_array_name for int_level, int_array_name in intermediate_arrays if int_level == src_level and int_array_name.startswith("H'") ][-1] if array_name == "HL": src_y = y * 2 elif array_name == "HH": src_y = (y * 2) + 1 else: # Should never reach this point so long as only # nops are omitted assert False out[(level, array_name, x, y)] = analysis_values.get(( src_level, src_array_name, src_x, src_y, ), out.get(( src_level, src_array_name, src_x, src_y, ))) return out
def test_nop(self, stage): a = SymbolArray(2, "a") l = LiftedArray(a, stage, 0) assert l.nop is False
def test_nop(self): a = SymbolArray(1) assert SymbolicPeriodicCachingArray(a, a).nop is False sa = LeftShiftedArray(a, 0) assert SymbolicPeriodicCachingArray(sa, a).nop is True
def test_nop(self): s = SymbolArray(1) assert LeftShiftedArray(s, 3).nop is False assert LeftShiftedArray(s, 0).nop is True
def test_bad_arguments(self, steps, offsets): a = SymbolArray(3, "v") with pytest.raises(TypeError): SubsampledArray(a, steps, offsets)
def test_nop(self): a = SymbolArray(3, "v") s = SubsampledArray(a, (1, 2, 3), (0, 10, 20)) assert s.nop is True
def test_nop(self): a = SymbolArray(2, "a") b = SymbolArray(2, "b") i = InterleavedArray(a, b, 1) assert i.nop is True
def test_interleave_dimension_out_of_range(self): a = SymbolArray(2, "a") b = SymbolArray(2, "b") with pytest.raises(TypeError): InterleavedArray(a, b, 2)