Source code for fusion.functions

"""
Direct support functions for VISWIR image processing and manipulation.
"""

# =============================================================================
# FILENAME:       functions.py
# DESCRIPTION:    Ce fichier contient les fonctions utilisées par le coeur de la
#                 fusion : "viswir_core_fusion" ; utilisées dans le cadre d'un 
#                 projet de fusion d'images Visible et SWIR : VISWIR.
#  
# REPOSITORY:     https://github.com/comsee-research/VISWIR.git
#
# AUTHOR:         [Riffard Alexandre]
# EMAIL:          [alexandre.riffard@uca.fr]
# CREATED:        [16-04-2025]
# LAST UPDATED:   [30-04-2025]
# VERSION:        1.0
#
# LICENSE:        GNU LESSER GENERAL PUBLIC LICENSE (voir LICENSE dans le dépôt)
#
# USAGE:          - Importer les fonctions depuis ce fichier dans le fichier "fusion.py".
#                 - Ces fonctions n'ont pas pour vocation à être utilisées en dehors du coeur de la fusion.
#
# DEPENDENCIES:   - numpy
#                 - skimage
#                 - cv2
#                 - scipy
#
# NOTES:
#   - ...
#
# CHANGELOG:
#   - [16-04-2025]: Création initiale du fichier à partir d'un ancien notebook.
#   - [29-04-2025]: Ajout de documentation dans certaines fonctions.
#
# =============================================================================

import numpy as np
import cv2
from scipy.signal import convolve2d
from scipy.ndimage import convolve, uniform_filter
import numpy.typing as npt



[docs] def pyramid_filter(): """ Generate a 1D Gaussian filter for pyramid construction. Returns ------- numpy.ndarray 1D Gaussian filter coefficients. """ return np.array([0.0625, 0.25, 0.375, 0.25, 0.0625]) # [1, 4, 6, 4, 1] / 16
[docs] def laplacian_pyramid(image:npt.NDArray, levels:int|None=None) -> list[npt.NDArray]: """ Build a Laplacian pyramid with explicit control over filtering and interpolation. Parameters ---------- image : numpy.ndarray Input image. levels : int, optional Number of pyramid levels. If None, it is computed automatically based on the smallest image dimension. Returns ------- list of numpy.ndarray List of Laplacian pyramid levels. """ r, c = image.shape[:2] if levels is None: levels = int(np.floor(np.log2(min(r, c)))) pyr = [] filter_1d = pyramid_filter() current_image = image for _ in range(levels - 1): # Appliquer un filtre passe-bas (filtrage Gaussien) low_pass = convolve2d(current_image, filter_1d[:, None], mode='same', boundary='symm') low_pass = convolve2d(low_pass, filter_1d[None, :], mode='same', boundary='symm') # Sous-échantillonnage downsampled = low_pass[::2, ::2] # Interpolation upsampled = np.zeros_like(current_image) upsampled[::2, ::2] = downsampled reconstructed = convolve2d(upsampled, filter_1d[:, None], mode='same', boundary='symm') reconstructed = convolve2d(reconstructed, filter_1d[None, :], mode='same', boundary='symm') # Calcul de la différence pour obtenir le niveau Laplacien laplacian = current_image - reconstructed pyr.append(laplacian) # Mise à jour de l'image courante pour le niveau suivant current_image = downsampled pyr.append(current_image) # Ajouter le dernier niveau (résidu) return pyr
[docs] def reconstruct_laplacian_pyramid(pyr): """ Reconstruct an image from a Laplacian pyramid. Parameters ---------- pyr : list of numpy.ndarray Laplacian pyramid (list of images). Returns ------- numpy.ndarray Reconstructed image. """ filter_1d = pyramid_filter() # Filtre gaussien [1, 4, 6, 4, 1] / 16 reconstructed = pyr[-1] # Niveau le plus bas de la pyramide for level in reversed(pyr[:-1]): # Upsampling avec adaptation des dimensions # upsampled = cv2.resize(reconstructed, (level.shape[1], level.shape[0]), interpolation=cv2.INTER_LINEAR) # Sur-échantillonner et filtrer avec un filtre gaussien --> [1, 4, 6, 4, 1] / 16 upsampled = np.zeros_like(level) # upsampled[::2, ::2] = 4*reconstructed upsampled[::2, ::2] = reconstructed # print(upsampled) # Appliquer un filtrage gaussien après l'upsampling filtered = convolve2d(upsampled, filter_1d[:, None], mode='same', boundary='symm') # Convolution verticale filtered = convolve2d(filtered, filter_1d[None, :], mode='same', boundary='symm') # Convolution horizontale # Ajouter le niveau courant pour reconstruire reconstructed = filtered + level #* 1.5 return reconstructed
[docs] def reconstruct_laplacian_pyramid_2(pyr): """ Alternative reconstruction of an image from a Laplacian pyramid. Parameters ---------- pyr : list of numpy.ndarray Laplacian pyramid. Returns ------- numpy.ndarray Reconstructed image. """ nlev = len(pyr) R = pyr[-1] # Commencer avec le résidu passe-bas filter = pyramid_filter() for l in range(nlev - 2, -1, -1): odd = 2 * np.array(R.shape) - np.array(pyr[l].shape) R = pyr[l] + my_upsample(R, odd, filter) return R
# Fonction utilisé pour des tests uniquement
[docs] def reconstruct_without_laplacian(pyr): """ Reconstruct an image from a pyramid without Laplacian levels. This function performs only upsampling and filtering, without adding Laplacian residuals. Parameters ---------- pyr : list of numpy.ndarray Pyramid levels. Returns ------- numpy.ndarray Reconstructed image. Notes ------- Function used for tests only. """ nlev = len(pyr) R = pyr[-1] # Commencer avec le résidu passe-bas filter = pyramid_filter() for l in range(nlev - 2, -1, -1): odd = 2 * np.array(R.shape) - np.array(pyr[l].shape) R = my_upsample(R, odd, filter) # Seulement l'upsampling et le filtrage, sans ajout du niveau courant return R
[docs] def gaussian_pyramid(image, levels): # Version avec OpenCV (fonctionne tout aussi bien que l'original) """ Build a Gaussian pyramid from the input image. Use OpenCV. Parameters ---------- image : numpy.ndarray Input image. levels : int Number of pyramid levels. Returns ------- list of numpy.ndarray List of Gaussian pyramid levels. """ pyramid = [image] for _ in range(levels - 1): image = cv2.pyrDown(image) pyramid.append(image) return pyramid
[docs] def my_upsample(image, odd, filter): """ Custom upsampling with Gaussian filtering. Parameters ---------- image : numpy.ndarray Input image to upsample. odd : tuple of int Adjustment values for odd dimensions. filter : numpy.ndarray 1D Gaussian filter. Returns ------- numpy.ndarray Upsampled and filtered image. """ # Sur-échantillonner l'image upsampled = np.zeros((image.shape[0] * 2, image.shape[1] * 2)) upsampled[::2, ::2] = image # Appliquer un filtrage gaussien après l'upsampling filtered = convolve(upsampled, filter[:, None], mode='mirror') # Convolution verticale filtered = convolve(filtered, filter[None, :], mode='mirror') # Convolution horizontale # Ajuster les dimensions si nécessaire if odd[0] > 0: filtered = filtered[:-1, :] if odd[1] > 0: filtered = filtered[:, :-1] return filtered
[docs] def downsample(image, filter=None): """ Reduce the resolution of an image by filtering and subsampling. Parameters ---------- image : numpy.ndarray Input image. filter : numpy.ndarray, optional Filter to apply before downsampling. Returns ------- numpy.ndarray Downsampled image. """ if filter is not None: image = cv2.filter2D(image, -1, filter) return cv2.pyrDown(image)
[docs] def calculate_levels(image): """ Dynamically compute the number of pyramid levels. Parameters ---------- image : numpy.ndarray Input image. Returns ------- int Number of pyramid levels. """ min_dim = min(image.shape[:2]) return int(np.floor(np.log2(min_dim)))
###---------------------------------------------------------------------------------------------------###
[docs] def local_std(image, nhood): """ Compute the local standard deviation of an image. Parameters ---------- image : numpy.ndarray Input image. nhood : numpy.ndarray Neighborhood (window) used for local statistics. Returns ------- numpy.ndarray Local standard deviation map. """ # return np.sqrt(uniform_filter(image ** 2, nhood.shape) - uniform_filter(image, nhood.shape) ** 2) return np.sqrt(np.maximum(uniform_filter(image ** 2, nhood.shape) - uniform_filter(image, nhood.shape) ** 2, 0))
[docs] def local_visibility(image, size1, sigma1, sigma2): """ Compute local visibility of an image. Parameters ---------- image : numpy.ndarray Input image. size1 : int Kernel size for Gaussian filtering. sigma1 : float Standard deviation for the first Gaussian filter. sigma2 : float Standard deviation for the second Gaussian filter. Returns ------- numpy.ndarray Local visibility map. """ gaussian1 = cv2.getGaussianKernel(size1, sigma1) gaussian2 = cv2.getGaussianKernel(size1, sigma2) IM = cv2.filter2D(image, -1, gaussian1 @ gaussian1.T) noise = image - IM return np.sqrt(cv2.filter2D(noise ** 2, -1, gaussian2 @ gaussian2.T))