"""
Модуль многокритериального анализа на основе фронта Парето.
Реализует анализ производительности алгоритмов слепой деконволюции
по множеству критериев с построением и визуализацией фронта Парето.
Возможности:
- 3D визуализация поверхности Парето
- 2D проекции для попарного анализа критериев
- Статистическое сравнение алгоритмов
- Тепловые карты производительности
Автор: Беззаборов А.А.
"""
from __future__ import annotations
import os
import warnings
from pathlib import Path
from typing import Dict, Any, Optional, List
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.axes import Axes
import cv2 as cv
from .base import ProcessingExtension, logger
[документация]
class ParetoFrontAnalyzer(ProcessingExtension):
"""
Многокритериальный анализ производительности с построением фронта Парето.
Предоставляет комплексный анализ производительности алгоритмов
по нескольким конкурирующим критериям (качество vs скорость,
устойчивость к шуму vs размытию).
Фронт Парето представляет множество недоминируемых решений, где
ни один критерий не может быть улучшен без ухудшения другого.
Возможности:
- 3D визуализация поверхности Парето (шум x размытие x качество)
- 2D проекции для попарного анализа критериев
- Статистическое сравнение алгоритмов
- Тепловые карты производительности
Точка x* является Парето-оптимальной, если не существует другой точки x,
такой что f_i(x) >= f_i(x*) для всех критериев i, и f_j(x) > f_j(x*)
хотя бы для одного критерия j.
"""
# Параметры визуализации
FIGURE_DPI = 300
COLORMAP = 'viridis'
MARKER_SIZE = 60
LINE_WIDTH = 2
ALPHA = 0.7
[документация]
def __init__(
self,
processing_instance: Any,
output_folder: str = "pareto_analysis"
):
"""
Инициализация анализатора Парето.
Параметры
---------
processing_instance : Any
Ссылка на объект Processing.
output_folder : str
Директория для сохранения результатов анализа.
"""
super().__init__(processing_instance, output_folder)
# Подавление предупреждений matplotlib
warnings.filterwarnings('ignore', category=UserWarning)
# Настройка стиля графиков
try:
plt.style.use('seaborn-v0_8-whitegrid')
except OSError:
plt.style.use('ggplot')
[документация]
def execute(self, save_figures: bool = True) -> pd.DataFrame:
"""
Выполнение комплексного анализа фронта Парето.
Параметры
---------
save_figures : bool, по умолчанию True
Сохранять сгенерированные графики.
Возвращает
----------
pd.DataFrame
DataFrame со всеми данными анализа.
"""
logger.info("=" * 60)
logger.info("PARETO FRONT ANALYSIS")
logger.info("=" * 60)
# Сбор данных
data = self._collect_analysis_data()
if not data:
logger.error("No data available for analysis")
return pd.DataFrame()
df = pd.DataFrame(data)
logger.info(f"Collected {len(df)} data points for analysis")
# Генерация визуализаций
if len(df) >= 10:
self._plot_3d_pareto_surfaces(df, save_figures)
else:
logger.warning(
f"Insufficient data for 3D analysis ({len(df)} points, need >= 10)"
)
self._plot_2d_pareto_fronts(df, save_figures)
self._plot_algorithm_comparison(df, save_figures)
self._plot_performance_heatmap(df, save_figures)
# Статистический анализ
self._print_statistical_summary(df)
self._print_pareto_optimal_solutions(df)
# Сохранение данных в CSV
if save_figures:
csv_path = self.output_folder / 'analysis_data.csv'
df.to_csv(csv_path, index=False, encoding='utf-8')
logger.info(f"Data saved to {csv_path}")
return df
def _collect_analysis_data(self) -> List[Dict[str, Any]]:
"""
Сбор данных производительности из обработанных изображений.
Возвращает
----------
List[Dict]
Список словарей с метриками производительности.
"""
analysis_data = []
for img in self.processing.images:
try:
original = img.get_original_image()
if original is None:
continue
blurred_array = img.get_blurred_array()
algorithms = img.get_algorithm()
for blurred_path in blurred_array:
blurred = self._load_image(blurred_path)
if blurred is None:
continue
# Вычисление характеристик деградации
blur_intensity = self._compute_blur_intensity(blurred)
noise_intensity = self._compute_noise_intensity(blurred)
blurred_psnr = img.get_blurred_PSNR().get(str(blurred_path), np.nan)
blurred_ssim = img.get_blurred_SSIM().get(str(blurred_path), np.nan)
for alg_name in algorithms:
restored_psnr = img.get_PSNR().get(
(str(blurred_path), str(alg_name)), np.nan
)
restored_ssim = img.get_SSIM().get(
(str(blurred_path), str(alg_name)), np.nan
)
# Получение времени обработки при наличии
process_time = self._get_process_time(
img, blurred_path, alg_name
)
# Пропуск некорректных данных
if np.isnan(restored_psnr):
continue
analysis_data.append({
'image': os.path.basename(img.get_original()),
'algorithm': alg_name,
'filter': img.get_filters().get(str(blurred_path), 'unknown'),
'blur_intensity': blur_intensity,
'noise_intensity': noise_intensity,
'psnr': restored_psnr,
'ssim': restored_ssim,
'psnr_improvement': restored_psnr - blurred_psnr,
'ssim_improvement': restored_ssim - blurred_ssim,
'original_psnr': blurred_psnr,
'time': process_time
})
except Exception as e:
logger.warning(f"Error processing {img.get_original()}: {e}")
continue
return analysis_data
def _load_image(self, path: str) -> Optional[np.ndarray]:
"""
Загрузка изображения по указанному пути.
Параметры
---------
path : str
Путь к файлу изображения.
Возвращает
----------
Optional[np.ndarray]
Загруженное изображение или None при ошибке.
"""
try:
if not os.path.exists(path):
return None
image = cv.imread(path, cv.IMREAD_GRAYSCALE)
return image.astype(np.float32) if image is not None else None
except Exception as e:
logger.warning(f"Failed to load {path}: {e}")
return None
def _compute_blur_intensity(self, image: np.ndarray) -> float:
"""
Вычисление интенсивности размытия через дисперсию лапласиана.
Дисперсия лапласиана является мерой резкости изображения.
Низкие значения указывают на большее размытие.
Параметры
---------
image : np.ndarray
Входное изображение.
Возвращает
----------
float
Нормализованная интенсивность размытия в диапазоне [0, 1].
"""
try:
laplacian = cv.Laplacian(image.astype(np.uint8), cv.CV_64F)
variance = laplacian.var()
# Нормализация: высокая дисперсия = меньше размытия, инвертируем
return 1.0 - min(variance / 1000.0, 1.0)
except Exception:
return np.nan
def _compute_noise_intensity(self, image: np.ndarray) -> float:
"""
Оценка интенсивности шума по гладким областям изображения.
Использует блочную оценку стандартного отклонения в
предположительно гладких областях изображения.
Параметры
---------
image : np.ndarray
Входное изображение.
Возвращает
----------
float
Нормализованная интенсивность шума в диапазоне [0, 1].
"""
try:
h, w = image.shape
block_size = 32
std_values = []
for i in range(0, h - block_size, block_size):
for j in range(0, w - block_size, block_size):
block = image[i:i+block_size, j:j+block_size]
# MAD (медианное абсолютное отклонение) для робастной оценки
median = np.median(block)
mad = np.median(np.abs(block - median))
std_values.append(mad * 1.4826) # Масштабирование к эквиваленту std
if not std_values:
return np.nan
# Используем минимальное std как оценку шума (самая гладкая область)
noise_estimate = np.percentile(std_values, 10)
return min(noise_estimate / 50.0, 1.0)
except Exception:
return np.nan
def _get_process_time(
self,
img: Any,
blurred_path: str,
alg_name: str
) -> float:
"""
Получение времени обработки при наличии.
Параметры
---------
img : Any
Объект изображения.
blurred_path : str
Путь к размытому изображению.
alg_name : str
Имя алгоритма.
Возвращает
----------
float
Время обработки или NaN при отсутствии данных.
"""
if not hasattr(img, 'get_process_time'):
return np.nan
try:
time_data = img.get_process_time()
if isinstance(time_data, dict):
return time_data.get((str(blurred_path), str(alg_name)), np.nan)
return float(time_data)
except Exception:
return np.nan
def _find_pareto_front(
self,
points: np.ndarray,
maximize: List[bool]
) -> np.ndarray:
"""
Поиск Парето-оптимальных точек.
Использует алгоритм недоминируемой сортировки.
Параметры
---------
points : np.ndarray
Массив размера (n_points, n_objectives).
maximize : List[bool]
Направление оптимизации для каждого критерия.
Возвращает
----------
np.ndarray
Булева маска, указывающая на Парето-оптимальные точки.
"""
n_points = len(points)
is_pareto = np.ones(n_points, dtype=bool)
# Преобразование к задаче максимизации
points_max = points.copy()
for i, m in enumerate(maximize):
if not m:
points_max[:, i] = -points_max[:, i]
for i in range(n_points):
if not is_pareto[i]:
continue
for j in range(n_points):
if i == j or not is_pareto[j]:
continue
# Проверка доминирования j над i
if np.all(points_max[j] >= points_max[i]) and \
np.any(points_max[j] > points_max[i]):
is_pareto[i] = False
break
return is_pareto
def _plot_3d_pareto_surfaces(
self,
df: pd.DataFrame,
save: bool = True
) -> None:
"""
Построение 3D визуализации поверхности Парето.
Параметры
---------
df : pd.DataFrame
Данные для анализа.
save : bool
Сохранять график в файл.
"""
from mpl_toolkits.mplot3d import Axes3D
from scipy.interpolate import Rbf
fig = plt.figure(figsize=(20, 16))
algorithms = df['algorithm'].unique()
colors = plt.cm.Set2(np.linspace(0, 1, len(algorithms)))
# График 1: 3D точки с поверхностями
ax1 = fig.add_subplot(221, projection='3d')
for i, alg in enumerate(algorithms):
alg_data = df[df['algorithm'] == alg]
if len(alg_data) < 4:
ax1.scatter(
alg_data['noise_intensity'],
alg_data['blur_intensity'],
alg_data['psnr'],
c=[colors[i]], label=alg, s=self.MARKER_SIZE, alpha=0.8
)
continue
x = alg_data['noise_intensity'].values
y = alg_data['blur_intensity'].values
z = alg_data['psnr'].values
try:
# Создание интерполированной поверхности
xi = np.linspace(x.min(), x.max(), 15)
yi = np.linspace(y.min(), y.max(), 15)
xi, yi = np.meshgrid(xi, yi)
rbf = Rbf(x, y, z, function='thin_plate', smooth=0.5)
zi = rbf(xi, yi)
ax1.plot_surface(
xi, yi, zi, alpha=0.4, color=colors[i]
)
ax1.scatter(x, y, z, c=[colors[i]], label=alg, s=30, alpha=0.9)
except Exception as e:
logger.debug(f"Surface interpolation failed for {alg}: {e}")
ax1.scatter(x, y, z, c=[colors[i]], label=alg, s=self.MARKER_SIZE)
ax1.set_xlabel('Noise Intensity', fontsize=12)
ax1.set_ylabel('Blur Intensity', fontsize=12)
ax1.set_zlabel('PSNR (dB)', fontsize=12)
ax1.set_title('3D Pareto Surface: Degradation vs Quality', fontsize=14)
ax1.legend(loc='upper left', fontsize=10)
# График 2: Поверхность улучшения PSNR
ax2 = fig.add_subplot(222, projection='3d')
if 'psnr_improvement' in df.columns:
for i, alg in enumerate(algorithms):
alg_data = df[df['algorithm'] == alg]
ax2.scatter(
alg_data['noise_intensity'],
alg_data['blur_intensity'],
alg_data['psnr_improvement'],
c=[colors[i]], label=alg, s=self.MARKER_SIZE, alpha=0.8
)
ax2.set_xlabel('Noise Intensity')
ax2.set_ylabel('Blur Intensity')
ax2.set_zlabel('PSNR Improvement (dB)')
ax2.set_title('PSNR Improvement by Degradation Level')
# График 3: 2D проекция - Шум vs PSNR
ax3 = fig.add_subplot(223)
self._plot_2d_with_pareto(
ax3, df, 'noise_intensity', 'psnr',
'Noise Intensity', 'PSNR (dB)',
'Pareto Front: Noise Robustness',
maximize_x=False, maximize_y=True
)
# График 4: 2D проекция - Размытие vs PSNR
ax4 = fig.add_subplot(224)
self._plot_2d_with_pareto(
ax4, df, 'blur_intensity', 'psnr',
'Blur Intensity', 'PSNR (dB)',
'Pareto Front: Blur Robustness',
maximize_x=False, maximize_y=True
)
plt.tight_layout()
if save:
filepath = self.output_folder / '3d_pareto_analysis.png'
plt.savefig(filepath, dpi=self.FIGURE_DPI, bbox_inches='tight')
logger.info(f"Saved 3D analysis to {filepath}")
plt.show()
def _plot_2d_with_pareto(
self,
ax: Axes,
df: pd.DataFrame,
x_col: str,
y_col: str,
x_label: str,
y_label: str,
title: str,
maximize_x: bool = True,
maximize_y: bool = True
) -> None:
"""
Построение 2D графика с выделенным фронтом Парето.
Параметры
---------
ax : Axes
Объект осей matplotlib.
df : pd.DataFrame
Данные.
x_col, y_col : str
Имена столбцов для осей x и y.
x_label, y_label : str
Подписи осей.
title : str
Заголовок графика.
maximize_x, maximize_y : bool
Направление оптимизации для каждой оси.
"""
algorithms = df['algorithm'].unique()
colors = plt.cm.Set2(np.linspace(0, 1, len(algorithms)))
# Отображение всех точек
for i, alg in enumerate(algorithms):
alg_data = df[df['algorithm'] == alg]
ax.scatter(
alg_data[x_col], alg_data[y_col],
c=[colors[i]], label=alg, s=self.MARKER_SIZE, alpha=self.ALPHA
)
# Поиск и выделение фронта Парето
valid_data = df[[x_col, y_col]].dropna()
if len(valid_data) > 0:
points = valid_data.values
pareto_mask = self._find_pareto_front(points, [maximize_x, maximize_y])
pareto_points = df.loc[valid_data.index[pareto_mask]]
if len(pareto_points) > 1:
# Сортировка для построения линии
pareto_sorted = pareto_points.sort_values(x_col)
ax.plot(
pareto_sorted[x_col], pareto_sorted[y_col],
'r--', linewidth=self.LINE_WIDTH, label='Pareto Front',
zorder=10
)
ax.set_xlabel(x_label, fontsize=11)
ax.set_ylabel(y_label, fontsize=11)
ax.set_title(title, fontsize=12)
ax.legend(fontsize=9)
ax.grid(True, alpha=0.3)
def _plot_2d_pareto_fronts(
self,
df: pd.DataFrame,
save: bool = True
) -> None:
"""
Построение 2D визуализаций фронта Парето.
Параметры
---------
df : pd.DataFrame
Данные для анализа.
save : bool
Сохранять график в файл.
"""
fig, axes = plt.subplots(2, 2, figsize=(14, 12))
pareto_configs = [
('psnr', 'time', 'PSNR (dB)', 'Time (s)',
'Quality vs Speed', True, False),
('psnr', 'noise_intensity', 'PSNR (dB)', 'Noise Intensity',
'Quality vs Noise Robustness', True, False),
('ssim', 'blur_intensity', 'SSIM', 'Blur Intensity',
'Structural Similarity vs Blur', True, False),
('psnr_improvement', 'noise_intensity', 'PSNR Improvement (dB)',
'Noise Intensity', 'Improvement vs Noise', True, False)
]
for idx, (x_col, y_col, x_label, y_label, title, max_x, max_y) in enumerate(pareto_configs):
ax = axes[idx // 2, idx % 2]
if x_col in df.columns and y_col in df.columns:
valid = df[[x_col, y_col, 'algorithm']].dropna()
if len(valid) > 0:
self._plot_2d_with_pareto(
ax, valid, x_col, y_col, x_label, y_label, title, max_x, max_y
)
else:
ax.text(0.5, 0.5, f"Data not available\n({x_col}, {y_col})",
ha='center', va='center', transform=ax.transAxes)
ax.set_title(title)
plt.tight_layout()
if save:
filepath = self.output_folder / '2d_pareto_fronts.png'
plt.savefig(filepath, dpi=self.FIGURE_DPI, bbox_inches='tight')
logger.info(f"Saved 2D fronts to {filepath}")
plt.show()
def _plot_algorithm_comparison(
self,
df: pd.DataFrame,
save: bool = True
) -> None:
"""
Построение визуализаций для сравнения алгоритмов.
Параметры
---------
df : pd.DataFrame
Данные для анализа.
save : bool
Сохранять график в файл.
"""
fig, axes = plt.subplots(2, 2, figsize=(14, 12))
algorithms = df['algorithm'].unique()
colors = plt.cm.Set2(np.linspace(0, 1, len(algorithms)))
# График 1: Box plot PSNR по алгоритмам
ax1 = axes[0, 0]
df.boxplot(column='psnr', by='algorithm', ax=ax1)
ax1.set_title('PSNR Distribution by Algorithm', fontsize=12)
ax1.set_xlabel('Algorithm')
ax1.set_ylabel('PSNR (dB)')
plt.sca(ax1)
plt.xticks(rotation=45, ha='right')
# График 2: Среднее PSNR с доверительными интервалами
ax2 = axes[0, 1]
grouped = df.groupby('algorithm')['psnr'].agg(['mean', 'std'])
x_pos = range(len(grouped))
ax2.bar(x_pos, grouped['mean'], yerr=grouped['std'],
color=colors[:len(grouped)], capsize=5, alpha=0.8)
ax2.set_xticks(x_pos)
ax2.set_xticklabels(grouped.index, rotation=45, ha='right')
ax2.set_ylabel('PSNR (dB)')
ax2.set_title('Mean PSNR with Standard Deviation', fontsize=12)
ax2.grid(True, alpha=0.3, axis='y')
# График 3: Улучшение PSNR по алгоритмам
ax3 = axes[1, 0]
if 'psnr_improvement' in df.columns:
improvement = df.groupby('algorithm')['psnr_improvement'].mean()
bars = ax3.bar(range(len(improvement)), improvement.values,
color=colors[:len(improvement)], alpha=0.8)
ax3.set_xticks(range(len(improvement)))
ax3.set_xticklabels(improvement.index, rotation=45, ha='right')
ax3.set_ylabel('Average PSNR Improvement (dB)')
ax3.set_title('Restoration Improvement', fontsize=12)
ax3.axhline(y=0, color='black', linestyle='-', linewidth=0.5)
ax3.grid(True, alpha=0.3, axis='y')
# Добавление подписей значений
for bar, val in zip(bars, improvement.values):
ax3.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.1,
f'{val:.1f}', ha='center', va='bottom', fontsize=9)
# График 4: Производительность по типам фильтров
ax4 = axes[1, 1]
if 'filter' in df.columns:
pivot = df.pivot_table(
values='psnr', index='algorithm', columns='filter', aggfunc='mean'
)
pivot.plot(kind='bar', ax=ax4, width=0.8, alpha=0.8)
ax4.set_ylabel('PSNR (dB)')
ax4.set_title('Performance by Filter Type', fontsize=12)
ax4.legend(title='Filter', bbox_to_anchor=(1.02, 1), loc='upper left')
plt.sca(ax4)
plt.xticks(rotation=45, ha='right')
plt.tight_layout()
if save:
filepath = self.output_folder / 'algorithm_comparison.png'
plt.savefig(filepath, dpi=self.FIGURE_DPI, bbox_inches='tight')
logger.info(f"Saved comparison to {filepath}")
plt.show()
def _plot_performance_heatmap(
self,
df: pd.DataFrame,
save: bool = True
) -> None:
"""
Построение тепловых карт производительности.
Параметры
---------
df : pd.DataFrame
Данные для анализа.
save : bool
Сохранять график в файл.
"""
if 'filter' not in df.columns:
logger.warning("No filter data for heatmap")
return
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
# Тепловая карта 1: PSNR
ax1 = axes[0]
try:
pivot_psnr = df.pivot_table(
values='psnr', index='algorithm', columns='filter', aggfunc='mean'
)
im1 = ax1.imshow(pivot_psnr.values, cmap='RdYlGn', aspect='auto')
ax1.set_xticks(range(len(pivot_psnr.columns)))
ax1.set_yticks(range(len(pivot_psnr.index)))
ax1.set_xticklabels(pivot_psnr.columns, rotation=45, ha='right')
ax1.set_yticklabels(pivot_psnr.index)
ax1.set_title('PSNR Heatmap (dB)', fontsize=12)
# Добавление текстовых аннотаций
for i in range(len(pivot_psnr.index)):
for j in range(len(pivot_psnr.columns)):
val = pivot_psnr.values[i, j]
if not np.isnan(val):
ax1.text(j, i, f'{val:.1f}', ha='center', va='center',
fontsize=9, color='black')
plt.colorbar(im1, ax=ax1, label='PSNR (dB)')
except Exception as e:
logger.warning(f"PSNR heatmap failed: {e}")
# Тепловая карта 2: SSIM
ax2 = axes[1]
if 'ssim' in df.columns:
try:
pivot_ssim = df.pivot_table(
values='ssim', index='algorithm', columns='filter', aggfunc='mean'
)
im2 = ax2.imshow(pivot_ssim.values, cmap='RdYlGn', aspect='auto')
ax2.set_xticks(range(len(pivot_ssim.columns)))
ax2.set_yticks(range(len(pivot_ssim.index)))
ax2.set_xticklabels(pivot_ssim.columns, rotation=45, ha='right')
ax2.set_yticklabels(pivot_ssim.index)
ax2.set_title('SSIM Heatmap', fontsize=12)
for i in range(len(pivot_ssim.index)):
for j in range(len(pivot_ssim.columns)):
val = pivot_ssim.values[i, j]
if not np.isnan(val):
ax2.text(j, i, f'{val:.3f}', ha='center', va='center',
fontsize=9, color='black')
plt.colorbar(im2, ax=ax2, label='SSIM')
except Exception as e:
logger.warning(f"SSIM heatmap failed: {e}")
plt.tight_layout()
if save:
filepath = self.output_folder / 'performance_heatmaps.png'
plt.savefig(filepath, dpi=self.FIGURE_DPI, bbox_inches='tight')
logger.info(f"Saved heatmaps to {filepath}")
plt.show()
def _print_statistical_summary(self, df: pd.DataFrame) -> None:
"""
Вывод статистической сводки результатов.
Параметры
---------
df : pd.DataFrame
Данные для анализа.
"""
print("\n" + "=" * 70)
print("STATISTICAL SUMMARY")
print("=" * 70)
# Общая статистика по алгоритмам
print("\nPerformance by Algorithm:")
print("-" * 50)
stats = df.groupby('algorithm').agg({
'psnr': ['mean', 'std', 'min', 'max', 'count']
}).round(3)
stats.columns = ['Mean PSNR', 'Std', 'Min', 'Max', 'Count']
print(stats.to_string())
# Статистика SSIM
if 'ssim' in df.columns:
print("\nSSIM Statistics:")
print("-" * 50)
ssim_stats = df.groupby('algorithm')['ssim'].agg(['mean', 'std']).round(4)
print(ssim_stats.to_string())
# Лучшие алгоритмы для различных условий
print("\nBest Algorithm for Each Condition:")
print("-" * 50)
# Высокий шум
if 'noise_intensity' in df.columns:
high_noise = df[df['noise_intensity'] > df['noise_intensity'].median()]
if len(high_noise) > 0:
best_noise = high_noise.groupby('algorithm')['psnr'].mean().idxmax()
score = high_noise.groupby('algorithm')['psnr'].mean().max()
print(f" High noise conditions: {best_noise} (PSNR: {score:.2f} dB)")
# Сильное размытие
if 'blur_intensity' in df.columns:
high_blur = df[df['blur_intensity'] > df['blur_intensity'].median()]
if len(high_blur) > 0:
best_blur = high_blur.groupby('algorithm')['psnr'].mean().idxmax()
score = high_blur.groupby('algorithm')['psnr'].mean().max()
print(f" High blur conditions: {best_blur} (PSNR: {score:.2f} dB)")
# Лучший в среднем
best_overall = df.groupby('algorithm')['psnr'].mean().idxmax()
score_overall = df.groupby('algorithm')['psnr'].mean().max()
print(f" Overall best: {best_overall} (PSNR: {score_overall:.2f} dB)")
def _print_pareto_optimal_solutions(self, df: pd.DataFrame) -> None:
"""
Вывод Парето-оптимальных решений.
Параметры
---------
df : pd.DataFrame
Данные для анализа.
"""
print("\n" + "=" * 70)
print("PARETO-OPTIMAL SOLUTIONS")
print("=" * 70)
# Поиск фронта Парето для PSNR vs время обработки
if 'time' in df.columns:
valid = df[['psnr', 'time', 'algorithm']].dropna()
if len(valid) > 0:
points = valid[['psnr', 'time']].values
pareto_mask = self._find_pareto_front(
points, maximize=[True, False]
)
pareto_solutions = valid[pareto_mask]
print("\nQuality vs Speed Pareto Front:")
print("-" * 50)
for _, row in pareto_solutions.iterrows():
print(f" {row['algorithm']}: PSNR={row['psnr']:.2f} dB, "
f"Time={row['time']:.3f} s")
# Поиск фронта Парето для PSNR vs устойчивость к шуму
if 'noise_intensity' in df.columns:
# Группировка по алгоритмам и вычисление средней производительности
algo_stats = df.groupby('algorithm').agg({
'psnr': 'mean',
'noise_intensity': 'mean'
}).reset_index()
points = algo_stats[['psnr', 'noise_intensity']].values
pareto_mask = self._find_pareto_front(points, maximize=[True, False])
pareto_algos = algo_stats[pareto_mask]
print("\nQuality vs Noise Robustness Pareto Front:")
print("-" * 50)
for _, row in pareto_algos.iterrows():
print(f" {row['algorithm']}: PSNR={row['psnr']:.2f} dB")
print("\n" + "=" * 70)