John
John

Reputation: 3070

How to perform a fast cut zero edge in python?

I have a binary image size of 256x256x256, where the foreground region is located in a small region, and I have a lot of zero margins. I want to cut zero edges by finding the minimum and maximum coordinate of points that has pixel non-zero in images. It worked but it spends time-consuming. I post my code and could you tell me how we can make it faster?

For the image size of 256x256x256, it takes about 0.13024640083312988 seconds. This is the code and you can run online at https://repl.it/repls/AnxiousExoticBackup

import numpy as np
import time

def cut_edge(image, keep_margin):
    '''
    function that cuts zero edge
    '''
    D, H, W = image.shape
    D_s, D_e = 0, D - 1
    H_s, H_e = 0, H - 1
    W_s, W_e = 0, W - 1

    while D_s < D:
        if image[D_s].sum() != 0:
            break
        D_s += 1
    while D_e > D_s:
        if image[D_e].sum() != 0:
            break
        D_e -= 1
    while H_s < H:
        if image[:, H_s].sum() != 0:
            break
        H_s += 1
    while H_e > H_s:
        if image[:, H_e].sum() != 0:
            break
        H_e -= 1
    while W_s < W:
        if image[:, :, W_s].sum() != 0:
            break
        W_s += 1
    while W_e > W_s:
        if image[:, :, W_e].sum() != 0:
            break
        W_e -= 1

    if keep_margin != 0:
        D_s = max(0, D_s - keep_margin)
        D_e = min(D - 1, D_e + keep_margin)
        H_s = max(0, H_s - keep_margin)
        H_e = min(H - 1, H_e + keep_margin)
        W_s = max(0, W_s - keep_margin)
        W_e = min(W - 1, W_e + keep_margin)

    return int(D_s), int(D_e)+1, int(H_s), int(H_e)+1, int(W_s), int(W_e)+1

image = np.zeros ((256,256,256),dtype=np.float32)
ones_D_min, ones_D_max, ones_H_min, ones_H_max,ones_W_min, ones_W_max= 100,200, 90,150, 60,200
image[ones_D_min: ones_D_max,ones_H_min:ones_H_max, ones_W_min:ones_W_max]=1
t0=time.time()
ones_D_min_result, ones_D_max_result, ones_H_min_result, ones_H_max_result, ones_W_min_result, ones_W_max_result= cut_edge(image,0)
t1=time.time()
print ('Time consuming ', t1-t0)
print (ones_D_min, ones_D_max, ones_H_min, ones_H_max,ones_W_min, ones_W_max)
print (ones_D_min_result, ones_D_max_result, ones_H_min_result, ones_H_max_result, ones_W_min_result, ones_W_max_result)

Upvotes: 2

Views: 272

Answers (2)

Ben.T
Ben.T

Reputation: 29635

You can use the fact that if you sum over an axis of your 3D array, then the value will still be 0 where there is no 1 in the row (or column or third dimension) depending on which axis parameter. Then by using any in one of the two other directions and np.argwhere you will get the index where there is at least one 1 in the other axis. Using min and max will give the value you are looking for. Here is the function:

def cut_edge_2(image, keep_margin):
    im_sum0 = (image.sum(0) !=0)
    im_sum1 = (image.sum(1) !=0)
    ones_D = np.argwhere(im_sum1.any(1))
    ones_H = np.argwhere(im_sum0.any(1))
    ones_W = np.argwhere(im_sum0.any(0))
    if keep_margin != 0:
        D, H, W = image.shape
        return (max( 0, ones_D.min() - keep_margin), min(D, ones_D.max() + keep_margin+1), 
                max( 0, ones_H.min() - keep_margin), min(H, ones_H.max() + keep_margin+1),
                max( 0, ones_W.min() - keep_margin), min(W, ones_W.max() + keep_margin+1))
    return (ones_D.min(), ones_D.max() +1, 
            ones_H.min(), ones_H.max() +1,           
            ones_W.min(), ones_W.max() +1)

You get the same result than with your function:

print (cut_edge(image,0))
#(100, 200, 90, 150, 60, 200)
print (cut_edge_2(image,0))
#(100, 200, 90, 150, 60, 200)

print (cut_edge(image,60))
#(40, 256, 30, 210, 0, 256)
print (cut_edge_2(image,60))
#(40, 256, 30, 210, 0, 256)

and some timeit:

%timeit cut_edge(image,0)
#93 ms ± 7.62 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit cut_edge_2(image,0)
#25.3 ms ± 8.4 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit cut_edge_2(image,1)
#26.2 ms ± 4.73 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit cut_edge(image,1)
#95.4 ms ± 6.63 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

It is faster.

Upvotes: 1

Colin Dickie
Colin Dickie

Reputation: 910

Your function can be improved significantly using numpy's built-in functions:

def cut_edge(image, keep_margin):
    '''
    function that cuts zero edge
    '''

    #Calculate sum along each axis
    D_sum = np.sum(image, axis=(1,2)) #0
    H_sum = np.sum(image, axis=(0,2)) #1
    W_sum = np.sum(image, axis=(0,1)) #2

    #Find the non-zero values
    W_nz = np.nonzero(W_sum)[0]
    H_nz = np.nonzero(H_sum)[0]
    D_nz = np.nonzero(D_sum)[0]

    #Take the first and last entries for start and end
    D_s = D_nz[0]
    D_e = D_nz[-1]
    H_s = H_nz[0]
    H_e = H_nz[-1]
    W_s = W_nz[0]
    W_e = W_nz[-1]


    if keep_margin != 0:
        D_s = max(0, D_s - keep_margin)
        D_e = min(D - 1, D_e + keep_margin)
        H_s = max(0, H_s - keep_margin)
        H_e = min(H - 1, H_e + keep_margin)
        W_s = max(0, W_s - keep_margin)
        W_e = min(W - 1, W_e + keep_margin)

    return D_s, D_e+1, H_s, H_e+1, W_s, W_e+1

And the result:

Time consuming  0.0963144302368164

Upvotes: 3

Related Questions