jeudi 15 juin 2023

How to avoid recomputing several variables without completely refactoring code

I have a category of functions 'detectors', that, given a video, a space scale parameter and a time scale parameter, detect some interest points. I wrote a 'MultiscaleDetector' function, that basically takes a list of space scale parameters and a 'detector' function as parameters and executes the given detector factor for every scale.

This is how they look:

def GaborDetector(v, sigma, tau, threshold, num_points):
    """
    Gabor Detector
    
    Keyword arguments:
    video -- input video (y_len, x_len, frames)
    sigma -- Gaussian kernel space standard deviation
    tau -- Gaussian kernel time standard deviation
    kappa -- Gabor response threshold
    """
    # setup video
    video = v.copy()
    video = video.astype(float)/video.max()
    video = video_smoothen_space(video, sigma)
    # first define a linspace of width -2tau to 2tau
    time = np.linspace(-2*tau, 2*tau, int(4*tau+1))
    omega = 4/tau
    # define the gabor filters
    h_ev = np.exp(-time**2/(2*tau**2)) * np.cos(2*np.pi*omega*time)
    h_od = np.exp(-time**2/(2*tau**2)) * np.sin(2*np.pi*omega*time)
    # normalize the L1 norm
    h_ev /= np.linalg.norm(h_ev, ord=1)
    h_od /= np.linalg.norm(h_od, ord=1)
    # compute the response
    response = (scp.convolve1d(video, h_ev, axis=2) ** 2) + (scp.convolve1d(video, h_od, axis=2) ** 2)
    points = interest_points(response, num=num_points, threshold=threshold, scale=sigma)
    return points
def MultiscaleDetector(detector, video, sigmas, tau, num_points):
    """
    Multiscale Detector

    Executes a detector at multiple scales. Detector has to be a function that
    takes a video as input, along with other parameters, and returns a list of interest points.

    
    Keyword arguments:
    detector -- function that returns interest points
    video -- input video (y_len, x_len, frames)
    sigmas -- list of scales
    """
    
    # for every scale, compute the response
    points = []
    for sigm in sigmas:
        found = detector(video, sigm, tau)
        points.append(found)

    # filter the points, currently irrelevant

As you can maybe already notice, there is some heavy computation done inside the "GaborDetector" function, that depends only on the time parameter. Therefore, the "MultiscaleDetector" function unnecessarily recomputes these variables in each call.

In an attempt to avoid refactoring the code, I came up with what I wished was a nice trick, but believed it would be vain:

def MultiscaleDetector(detector, video, sigmas, tau, num_points):
    """
    Multiscale Detector

    Executes a detector at multiple scales. Detector has to be a function that
    takes a video as input, along with other parameters, and returns a list of interest points.

    
    Keyword arguments:
    detector -- function that returns interest points
    video -- input video (y_len, x_len, frames)
    sigmas -- list of scales
    """
    
    optimization_trick = lambda s_param: detector(video, s_param, tau)
    # for every scale, compute the response
    points = []
    for sigm in sigmas:
        found = optimization_trick(sigm)
        points.append(found)

    # filter the points, currently irrelevant

I hoped that the variables dependent only on tau would remain "stored" somehow in the "optimization_trick" and avoid being recomputed. However, when I timed the different implementations, the difference was around 0.02 seconds, with the "optimized" function being faster.

Aucun commentaire:

Enregistrer un commentaire