def __call__(self, image: np.array, brightness: np.array) -> np.array:
     # calculate laplace cost
     l_cost = self.get_laplace_cost(image)
     l_cost = unfold(l_cost)
     l_cost = np.ceil(self.laplace_w * l_cost)
     # calculate direction costs
     d_cost = self.get_direction_cost(brightness)
     d_cost = np.ceil(self.direction_w * d_cost)
     # calculate total static cost
     total_cost = np.squeeze(l_cost + d_cost)
     return total_cost
 def calculate_single_laplace_cost(image: np.array, laplace_kernel_sz: int) -> np.array:
     laplace_map = laplace(image, ksize=laplace_kernel_sz)
     laplace_map = laplace_map[None]
     # create map of neighbouring pixels costs
     cost_map = unfold(laplace_map)
     cost_map = flatten_first_dims(np.squeeze(cost_map))
     # leave only direct neighbours
     cost_map = cost_map[[1, 3, 5, 7], :, :]
     # get max elements with the opposite sign
     signs = np.sign(laplace_map)
     opposites = cost_map * (cost_map * signs < 0)
     opposites = np.max(np.abs(opposites), axis=0)
     output = np.abs(laplace_map) > opposites
     return output
        def get_shifted_feats(directions):
            shifts = np.round(directions * k_distance).astype(np.int)
            grid = np.stack(np.meshgrid(np.arange(h), np.arange(w), indexing='ij'))

            # calculate coords of inner/outer pixels
            coords = grid + shifts
            # clip values
            coords[coords < 0] = 0
            coords[:, :, -k_distance:] = np.clip(coords[:, :, -k_distance:], 0, w - 1)
            coords[:, -k_distance:, :] = np.clip(coords[:, -k_distance:, :], 0, h - 1)
            # get required pixels
            feats = image[coords[0].reshape(-1), coords[1].reshape(-1)].reshape(h, w)
            feats = feats / (np.max(feats) + eps)
            feats = np.ceil((n_values - 1) * feats)
            feats = unfold(feats[None]).astype(np.int)
            return feats
    def get_magnitude_features(image: np.array, n_values: int, std: int) -> np.array:
        n_channels, *shape = image.shape
        grads = np.zeros((n_channels,) + tuple(shape))

        # process each RGB channel
        for i, channel in enumerate(image):
            channel = gaussian(channel, std)
            grads[i] = sobel(channel)

        # choose maximum over the channels
        grads = np.max(grads, axis=0, keepdims=True)
        grads = grads - np.min(grads)

        cost = 1 - grads / np.max(grads)
        cost = np.ceil((n_values - 1) * cost).astype(np.int)
        cost = unfold(cost).astype(int)
        return cost
    def get_direction_cost(image: np.array, eps=1e-6) -> np.array:
        # calculate vectors perpendicular to gradients
        grads = np.stack([sobel_v(image), -sobel_h(image)])
        grads /= (np.linalg.norm(grads, axis=0) + eps)

        unfolded_grads = unfold(grads)
        grads = grads[:, None, None, ...]

        # calculate dot products
        spatial_feats = create_spatial_feats(image.shape)
        link_feats = np.einsum('i..., i...', spatial_feats, grads)
        # get d_p features
        local_feats = np.abs(link_feats)
        # get d_q features
        sign_mask = np.sign(link_feats)
        distant_feats = sign_mask * np.einsum('i..., i...', spatial_feats, unfolded_grads)
        # calculate total gradient direction cost
        total_cost = 2 / (3 * np.pi) * (np.arccos(local_feats) + np.arccos(distant_feats))
        return total_cost
 def get_direct_pixel_feats(image: np.array, n_values: int) -> np.array:
     local_feats = image / np.max(image)
     local_feats = np.ceil((n_values - 1) * local_feats).astype(np.int)
     local_feats = unfold(local_feats[None]).astype(np.int)
     return local_feats