From 96cc032161b309bf01ba76d6e820c33c39f0b1e4 Mon Sep 17 00:00:00 2001 From: Chirayu Date: Wed, 6 Oct 2021 18:30:34 -0700 Subject: [PATCH 1/7] separate padding from zoomout kernel, load balances corner threads --- .../intensity/kernel/cuda_kernel_source.py | 103 +++++++----------- .../cucim/core/operations/intensity/zoom.py | 9 ++ 2 files changed, 47 insertions(+), 65 deletions(-) diff --git a/python/cucim/src/cucim/core/operations/intensity/kernel/cuda_kernel_source.py b/python/cucim/src/cucim/core/operations/intensity/kernel/cuda_kernel_source.py index 9c854486b..23d9de4d5 100644 --- a/python/cucim/src/cucim/core/operations/intensity/kernel/cuda_kernel_source.py +++ b/python/cucim/src/cucim/core/operations/intensity/kernel/cuda_kernel_source.py @@ -169,74 +169,47 @@ output_tensor[(blockIdx.z * pitch) + ((out_pixel_h + out_h_start) * input_w) + (out_pixel_w + out_w_start)] = sum_; + } +} - // replicate along top edge - if (out_pixel_h == 0) { - for (int ik = 0; ik < out_h_start; ik++) - output_tensor[(blockIdx.z * pitch) + - ((out_pixel_h + ik) * input_w) + - (out_pixel_w + out_w_start)] = sum_; - } - - // replicate along bottom edge - if (out_pixel_h == (output_h - 1)) { - for (int ik = 1; ik <= out_h_end; ik++) - output_tensor[(blockIdx.z * pitch) + - ((out_h_start + out_pixel_h + ik) * input_w) + - (out_pixel_w + out_w_start)] = sum_; - } - - // replicate along left edge - if (out_pixel_w == 0) { - for (int ik = 0; ik < out_w_start; ik++) - output_tensor[(blockIdx.z * pitch) + - ((out_pixel_h + out_h_start) * input_w) + ik] = sum_; - } - - // replicate along right edge - if (out_pixel_w == (output_w - 1)) { - for (int ik = 1; ik <= out_w_end; ik++) - output_tensor[(blockIdx.z * pitch) + - ((out_pixel_h + out_h_start) * input_w) + - (out_pixel_w + out_w_start + ik)] = sum_; - } +__global__ void zoomout_edge_pad(float *output_tensor, int height, int width, int pitch, + int no_padding_h_start, int no_padding_w_start, + int no_padding_h_end, int no_padding_w_end) { + // H -> block Y, row + // W -> block X, col - // corner replication not very friendly if large area to patch - - // single thread issues stores - // ToDo: Consider adding another kernel for corner padding + int out_pixel_h = blockIdx.y * blockDim.y + threadIdx.y; + int out_pixel_w = blockIdx.x * blockDim.x + threadIdx.x; + int image_start_offset = blockIdx.z * pitch; - // top left corner - if (out_pixel_h == 0 && out_pixel_w == 0) { - for (int ik = 0; ik < out_h_start; ik++) { - for (int il = 0; il < out_w_start; il++) - output_tensor[(blockIdx.z * pitch) + (ik * input_w) + il] = sum_; - } - } - // top right corner - if (out_pixel_h == 0 && out_pixel_w == (output_w - 1)) { - for (int ik = 0; ik < out_h_start; ik++) { - for (int il = 1; il <= out_w_end; il++) - output_tensor[(blockIdx.z * pitch) + (ik * input_w) + - (out_pixel_w + out_w_start + il)] = sum_; - } - } - // bottom left corner - if (out_pixel_h == (output_h - 1) && out_pixel_w == 0) { - for (int ik = 1; ik <= out_h_end; ik++) { - for (int il = 0; il < out_w_start; il++) - output_tensor[(blockIdx.z * pitch) + - ((out_h_start + out_pixel_h + ik) * input_w) + - il] = sum_; - } - } - // bottom right corner - if (out_pixel_h == (output_h - 1) && out_pixel_w == (output_w - 1)) { - for (int ik = 1; ik <= out_h_end; ik++) { - for (int il = 1; il <= out_w_end; il++) - output_tensor[(blockIdx.z * pitch) + - ((out_h_start + out_pixel_h + ik) * input_w) + - (out_pixel_w + out_w_start + il)] = sum_; - } + // no_padding_h_end, no_padding_w_end --> w_cropped+wstart, same for height + int out_location = (blockIdx.z * pitch) + (out_pixel_h * width) + out_pixel_w; + + if (out_pixel_h < height && out_pixel_w < width) { + if (out_pixel_h < no_padding_h_start && out_pixel_w >= no_padding_w_start && out_pixel_w < no_padding_w_end) { + // top pad + output_tensor[out_location] = output_tensor[(blockIdx.z * pitch) + (no_padding_h_start * width) + out_pixel_w]; + } else if (out_pixel_h >= no_padding_h_end && out_pixel_w >= no_padding_w_start && out_pixel_w < no_padding_w_end) { + // bottom pad + output_tensor[out_location] = output_tensor[(blockIdx.z * pitch) + ((no_padding_h_end-1) * width) + out_pixel_w]; + } else if (out_pixel_w < no_padding_w_start && out_pixel_h >= no_padding_h_start && out_pixel_h < no_padding_h_end) { + // left pad + output_tensor[out_location] = output_tensor[(blockIdx.z * pitch) + (out_pixel_h * width) + no_padding_w_start]; + } else if (out_pixel_w >= no_padding_w_end && out_pixel_h >= no_padding_h_start && out_pixel_h < no_padding_h_end) { + // right pad + output_tensor[out_location] = output_tensor[(blockIdx.z * pitch) + (out_pixel_h * width) + (no_padding_w_end-1)]; + } else if (out_pixel_h < no_padding_h_start && out_pixel_w < no_padding_w_start) { + // top-left corner + output_tensor[out_location] = output_tensor[(blockIdx.z * pitch) + (no_padding_h_start * width) + no_padding_w_start]; + } else if (out_pixel_h < no_padding_h_start && out_pixel_w >= no_padding_w_end) { + // top-right corner + output_tensor[out_location] = output_tensor[(blockIdx.z * pitch) + (no_padding_h_start * width) + (no_padding_w_end-1)]; + } else if (out_pixel_h >= no_padding_h_end && out_pixel_w < no_padding_w_start) { + // bottom-left corner + output_tensor[out_location] = output_tensor[(blockIdx.z * pitch) + ((no_padding_h_end-1) * width) + no_padding_w_start]; + } else if (out_pixel_h >= no_padding_h_end && out_pixel_w >= no_padding_w_end) { + // bottom-right corner + output_tensor[out_location] = output_tensor[(blockIdx.z * pitch) + ((no_padding_h_end-1) * width) + (no_padding_w_end-1)]; } } } diff --git a/python/cucim/src/cucim/core/operations/intensity/zoom.py b/python/cucim/src/cucim/core/operations/intensity/zoom.py index 4d197f62d..a08458eeb 100644 --- a/python/cucim/src/cucim/core/operations/intensity/zoom.py +++ b/python/cucim/src/cucim/core/operations/intensity/zoom.py @@ -160,6 +160,15 @@ def get_block_size(output_size_cu, H, W): np.int32(pad_dims[1][0]), np.int32(pad_dims[1][1])), shared_mem=smem_size) + # padding kernel + kernel = CUDA_KERNELS.get_function("zoomout_edge_pad") + grid = (int((W - 1) / block[0] + 1) , int((H - 1) / block[1] + 1), C*N) + kernel(grid, block_config, + args=(result, np.int32(H), np.int32(W), np.int32(pitch), + np.int32(pad_dims[0][0]), np.int32(pad_dims[1][0]), + np.int32(pad_dims[0][1]+output_size_cu[2]), + np.int32(pad_dims[1][1]+output_size_cu[3]))) + else: raise Exception("Can only handle simultaneous \ expansion(or shrinkage) in both H,W dimension, \ From 407369328d0790fb9237edc644077a687b604459 Mon Sep 17 00:00:00 2001 From: Chirayu Date: Mon, 11 Oct 2021 01:15:10 -0700 Subject: [PATCH 2/7] Fix style checker errors --- .../intensity/kernel/cuda_kernel_source.py | 55 +++++++++++++------ .../cucim/core/operations/intensity/zoom.py | 4 +- 2 files changed, 42 insertions(+), 17 deletions(-) diff --git a/python/cucim/src/cucim/core/operations/intensity/kernel/cuda_kernel_source.py b/python/cucim/src/cucim/core/operations/intensity/kernel/cuda_kernel_source.py index 23d9de4d5..92a477116 100644 --- a/python/cucim/src/cucim/core/operations/intensity/kernel/cuda_kernel_source.py +++ b/python/cucim/src/cucim/core/operations/intensity/kernel/cuda_kernel_source.py @@ -186,30 +186,53 @@ int out_location = (blockIdx.z * pitch) + (out_pixel_h * width) + out_pixel_w; if (out_pixel_h < height && out_pixel_w < width) { - if (out_pixel_h < no_padding_h_start && out_pixel_w >= no_padding_w_start && out_pixel_w < no_padding_w_end) { + if (out_pixel_h < no_padding_h_start && out_pixel_w >= no_padding_w_start + && out_pixel_w < no_padding_w_end) { // top pad - output_tensor[out_location] = output_tensor[(blockIdx.z * pitch) + (no_padding_h_start * width) + out_pixel_w]; - } else if (out_pixel_h >= no_padding_h_end && out_pixel_w >= no_padding_w_start && out_pixel_w < no_padding_w_end) { + output_tensor[out_location] = output_tensor[(blockIdx.z * pitch) + + (no_padding_h_start * width) + out_pixel_w]; + } else if (out_pixel_h >= no_padding_h_end + && out_pixel_w >= no_padding_w_start + && out_pixel_w < no_padding_w_end) { // bottom pad - output_tensor[out_location] = output_tensor[(blockIdx.z * pitch) + ((no_padding_h_end-1) * width) + out_pixel_w]; - } else if (out_pixel_w < no_padding_w_start && out_pixel_h >= no_padding_h_start && out_pixel_h < no_padding_h_end) { + output_tensor[out_location] = output_tensor[(blockIdx.z * pitch) + + ((no_padding_h_end-1) * width) + out_pixel_w]; + } else if (out_pixel_w < no_padding_w_start + && out_pixel_h >= no_padding_h_start + && out_pixel_h < no_padding_h_end) { // left pad - output_tensor[out_location] = output_tensor[(blockIdx.z * pitch) + (out_pixel_h * width) + no_padding_w_start]; - } else if (out_pixel_w >= no_padding_w_end && out_pixel_h >= no_padding_h_start && out_pixel_h < no_padding_h_end) { + output_tensor[out_location] = output_tensor[(blockIdx.z * pitch) + + (out_pixel_h * width) + no_padding_w_start]; + } else if (out_pixel_w >= no_padding_w_end + && out_pixel_h >= no_padding_h_start + && out_pixel_h < no_padding_h_end) { // right pad - output_tensor[out_location] = output_tensor[(blockIdx.z * pitch) + (out_pixel_h * width) + (no_padding_w_end-1)]; - } else if (out_pixel_h < no_padding_h_start && out_pixel_w < no_padding_w_start) { + output_tensor[out_location] = output_tensor[(blockIdx.z * pitch) + + (out_pixel_h * width) + (no_padding_w_end-1)]; + } else if (out_pixel_h < no_padding_h_start + && out_pixel_w < no_padding_w_start) { // top-left corner - output_tensor[out_location] = output_tensor[(blockIdx.z * pitch) + (no_padding_h_start * width) + no_padding_w_start]; - } else if (out_pixel_h < no_padding_h_start && out_pixel_w >= no_padding_w_end) { + output_tensor[out_location] = output_tensor[(blockIdx.z * pitch) + + (no_padding_h_start * width) + + no_padding_w_start]; + } else if (out_pixel_h < no_padding_h_start + && out_pixel_w >= no_padding_w_end) { // top-right corner - output_tensor[out_location] = output_tensor[(blockIdx.z * pitch) + (no_padding_h_start * width) + (no_padding_w_end-1)]; - } else if (out_pixel_h >= no_padding_h_end && out_pixel_w < no_padding_w_start) { + output_tensor[out_location] = output_tensor[(blockIdx.z * pitch) + + (no_padding_h_start * width) + + (no_padding_w_end-1)]; + } else if (out_pixel_h >= no_padding_h_end + && out_pixel_w < no_padding_w_start) { // bottom-left corner - output_tensor[out_location] = output_tensor[(blockIdx.z * pitch) + ((no_padding_h_end-1) * width) + no_padding_w_start]; - } else if (out_pixel_h >= no_padding_h_end && out_pixel_w >= no_padding_w_end) { + output_tensor[out_location] = output_tensor[(blockIdx.z * pitch) + + ((no_padding_h_end-1) * width) + + no_padding_w_start]; + } else if (out_pixel_h >= no_padding_h_end + && out_pixel_w >= no_padding_w_end) { // bottom-right corner - output_tensor[out_location] = output_tensor[(blockIdx.z * pitch) + ((no_padding_h_end-1) * width) + (no_padding_w_end-1)]; + output_tensor[out_location] = output_tensor[(blockIdx.z * pitch) + + ((no_padding_h_end-1) * width) + + (no_padding_w_end-1)]; } } } diff --git a/python/cucim/src/cucim/core/operations/intensity/zoom.py b/python/cucim/src/cucim/core/operations/intensity/zoom.py index a08458eeb..987f66406 100644 --- a/python/cucim/src/cucim/core/operations/intensity/zoom.py +++ b/python/cucim/src/cucim/core/operations/intensity/zoom.py @@ -162,7 +162,9 @@ def get_block_size(output_size_cu, H, W): shared_mem=smem_size) # padding kernel kernel = CUDA_KERNELS.get_function("zoomout_edge_pad") - grid = (int((W - 1) / block[0] + 1) , int((H - 1) / block[1] + 1), C*N) + grid = (int((W - 1) / block_config[0] + 1), + int((H - 1) / block_config[1] + 1), + C * N) kernel(grid, block_config, args=(result, np.int32(H), np.int32(W), np.int32(pitch), np.int32(pad_dims[0][0]), np.int32(pad_dims[1][0]), From 24a9adcecc942722a9dcdb7922677ee6682fa23b Mon Sep 17 00:00:00 2001 From: Chirayu Date: Mon, 11 Oct 2021 01:18:32 -0700 Subject: [PATCH 3/7] Remove more style errors --- .../core/operations/intensity/kernel/cuda_kernel_source.py | 7 ++++--- python/cucim/src/cucim/core/operations/intensity/zoom.py | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/python/cucim/src/cucim/core/operations/intensity/kernel/cuda_kernel_source.py b/python/cucim/src/cucim/core/operations/intensity/kernel/cuda_kernel_source.py index 92a477116..4a0f6d6e1 100644 --- a/python/cucim/src/cucim/core/operations/intensity/kernel/cuda_kernel_source.py +++ b/python/cucim/src/cucim/core/operations/intensity/kernel/cuda_kernel_source.py @@ -172,9 +172,10 @@ } } -__global__ void zoomout_edge_pad(float *output_tensor, int height, int width, int pitch, - int no_padding_h_start, int no_padding_w_start, - int no_padding_h_end, int no_padding_w_end) { +__global__ void zoomout_edge_pad(float *output_tensor, int height, int width, + int pitch, int no_padding_h_start, + int no_padding_w_start, + int no_padding_h_end, int no_padding_w_end) { // H -> block Y, row // W -> block X, col diff --git a/python/cucim/src/cucim/core/operations/intensity/zoom.py b/python/cucim/src/cucim/core/operations/intensity/zoom.py index 987f66406..f418763f6 100644 --- a/python/cucim/src/cucim/core/operations/intensity/zoom.py +++ b/python/cucim/src/cucim/core/operations/intensity/zoom.py @@ -168,8 +168,8 @@ def get_block_size(output_size_cu, H, W): kernel(grid, block_config, args=(result, np.int32(H), np.int32(W), np.int32(pitch), np.int32(pad_dims[0][0]), np.int32(pad_dims[1][0]), - np.int32(pad_dims[0][1]+output_size_cu[2]), - np.int32(pad_dims[1][1]+output_size_cu[3]))) + np.int32(pad_dims[0][1] + output_size_cu[2]), + np.int32(pad_dims[1][1] + output_size_cu[3]))) else: raise Exception("Can only handle simultaneous \ From 6a3c0706e3ce384600b4dce99148f3255600d9e1 Mon Sep 17 00:00:00 2001 From: Chirayu Date: Thu, 14 Oct 2021 11:14:07 -0700 Subject: [PATCH 4/7] Fix param bug to padding kernel, add unit test for zoom out --- .../intensity/tests/test_rand_zoom.py | 25 +++++++++++-- .../operations/intensity/tests/test_zoom.py | 34 ++++++++++++++++-- .../intensity/tests/zoomout_padded.png | Bin 0 -> 267305 bytes .../cucim/core/operations/intensity/zoom.py | 4 +-- 4 files changed, 57 insertions(+), 6 deletions(-) create mode 100644 python/cucim/src/cucim/core/operations/intensity/tests/zoomout_padded.png diff --git a/python/cucim/src/cucim/core/operations/intensity/tests/test_rand_zoom.py b/python/cucim/src/cucim/core/operations/intensity/tests/test_rand_zoom.py index 85641383b..1c9c3af52 100644 --- a/python/cucim/src/cucim/core/operations/intensity/tests/test_rand_zoom.py +++ b/python/cucim/src/cucim/core/operations/intensity/tests/test_rand_zoom.py @@ -15,9 +15,12 @@ def get_input_arr(): return arr -def get_zoomed_data(): +def get_zoomed_data(zoomout=False): dirname = os.path.dirname(__file__) - img1 = Image.open(os.path.join(os.path.abspath(dirname), "zoomed.png")) + if not zoomout: + img1 = Image.open(os.path.join(os.path.abspath(dirname), "zoomed.png")) + else: + img1 = Image.open(os.path.join(os.path.abspath(dirname), "zoomout_padded.png")) arr_o = np.asarray(img1) arr_o = np.transpose(arr_o) return arr_o @@ -54,3 +57,21 @@ def test_rand_zoom_batchinput(): for i in range(np_output.shape[0]): assert np.allclose(np_output[i], zoomed_arr) + + +def test_rand_zoomout_numpy_input(): + arr = get_input_arr() + zoomed_arr = get_zoomed_data(True) + output = its.rand_zoom(arr, prob=1.0, min_zoom=0.85, max_zoom=0.85) + assert np.allclose(output, zoomed_arr) + + +def test_rand_zoomout_batchinput(): + arr = get_input_arr() + zoomed_arr = get_zoomed_data(True) + arr_batch = np.stack((arr,) * 8, axis=0) + np_output = its.rand_zoom(arr_batch, prob=1.0, min_zoom=0.85, max_zoom=0.85) + assert np_output.shape[0] == 8 + + for i in range(np_output.shape[0]): + assert np.allclose(np_output[i], zoomed_arr) diff --git a/python/cucim/src/cucim/core/operations/intensity/tests/test_zoom.py b/python/cucim/src/cucim/core/operations/intensity/tests/test_zoom.py index 1c80d6673..2a9f68b5c 100644 --- a/python/cucim/src/cucim/core/operations/intensity/tests/test_zoom.py +++ b/python/cucim/src/cucim/core/operations/intensity/tests/test_zoom.py @@ -16,9 +16,12 @@ def get_input_arr(): return arr -def get_zoomed_data(): +def get_zoomed_data(zoomout=False): dirname = os.path.dirname(__file__) - img1 = Image.open(os.path.join(os.path.abspath(dirname), "zoomed.png")) + if not zoomout: + img1 = Image.open(os.path.join(os.path.abspath(dirname), "zoomed.png")) + else: + img1 = Image.open(os.path.join(os.path.abspath(dirname), "zoomout_padded.png")) arr_o = np.asarray(img1) arr_o = np.transpose(arr_o) return arr_o @@ -59,3 +62,30 @@ def test_zoom_batchinput(): for i in range(np_output.shape[0]): assert np.allclose(np_output[i], zoomed_arr) + + +def test_zoomout_numpy_input(): + arr = get_input_arr() + zoomed_arr = get_zoomed_data(True) + output = its.zoom(arr, [0.85, 0.85]) + assert np.allclose(output, zoomed_arr) + + +def test_zoomout_cupy_input(): + arr = get_input_arr() + zoomed_arr = get_zoomed_data(True) + cupy_arr = cupy.asarray(arr) + cupy_output = its.zoom(cupy_arr, [0.85, 0.85]) + np_output = cupy.asnumpy(cupy_output) + assert np.allclose(np_output, zoomed_arr) + + +def test_zoomout_batchinput(): + arr = get_input_arr() + zoomed_arr = get_zoomed_data(True) + arr_batch = np.stack((arr,) * 8, axis=0) + np_output = its.zoom(arr_batch, [0.85, 0.85]) + assert np_output.shape[0] == 8 + + for i in range(np_output.shape[0]): + assert np.allclose(np_output[i], zoomed_arr) diff --git a/python/cucim/src/cucim/core/operations/intensity/tests/zoomout_padded.png b/python/cucim/src/cucim/core/operations/intensity/tests/zoomout_padded.png new file mode 100644 index 0000000000000000000000000000000000000000..a92b7f33f54ae116721e91787247ac2e21bb2414 GIT binary patch literal 267305 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