What is the best script in python that replicates matlabs imresize3?

Viewed 52

I am translating code from MATLAB to python but cannot perfectly replicate the results of MATLAB's imresize3. My input is a 101x101x101 array. First four inputs ([0,0:3,0] or (1,1:4,1)) are: 0.3819 0.4033 0.4336 0.2767. The data input for both languages is identical.

sampleQDNormSmall = imresize3(sampleQDNorm,0.5);

This results in a 51x51x51 array where the first four values (1,1:4,1) for example are: 0.3443 0.2646 0.2700 0.2835

Now I've tried two different pieces of code in python to replicate these results:

from skimage.transform import resize
from skimage.transform import rescale

sampleQDNormSmall = resize(sampleQDNorm,(0.5*sampleQDNorm.shape[0],0.5*sampleQDNorm.shape[1],0.5*sampleQDNorm.shape[2]),order=3,anti_aliasing=True);

sampleQDNormSmall1=rescale(sampleQDNorm,0.5,order=3,anti_aliasing=True)

The first one gives a 51x51x51 array that has the first four values [0,0:3,0] of: 0.3452 0.2669 0.2774 0.3099. Which is very close but not exactly the same numerical outputs. I don't know enough about the optional arguments to know might get me a better result.

The second one gives a 50x50x50 array that has the first four values [0,0:3,0] of: 0.3422 0.2623 0.2810 0.3006. This is a different output array size and also doesn't reproduce the same numerical outputs as the MATLAB code or the other python function

I don't know enough about the optional arguments to know might get me a better result. I know for this type of array, MATLAB's default is cubic interpolation which is why I am using order 3 in python. The default for anti-aliasing in both is true. I have a two bigger arrays that I am having the same issues with: a (873x873x873) array and a bool (873x873x873) array.

The MATLAB code I'm using is considered the "correct answer" for the work I am doing so I am trying to replicate the results as accurately as possible into python. Please let me know what I can try in python to reproduce the correct data.

sampleQDNorm is roughly random decimals between 0 and 1 for [0:100,0:100,0:100] and is padded with zeros on sides [:,:,101],[:,101,:],[101,:,:]

1 Answers

Getting the exact same result as MATLAB imresize3 is challenging.

One reason is that MATLAB enables Antialiasing filter by default, and I can't seem to find the equivalent Python implementation.

The closet existing Python alternatives are described in this post.
scipy.ndimage.zoom supports 3D resizing.
It could be that skimage.transform.resize gives closer result, but none are identical to MATLAB result.


Reimplementing imresize3:

Looking at the MATLAB implementation of imresize3 (MATLAB source code), it is apparent that MATLAB implementation "simply" uses resize along each axis:

  • Resize (by half) along the vertical axis.
  • Resize the above result (by half) along the horizontal axis.
  • Resize the above result (by half) along the depth axis.

Here is a MATLAB codes sample that demonstrates the implementation (using cubic interpolation):

I1 = imread('peppers.png');
I2 = imresize(imread('autumn.tif'), [size(I1, 1), size(I1, 2)]);
I3 = imresize(imread('football.jpg'), [size(I1, 1), size(I1, 2)]);
I4 = imresize(imread('cameraman.tif'), [size(I1, 1), size(I1, 2)]);
I = cat(3, I1, I2, I3, I4);
J = imresize3(I, 0.5, 'cubic', 'Antialiasing', false);

imwrite(I1, '/Tmp/I1.png');
imwrite(I2, '/Tmp/I2.png');
imwrite(I3, '/Tmp/I3.png');
imwrite(I4, '/Tmp/I4.png');

imwrite(J(:,:,1), '/Tmp/J1.png');
imwrite(J(:,:,2), '/Tmp/J2.png');
imwrite(J(:,:,3), '/Tmp/J3.png');
imwrite(J(:,:,4), '/Tmp/J4.png');
imwrite(J(:,:,5), '/Tmp/J5.png');

K = cubicResize3(I, 0.5);

max_abs_diff = max(abs(double(J(:)) - double(K(:))));

disp(['max_abs_diff = ', num2str(max_abs_diff)])


function B = cubicResize3(A, scale)
    order = [1 2 3];
    
    B = A;
    for k = 1:numel(order)
        dim = order(k);       
        B = cubicResizeAlongDim(B, dim, scale);
    end    
end


function out = cubicResizeAlongDim(in, dim, scale)
    % If dim is 3, permute the input matrix so that the third dimension
    % becomes the first dimension.  This way, we can resize along the
    % third dimensions as though we were resizing along the first dimension.
    isThirdDimResize = (dim == 3);
    if isThirdDimResize
        in = permute(in, [3 2 1]);
        dim = 1;
    end
    
    if dim == 1
        out_rows = round(size(in, 1)*scale);
        out_cols = size(in, 2);
    else % dim == 2
        out_rows = size(in, 1);
        out_cols = round(size(in,2)*scale);
    end
    
    out = zeros(out_rows, out_cols, size(in, 3), class(in));  % Allocate array for storing the output.
    
    for i = 1:size(in, 3)
        % Resize each color plane separately
        out(:, :, i) = imresize(in(:, :, i), [out_rows, out_cols], 'bicubic', 'Antialiasing', false);
    end

    % Permute back so that the original dimensions are restored if we were
    % resizing along the third dimension.
    if isThirdDimResize
        out = permute(out, [3 2 1]);
    end
end

The result is max_abs_diff = 0, meaning that cubicResize3 and imresize3 gave the same output.

Note:
The above implementation stores images in Tmp folder to be used a input for testing Python implementation.


Here is a Python implementation using OpenCV:

import numpy as np
import cv2
#from scipy.ndimage import zoom

def cubic_resize_along_dim(inp, dim, scale):
    """ Implementation is based on MATLAB source code of resizeAlongDim function """
    # If dim is 3, permute the input matrix so that the third dimension
    # becomes the first dimension.  This way, we can resize along the
    # third dimensions as though we were resizing along the first dimension.
    is_third_dim_resize = (dim == 2)
    if is_third_dim_resize:
        inp = np.swapaxes(inp, 2, 0).copy()  # in = permute(in, [3 2 1])
        dim = 0
    
    if dim == 0:
        out_rows = int(np.round(inp.shape[0]*scale))  # out_rows = round(size(in, 1)*scale);
        out_cols = inp.shape[1]  # out_cols = size(in, 2);
    else: # dim == 1
        out_rows = inp.shape[0]  # out_rows = size(in, 1);
        out_cols = int(np.round(inp.shape[1]*scale))  # out_cols = round(size(in,2)*scale);
    
    out = np.zeros((out_rows, out_cols, inp.shape[2]), inp.dtype)  # out = zeros(out_rows, out_cols, size(in, 3), class(in));  % Allocate array for storing the output.
    
    for i in range(inp.shape[2]):
        # Resize each color plane separately
        out[:, :, i] = cv2.resize(inp[:, :, i], (out_cols, out_rows), interpolation=cv2.INTER_CUBIC)  # out(:, :, i) = imresize(inp(:, :, i), [out_rows, out_cols], 'bicubic', 'Antialiasing', false);

    # Permute back so that the original dimensions are restored if we were
    # resizing along the third dimension.
    if is_third_dim_resize:
        out = np.swapaxes(out, 2, 0) # out = permute(out, [3 2 1]);

    return out


def cubic_resize3(a, scale):
    b = a.copy()
    for k in range(3):
        b = cubic_resize_along_dim(b, k, scale)

    return b


# Build 3D input image (10 channels with resolution 512x384).
i1 = cv2.cvtColor(cv2.imread('/Tmp/I1.png', cv2.IMREAD_UNCHANGED), cv2.COLOR_BGR2RGB)
i2 = cv2.cvtColor(cv2.imread('/Tmp/I2.png', cv2.IMREAD_UNCHANGED), cv2.COLOR_BGR2RGB)
i3 = cv2.cvtColor(cv2.imread('/Tmp/I3.png', cv2.IMREAD_UNCHANGED), cv2.COLOR_BGR2RGB)
i4 = cv2.imread('/Tmp/I4.png', cv2.IMREAD_UNCHANGED)
im = np.dstack((i1, i2, i3, i4))  # Stack arrays along the third axis

# Read and adjust MATLAB output (out_mat is used as reference for testing).
# out_mat is the result of J = imresize3(I, 0.5, 'cubic', 'Antialiasing', false);
j1 = cv2.imread('/Tmp/J1.png', cv2.IMREAD_UNCHANGED)
j2 = cv2.imread('/Tmp/J2.png', cv2.IMREAD_UNCHANGED)
j3 = cv2.imread('/Tmp/J3.png', cv2.IMREAD_UNCHANGED)
j4 = cv2.imread('/Tmp/J4.png', cv2.IMREAD_UNCHANGED)
j5 = cv2.imread('/Tmp/J5.png', cv2.IMREAD_UNCHANGED)
out_mat = np.dstack((j1, j2, j3, j4, j5))  # Stack arrays along the third axis

#out_py = zoom(im, 0.5, order=3, mode='reflect')

# Execute 3D resize in Python
out_py = cubic_resize3(im, 0.5)

abs_diff = np.absolute(out_mat.astype(np.int16) - out_py.astype(np.int16))

print(f'max_abs_diff = {abs_diff.max()}')

The Python implementation reads the input files stored by MATLAB (and convert from BGR to RGB when required).

The implementation compares the result of cubic_resize3 with the MATLAB output of imresize3.

The maximum difference is 12 (not zero).
Apparently cv2.resize and MATLAB imresize gives slightly different results.


Update:

Replacing:
out[:, :, i] = cv2.resize(inp[:, :, i], (out_cols, out_rows), interpolation=cv2.INTER_CUBIC)
with:
out[:, :, i] = transform.resize(inp[:, :, i], (out_rows, out_cols), order=3, mode='edge', anti_aliasing=False, preserve_range=True)
Reduces the maximum difference to 4.

Related