1616 colors , image as mimage , patches , pyplot as plt , style , rcParams )
1717from matplotlib .image import (AxesImage , BboxImage , FigureImage ,
1818 NonUniformImage , PcolorImage )
19+ from matplotlib .patches import Rectangle
1920from matplotlib .testing .decorators import check_figures_equal , image_comparison
2021from matplotlib .transforms import Bbox , Affine2D , Transform , TransformedBbox
2122import matplotlib .ticker as mticker
2223
2324import pytest
2425
2526
27+ @pytest .fixture
28+ def nonaffine_identity ():
29+ """Non-affine identity transform for compositing with any affine transform"""
30+ class NonAffineIdentityTransform (Transform ):
31+ input_dims = 2
32+ output_dims = 2
33+
34+ def inverted (self ):
35+ return self
36+ return NonAffineIdentityTransform ()
37+
38+
2639@image_comparison (['interp_alpha.png' ], remove_text = True )
2740def test_alpha_interp ():
2841 """Test the interpolation of the alpha channel on RGBA images"""
@@ -1679,7 +1692,7 @@ def test__resample_valid_output():
16791692 np .full (256 , 0.9 )]).reshape (1 , - 1 )),
16801693 ]
16811694)
1682- def test_resample_nonaffine (data , interpolation , expected ):
1695+ def test_resample_nonaffine (data , interpolation , expected , nonaffine_identity ):
16831696 # Test that both affine and nonaffine transforms resample to the correct answer
16841697
16851698 # If the array is constant, the tolerance can be tight
@@ -1695,13 +1708,7 @@ def test_resample_nonaffine(data, interpolation, expected):
16951708
16961709 # Create a nonaffine version of the same transform
16971710 # by compositing with a nonaffine identity transform
1698- class NonAffineIdentityTransform (Transform ):
1699- input_dims = 2
1700- output_dims = 2
1701-
1702- def inverted (self ):
1703- return self
1704- nonaffine_transform = NonAffineIdentityTransform () + affine_transform
1711+ nonaffine_transform = nonaffine_identity + affine_transform
17051712
17061713 nonaffine_result = np .empty_like (expected )
17071714 mimage .resample (data , nonaffine_result , nonaffine_transform ,
@@ -1873,3 +1880,115 @@ def test_interpolation_stage_rgba_respects_alpha_param(fig_test, fig_ref, intp_s
18731880 (im_rgb , new_array_alpha .reshape ((ny , nx , 1 ))), axis = - 1
18741881 ), interpolation_stage = intp_stage
18751882 )
1883+
1884+
1885+ @image_comparison (['nn_pixel_alignment.png' ])
1886+ def test_nn_pixel_alignment (nonaffine_identity ):
1887+ fig , axs = plt .subplots (2 , 3 )
1888+
1889+ for j , N in enumerate ([3 , 7 , 11 ]):
1890+ # In each column, the plots use the same data array
1891+ data = np .arange (N ** 2 ).reshape ((N , N )) % 4
1892+ seps = np .arange (- 0.5 , N )
1893+
1894+ for i in range (2 ):
1895+ if i == 0 :
1896+ # Top row uses an affine transform
1897+ axs [i , j ].imshow (data , cmap = 'Grays' , interpolation = 'nearest' )
1898+ else :
1899+ # Bottom row uses a non-affine transform
1900+ axs [i , j ].imshow (data , cmap = 'Grays' , interpolation = 'nearest' ,
1901+ transform = nonaffine_identity + axs [i , j ].transData )
1902+
1903+ axs [i , j ].set_axis_off ()
1904+ axs [i , j ].vlines (seps , - 1 , N , lw = 0.5 , color = 'red' , ls = 'dashed' )
1905+ axs [i , j ].hlines (seps , - 1 , N , lw = 0.5 , color = 'red' , ls = 'dashed' )
1906+
1907+
1908+ @image_comparison (['image_bounds_handling.png' ], tol = 0.006 )
1909+ def test_image_bounds_handling (nonaffine_identity ):
1910+ # TODO: The second and third panels in the bottom row show that the handling of
1911+ # image bounds is bugged for non-affine transforms and non-nearest-neighbor
1912+ # interpolation. If this bug gets fixed, the baseline image should be updated.
1913+
1914+ fig , axs = plt .subplots (2 , 3 )
1915+
1916+ N = 11
1917+
1918+ for j , interpolation in enumerate (['nearest' , 'hanning' , 'bilinear' ]):
1919+ data = np .arange (N ** 2 ).reshape ((N , N ))
1920+ data = data / N ** 2 + (data % 4 ) / 6
1921+ rotation = Affine2D ().rotate_around (N / 2 - 0.5 , N / 2 - 0.5 , 1 )
1922+
1923+ for i in range (2 ):
1924+ transform = rotation + axs [i , j ].transData
1925+ if i == 1 :
1926+ # Bottom row uses a non-affine transform
1927+ transform = nonaffine_identity + transform
1928+
1929+ axs [i , j ].imshow (data , cmap = 'Grays' , interpolation = interpolation ,
1930+ transform = transform )
1931+
1932+ axs [i , j ].set_axis_off ()
1933+ box = Rectangle ((- 0.5 , - 0.5 ), N , N ,
1934+ edgecolor = 'red' , facecolor = 'none' , lw = 0.5 , ls = 'dashed' ,
1935+ transform = rotation + axs [i , j ].transData )
1936+ axs [i , j ].add_artist (box )
1937+
1938+
1939+ @image_comparison (['rgba_clean_edges.png' ], tol = 0.003 )
1940+ def test_rgba_clean_edges ():
1941+ np .random .seed (19680801 + 9 ) # same as in test_upsampling()
1942+ data = np .random .rand (8 , 8 )
1943+ data = np .stack ([data , data ])
1944+ data [1 , 2 :4 , 2 :4 ] = np .nan
1945+
1946+ rotation = Affine2D ().rotate_around (3.5 , 3.5 , 1 )
1947+
1948+ fig , axs = plt .subplots (1 , 2 )
1949+
1950+ for i in range (2 ):
1951+ # Add background patches to check the fading to non-white colors
1952+ black = Rectangle ((3.75 , 2 ), 5 , 5 , color = 'black' , zorder = 0 )
1953+ gray = Rectangle ((0 , - 2 ), 3.75 , 4 , color = 'gray' , zorder = 0 )
1954+ partly_black = Rectangle ((3.75 , - 2 ), 5 , 4 , fc = 'black' , ec = 'none' ,
1955+ alpha = 0.5 , zorder = 0 )
1956+ axs [i ].add_patch (black )
1957+ axs [i ].add_patch (gray )
1958+ axs [i ].add_patch (partly_black )
1959+
1960+ axs [i ].imshow (data [i , ...],
1961+ interpolation = 'bilinear' , interpolation_stage = 'rgba' ,
1962+ transform = rotation + axs [i ].transData )
1963+
1964+ axs [i ].set_axis_off ()
1965+ axs [i ].set_xlim (- 2.5 , 9.5 )
1966+ axs [i ].set_ylim (- 2.5 , 9.5 )
1967+
1968+
1969+ @image_comparison (['affine_fill_to_edges.png' ])
1970+ def test_affine_fill_to_edges ():
1971+ # The two rows show the two settings of origin
1972+ # The three columns show the original and the two mirror flips
1973+ fig , axs = plt .subplots (2 , 3 )
1974+
1975+ N = 7
1976+ data = np .arange (N ** 2 ).reshape ((N , N )) % 3
1977+
1978+ transform = [Affine2D (),
1979+ Affine2D ().translate (0 , - N + 1 ).scale (1 , - 1 ),
1980+ Affine2D ().translate (- N + 1 , 0 ).scale (- 1 , 1 )]
1981+
1982+ for j in range (3 ):
1983+ for i in range (2 ):
1984+ origin = 'upper' if i == 0 else 'lower'
1985+
1986+ axs [i , j ].imshow (data , cmap = 'Grays' ,
1987+ interpolation = 'hanning' , origin = origin ,
1988+ transform = transform [j ] + axs [i , j ].transData )
1989+
1990+ axs [i , j ].set_axis_off ()
1991+ axs [i , j ].vlines ([- 0.5 , N - 0.5 ], - 1 , 2 , lw = 0.5 , color = 'red' )
1992+ axs [i , j ].vlines ([- 0.5 , N - 0.5 ], N - 3 , N , lw = 0.5 , color = 'red' )
1993+ axs [i , j ].hlines ([- 0.5 , N - 0.5 ], - 1 , 2 , lw = 0.5 , color = 'red' )
1994+ axs [i , j ].hlines ([- 0.5 , N - 0.5 ], N - 3 , N , lw = 0.5 , color = 'red' )
0 commit comments