示例#1
0
def test_make_variable_coeff_arrays():
    coeff_arrays = make_variable_coeff_arrays(2, 0, Argument("foobar"))

    assert isinstance(coeff_arrays[1]["HL"][2, 3], PyExp)
    assert coeff_arrays[1]["HL"][2, 3] == Argument("foobar")[1]["HL"][2, 3]

    # Check default argument works too since it is a somewhat exciting type...
    assert make_variable_coeff_arrays(
        2, 0)[1]["HL"][2, 3] == Argument("coeffs")[1]["HL"][2, 3]
 def synthesis_transform_output(
     self,
     filter_params,
     dwt_depth,
     dwt_depth_ho,
 ):
     synthesis_input_pyexp_arrays = make_variable_coeff_arrays(
         dwt_depth, dwt_depth_ho)
     return synthesis_transform(
         filter_params,
         filter_params,
         dwt_depth,
         dwt_depth_ho,
         synthesis_input_pyexp_arrays,
     )
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
示例#4
0
def 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,
):
    """
    Given a set of test patterns, compute the signal levels actually produced
    by them when passed through a real encoder/decoder.
    
    Parameters
    ==========
    wavelet_index : :py:class:`vc2_data_tables.WaveletFilters` or int
    wavelet_index_ho : :py:class:`vc2_data_tables.WaveletFilters` or int
    dwt_depth : int
    dwt_depth_ho : int
        The filter parameters.
    picture_bit_width : int
        The number of bits in the input pictures.
    quantisation_matrix : {level: {orient: value, ...}, ...}
        The quantisation matrix.
    max_quantisation_index : int
        The maximum quantisation index to try (e.g. as computed by
        :py:func:`quantisation_index_bound`). Each synthesis test pattern will
        be quantised with every quantisation index up to (and inclusing) this
        limit and the worst-case value for any quantisation index will be
        reported.
    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`, ...}
        The test patterns to assess, e.g. from
        :py:func:`static_filter_analysis` or
        :py:func:`optimise_synthesis_test_patterns`.
    
    Returns
    =======
    analysis_test_pattern_outputs : {(level, array_name, x, y): (lower_bound, upper_bound), ...}
    synthesis_test_pattern_outputs : {(level, array_name, x, y): ((lower_bound, qi), (upper_bound, qi)), ...}
        The worst-case signal levels achieved for each of the provided test
        signals when using minimising and maximising versions of the test
        pattern respectively.
        
        For the syntehsis test patterns, the quantisation index used to achieve
        the worst-case values is also reported.
        
        Includes values for *all* arrays and phases, even if array
        interleavings/subsamplings/renamings are omitted in the input
        arguments.
    """
    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)

    analysis_test_pattern_outputs = OrderedDict()
    for i, ((level, array_name, x, y),
            test_pattern) in enumerate(analysis_test_patterns.items()):
        logger.info(
            "Evaluating analysis test pattern %d of %d (Level %d, %s[%d, %d])...",
            i + 1,
            len(analysis_test_patterns),
            level,
            array_name,
            x,
            y,
        )
        analysis_test_pattern_outputs[(
            level, array_name, x, y)] = evaluate_analysis_test_pattern_output(
                h_filter_params=h_filter_params,
                v_filter_params=v_filter_params,
                dwt_depth=dwt_depth,
                dwt_depth_ho=dwt_depth_ho,
                level=level,
                array_name=array_name,
                test_pattern_specification=test_pattern,
                input_min=input_min,
                input_max=input_max,
            )

    _, synthesis_pyexps = synthesis_transform(
        h_filter_params,
        v_filter_params,
        dwt_depth,
        dwt_depth_ho,
        make_variable_coeff_arrays(dwt_depth, dwt_depth_ho),
    )

    # Re-add results for interleaved/renamed entries
    analysis_test_pattern_outputs = add_missing_analysis_values(
        h_filter_params,
        v_filter_params,
        dwt_depth,
        dwt_depth_ho,
        analysis_test_pattern_outputs,
    )

    synthesis_test_pattern_outputs = OrderedDict()
    for i, ((level, array_name, x, y),
            test_pattern) in enumerate(synthesis_test_patterns.items()):
        logger.info(
            "Evaluating synthesis test pattern %d of %d (Level %d, %s[%d, %d])...",
            i + 1,
            len(synthesis_test_patterns),
            level,
            array_name,
            x,
            y,
        )
        synthesis_test_pattern_outputs[(
            level, array_name, x, y)] = evaluate_synthesis_test_pattern_output(
                h_filter_params=h_filter_params,
                v_filter_params=v_filter_params,
                dwt_depth=dwt_depth,
                dwt_depth_ho=dwt_depth_ho,
                quantisation_matrix=quantisation_matrix,
                synthesis_pyexp=synthesis_pyexps[(
                    level, array_name)][test_pattern.target],
                test_pattern_specification=test_pattern,
                input_min=input_min,
                input_max=input_max,
                max_quantisation_index=max_quantisation_index,
            )

    # Re-add results for interleaved/renamed entries
    synthesis_test_pattern_outputs = add_missing_synthesis_values(
        h_filter_params,
        v_filter_params,
        dwt_depth,
        dwt_depth_ho,
        synthesis_test_pattern_outputs,
    )

    return (
        analysis_test_pattern_outputs,
        synthesis_test_pattern_outputs,
    )
示例#5
0
def 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,
):
    """
    Perform a greedy search based optimisation of a complete set of synthesis
    test patterns.
    
    See :ref:`optimisation` for details of the optimisation process and
    parameters.
    
    Parameters
    ==========
    wavelet_index : :py:class:`vc2_data_tables.WaveletFilters` or int
    wavelet_index_ho : :py:class:`vc2_data_tables.WaveletFilters` or int
    dwt_depth : int
    dwt_depth_ho : int
        The filter parameters.
    quantisation_matrix : {level: {orient: value, ...}, ...}
        The quantisation matrix in use.
    picture_bit_width : int
        The number of bits in the input pictures.
    synthesis_test_patterns : {(level, array_name, x, y): :py:class:`~vc2_bit_widths.patterns.TestPatternSpecification`, ...}
        Synthesis test patterns to use as the starting point for optimisation,
        as produced by e.g.  :py:func:`static_filter_analysis`.
    max_quantisation_index : int
        The maximum quantisation index to use, e.g. computed using
        :py:func:`quantisation_index_bound`.
    random_state : :py:class:`numpy.random.RandomState`
        The random number generator to use for the search.
    number_of_searches : int
        Repeat the greedy stochastic search process this many times for each
        test pattern. Since searches will tend to converge on local minima,
        increasing this parameter will tend to produce improved results.
    terminate_early : None or int
        If an integer, stop searching if the first ``terminate_early`` searches
        fail to find an improvement. If None, always performs all searches.
    added_corruption_rate : float
        The proportion of pixels to assign with a random value during each
        search attempt (0.0-1.0).
    removed_corruption_rate : float
        The proportion of pixels to reset to their starting value during each
        search attempt (0.0-1.0).
    base_iterations : int
        The initial number of search iterations to perform in each attempt.
    added_iterations_per_improvement : int
        The number of additional search iterations to perform whenever an
        improved picture is found.
    
    Returns
    =======
    optimised_test_patterns : {(level, array_name, x, y): :py:class:`~vc2_bit_widths.patterns.OptimisedTestPatternSpecification`, ...}
        The optimised test patterns.
        
        Note that arrays are omitted for arrays which are just interleavings of
        other arrays.
    """
    # Create PyExps for all synthesis filtering stages, used to decode test
    # encoded patterns
    h_filter_params = LIFTING_FILTERS[wavelet_index_ho]
    v_filter_params = LIFTING_FILTERS[wavelet_index]
    _, synthesis_pyexps = synthesis_transform(
        h_filter_params,
        v_filter_params,
        dwt_depth,
        dwt_depth_ho,
        make_variable_coeff_arrays(dwt_depth, dwt_depth_ho),
    )

    # Strip out all arrays which are simply interleavings of others (and
    # therefore don't need optimising several times)
    test_patterns_to_optimise = [
        (level, array_name, x, y, tp)
        for (level, array_name, x, y), tp in synthesis_test_patterns.items()
        if not synthesis_pyexps[(level, array_name)].nop
    ]

    input_min, input_max = signed_integer_range(picture_bit_width)

    optimised_test_patterns = OrderedDict()

    for signal_no, (level, array_name, x, y,
                    tp) in enumerate(test_patterns_to_optimise):
        synthesis_pyexp = synthesis_pyexps[(level, array_name)][tp.target]

        added_corruptions_per_iteration = int(
            np.ceil(len(tp.pattern) * added_corruption_rate))
        removed_corruptions_per_iteration = int(
            np.ceil(len(tp.pattern) * removed_corruption_rate))

        logger.info(
            "Optimising test pattern %d of %d (level %d, %s[%d, %d])",
            signal_no + 1,
            len(test_patterns_to_optimise),
            level,
            array_name,
            x,
            y,
        )

        best_ts = None

        for flip_polarity, log_message in [
            (False, "Maximising..."),
            (True, "Minimising..."),
        ]:
            logger.info(log_message)

            # Run the search starting from the maximising and minimising signal
            if flip_polarity:
                flipped_tp = invert_test_pattern_specification(tp)
            else:
                flipped_tp = tp

            new_ts = optimise_synthesis_maximising_test_pattern(
                h_filter_params=h_filter_params,
                v_filter_params=v_filter_params,
                dwt_depth=dwt_depth,
                dwt_depth_ho=dwt_depth_ho,
                quantisation_matrix=quantisation_matrix,
                synthesis_pyexp=synthesis_pyexp,
                test_pattern_specification=flipped_tp,
                input_min=input_min,
                input_max=input_max,
                max_quantisation_index=max_quantisation_index,
                random_state=random_state,
                number_of_searches=number_of_searches,
                terminate_early=terminate_early,
                added_corruptions_per_iteration=added_corruptions_per_iteration,
                removed_corruptions_per_iteration=
                removed_corruptions_per_iteration,
                base_iterations=base_iterations,
                added_iterations_per_improvement=
                added_iterations_per_improvement,
            )

            # NB: when given a -ve and +ve value with equal magnitude, the +ve one
            # should be kept because this may require an additional bit to
            # represent in two's compliment arithmetic (e.g. -512 is 10-bits, +512
            # is 11-bits)
            if (best_ts is None
                    or abs(new_ts.decoded_value) > abs(best_ts.decoded_value)
                    or (abs(new_ts.decoded_value) == abs(best_ts.decoded_value)
                        and new_ts.decoded_value > best_ts.decoded_value)):
                best_ts = new_ts

        logger.info(
            "Largest signal magnitude achieved = %d (qi=%d)",
            best_ts.decoded_value,
            best_ts.quantisation_index,
        )

        optimised_test_patterns[(level, array_name, x, y)] = best_ts

    return optimised_test_patterns
def test_fast_partial_analyse_quantise_synthesise(wavelet_index, wavelet_index_ho):
    # For this test we compare the output of the pseudocode encoder with the
    # partial decoder and check that they agree. To simplify this test, the
    # Haar transform is used which is edge effect free and therefore any/every
    # pixel decoded by this implementation should exactly match to pseudocode.
    h_filter_params = LIFTING_FILTERS[wavelet_index_ho]
    v_filter_params = LIFTING_FILTERS[wavelet_index]
    dwt_depth = 1
    dwt_depth_ho = 2
    
    # We're using the wrong matrix here (since the default matrices don't
    # include this type of asymmetric filter) but this is unimportant since any
    # matrix will do...
    quantisation_matrix = QUANTISATION_MATRICES[(
        wavelet_index,
        wavelet_index,
        dwt_depth,
        dwt_depth_ho,
    )]
    
    # This should be enough to get to the point where all transform
    # coefficients are zero in this test
    quantisation_indices = list(range(64))
    
    # Create a test image
    width = 16
    height = 4
    rand = np.random.RandomState(1)
    picture = rand.randint(-512, 511, (height, width))
    
    # Encode the picture using the pseudocode
    state = State(
        wavelet_index=wavelet_index,
        wavelet_index_ho=wavelet_index_ho,
        dwt_depth=dwt_depth,
        dwt_depth_ho=dwt_depth_ho,
    )
    pseudocode_coeffs = dwt(state, picture.tolist())
    
    # Quantise/Decode using the pseudocode at each quantisation level in turn
    # to generate 'model answers' for every pixel
    pseudocode_decoded_pictures = []
    for qi in quantisation_indices:
        pseudocode_decoded_pictures.append(idwt(state, {
            level: {
                orient: [
                    [
                        quant_roundtrip(
                            value,
                            max(0, qi - quantisation_matrix[level][orient]),
                        )
                        for value in row
                    ]
                    for row in array
                ]
                for orient, array in orients.items()
            }
            for level, orients in pseudocode_coeffs.items()
        }))
    
    # Create decoder function expressions
    synthesis_expressions, _ = synthesis_transform(
        h_filter_params,
        v_filter_params,
        dwt_depth,
        dwt_depth_ho,
        make_variable_coeff_arrays(dwt_depth, dwt_depth_ho)
    )
    
    # Check decoding of every pixel individually matches the pseudocode at
    # every quantisation level.
    for y in range(height):
        for x in range(width):
            codec = FastPartialAnalyseQuantiseSynthesise(
                h_filter_params,
                v_filter_params,
                dwt_depth,
                dwt_depth_ho,
                quantisation_matrix,
                quantisation_indices,
                synthesis_expressions[x, y],
            )
            decoded_values = codec.analyse_quantise_synthesise(picture.copy())
            for reference_decoded_picture, decoded_value in zip(
                pseudocode_decoded_pictures,
                decoded_values,
            ):
                assert decoded_value == reference_decoded_picture[y][x]
示例#7
0
    def test_filters_match_pseudocode(self, wavelet_index, wavelet_index_ho):
        # This test checks that the filters implement the same behaviour as the
        # VC-2 pseudocode, including compatible operation ordering. This test is
        # carried out on a relatively small Haar transform because::
        #
        # * The Haar transform is free from edge effects making the
        #   InfiniteArray implementation straight-forwardly equivalent to the
        #   pseudocode behaviour in all cases (not just non-edge cases)
        # * The Haar transform is available in a form with and without the bit
        #   shift so we can check that the bit shift parameter is used
        #   correctly and taken from the correct wavelet index.
        # * Using large transform depths or filters produces very large
        #   functions for analysis transforms under PyExp which can crash
        #   Python interpreters. (In practice they'll only ever be generated
        #   for synthesis transforms which produce small code even for large
        #   transforms)

        width = 16
        height = 8

        dwt_depth = 1
        dwt_depth_ho = 2

        # Create a random picture to analyse
        rand = np.random.RandomState(1)
        random_input_picture = rand.randint(-512, 511, (height, width))

        # Analyse using pseudocode
        state = State(
            wavelet_index=wavelet_index,
            wavelet_index_ho=wavelet_index_ho,
            dwt_depth=dwt_depth,
            dwt_depth_ho=dwt_depth_ho,
        )
        pseudocode_coeffs = dwt(state, random_input_picture.tolist())

        # Analyse using InfiniteArrays
        h_filter_params = tables.LIFTING_FILTERS[wavelet_index_ho]
        v_filter_params = tables.LIFTING_FILTERS[wavelet_index]
        ia_coeffs, _ = analysis_transform(
            h_filter_params,
            v_filter_params,
            dwt_depth,
            dwt_depth_ho,
            VariableArray(2, Argument("picture")),
        )

        # Compare analysis results
        for level in pseudocode_coeffs:
            for orient in pseudocode_coeffs[level]:
                pseudocode_data = pseudocode_coeffs[level][orient]
                for row, row_data in enumerate(pseudocode_data):
                    for col, pseudocode_value in enumerate(row_data):
                        # Create and call a function to compute this value via
                        # InfiniteArrays/PyExp
                        expr = ia_coeffs[level][orient][col, row]
                        f = expr.make_function()
                        # NB: Array is transposed to support (x, y) indexing
                        ia_value = f(random_input_picture.T)

                        assert ia_value == pseudocode_value

        # Synthesise using pseudocode
        pseudocode_picture = idwt(state, pseudocode_coeffs)

        # Synthesise using InfiniteArrays
        ia_picture, _ = synthesis_transform(
            h_filter_params,
            v_filter_params,
            dwt_depth,
            dwt_depth_ho,
            make_variable_coeff_arrays(dwt_depth, dwt_depth_ho),
        )

        # Create numpy-array based coeff data for use by
        # InfiniteArray-generated functions (NB: arrays are transposed to
        # support (x, y) indexing.
        ia_coeffs_data = {
            level: {
                orient: np.array(array, dtype=np.int64).T
                for orient, array in orients.items()
            }
            for level, orients in pseudocode_coeffs.items()
        }

        # Compare synthesis results

        for row, row_data in enumerate(pseudocode_picture):
            for col, pseudocode_value in enumerate(row_data):
                # Create and call a function to compute this value via
                # InfiniteArrays/PyExp
                expr = ia_picture[col, row]
                f = expr.make_function()
                # NB: Arrays are transposed to support (x, y) indexing
                ia_value = f(ia_coeffs_data)

                assert ia_value == pseudocode_value
def test_greedy_stochastic_search():
    # This test is fairly crude. It just tests that the search terminates, does
    # not crash and improves on the initial input signal, however slightly.

    wavelet_index = WaveletFilters.le_gall_5_3
    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 = QUANTISATION_MATRICES[(
        wavelet_index,
        wavelet_index_ho,
        dwt_depth,
        dwt_depth_ho,
    )]

    quantisation_indices = list(range(64))

    input_min = -511
    input_max = 256

    width = 16
    height = 8

    # Arbitrary test pattern
    rand = np.random.RandomState(1)
    input_pattern = rand.choice((input_min, input_max), (height, width))

    # Restrict search-space to bottom-right three quarters only
    search_slice = (slice(height // 4, None), slice(width // 4, None))

    # Arbitrary output pixel
    output_array, intermediate_arrays = synthesis_transform(
        h_filter_params,
        v_filter_params,
        dwt_depth,
        dwt_depth_ho,
        make_variable_coeff_arrays(dwt_depth, dwt_depth_ho),
    )
    synthesis_pyexp = output_array[width // 2, height // 2]

    codec = FastPartialAnalyseQuantiseSynthesise(
        h_filter_params,
        v_filter_params,
        dwt_depth,
        dwt_depth_ho,
        quantisation_matrix,
        quantisation_indices,
        synthesis_pyexp,
    )

    kwargs = {
        "starting_pattern": input_pattern.copy(),
        "search_slice": search_slice,
        "input_min": input_min,
        "input_max": input_max,
        "codec": codec,
        "random_state": np.random.RandomState(1),
        "added_corruptions_per_iteration": 1,
        "removed_corruptions_per_iteration": 1,
        "added_iterations_per_improvement": 50,
    }

    # Get the baseline after no searches performed
    base_input_pattern, base_decoded_value, base_qi, decoded_values = greedy_stochastic_search(
        base_iterations=0, **kwargs)
    assert decoded_values == []

    # Check that when run for some time we get an improved result
    new_input_pattern, new_decoded_value, new_qi, decoded_values = greedy_stochastic_search(
        base_iterations=100, **kwargs)
    assert not np.array_equal(new_input_pattern, base_input_pattern)
    assert abs(new_decoded_value) > abs(base_decoded_value)
    assert new_qi in quantisation_indices
    assert len(decoded_values) > 100
    assert decoded_values[-1] == new_decoded_value

    # Check haven't mutated the supplied starting pattern argument
    assert np.array_equal(kwargs["starting_pattern"], input_pattern)

    # Check that only the specified slice was modified
    before = input_pattern.copy()
    before[search_slice] = 0
    after = new_input_pattern.copy()
    after[search_slice] = 0
    assert np.array_equal(before, after)

    # Check that all values are in range
    assert np.all((new_input_pattern >= input_min)
                  & (new_input_pattern <= input_max))
示例#9
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