File size: 23,123 Bytes
25e074b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
#!/usr/bin/env python3
"""
Parameter behavior analysis for mosaic generator
Focuses on: parameter degradation effects, CIFAR comparison, cross-image consistency
Analyzes three images: akaza, IMG_5090, IMG_6914
"""

import json
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from pathlib import Path
import re
from collections import defaultdict
import warnings
warnings.filterwarnings('ignore')

# Configuration
METRICS_DIR = Path('./output/test_result/metrics')
OUTPUT_DIR = Path('./output/analysis')
TARGET_IMAGES = ['akaza', 'IMG_3378', 'IMG_5090', 'IMG_6914']

# Visualization settings
plt.style.use('default')
sns.set_palette("husl")

def setup_directories():
    """Create analysis output directories"""
    OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
    for image in TARGET_IMAGES:
        (OUTPUT_DIR / image).mkdir(parents=True, exist_ok=True)
    print(f"[INFO] Created analysis directories under {OUTPUT_DIR}")

def parse_filename(filename):
    """Parse filename to extract parameters"""
    # Pattern: {image}-min{min}-max{max}-sub{sub}-quant{quant}-{tile}_metrics.json
    pattern = r'(.+)-min(\d+)-max(\d+)-sub([0-9p]+)-quant(\d+)-(.+)_metrics\.json'
    match = re.match(pattern, filename)

    if not match:
        return None

    image, min_size, max_size, sub_threshold, quantization, tile_library = match.groups()

    # Convert threshold back from string (0p1 -> 0.1)
    sub_threshold = sub_threshold.replace('p', '.')

    return {
        'image': image,
        'min_size': int(min_size),
        'max_size': int(max_size),
        'sub_threshold': float(sub_threshold),
        'quantization': int(quantization),
        'tile_library': tile_library
    }

def load_metrics_data():
    """Load all metrics data and organize by image"""
    data_by_image = defaultdict(list)

    print("[INFO] Loading metrics data...")

    for json_file in METRICS_DIR.glob('*.json'):
        # Skip files not belonging to our target images
        if not any(img in json_file.name for img in TARGET_IMAGES):
            continue

        parsed = parse_filename(json_file.name)
        if parsed is None:
            continue

        # Load metrics
        try:
            with open(json_file, 'r') as f:
                metrics = json.load(f)

            # Combine parameters and metrics
            record = {**parsed, **metrics}
            data_by_image[parsed['image']].append(record)

        except Exception as e:
            print(f"[WARNING] Failed to load {json_file.name}: {e}")

    # Convert to DataFrames
    dfs = {}
    for image, records in data_by_image.items():
        df = pd.DataFrame(records)
        dfs[image] = df
        print(f"[INFO] Loaded {len(df)} records for {image}")

    return dfs

def create_heatmaps(df, image_name):
    """Create heatmap visualizations for an image"""
    print(f"[INFO] Creating heatmaps for {image_name}")

    fig, axes = plt.subplots(2, 2, figsize=(16, 12))
    fig.suptitle(f'Parameter Impact Heatmaps - {image_name}', fontsize=16, fontweight='bold')

    # 1. Min_size vs Max_size colored by SSIM
    pivot1 = df.pivot_table(values='SSIM', index='min_size', columns='max_size', aggfunc='mean')
    sns.heatmap(pivot1, annot=True, fmt='.3f', ax=axes[0,0], cmap='RdYlGn')
    axes[0,0].set_title('SSIM by Min/Max Size')
    axes[0,0].set_xlabel('Max Size')
    axes[0,0].set_ylabel('Min Size')

    # 2. Quantization vs Tile_library colored by Overall_Quality
    pivot2 = df.pivot_table(values='Overall_Quality', index='quantization', columns='tile_library', aggfunc='mean')
    sns.heatmap(pivot2, annot=True, fmt='.3f', ax=axes[0,1], cmap='RdYlGn')
    axes[0,1].set_title('Overall Quality by Quantization/Tile Library')
    axes[0,1].set_xlabel('Tile Library')
    axes[0,1].set_ylabel('Quantization')

    # 3. Subdivision vs Quantization colored by MSE (inverted colormap since lower MSE is better)
    pivot3 = df.pivot_table(values='MSE', index='sub_threshold', columns='quantization', aggfunc='mean')
    sns.heatmap(pivot3, annot=True, fmt='.0f', ax=axes[1,0], cmap='RdYlGn_r')
    axes[1,0].set_title('MSE by Subdivision/Quantization')
    axes[1,0].set_xlabel('Quantization')
    axes[1,0].set_ylabel('Subdivision Threshold')

    # 4. Tile library vs Min_size colored by Edge_Similarity
    pivot4 = df.pivot_table(values='Edge_Similarity', index='tile_library', columns='min_size', aggfunc='mean')
    sns.heatmap(pivot4, annot=True, fmt='.3f', ax=axes[1,1], cmap='RdYlGn')
    axes[1,1].set_title('Edge Similarity by Tile Library/Min Size')
    axes[1,1].set_xlabel('Min Size')
    axes[1,1].set_ylabel('Tile Library')

    plt.tight_layout()
    output_path = OUTPUT_DIR / image_name / 'heatmaps.png'
    plt.savefig(output_path, dpi=300, bbox_inches='tight')
    plt.close()
    print(f"[INFO] Saved heatmaps to {output_path}")

def create_metric_comparisons(df, image_name):
    """Create metric comparison charts"""
    print(f"[INFO] Creating metric comparisons for {image_name}")

    fig, axes = plt.subplots(2, 2, figsize=(16, 12))
    fig.suptitle(f'Metric Comparisons - {image_name}', fontsize=16, fontweight='bold')

    # 1. Box plots by tile library
    metrics_to_plot = ['SSIM', 'Histogram_Correlation', 'Edge_Similarity', 'Overall_Quality']

    for i, metric in enumerate(metrics_to_plot):
        ax = axes[i//2, i%2]
        df.boxplot(column=metric, by='tile_library', ax=ax)
        ax.set_title(f'{metric} by Tile Library')
        ax.set_xlabel('Tile Library')
        ax.set_ylabel(metric)
        ax.grid(True, alpha=0.3)

    plt.suptitle('')  # Remove automatic title
    fig.suptitle(f'Metric Distribution by Tile Library - {image_name}', fontsize=16, fontweight='bold')
    plt.tight_layout()

    output_path = OUTPUT_DIR / image_name / 'metric_comparison.png'
    plt.savefig(output_path, dpi=300, bbox_inches='tight')
    plt.close()
    print(f"[INFO] Saved metric comparison to {output_path}")

def create_parameter_impact(df, image_name):
    """Create parameter impact analysis"""
    print(f"[INFO] Creating parameter impact analysis for {image_name}")

    fig, axes = plt.subplots(2, 3, figsize=(18, 12))
    fig.suptitle(f'Parameter Impact Analysis - {image_name}', fontsize=16, fontweight='bold')

    # 1a. Quality metrics by quantization
    quantization_impact = df.groupby('quantization')[['MSE', 'SSIM', 'Histogram_Correlation', 'Edge_Similarity']].mean()

    quality_metrics = ['SSIM', 'Histogram_Correlation', 'Edge_Similarity']
    quantization_impact[quality_metrics].plot(kind='bar', ax=axes[0,0])
    axes[0,0].set_title('Quality Metrics by Quantization')
    axes[0,0].set_xlabel('Quantization')
    axes[0,0].set_ylabel('Metric Value')
    axes[0,0].legend()
    axes[0,0].tick_params(axis='x', rotation=0)
    axes[0,0].grid(True, alpha=0.3)

    # 1b. MSE by quantization (separate subplot)
    quantization_impact['MSE'].plot(kind='bar', ax=axes[0,1], color='red')
    axes[0,1].set_title('MSE by Quantization')
    axes[0,1].set_xlabel('Quantization')
    axes[0,1].set_ylabel('MSE')
    axes[0,1].tick_params(axis='x', rotation=0)
    axes[0,1].grid(True, alpha=0.3)

    # 2. Size ratio impact (max/min)
    df['size_ratio'] = df['max_size'] / df['min_size']
    size_ratio_impact = df.groupby('size_ratio')['Overall_Quality'].mean()
    size_ratio_impact.plot(kind='bar', ax=axes[0,2], color='skyblue')
    axes[0,2].set_title('Overall Quality by Size Ratio (Max/Min)')
    axes[0,2].set_xlabel('Size Ratio')
    axes[0,2].set_ylabel('Overall Quality')
    axes[0,2].tick_params(axis='x', rotation=45)
    axes[0,2].grid(True, alpha=0.3)

    # 3. Subdivision threshold impact
    sub_impact = df.groupby('sub_threshold')['Overall_Quality'].mean()
    sub_impact.plot(kind='bar', ax=axes[1,0], color='lightcoral')
    axes[1,0].set_title('Overall Quality by Subdivision Threshold')
    axes[1,0].set_xlabel('Subdivision Threshold')
    axes[1,0].set_ylabel('Overall Quality')
    axes[1,0].tick_params(axis='x', rotation=0)
    axes[1,0].grid(True, alpha=0.3)

    # 4a. Quality metrics by tile library
    tile_performance = df.groupby('tile_library')[['MSE', 'SSIM', 'Histogram_Correlation', 'Edge_Similarity']].mean()
    quality_metrics = ['SSIM', 'Histogram_Correlation', 'Edge_Similarity']
    tile_performance[quality_metrics].plot(kind='bar', ax=axes[1,1])
    axes[1,1].set_title('Quality Metrics by Tile Library')
    axes[1,1].set_xlabel('Tile Library')
    axes[1,1].set_ylabel('Metric Value')
    axes[1,1].legend()
    axes[1,1].tick_params(axis='x', rotation=45)
    axes[1,1].grid(True, alpha=0.3)

    # 4b. MSE by tile library
    tile_performance['MSE'].plot(kind='bar', ax=axes[1,2], color='red')
    axes[1,2].set_title('MSE by Tile Library')
    axes[1,2].set_xlabel('Tile Library')
    axes[1,2].set_ylabel('MSE')
    axes[1,2].tick_params(axis='x', rotation=45)
    axes[1,2].grid(True, alpha=0.3)

    plt.tight_layout()
    output_path = OUTPUT_DIR / image_name / 'parameter_impact.png'
    plt.savefig(output_path, dpi=300, bbox_inches='tight')
    plt.close()
    print(f"[INFO] Saved parameter impact to {output_path}")

def analyze_parameter_degradation(df, image_name):
    """Analyze which parameters cause quality degradation (for artistic/mosaic effects)"""
    print(f"[INFO] Analyzing parameter degradation effects for {image_name}")

    fig, axes = plt.subplots(2, 2, figsize=(16, 12))
    fig.suptitle(f'Parameter Degradation Analysis (For Artistic Effects) - {image_name}', fontsize=16, fontweight='bold')

    # 1. Which parameters INCREASE MSE (worse quality = more artistic)
    mse_by_param = {}
    mse_by_param['quantization'] = df.groupby('quantization')['MSE'].mean()
    mse_by_param['min_size'] = df.groupby('min_size')['MSE'].mean()
    mse_by_param['max_size'] = df.groupby('max_size')['MSE'].mean()
    mse_by_param['sub_threshold'] = df.groupby('sub_threshold')['MSE'].mean()

    # Plot MSE increase trends
    ax = axes[0,0]
    colors = ['red', 'orange', 'green', 'blue']
    for i, (param, values) in enumerate(mse_by_param.items()):
        ax.plot(values.index, values.values, marker='o', label=param, color=colors[i], linewidth=2)

    ax.set_title('MSE by Parameters (Higher = More Artistic)')
    ax.set_xlabel('Parameter Value')
    ax.set_ylabel('Average MSE')
    ax.legend()
    ax.grid(True, alpha=0.3)

    # 2. Which parameters DECREASE SSIM (worse similarity = more artistic)
    ssim_by_param = {}
    ssim_by_param['quantization'] = df.groupby('quantization')['SSIM'].mean()
    ssim_by_param['min_size'] = df.groupby('min_size')['SSIM'].mean()
    ssim_by_param['max_size'] = df.groupby('max_size')['SSIM'].mean()
    ssim_by_param['sub_threshold'] = df.groupby('sub_threshold')['SSIM'].mean()

    ax = axes[0,1]
    for i, (param, values) in enumerate(ssim_by_param.items()):
        ax.plot(values.index, values.values, marker='s', label=param, color=colors[i], linewidth=2)

    ax.set_title('SSIM by Parameters (Lower = More Artistic)')
    ax.set_xlabel('Parameter Value')
    ax.set_ylabel('Average SSIM')
    ax.legend()
    ax.grid(True, alpha=0.3)

    # 3. Parameter impact ranking for degradation
    degradation_impact = {}

    # Calculate impact as difference between max and min values (normalized)
    for param in ['quantization', 'min_size', 'max_size', 'sub_threshold']:
        mse_range = mse_by_param[param].max() - mse_by_param[param].min()
        ssim_range = ssim_by_param[param].max() - ssim_by_param[param].min()

        # Normalize by overall metric range
        mse_impact = mse_range / df['MSE'].std()
        ssim_impact = ssim_range / df['SSIM'].std()

        degradation_impact[param] = (mse_impact + ssim_impact) / 2

    # Plot simple impact ranking
    ax = axes[1,0]
    params = list(degradation_impact.keys())
    impacts = list(degradation_impact.values())

    ax.bar(params, impacts, color='lightcoral')
    ax.set_title('Parameter Impact on Quality Degradation')
    ax.set_xlabel('Parameters')
    ax.set_ylabel('Impact Score')
    ax.tick_params(axis='x', rotation=45)
    ax.grid(True, alpha=0.3)

    # Add score labels
    for i, (param, impact) in enumerate(zip(params, impacts)):
        ax.text(i, impact + 0.05, f'{impact:.2f}', ha='center', va='bottom')

    # 4. Empty subplot (remove recommendations)
    axes[1,1].axis('off')

    plt.tight_layout()
    output_path = OUTPUT_DIR / image_name / 'parameter_degradation.png'
    plt.savefig(output_path, dpi=300, bbox_inches='tight')
    plt.close()
    print(f"[INFO] Saved parameter degradation analysis to {output_path}")

def analyze_cifar_comparison(df, image_name):
    """Direct comparison between CIFAR-10 and CIFAR-100"""
    print(f"[INFO] Analyzing CIFAR-10 vs CIFAR-100 comparison for {image_name}")

    # Filter data for CIFAR comparisons only
    cifar_data = df[df['tile_library'].isin(['cifar-10', 'cifar-100'])].copy()

    if len(cifar_data) == 0:
        print(f"[WARNING] No CIFAR data found for {image_name}")
        return

    fig, axes = plt.subplots(2, 2, figsize=(16, 12))
    fig.suptitle(f'CIFAR-10 vs CIFAR-100 Direct Comparison - {image_name}', fontsize=16, fontweight='bold')

    # 1a. Quality metrics comparison
    metrics = ['MSE', 'SSIM', 'Histogram_Correlation', 'Edge_Similarity']
    cifar_comparison = cifar_data.groupby('tile_library')[metrics].mean()

    quality_metrics = ['SSIM', 'Histogram_Correlation', 'Edge_Similarity']
    cifar_comparison[quality_metrics].T.plot(kind='bar', ax=axes[0,0], color=['lightblue', 'lightcoral'])
    axes[0,0].set_title('Quality Metrics Comparison')
    axes[0,0].set_ylabel('Metric Value')
    axes[0,0].legend(title='Tile Library')
    axes[0,0].tick_params(axis='x', rotation=45)
    axes[0,0].grid(True, alpha=0.3)

    # 1b. MSE comparison (separate subplot)
    cifar_comparison['MSE'].plot(kind='bar', ax=axes[0,1], color=['red', 'orange'])
    axes[0,1].set_title('MSE Comparison')
    axes[0,1].set_ylabel('MSE')
    axes[0,1].legend(['CIFAR-10', 'CIFAR-100'])
    axes[0,1].grid(True, alpha=0.3)

    # 2. Paired comparison for same parameter settings
    # Create parameter combination identifier
    cifar_data['param_combo'] = (cifar_data['min_size'].astype(str) + '_' +
                                 cifar_data['max_size'].astype(str) + '_' +
                                 cifar_data['sub_threshold'].astype(str) + '_' +
                                 cifar_data['quantization'].astype(str))

    # Find configurations that exist for both CIFAR types
    cifar10_combos = set(cifar_data[cifar_data['tile_library'] == 'cifar-10']['param_combo'])
    cifar100_combos = set(cifar_data[cifar_data['tile_library'] == 'cifar-100']['param_combo'])
    common_combos = cifar10_combos.intersection(cifar100_combos)

    paired_data = []
    for combo in common_combos:
        combo_data = cifar_data[cifar_data['param_combo'] == combo]
        if len(combo_data) == 2:  # Should have both CIFAR-10 and CIFAR-100
            cifar10_row = combo_data[combo_data['tile_library'] == 'cifar-10'].iloc[0]
            cifar100_row = combo_data[combo_data['tile_library'] == 'cifar-100'].iloc[0]

            paired_data.append({
                'combo': combo,
                'cifar10_ssim': cifar10_row['SSIM'],
                'cifar100_ssim': cifar100_row['SSIM'],
                'cifar10_mse': cifar10_row['MSE'],
                'cifar100_mse': cifar100_row['MSE'],
                'ssim_diff': cifar10_row['SSIM'] - cifar100_row['SSIM'],
                'mse_diff': cifar10_row['MSE'] - cifar100_row['MSE']
            })

    paired_df = pd.DataFrame(paired_data)

    # 2. Win/Loss analysis
    ax = axes[1,0]
    if len(paired_df) > 0:
        ssim_wins = (paired_df['ssim_diff'] > 0).sum()  # CIFAR-10 wins
        ssim_losses = (paired_df['ssim_diff'] < 0).sum()  # CIFAR-100 wins

        mse_wins = (paired_df['mse_diff'] < 0).sum()  # CIFAR-10 wins (lower MSE is better)
        mse_losses = (paired_df['mse_diff'] > 0).sum()  # CIFAR-100 wins

        categories = ['SSIM', 'MSE']
        cifar10_scores = [ssim_wins, mse_wins]
        cifar100_scores = [ssim_losses, mse_losses]

        x = np.arange(len(categories))
        width = 0.35

        ax.bar(x - width/2, cifar10_scores, width, label='CIFAR-10 Wins', color='lightblue')
        ax.bar(x + width/2, cifar100_scores, width, label='CIFAR-100 Wins', color='lightcoral')

        ax.set_title(f'Win/Loss Analysis ({len(paired_df)} comparisons)')
        ax.set_ylabel('Number of Wins')
        ax.set_xticks(x)
        ax.set_xticklabels(categories)
        ax.legend()
        ax.grid(True, alpha=0.3)

        # Add value labels
        for i, (c10, c100) in enumerate(zip(cifar10_scores, cifar100_scores)):
            ax.text(i - width/2, c10 + 0.5, str(c10), ha='center', va='bottom')
            ax.text(i + width/2, c100 + 0.5, str(c100), ha='center', va='bottom')

    # 3. Performance difference distribution
    ax = axes[1,1]
    if len(paired_df) > 0:
        ax.hist(paired_df['ssim_diff'], bins=20, alpha=0.7, color='skyblue', edgecolor='black')
        ax.axvline(x=0, color='red', linestyle='--', linewidth=2)
        ax.set_title('SSIM Difference Distribution (CIFAR-10 - CIFAR-100)')
        ax.set_xlabel('SSIM Difference')
        ax.set_ylabel('Frequency')
        ax.grid(True, alpha=0.3)

    plt.tight_layout()
    output_path = OUTPUT_DIR / image_name / 'cifar_comparison.png'
    plt.savefig(output_path, dpi=300, bbox_inches='tight')
    plt.close()
    print(f"[INFO] Saved CIFAR comparison to {output_path}")

def analyze_size_consistency(all_dfs):
    """Analyze if min/max size effects are consistent across images"""
    print("[INFO] Analyzing min/max size consistency across images")

    fig, axes = plt.subplots(2, 2, figsize=(16, 12))
    fig.suptitle('Min/Max Size Effects Consistency Across Images', fontsize=16, fontweight='bold')

    # Combine all data
    combined_data = []
    for image_name, df in all_dfs.items():
        df_copy = df.copy()
        df_copy['image'] = image_name
        combined_data.append(df_copy)

    combined_df = pd.concat(combined_data, ignore_index=True)

    # 1. Min size effect on SSIM across images
    ax = axes[0,0]
    for image in TARGET_IMAGES:
        image_data = combined_df[combined_df['image'] == image]
        min_size_effect = image_data.groupby('min_size')['SSIM'].mean()
        ax.plot(min_size_effect.index, min_size_effect.values, marker='o', label=image, linewidth=2)

    ax.set_title('Min Size Effect on SSIM')
    ax.set_xlabel('Min Size')
    ax.set_ylabel('Average SSIM')
    ax.legend()
    ax.grid(True, alpha=0.3)

    # 2. Max size effect on SSIM across images
    ax = axes[0,1]
    for image in TARGET_IMAGES:
        image_data = combined_df[combined_df['image'] == image]
        max_size_effect = image_data.groupby('max_size')['SSIM'].mean()
        ax.plot(max_size_effect.index, max_size_effect.values, marker='s', label=image, linewidth=2)

    ax.set_title('Max Size Effect on SSIM')
    ax.set_xlabel('Max Size')
    ax.set_ylabel('Average SSIM')
    ax.legend()
    ax.grid(True, alpha=0.3)

    # 3. Size ratio consistency
    combined_df['size_ratio'] = combined_df['max_size'] / combined_df['min_size']

    ax = axes[1,0]
    for image in TARGET_IMAGES:
        image_data = combined_df[combined_df['image'] == image]
        ratio_effect = image_data.groupby('size_ratio')['Overall_Quality'].mean()
        ax.plot(ratio_effect.index, ratio_effect.values, marker='^', label=image, linewidth=2)

    ax.set_title('Size Ratio Effect on Overall Quality')
    ax.set_xlabel('Size Ratio (Max/Min)')
    ax.set_ylabel('Average Overall Quality')
    ax.legend()
    ax.grid(True, alpha=0.3)

    # 4. Size ratio effect comparison
    ax = axes[1,1]

    # Show correlation values as a simple bar chart
    consistency_analysis = {}

    for size_param in ['min_size', 'max_size']:
        param_effects = {}
        for image in TARGET_IMAGES:
            image_data = combined_df[combined_df['image'] == image]
            effect = image_data.groupby(size_param)['SSIM'].mean()
            param_effects[image] = effect

        # Calculate correlation between images
        correlations = []
        images = list(param_effects.keys())
        for i in range(len(images)):
            for j in range(i+1, len(images)):
                # Find common parameter values
                common_params = set(param_effects[images[i]].index).intersection(
                    set(param_effects[images[j]].index)
                )
                if len(common_params) > 1:
                    vals_i = [param_effects[images[i]][p] for p in common_params]
                    vals_j = [param_effects[images[j]][p] for p in common_params]
                    corr = np.corrcoef(vals_i, vals_j)[0,1]
                    correlations.append(corr)

        consistency_analysis[size_param] = np.mean(correlations) if correlations else 0

    # Plot consistency as bar chart
    params = list(consistency_analysis.keys())
    correlations = list(consistency_analysis.values())

    ax.bar(params, correlations, color=['skyblue', 'lightgreen'])
    ax.set_title('Parameter Consistency Across Images')
    ax.set_xlabel('Size Parameters')
    ax.set_ylabel('Average Correlation')
    ax.set_ylim(0, 1)
    ax.grid(True, alpha=0.3)

    # Add correlation values
    for i, (param, corr) in enumerate(zip(params, correlations)):
        ax.text(i, corr + 0.02, f'{corr:.3f}', ha='center', va='bottom')

    plt.tight_layout()
    output_path = OUTPUT_DIR / 'size_consistency_analysis.png'
    plt.savefig(output_path, dpi=300, bbox_inches='tight')
    plt.close()
    print(f"[INFO] Saved size consistency analysis to {output_path}")


def main():
    """Main analysis function"""
    print("=" * 60)
    print("MOSAIC PARAMETER BEHAVIOR ANALYSIS")
    print("=" * 60)

    setup_directories()

    # Load data
    all_dfs = load_metrics_data()

    if not all_dfs:
        print("[ERROR] No data loaded. Check metrics directory.")
        return

    # Analyze each image
    for image_name, df in all_dfs.items():
        print(f"\n[INFO] Analyzing {image_name}...")

        # Keep original heatmaps and parameter impact
        create_heatmaps(df, image_name)
        create_parameter_impact(df, image_name)

        # New analyses
        analyze_parameter_degradation(df, image_name)
        analyze_cifar_comparison(df, image_name)

    # Cross-image consistency analysis
    analyze_size_consistency(all_dfs)

    print("\n" + "=" * 60)
    print("ANALYSIS COMPLETE!")
    print("=" * 60)
    print(f"Results saved to: {OUTPUT_DIR}")
    print("\nGenerated analyses:")
    print("• heatmaps.png - Parameter impact heatmaps")
    print("• parameter_impact.png - Parameter effect analysis")
    print("• parameter_degradation.png - Settings for artistic effects")
    print("• cifar_comparison.png - CIFAR-10 vs CIFAR-100 comparison")
    print("• size_consistency_analysis.png - Cross-image size effect consistency")

if __name__ == "__main__":
    main()