# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import math import unittest import numpy as np from op_test import get_device_place, is_custom_device import paddle from paddle import base from paddle.base import core from paddle.nn import Upsample from paddle.nn.functional import interpolate def bilinear_interp_np_old( input, out_h, out_w, scale_h=0, scale_w=0, out_size=None, actual_shape=None, align_corners=True, align_mode=0, data_layout='NCHW', ): """bilinear interpolation implement in shape [N, C, H, W]""" if data_layout == "NHWC": input = np.transpose(input, (0, 3, 1, 2)) # NHWC => NCHW if out_size is not None: out_h = out_size[0] out_w = out_size[1] if actual_shape is not None: out_h = actual_shape[0] out_w = actual_shape[1] batch_size, channel, in_h, in_w = input.shape ratio_h = ratio_w = 0.0 if out_h > 1: if align_corners: ratio_h = (in_h - 1.0) / (out_h - 1.0) else: if scale_h > 0: ratio_h = 1.0 / scale_h else: ratio_h = 1.0 * in_h / out_h if out_w > 1: if align_corners: ratio_w = (in_w - 1.0) / (out_w - 1.0) else: if scale_w > 0: ratio_w = 1.0 / scale_w else: ratio_w = 1.0 * in_w / out_w out = np.zeros((batch_size, channel, out_h, out_w)) for i in range(out_h): if align_mode == 0 and not align_corners: h = int(ratio_h * (i + 0.5) - 0.5) else: h = int(ratio_h * i) h = max(0, h) hid = 1 if h < in_h - 1 else 0 if align_mode == 0 and not align_corners: idx_src_h = max(ratio_h * (i + 0.5) - 0.5, 0) h1lambda = idx_src_h - h else: h1lambda = ratio_h * i - h h2lambda = 1.0 - h1lambda for j in range(out_w): if align_mode == 0 and not align_corners: w = int(ratio_w * (j + 0.5) - 0.5) else: w = int(ratio_w * j) w = max(0, w) wid = 1 if w < in_w - 1 else 0 if align_mode == 0 and not align_corners: idx_src_w = max(ratio_w * (j + 0.5) - 0.5, 0) w1lambda = idx_src_w - w else: w1lambda = ratio_w * j - w w2lambda = 1.0 - w1lambda out[:, :, i, j] = h2lambda * ( w2lambda * input[:, :, h, w] + w1lambda * input[:, :, h, w + wid] ) + h1lambda * ( w2lambda * input[:, :, h + hid, w] + w1lambda * input[:, :, h + hid, w + wid] ) if data_layout == "NHWC": out = np.transpose(out, (0, 2, 3, 1)) # NCHW => NHWC return out.astype(input.dtype) def bilinear_interp_np( input, out_h, out_w, scale_h=0, scale_w=0, out_size=None, actual_shape=None, align_corners=True, align_mode=0, data_layout='NCHW', ): """bilinear interpolation implement in shape [N, C, H, W]""" # TODO(zrr1999): The CPU has modified its implementation for alignment with the XPU, but it cannot align with the GPU if not (core.is_compiled_with_cuda() or core.is_compiled_with_rocm()): return bilinear_interp_np_old( input, out_h, out_w, scale_h, scale_w, out_size, actual_shape, align_corners, align_mode, data_layout, ) if data_layout == "NHWC": input = np.transpose(input, (0, 3, 1, 2)) # NHWC => NCHW if out_size is not None: out_h = out_size[0] out_w = out_size[1] if actual_shape is not None: out_h = actual_shape[0] out_w = actual_shape[1] batch_size, channel, in_h, in_w = input.shape # Standard bilinear interpolation (no anti-aliasing) ratio_h = ratio_w = 0.0 if align_corners: if out_h > 1: ratio_h = (in_h - 1.0) / (out_h - 1.0) else: if scale_h > 0: ratio_h = 1.0 / scale_h else: ratio_h = in_h / out_h if align_corners: if out_w > 1: ratio_w = (in_w - 1.0) / (out_w - 1.0) else: if scale_w > 0: ratio_w = 1.0 / scale_w else: ratio_w = in_w / out_w out = np.zeros((batch_size, channel, out_h, out_w)) for i in range(out_h): if align_mode == 0 and not align_corners: src_h = ratio_h * (i + 0.5) - 0.5 else: src_h = ratio_h * i h = int(np.floor(src_h)) h = max(0, min(h, in_h - 1)) hid = 1 if h < in_h - 1 else 0 h1lambda = max(0.0, min(1.0, src_h - h)) h2lambda = 1.0 - h1lambda for j in range(out_w): if align_mode == 0 and not align_corners: src_w = ratio_w * (j + 0.5) - 0.5 else: src_w = ratio_w * j w = int(np.floor(src_w)) w = max(0, min(w, in_w - 1)) wid = 1 if w < in_w - 1 else 0 w1lambda = max(0.0, min(1.0, src_w - w)) w2lambda = 1.0 - w1lambda h_next = min(h + hid, in_h - 1) w_next = min(w + wid, in_w - 1) out[:, :, i, j] = h2lambda * ( w2lambda * input[:, :, h, w] + w1lambda * input[:, :, h, w_next] ) + h1lambda * ( w2lambda * input[:, :, h_next, w] + w1lambda * input[:, :, h_next, w_next] ) if data_layout == "NHWC": out = np.transpose(out, (0, 2, 3, 1)) # NCHW => NHWC return out.astype(input.dtype) def nearest_neighbor_interp3d_np( X, out_d, out_h, out_w, scale_d=0, scale_h=0, scale_w=0, out_size=None, actual_shape=None, align_corners=True, data_layout='NCHW', ): """nearest neighbor interpolation implement in shape [N, C, H, W]""" if data_layout == "NHWC": X = np.transpose(X, (0, 4, 1, 2, 3)) # NDHWC => NCDHW if out_size is not None: out_d = out_size[0] out_h = out_size[1] out_w = out_size[2] if actual_shape is not None: out_d = actual_shape[0] out_h = actual_shape[1] out_w = actual_shape[2] n, c, in_d, in_h, in_w = X.shape ratio_d = ratio_h = ratio_w = 0.0 if out_d > 1: if align_corners: ratio_d = (in_d - 1.0) / (out_d - 1.0) else: if scale_d > 0: ratio_d = 1.0 / scale_d else: ratio_d = 1.0 * in_d / out_d if out_h > 1: if align_corners: ratio_h = (in_h - 1.0) / (out_h - 1.0) else: if scale_h > 0: ratio_h = 1.0 / scale_h else: ratio_h = 1.0 * in_h / out_h if out_w > 1: if align_corners: ratio_w = (in_w - 1.0) / (out_w - 1.0) else: if scale_w > 0: ratio_w = 1.0 / scale_w else: ratio_w = 1.0 * in_w / out_w out = np.zeros((n, c, out_d, out_h, out_w)) if align_corners: for d in range(out_d): in_d = int(ratio_d * d + 0.5) for i in range(out_h): in_i = int(ratio_h * i + 0.5) for j in range(out_w): in_j = int(ratio_w * j + 0.5) out[:, :, d, i, j] = X[:, :, in_d, in_i, in_j] else: for d in range(out_d): in_d = int(ratio_d * d) for i in range(out_h): in_i = int(ratio_h * i) for j in range(out_w): in_j = int(ratio_w * j) out[:, :, d, i, j] = X[:, :, in_d, in_i, in_j] if data_layout == "NDHWC": out = np.transpose(out, (0, 2, 3, 4, 1)) # NCDHW => NDHWC return out.astype(X.dtype) def linear_interp_np( input, out_w, scale_w=0, out_size=None, actual_shape=None, align_corners=True, align_mode=0, data_layout='NCHW', ): if data_layout == "NHWC": input = np.transpose(input, (0, 2, 1)) # NHWC => NCHW if out_size is not None: out_w = out_size[0] if actual_shape is not None: out_w = actual_shape[0] batch_size, channel, in_w = input.shape ratio_w = 0.0 if out_w > 1: if align_corners: ratio_w = (in_w - 1.0) / (out_w - 1.0) else: if scale_w > 0: ratio_w = 1.0 / scale_w else: ratio_w = 1.0 * in_w / out_w out = np.zeros((batch_size, channel, out_w)) for j in range(out_w): if align_mode == 0 and not align_corners: w = int(ratio_w * (j + 0.5) - 0.5) else: w = int(ratio_w * j) w = max(0, w) wid = 1 if w < in_w - 1 else 0 if align_mode == 0 and not align_corners: idx_src_w = max(ratio_w * (j + 0.5) - 0.5, 0) w1lambda = idx_src_w - w else: w1lambda = ratio_w * j - w w2lambda = 1.0 - w1lambda out[:, :, j] = ( w2lambda * input[:, :, w] + w1lambda * input[:, :, w + wid] ) if data_layout == "NHWC": out = np.transpose(out, (0, 2, 1)) # NCHW => NHWC return out.astype(input.dtype) class TestBilinearInterpOpAPI_RecomputeScaleFactor(unittest.TestCase): def test_case(self): if core.is_compiled_with_cuda() or is_custom_device(): place = get_device_place() else: place = core.CPUPlace() with base.dygraph.guard(place): # Create input data input_data = np.random.random((2, 3, 7, 8)).astype("float32") input_x = paddle.to_tensor(input_data) scale_factor = 1.6 in_h, in_w = input_data.shape[2], input_data.shape[3] expected_out_h = math.floor(in_h * scale_factor) expected_out_w = math.floor(in_w * scale_factor) # Calculate expected result expect_res = bilinear_interp_np( input_data, out_h=expected_out_h, out_w=expected_out_w, align_corners=False, ) # Test with scalar scale_factor and recompute_scale_factor=True out1 = interpolate( x=input_x, scale_factor=scale_factor, mode="bilinear", align_corners=False, recompute_scale_factor=True, ) # Verify results match np.testing.assert_allclose(out1.numpy(), expect_res, rtol=1e-05) assert out1.shape[2] == expected_out_h assert out1.shape[3] == expected_out_w class TestBilinearInterpOpAPI_RecomputeScaleFactorList(unittest.TestCase): def test_case(self): if core.is_compiled_with_cuda() or is_custom_device(): place = get_device_place() else: place = core.CPUPlace() with base.dygraph.guard(place): # Create input data input_data = np.random.random((2, 3, 9, 6)).astype("float32") input_x = paddle.to_tensor(input_data) scale_h, scale_w = 2.3, 0.7 in_h, in_w = input_data.shape[2], input_data.shape[3] expected_out_h = math.floor(in_h * scale_h) expected_out_w = math.floor(in_w * scale_w) # Calculate expected result expect_res = bilinear_interp_np( input_data, out_h=expected_out_h, out_w=expected_out_w, align_corners=True, ) # Test with list scale_factor and recompute_scale_factor=True scale_list = [scale_h, scale_w] out = interpolate( x=input_x, scale_factor=scale_list, mode="bilinear", align_corners=True, recompute_scale_factor=True, ) # Verify results match np.testing.assert_allclose(out.numpy(), expect_res, rtol=1e-05) assert out.shape[2] == expected_out_h assert out.shape[3] == expected_out_w class TestBilinearInterpOpAPI_RecomputeScaleFactorDifferentTensors( unittest.TestCase ): def test_case(self): if core.is_compiled_with_cuda() or is_custom_device(): place = get_device_place() else: place = core.CPUPlace() with base.dygraph.guard(place): # Create input data input_data = np.random.random((2, 3, 9, 6)).astype("float32") input_x = paddle.to_tensor(input_data) scale_h, scale_w = 2.3, 0.7 scale_tensor = paddle.to_tensor([scale_h, scale_w], dtype="float32") # Calculate expected output size with floor in_h, in_w = input_data.shape[2], input_data.shape[3] expected_out_h = math.floor(in_h * scale_h) expected_out_w = math.floor(in_w * scale_w) # Calculate expected result expect_res = bilinear_interp_np( input_data, out_h=expected_out_h, out_w=expected_out_w, align_corners=True, ) # Test with tensor scale_factor and recompute_scale_factor=True out = interpolate( x=input_x, scale_factor=scale_tensor, mode="bilinear", align_corners=True, recompute_scale_factor=True, ) # Verify results match np.testing.assert_allclose(out.numpy(), expect_res, rtol=1e-05) assert out.shape[2] == expected_out_h assert out.shape[3] == expected_out_w class TestBilinearInterpOpAPI_RecomputeScaleFactorScalarTensor( unittest.TestCase ): def test_case(self): if core.is_compiled_with_cuda() or is_custom_device(): place = get_device_place() else: place = core.CPUPlace() with base.dygraph.guard(place): # Create input data input_data = np.random.random((2, 3, 7, 8)).astype("float32") input_x = paddle.to_tensor(input_data) # Create a scalar tensor with empty shape [] scale_value = 1.6 scale_tensor = paddle.to_tensor(scale_value, dtype="float32") in_h, in_w = input_data.shape[2], input_data.shape[3] expected_out_h = math.floor(in_h * scale_value) expected_out_w = math.floor(in_w * scale_value) # Calculate expected result expect_res = bilinear_interp_np( input_data, out_h=expected_out_h, out_w=expected_out_w, align_corners=False, ) # Test with tensor scale_factor and recompute_scale_factor=True out = interpolate( x=input_x, scale_factor=scale_tensor, mode="bilinear", align_corners=False, recompute_scale_factor=True, ) # Verify results match np.testing.assert_allclose(out.numpy(), expect_res, rtol=1e-05) assert out.shape[2] == expected_out_h assert out.shape[3] == expected_out_w class TestNearestInterpOpAPI_RecomputeScaleFactor(unittest.TestCase): def test_case(self): if core.is_compiled_with_cuda() or is_custom_device(): place = get_device_place() else: place = core.CPUPlace() with base.dygraph.guard(place): # Create input data input_data = np.random.random((2, 3, 4, 7, 8)).astype("float32") input_x = paddle.to_tensor(input_data) scale_factor = 1.6 in_d, in_h, in_w = ( input_data.shape[2], input_data.shape[3], input_data.shape[4], ) expected_out_d = math.floor(in_d * scale_factor) expected_out_h = math.floor(in_h * scale_factor) expected_out_w = math.floor(in_w * scale_factor) # Calculate expected result expect_res = nearest_neighbor_interp3d_np( input_data, out_d=expected_out_d, out_h=expected_out_h, out_w=expected_out_w, align_corners=False, ) # Test with scalar scale_factor and recompute_scale_factor=True out = interpolate( x=input_x, scale_factor=scale_factor, mode="nearest", align_corners=False, recompute_scale_factor=True, ) # Verify results match np.testing.assert_allclose(out.numpy(), expect_res, rtol=1e-05) assert out.shape[2] == expected_out_d assert out.shape[3] == expected_out_h assert out.shape[4] == expected_out_w class TestLinearInterpOpAPI_RecomputeScaleFactor(unittest.TestCase): def test_case(self): if core.is_compiled_with_cuda() or is_custom_device(): place = get_device_place() else: place = core.CPUPlace() with base.dygraph.guard(place): # Create 3D input data input_data = np.random.random((2, 3, 8)).astype("float32") input_x = paddle.to_tensor(input_data) scale_factor = 1.6 in_w = input_data.shape[2] expected_out_w = math.floor(in_w * scale_factor) # Calculate expected result expect_res = linear_interp_np( input_data, out_w=expected_out_w, align_corners=False, ) # Test with scalar scale_factor and recompute_scale_factor=True out = interpolate( x=input_x, scale_factor=scale_factor, mode="linear", align_corners=False, recompute_scale_factor=True, ) # Verify results match np.testing.assert_allclose(out.numpy(), expect_res, rtol=1e-05) assert out.shape[2] == expected_out_w class TestInterpRecomputeScaleFactorError(unittest.TestCase): def test_size_and_recompute_scale_factor_error(self): if core.is_compiled_with_cuda() or is_custom_device(): place = get_device_place() else: place = core.CPUPlace() with base.dygraph.guard(place): # Create input data input_data = np.random.random((2, 3, 7, 8)).astype("float32") input_x = paddle.to_tensor(input_data) def test_invalid_params(): out = interpolate( x=input_x, size=[14, 16], scale_factor=2.0, mode="bilinear", align_corners=False, recompute_scale_factor=True, ) self.assertRaises(ValueError, test_invalid_params) def test_invalid_params_upsample(): upsample = Upsample( size=[14, 16], scale_factor=2.0, mode="bilinear", align_corners=False, recompute_scale_factor=True, ) out = upsample(input_x) self.assertRaises(ValueError, test_invalid_params_upsample) class TestInterpRecomputeScaleFactorScaleShapeError(unittest.TestCase): def test_incorrect_scale_shape(self): if core.is_compiled_with_cuda() or is_custom_device(): place = get_device_place() else: place = core.CPUPlace() with base.dygraph.guard(place): # Create input data - 4D tensor (N, C, H, W) input_data = np.random.random((2, 3, 7, 8)).astype("float32") input_x = paddle.to_tensor(input_data) # For a 4D tensor, dim = len(x.shape) - 2 = 2, so scale_factor should be of length 2 # Providing a scale_factor of length 3 should trigger the error scale_list = [1.5, 2.0, 0.5] def test_invalid_scale_shape(): out = interpolate( x=input_x, scale_factor=scale_list, mode="bilinear", align_corners=False, recompute_scale_factor=True, ) self.assertRaises(ValueError, test_invalid_scale_shape) # TTest with a 5D tensor input_data_5d = np.random.random((2, 3, 4, 7, 8)).astype("float32") input_x_5d = paddle.to_tensor(input_data_5d) # For a 5D tensor, dim = len(x.shape) - 2 = 3, so scale_factor should be of length 3 # Providing a scale_factor of length 2 should trigger the error scale_list_5d = [1.5, 2.0] def test_invalid_scale_shape_5d(): out = interpolate( x=input_x_5d, scale_factor=scale_list_5d, mode="nearest", align_corners=False, recompute_scale_factor=True, ) self.assertRaises(ValueError, test_invalid_scale_shape_5d) if __name__ == "__main__": unittest.main()