Source code for fusion.metrics

"""
Metrics computation (SSIM, NIQE, etc.) for VISWIR image quality assessment.
"""

# =============================================================================
# FILENAME:       metrics.py
# DESCRIPTION:    Ce fichier contient les fonctions de calcul des métriques de
#                 qualité d'image (avec ou sans référence) 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 pour évaluer la
#                 qualité des images fusionnées.
#
# DEPENDENCIES:   - numpy
#                 - skimage
#                 - sewar
#                 - scipy 
#                 - torch
#                 - pytorch_msssim
#                 - brisque
#
# NOTES:
#   - Toutes les images doivent être normalisées dans [0, 1] (float64 ou float32).
#   - Certaines métriques nécessitent une image de référence.
#   - Compatible avec les images monochromes ou RGB. (Certaines métriques peuvent ne pas fonctionner pour les images monochromes.)
#
# CHANGELOG:
#   - [16-04-2025]: Création initiale du fichier à partir d'un ancien notebook.
#   - [16-04-2025]-[29-04-2025]: Correction des calculs et vérification.
#   - [30-04-2025]: Ajout des métriques ERGAS, SAM et VIF (via Sewar).
#   - [01-07-2025]: Nettoyage mémoire.
#
# =============================================================================

import sys
import os
import gc

# import cv2
# from PIL import Image
import numpy as np
from scipy.stats import entropy, pearsonr
from skimage.metrics import mean_squared_error, peak_signal_noise_ratio as psnr
from skimage.metrics import structural_similarity as ssim

import torch
from pytorch_msssim import ms_ssim
import brisque
from sewar.full_ref import uqi#, msssim, ssim as sewar_ssim, scc, rmse
from sewar.full_ref import ergas, sam, vifp
# from sewar.no_ref import d_lambda, d_s, qnr

import warnings
from fusion.utils import to_grayscale_array_skimage, safe_float, from_grey_to_rgb_array#, to_rgb_array, ensure_range_255, normalize_image, ensure_grayscale, preprocess_images_for_metrics
from fusion.utils import print_image_info#, to_float32

# Import pour un appel du fichier en mode script :
from skimage.io import imread
from skimage.transform import resize

# Import for NIQE
from fusion.NIQE.niqe import calculate_niqe # Based on the Matlab version of the metrics.

# Import for PIQE
from pypiqe import piqe # A python version of Matlab's Perception based Image Quality Evaluator (PIQE) no-reference image quality score

# === Enregistreurs de métriques dynamiques ===
no_ref_metrics = {}
full_ref_metrics = {}

[docs] def register_metric(name, ref_required=False): """ Decorator to register a metric function in the appropriate registry. Parameters ---------- name : str Name of the metric. ref_required : bool, default=False Whether the metric requires a reference image. Returns ------- function Wrapped metric function. """ def decorator(func): """ Decorator to register a metric function. """ wrapped = safe(func, name) if ref_required: full_ref_metrics[name] = wrapped else: no_ref_metrics[name] = wrapped return wrapped return decorator
# === Utilitaires ===
[docs] def safe(func, name): """ Wrap a metric function to ensure safe execution. Parameters ---------- func : callable Metric function to wrap. name : str Name of the metric. Returns ------- callable Wrapped function that returns None if an exception occurs. """ def wrapper(*args, **kwargs): """ Safe execution wrapper. """ try: result = func(*args, **kwargs) return safe_float(result) except Exception as e: warnings.warn(f"Erreur dans la métrique '{name}': {e}") return None return wrapper
# === Fonction principale ===
[docs] def compute_all_metrics(I_ref=None, I_fused=None): """ Compute all registered metrics for a fused image (and optionally a reference image). Parameters ---------- I_ref : numpy.ndarray, optional Reference image (used for full-reference metrics). I_fused : numpy.ndarray Fused image. Returns ------- dict Dictionary mapping metric names to their computed values. Raises ------ ValueError If the fused image is None. """ results = {} # if I_ref is None or I_fused is None: if I_fused is None: raise ValueError("Erreur : Une des images est 'None'. Veuillez fournir des images valides.") # Conversion des types pour éviter les warnings et garantir une cohérence if I_ref is not None: if I_ref.dtype == np.uint8: I_ref = I_ref.astype(np.float32) / 255.0 elif I_ref.dtype == np.uint16: I_ref = I_ref.astype(np.float32) / 65535.0 elif I_ref.dtype != np.float32: I_ref = I_ref.astype(np.float32) if I_fused is not None: if I_fused.dtype == np.uint8: I_fused = I_fused.astype(np.float32) / 255.0 elif I_fused.dtype == np.uint16: I_fused = I_fused.astype(np.float32) / 65535.0 elif I_fused.dtype != np.float32: I_fused = I_fused.astype(np.float32) if I_fused is not None: for name, func in no_ref_metrics.items(): results[name] = func(I_fused) if I_ref is not None and I_fused is not None: for name, func in full_ref_metrics.items(): results[name] = func(I_ref, I_fused) if I_ref is not None: del I_ref del I_fused gc.collect() return results
# === Fonctions de calcule des métriques === ### Métriques simples : # Fonction pour calculer l'entropie
[docs] @register_metric("entropy", ref_required=False) def calculate_entropy(image): """ Compute the entropy of an image. Parameters ---------- image : numpy.ndarray Input image (float32, normalized to [0, 1]). Returns ------- float Entropy value of the image. """ hist, _ = np.histogram(image.flatten(), bins=256, range=[0, 1]) # Correction de la plage hist_normalized = hist / hist.sum() return float(entropy(hist_normalized))
# Fonction pour calculer l'entropie normalisée
[docs] @register_metric("normalized_entropy", ref_required=False) def calculate_entropy_normalized(image): """ Compute the normalized entropy of an image. Parameters ---------- image : numpy.ndarray Input image (float32, normalized to [0, 1]). Returns ------- float Normalized entropy value (relative to maximum entropy for 8-bit images). """ hist, _ = np.histogram(image.flatten(), bins=256, range=[0, 1]) # Correction de la plage hist_normalized = hist / hist.sum() entropy_value = entropy(hist_normalized) # Normalisation max_entropy = np.log2(256) # Pour une image 8-bit normalized_entropy = entropy_value / max_entropy return float(normalized_entropy)
# Fonction pour calculer l'écart type (standard deviation)
[docs] @register_metric("std_normalized", ref_required=False) def calculate_std(image): """ Compute the standard deviation of an image. Parameters ---------- image : numpy.ndarray Input image. Returns ------- float Standard deviation of pixel intensities. """ # image = ensure_range_255(image) return float(np.std(image))
# Fonction pour calculer le gradient moyen (mean gradient)
[docs] @register_metric("mean_gradient_normalized", ref_required=False) def calculate_mean_gradient(image): """ Compute the mean gradient magnitude of an image. Parameters ---------- image : numpy.ndarray Input image. Returns ------- float Mean gradient magnitude. """ grad_x = np.gradient(image, axis=0) grad_y = np.gradient(image, axis=1) gradient_magnitude = np.sqrt(grad_x**2 + grad_y**2) return float(np.mean(gradient_magnitude))
# Fonction pour calculer le MSE
[docs] @register_metric("mse_norm", ref_required=True) def calculate_mse(image1, image2): """ Compute the Mean Squared Error (MSE) between two images. Parameters ---------- image1 : numpy.ndarray Reference image. image2 : numpy.ndarray Fused image. Returns ------- float Mean squared error value. """ return float(mean_squared_error(image1, image2))
# Fonction pour calculer le RMSE
[docs] @register_metric("rmse_norm", ref_required=True) def calculate_rmse(image1, image2): """ Compute the Root Mean Squared Error (RMSE) between two images. Parameters ---------- image1 : numpy.ndarray Reference image. image2 : numpy.ndarray Fused image. Returns ------- float Root mean squared error value. """ return np.sqrt(mean_squared_error(image1, image2))
# Fonction pour calculer le coefficient de corrélation (entre deux images)
[docs] @register_metric("correlation", ref_required=True) def calculate_correlation(image1, image2): """ Compute the Pearson correlation coefficient between two images. Parameters ---------- image1 : numpy.ndarray Reference image. image2 : numpy.ndarray Fused image. Returns ------- float Correlation coefficient. """ return float(pearsonr(image1.flatten(), image2.flatten())[0])
[docs] @register_metric("snr_greyscale", ref_required=True) def calculate_snr(image1, image2): """ Compute the Signal-to-Noise Ratio (SNR) between two grayscale images. Parameters ---------- image1 : numpy.ndarray Reference image. image2 : numpy.ndarray Fused image. Returns ------- float Signal-to-noise ratio in decibels (dB). """ image1 = to_grayscale_array_skimage(image1) image2 = to_grayscale_array_skimage(image2) # Calculer la puissance du signal et la puissance du bruit signal_power = np.sum(image1 ** 2) noise_power = np.sum((image1 - image2) ** 2) # Éviter la division par zéro if noise_power == 0: return float('inf') # Calculer le rapport signal/bruit (SNR) snr = 10 * np.log10(signal_power / noise_power) return float(snr)
[docs] @register_metric("snr_color_per_channel", ref_required=True) def calculate_snr_color_per_channel(image1, image2): """ Compute the Signal-to-Noise Ratio (SNR) per channel for two color images. Parameters ---------- image1 : numpy.ndarray Reference image (color). image2 : numpy.ndarray Fused image (color). Returns ------- float Average SNR across all channels (in dB). """ snrs = [] for c in range(image1.shape[-1]): signal_power = np.sum(image1[..., c] ** 2) noise_power = np.sum((image1[..., c] - image2[..., c]) ** 2) if noise_power == 0: snr_c = float('inf') else: snr_c = 10 * np.log10(signal_power / noise_power) snrs.append(snr_c) return float(np.mean(snrs))
[docs] @register_metric("snr_color_global", ref_required=True) def calculate_snr_color(image1, image2): """ Compute the global Signal-to-Noise Ratio (SNR) between two color images. Parameters ---------- image1 : numpy.ndarray Reference image (color). image2 : numpy.ndarray Fused image (color). Returns ------- float Global SNR value (in dB). """ signal_power = np.sum(image1 ** 2) noise_power = np.sum((image1 - image2) ** 2) if noise_power == 0: return float('inf') snr = 10 * np.log10(signal_power / noise_power) return float(snr)
# Fonction pour calculer le PSNR
[docs] @register_metric("psnr", ref_required=True) def calculate_psnr(image1, image2): """ Compute the Peak Signal-to-Noise Ratio (PSNR) between two images. Parameters ---------- image1 : numpy.ndarray Reference image. image2 : numpy.ndarray Fused image. Returns ------- float PSNR value in decibels (dB). """ return float(psnr(image1, image2, data_range=1))
[docs] @register_metric("spatial_frequency_color_norm", ref_required=False) def calculate_spatial_frequency_color(image): # Canal par canal. """ Compute the spatial frequency of a color image (per channel). Parameters ---------- image : numpy.ndarray Input image (color or grayscale). Returns ------- float Average spatial frequency across channels. """ if image.ndim == 3: sf_channels = [] for c in range(image.shape[2]): dx = np.diff(image[..., c], axis=1) dy = np.diff(image[..., c], axis=0) sf = np.sqrt(np.mean(dx**2) + np.mean(dy**2)) sf_channels.append(sf) return float(np.mean(sf_channels)) # ou np.linalg.norm(sf_channels) else: return calculate_spatial_frequency_grey(image)
[docs] @register_metric("spatial_frequency_grey_norm", ref_required=False) def calculate_spatial_frequency_grey(image): """ Compute the spatial frequency of a grayscale image. Parameters ---------- image : numpy.ndarray Input grayscale image. Returns ------- float Spatial frequency value. """ image = to_grayscale_array_skimage(image) dx = np.diff(image, axis=1) dy = np.diff(image, axis=0) sf = float(np.sqrt(np.mean(dx**2) + np.mean(dy**2))) return sf
# Fonction pour calculer l'intensité moyenne des pixels
[docs] @register_metric("mean_intensity_norm", ref_required=False) def calculate_mean_intensity(image): """ Compute the mean pixel intensity of an image. Parameters ---------- image : numpy.ndarray Input image. Returns ------- float Mean intensity value. """ # image = ensure_range_255(image) return float(np.mean(image))
### Métriques avancées : @register_metric("ssim_grey", ref_required=True) def calculate_ssim(image1, image2): # (Structural Similarity Index Measure) """ Compute the Structural Similarity Index (SSIM) between two grayscale images. Parameters ---------- image1 : numpy.ndarray Reference grayscale image. image2 : numpy.ndarray Fused grayscale image. Returns ------- float SSIM value in [0, 1]. """ image1_gray = to_grayscale_array_skimage(image1) image2_gray = to_grayscale_array_skimage(image2) return float(ssim(image1_gray, image2_gray, data_range=1.0))
[docs] @register_metric("ssim_color", ref_required=True) def calculate_ssim(image1, image2): # (Structural Similarity Index Measure) """ Compute the Structural Similarity Index (SSIM) between two color images. Parameters ---------- image1 : numpy.ndarray Reference color image. image2 : numpy.ndarray Fused color image. Returns ------- float SSIM value in [0, 1]. """ return float(ssim(image1, image2, data_range=1.0, channel_axis=-1))
[docs] @register_metric("ms_ssim_pytorch_greyscale", ref_required=True) def calculate_ms_ssim_pytorch(image1, image2): """ Compute the Multi-Scale Structural Similarity Index (MS-SSIM) between two grayscale images using PyTorch. Parameters ---------- image1 : numpy.ndarray Reference grayscale image (float32, normalized [0, 1]). image2 : numpy.ndarray Fused grayscale image (float32, normalized [0, 1]). Returns ------- float MS-SSIM value in [0, 1]. """ image1_grey = to_grayscale_array_skimage(image1) image2_grey = to_grayscale_array_skimage(image2) # Convertir en tenseurs sans rediviser par 255.0 (déjà en float32 [0,1]) img1_tensor = torch.from_numpy(image1_grey).unsqueeze(0).unsqueeze(0) img2_tensor = torch.from_numpy(image2_grey).unsqueeze(0).unsqueeze(0) result = float(ms_ssim(img1_tensor, img2_tensor, data_range=1.0).item()) return result
[docs] @register_metric("ms_ssim_pytorch_color", ref_required=True) def calculate_ms_ssim_pytorch_color(image1, image2): """ Compute the Multi-Scale Structural Similarity Index (MS-SSIM) between two color images using PyTorch. Parameters ---------- image1 : numpy.ndarray Reference color image (float32, normalized [0, 1]). image2 : numpy.ndarray Fused color image (float32, normalized [0, 1]). Returns ------- float MS-SSIM value in [0, 1]. """ # Vérifie que les images sont bien au format attendu : (H, W, 3), float32, [0,1] assert image1.ndim == 3 and image1.shape[2] == 3, "image1 n'est pas une image couleur" assert image2.ndim == 3 and image2.shape[2] == 3, "image2 n'est pas une image couleur" assert image1.dtype == np.float32 and image2.dtype == np.float32, "Les images doivent être en float32" assert image1.min() >= 0.0 and image1.max() <= 1.0, "Les valeurs doivent être dans [0,1]" assert image2.min() >= 0.0 and image2.max() <= 1.0, "Les valeurs doivent être dans [0,1]" # Convertir (H, W, C) → (1, C, H, W) img1_tensor = torch.from_numpy(np.transpose(image1, (2, 0, 1))).unsqueeze(0) img2_tensor = torch.from_numpy(np.transpose(image2, (2, 0, 1))).unsqueeze(0) result = float(ms_ssim(img1_tensor, img2_tensor, data_range=1.0).item()) return result
[docs] @register_metric("brisque", ref_required=False) def calculate_brisque(image): """ Compute the BRISQUE (Blind/Referenceless Image Spatial Quality Evaluator) score. Parameters ---------- image : numpy.ndarray Input image (converted to RGB if necessary). Returns ------- float BRISQUE score (lower is better). """ # image = to_rgb_array(image) image = from_grey_to_rgb_array(image) # conversion en RGB si nécessaire # Créer une instance du calculateur BRISQUE brisque_calculator = brisque.BRISQUE() # Calculer et retourner le score BRISQUE score = brisque_calculator.score(image) return float(score)
[docs] @register_metric("gmsd_norm_grey", ref_required=True) def calculate_gmsd(image1, image2): # (Gradient Magnitude Similarity Deviation) """ Compute the Gradient Magnitude Similarity Deviation (GMSD) between two grayscale images. Parameters ---------- image1 : numpy.ndarray Reference grayscale image. image2 : numpy.ndarray Fused grayscale image. Returns ------- float GMSD value (lower indicates higher similarity). """ image1_gray = to_grayscale_array_skimage(image1) image2_gray = to_grayscale_array_skimage(image2) gx1, gy1 = np.gradient(image1_gray) gx2, gy2 = np.gradient(image2_gray) magnitude1 = np.sqrt(gx1**2 + gy1**2) magnitude2 = np.sqrt(gx2**2 + gy2**2) gms = (2 * magnitude1 * magnitude2 + 0.01) / (magnitude1**2 + magnitude2**2 + 0.01) return float(np.std(gms))
[docs] @register_metric("gmsd_norm_color", ref_required=True) def calculate_gmsd_color(image1, image2): """ Compute the Gradient Magnitude Similarity Deviation (GMSD) between two color images. Parameters ---------- image1 : numpy.ndarray Reference color image. image2 : numpy.ndarray Fused color image. Returns ------- float Average GMSD across channels. """ assert image1.shape[-1] == 3 and image2.shape[-1] == 3, "Les images doivent être en couleur (3 canaux)." gmsd_per_channel = [] for c in range(3): # Pour R, G, B img1_c = image1[..., c] img2_c = image2[..., c] gx1, gy1 = np.gradient(img1_c) gx2, gy2 = np.gradient(img2_c) mag1 = np.sqrt(gx1**2 + gy1**2) mag2 = np.sqrt(gx2**2 + gy2**2) gms = (2 * mag1 * mag2 + 0.01) / (mag1**2 + mag2**2 + 0.01) gmsd = np.std(gms) gmsd_per_channel.append(gmsd) result = float(np.mean(gmsd_per_channel)) return result
[docs] @register_metric("MAD_norm_grey", ref_required=True) def calculate_mad(image1, image2): # (Mean Absolute Deviation) """ Compute the Mean Absolute Deviation (MAD) between two grayscale images. Parameters ---------- image1 : numpy.ndarray Reference grayscale image. image2 : numpy.ndarray Fused grayscale image. Returns ------- float MAD value. """ image1_gray = to_grayscale_array_skimage(image1) image2_gray = to_grayscale_array_skimage(image2) return float(np.mean(np.abs(image1_gray - image2_gray)))
[docs] @register_metric("MAD_norm_color", ref_required=True) def calculate_mad_color(image1, image2): """ Compute the Mean Absolute Deviation (MAD) between two color images. Parameters ---------- image1 : numpy.ndarray Reference color image. image2 : numpy.ndarray Fused color image. Returns ------- float MAD value. """ assert image1.shape == image2.shape, "Les images doivent avoir la même forme." assert image1.ndim == 3 and image1.shape[-1] == 3, "Les images doivent être en couleur (3 canaux)." mad = float(np.mean(np.abs(image1 - image2))) return mad
@register_metric("UQI_grey", ref_required=True) def calculate_uqi(image1, image2): # (Universal Image Quality Index) """ Compute the Universal Image Quality Index (UQI) between two grayscale images. Parameters ---------- image1 : numpy.ndarray Reference grayscale image. image2 : numpy.ndarray Fused grayscale image. Returns ------- float UQI value in [-1, 1], where 1 indicates perfect similarity. """ image1_gray = to_grayscale_array_skimage(image1) image2_gray = to_grayscale_array_skimage(image2) return float(uqi(image1_gray, image2_gray))
[docs] @register_metric("UQI_color", ref_required=True) def calculate_uqi(image1, image2): # (Universal Image Quality Index) """ Compute the Universal Image Quality Index (UQI) between two color images. Parameters ---------- image1 : numpy.ndarray Reference color image. image2 : numpy.ndarray Fused color image. Returns ------- float UQI value in [-1, 1], where 1 indicates perfect similarity. """ return float(uqi(image1, image2))
### Autre Métriques issus de Sewar 0.4 : "https://github.com/andrewekhalel/sewar.git" # ERGAS - métrique avec référence
[docs] @register_metric("ergas", ref_required=True) def calculate_ergas(image1, image2, ratio=0.25): """ Compute the ERGAS (Relative Dimensionless Global Error of Synthesis). Parameters ---------- image1 : numpy.ndarray Reference image (ground truth), normalized to [0, 1]. image2 : numpy.ndarray Fused image, normalized to [0, 1]. ratio : float, default=0.25 Ratio between pixel sizes of the reference and fused images. Returns ------- float ERGAS value (lower is better). """ return float(ergas(image1, image2, r=ratio))
# SAM - métrique avec référence
[docs] @register_metric("sam", ref_required=True) def calculate_sam(image1, image2): """ Compute the Spectral Angle Mapper (SAM) between two images. Parameters ---------- image1 : numpy.ndarray Reference image (ground truth), normalized to [0, 1]. image2 : numpy.ndarray Fused image, normalized to [0, 1]. Returns ------- float SAM value in radians (lower is better). """ return float(sam(image1, image2))
# VIF - métrique avec référence
[docs] @register_metric("vif", ref_required=True) def calculate_vif(image1, image2, sigma_nsq=2): """ Compute the Visual Information Fidelity (VIF). Parameters ---------- image1 : numpy.ndarray Reference image (ground truth), normalized to [0, 1]. image2 : numpy.ndarray Fused image, normalized to [0, 1]. sigma_nsq : float, default=2 Variance of the visual noise. Returns ------- float VIF value (higher is better). """ return float(vifp(image1, image2, sigma_nsq=sigma_nsq))
#################################################### External Metrics
[docs] @register_metric("niqe", ref_required=False) def calculate_niqe_metric(image): """ Compute the NIQE (Natural Image Quality Evaluator) score without reference. Parameters ---------- image : numpy.ndarray Input image. Returns ------- float NIQE score (lower is better). """ img_gray = to_grayscale_array_skimage(image) score = calculate_niqe(img_gray) return float(score)
[docs] @register_metric("piqe", ref_required=False) def calculate_piqe(image): """ Compute the PIQE (Perception-based Image Quality Evaluator) score without reference. Parameters ---------- image : numpy.ndarray Input image. Returns ------- float PIQE score (lower is better). """ score, _, _, _ = piqe(image) return float(score)
#################################################### Non functional from Sewar # # D_lambda - sans référence # @register_metric("d_lambda", ref_required=True) # def calculate_d_lambda(ms_image, fused_image, p=1): # """ # Spectral Distortion Index (D_lambda) # Args: # ms_image (np.ndarray): Image multispectrale basse résolution, normalisée entre 0 et 1. # fused_image (np.ndarray): Image fusionnée haute résolution, normalisée entre 0 et 1. # Returns: # float: Valeur D_lambda. # """ # return float(d_lambda(ms_image, fused_image, p=p)) # # D_S - sans référence # @register_metric("d_s", ref_required=True) # def calculate_d_s(pan_image, ms_image, fused_image, q=1, r=4, ws=7): # """ # Spatial Distortion Index (D_S) # Args: # pan_image (np.ndarray): Image panchromatique haute résolution, normalisée entre 0 et 1. # ms_image (np.ndarray): Image multispectrale basse résolution, normalisée entre 0 et 1. # fused_image (np.ndarray): Image fusionnée, normalisée entre 0 et 1. # Returns: # float: Valeur D_S. # """ # return float(d_s(pan_image, ms_image, fused_image, q=q, r=r, ws=ws)) # # QNR - sans référence # @register_metric("qnr", ref_required=True) # def calculate_qnr(pan_image, ms_image, fused_image, alpha=1, beta=1, p=1, q=1, r=4, ws=7): # """ # Quality with No Reference (QNR) # Args: # pan_image (np.ndarray): Image panchromatique haute résolution, normalisée entre 0 et 1. # ms_image (np.ndarray): Image multispectrale basse résolution, normalisée entre 0 et 1. # fused_image (np.ndarray): Image fusionnée, normalisée entre 0 et 1. # Returns: # float: Valeur QNR. # """ # return float(qnr(pan_image, ms_image, fused_image, alpha=alpha, beta=beta, p=p, q=q, r=r, ws=ws)) #================================= DEBUG =================================# # print(f"\n[{tag}] dtype: {image.dtype}, min: {image.min()}, max: {image.max()}, shape: {image.shape}") if __name__ == "__main__": print(f"[INFO] Métriques sans référence : {list(no_ref_metrics.keys())}") print(f"[INFO] Métriques avec référence : {list(full_ref_metrics.keys())}") # img_file = cv2.imread(r"C:\Users\Riffard\Documents\Datasets\Fusion\Tests_carte_de_poids_et_pyramides\_test_VISWIR\temp2\proc\weight_0.70\beta_1.5\level_4\no_gamma\0003_fused_with_post_processing.tiff", cv2.IMREAD_GRAYSCALE) # ref_image_path = r"C:\Users\Riffard\Documents\Datasets\Cerema-17-18-June\21\4 - TarDal\Visible\0002.jpg" # ref_file = cv2.imread(ref_image_path, cv2.IMREAD_GRAYSCALE) # metric_file = calculate_brisque(img_file) # print("Metrique depuis fichier :", metric_file) if len(sys.argv) < 2: print("Usage : python metrics.py image_fusionnee.png [image_reference.png]") sys.exit(1) fused_path = sys.argv[1] ref_path = sys.argv[2] if len(sys.argv) > 2 else None if not os.path.exists(fused_path): print(f"Erreur : le fichier {fused_path} n'existe pas.") sys.exit(1) I_fused = imread(fused_path) if I_fused.ndim == 3 and I_fused.shape[2] == 4: print("[INFO] L’image fusionnée contient 4 canaux. Suppression du 4e (transparence ?).") I_fused = I_fused[:, :, :3] print("\n[INFO] Image fusionnée :") print_image_info(I_fused) I_ref = None if ref_path: if not os.path.exists(ref_path): print(f"Erreur : le fichier {ref_path} n'existe pas.") sys.exit(1) I_ref = imread(ref_path) print("\n[INFO] Image de référence :") print_image_info(I_ref) if I_fused.shape != I_ref.shape: print("[INFO] Redimensionnement de l'image fusionnée pour correspondre à l'image de référence") I_fused = resize(I_fused, I_ref.shape, preserve_range=True, anti_aliasing=True).astype(I_ref.dtype) metrics = compute_all_metrics(I_ref=I_ref, I_fused=I_fused) print("\n[RESULTATS] Métriques calculées :") for k, v in metrics.items(): if v is not None: print(f" {k}: {v:.6f}") else: print(f" {k}: Erreur ou valeur non calculée")