Spaces:
Running
Running
Commit
Β·
a1181d4
1
Parent(s):
7bbbbf5
Refactor PSNR normalization to use min-max scaling for improved accuracy. Remove unused quality threshold indicators and enhance the layout of the usage guide with clearer metric explanations. Implement debouncing in the auto-load function to prevent redundant processing of the same video pair.
Browse files
app.py
CHANGED
@@ -703,8 +703,18 @@ class FrameMetrics:
|
|
703 |
if f in psnr_frames
|
704 |
]
|
705 |
|
706 |
-
# Normalize PSNR to 0-1 scale
|
707 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
708 |
|
709 |
# Start with SSIM and normalized PSNR
|
710 |
quality_components = [ssim_common, psnr_normalized]
|
@@ -786,28 +796,6 @@ class FrameMetrics:
|
|
786 |
)
|
787 |
|
788 |
# Add quality threshold indicators if there are significant variations
|
789 |
-
if (
|
790 |
-
quality_range > 0.03
|
791 |
-
): # Show thresholds if there's meaningful variation
|
792 |
-
# Add reference lines for quality levels within the visible range
|
793 |
-
if y_min <= 0.9 <= y_max:
|
794 |
-
fig_overall.add_hline(
|
795 |
-
y=0.9,
|
796 |
-
line_dash="dot",
|
797 |
-
line_color="green",
|
798 |
-
line_width=1,
|
799 |
-
annotation_text="Excellent (0.9)",
|
800 |
-
annotation_position="right",
|
801 |
-
)
|
802 |
-
if y_min <= 0.8 <= y_max:
|
803 |
-
fig_overall.add_hline(
|
804 |
-
y=0.8,
|
805 |
-
line_dash="dot",
|
806 |
-
line_color="blue",
|
807 |
-
line_width=1,
|
808 |
-
annotation_text="Good (0.8)",
|
809 |
-
annotation_position="right",
|
810 |
-
)
|
811 |
|
812 |
if current_frame is not None:
|
813 |
fig_overall.add_vline(
|
@@ -919,7 +907,7 @@ class VideoFrameComparator:
|
|
919 |
return True
|
920 |
|
921 |
return False
|
922 |
-
except:
|
923 |
return False
|
924 |
|
925 |
def load_videos(self, video1_path, video2_path):
|
@@ -1416,7 +1404,6 @@ def create_app():
|
|
1416 |
print(f"DEBUG: Loaded {len(example_pairs)} example pairs")
|
1417 |
for i, pair in enumerate(example_pairs):
|
1418 |
print(f" Example {i + 1}: {pair}")
|
1419 |
-
all_videos = get_all_videos_from_json()
|
1420 |
|
1421 |
with gr.Blocks(
|
1422 |
title="Frame Arena - Video Frame Comparator",
|
@@ -1478,37 +1465,6 @@ def create_app():
|
|
1478 |
pointer-events: auto !important;
|
1479 |
}
|
1480 |
""",
|
1481 |
-
# js="""
|
1482 |
-
# function updatePlotColors() {
|
1483 |
-
# // Get current theme color
|
1484 |
-
# const bodyStyle = getComputedStyle(document.body);
|
1485 |
-
# const textColor = bodyStyle.getPropertyValue('--body-text-color') ||
|
1486 |
-
# bodyStyle.color ||
|
1487 |
-
# (bodyStyle.backgroundColor === 'rgb(255, 255, 255)' ? '#000000' : '#ffffff');
|
1488 |
-
# // Update all plot text elements
|
1489 |
-
# document.querySelectorAll('.plotly svg text').forEach(text => {
|
1490 |
-
# text.setAttribute('fill', textColor);
|
1491 |
-
# });
|
1492 |
-
# }
|
1493 |
-
# // Update colors on load and theme change
|
1494 |
-
# window.addEventListener('load', updatePlotColors);
|
1495 |
-
# // Watch for theme changes
|
1496 |
-
# const observer = new MutationObserver(updatePlotColors);
|
1497 |
-
# observer.observe(document.body, {
|
1498 |
-
# attributes: true,
|
1499 |
-
# attributeFilter: ['class', 'style']
|
1500 |
-
# });
|
1501 |
-
# // Also watch for CSS variable changes
|
1502 |
-
# if (window.CSS && CSS.supports('color', 'var(--body-text-color)')) {
|
1503 |
-
# const style = document.createElement('style');
|
1504 |
-
# style.textContent = `
|
1505 |
-
# .plotly svg text {
|
1506 |
-
# fill: var(--body-text-color, currentColor) !important;
|
1507 |
-
# }
|
1508 |
-
# `;
|
1509 |
-
# document.head.appendChild(style);
|
1510 |
-
# }
|
1511 |
-
# """,
|
1512 |
) as app:
|
1513 |
gr.Markdown("""
|
1514 |
# π¬ Frame Arena: Frame by frame comparisons of any videos
|
@@ -1560,7 +1516,7 @@ def create_app():
|
|
1560 |
# Add examples at the top for better UX
|
1561 |
if example_pairs:
|
1562 |
gr.Markdown("### π Example Video Comparisons")
|
1563 |
-
|
1564 |
examples=example_pairs,
|
1565 |
inputs=[video1_input, video2_input],
|
1566 |
label="Click any example to load video pairs:",
|
@@ -1618,28 +1574,11 @@ def create_app():
|
|
1618 |
lines=3,
|
1619 |
)
|
1620 |
|
1621 |
-
#
|
1622 |
-
with gr.
|
1623 |
-
|
1624 |
-
|
1625 |
-
|
1626 |
-
with gr.Row():
|
1627 |
-
mse_plot = gr.Plot(label="MSE", show_label=True)
|
1628 |
-
phash_plot = gr.Plot(label="pHash", show_label=True)
|
1629 |
-
|
1630 |
-
with gr.Row():
|
1631 |
-
color_plot = gr.Plot(label="Color Histogram", show_label=True)
|
1632 |
-
sharpness_plot = gr.Plot(label="Sharpness", show_label=True)
|
1633 |
-
|
1634 |
-
# Add comprehensive usage guide
|
1635 |
-
with gr.Accordion("π Usage Guide & Metrics Reference", open=False):
|
1636 |
-
with gr.Row() as info_section:
|
1637 |
-
with gr.Column():
|
1638 |
-
status_output = gr.Textbox(
|
1639 |
-
label="Status", interactive=False, lines=8
|
1640 |
-
)
|
1641 |
-
with gr.Column():
|
1642 |
-
gr.Markdown("""
|
1643 |
### π Metrics Explained
|
1644 |
- **SSIM**: Structural Similarity (1.0 = identical structure, 0.0 = completely different)
|
1645 |
- **PSNR**: Peak Signal-to-Noise Ratio in dB (higher = better quality, less noise)
|
@@ -1648,11 +1587,15 @@ def create_app():
|
|
1648 |
- **Color Histogram**: Color distribution correlation (1.0 = identical color patterns)
|
1649 |
- **Sharpness**: Laplacian variance per video (higher = sharper/more detailed images)
|
1650 |
- **Overall Quality**: Combined metric averaging SSIM, normalized PSNR, and pHash (when available)
|
1651 |
-
|
|
|
|
|
|
|
|
|
1652 |
|
1653 |
-
|
1654 |
-
|
1655 |
-
|
1656 |
### π― Quality Assessment Scale (Research-Based Thresholds)
|
1657 |
**SSIM Scale** (based on human perception studies):
|
1658 |
- π’ **Excellent (β₯0.9)**: Visually indistinguishable differences
|
@@ -1671,9 +1614,9 @@ def create_app():
|
|
1671 |
- π΅ **Similar (β€100)**: Small differences, good quality preservation
|
1672 |
- π‘ **Moderately Different (β€200)**: Noticeable but acceptable differences
|
1673 |
- π΄ **Very Different (>200)**: Significant pixel-level changes
|
1674 |
-
|
1675 |
-
|
1676 |
-
|
1677 |
### π Understanding Comparisons
|
1678 |
**Comparison Analysis**: Shows how similar/different the videos are
|
1679 |
- Most metrics indicate **similarity** - not which video "wins"
|
@@ -1690,7 +1633,20 @@ def create_app():
|
|
1690 |
- **Range**: 0.0 to 1.0 (higher = more similar videos overall)
|
1691 |
- **Purpose**: Provides a single metric when you need one overall assessment
|
1692 |
- **Limitation**: Different metrics may disagree; check individual metrics for details
|
1693 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1694 |
|
1695 |
# Connect examples to auto-loading
|
1696 |
if example_pairs:
|
@@ -1822,9 +1778,41 @@ def create_app():
|
|
1822 |
gr.Row(visible=False), # info_section
|
1823 |
)
|
1824 |
|
1825 |
-
# Enhanced auto-load function with
|
|
|
|
|
1826 |
def enhanced_auto_load(video1, video2):
|
1827 |
print(f"DEBUG: Input change detected! video1={video1}, video2={video2}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1828 |
return auto_load_when_examples_change(video1, video2)
|
1829 |
|
1830 |
# Auto-load when both video inputs change (triggered by examples)
|
|
|
703 |
if f in psnr_frames
|
704 |
]
|
705 |
|
706 |
+
# Normalize PSNR to 0-1 scale using min-max normalization
|
707 |
+
if psnr_common:
|
708 |
+
psnr_min = min(psnr_common)
|
709 |
+
psnr_max = max(psnr_common)
|
710 |
+
if psnr_max > psnr_min:
|
711 |
+
psnr_normalized = [
|
712 |
+
(p - psnr_min) / (psnr_max - psnr_min) for p in psnr_common
|
713 |
+
]
|
714 |
+
else:
|
715 |
+
psnr_normalized = [0.0 for _ in psnr_common]
|
716 |
+
else:
|
717 |
+
psnr_normalized = []
|
718 |
|
719 |
# Start with SSIM and normalized PSNR
|
720 |
quality_components = [ssim_common, psnr_normalized]
|
|
|
796 |
)
|
797 |
|
798 |
# Add quality threshold indicators if there are significant variations
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
799 |
|
800 |
if current_frame is not None:
|
801 |
fig_overall.add_vline(
|
|
|
907 |
return True
|
908 |
|
909 |
return False
|
910 |
+
except Exception:
|
911 |
return False
|
912 |
|
913 |
def load_videos(self, video1_path, video2_path):
|
|
|
1404 |
print(f"DEBUG: Loaded {len(example_pairs)} example pairs")
|
1405 |
for i, pair in enumerate(example_pairs):
|
1406 |
print(f" Example {i + 1}: {pair}")
|
|
|
1407 |
|
1408 |
with gr.Blocks(
|
1409 |
title="Frame Arena - Video Frame Comparator",
|
|
|
1465 |
pointer-events: auto !important;
|
1466 |
}
|
1467 |
""",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1468 |
) as app:
|
1469 |
gr.Markdown("""
|
1470 |
# π¬ Frame Arena: Frame by frame comparisons of any videos
|
|
|
1516 |
# Add examples at the top for better UX
|
1517 |
if example_pairs:
|
1518 |
gr.Markdown("### π Example Video Comparisons")
|
1519 |
+
gr.Examples(
|
1520 |
examples=example_pairs,
|
1521 |
inputs=[video1_input, video2_input],
|
1522 |
label="Click any example to load video pairs:",
|
|
|
1574 |
lines=3,
|
1575 |
)
|
1576 |
|
1577 |
+
# Add comprehensive usage guide underneath frame information & metrics
|
1578 |
+
with gr.Accordion("π Usage Guide & Metrics Reference", open=False):
|
1579 |
+
with gr.Row():
|
1580 |
+
with gr.Column():
|
1581 |
+
gr.Markdown("""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1582 |
### π Metrics Explained
|
1583 |
- **SSIM**: Structural Similarity (1.0 = identical structure, 0.0 = completely different)
|
1584 |
- **PSNR**: Peak Signal-to-Noise Ratio in dB (higher = better quality, less noise)
|
|
|
1587 |
- **Color Histogram**: Color distribution correlation (1.0 = identical color patterns)
|
1588 |
- **Sharpness**: Laplacian variance per video (higher = sharper/more detailed images)
|
1589 |
- **Overall Quality**: Combined metric averaging SSIM, normalized PSNR, and pHash (when available)
|
1590 |
+
""")
|
1591 |
+
with gr.Column() as info_section:
|
1592 |
+
status_output = gr.Textbox(
|
1593 |
+
label="Status", interactive=False, lines=16
|
1594 |
+
)
|
1595 |
|
1596 |
+
with gr.Row():
|
1597 |
+
with gr.Column():
|
1598 |
+
gr.Markdown("""
|
1599 |
### π― Quality Assessment Scale (Research-Based Thresholds)
|
1600 |
**SSIM Scale** (based on human perception studies):
|
1601 |
- π’ **Excellent (β₯0.9)**: Visually indistinguishable differences
|
|
|
1614 |
- π΅ **Similar (β€100)**: Small differences, good quality preservation
|
1615 |
- π‘ **Moderately Different (β€200)**: Noticeable but acceptable differences
|
1616 |
- π΄ **Very Different (>200)**: Significant pixel-level changes
|
1617 |
+
""")
|
1618 |
+
with gr.Column():
|
1619 |
+
gr.Markdown("""
|
1620 |
### π Understanding Comparisons
|
1621 |
**Comparison Analysis**: Shows how similar/different the videos are
|
1622 |
- Most metrics indicate **similarity** - not which video "wins"
|
|
|
1633 |
- **Range**: 0.0 to 1.0 (higher = more similar videos overall)
|
1634 |
- **Purpose**: Provides a single metric when you need one overall assessment
|
1635 |
- **Limitation**: Different metrics may disagree; check individual metrics for details
|
1636 |
+
""")
|
1637 |
+
|
1638 |
+
# Individual metric plots
|
1639 |
+
with gr.Row():
|
1640 |
+
ssim_plot = gr.Plot(label="SSIM", show_label=True)
|
1641 |
+
psnr_plot = gr.Plot(label="PSNR", show_label=True)
|
1642 |
+
|
1643 |
+
with gr.Row():
|
1644 |
+
mse_plot = gr.Plot(label="MSE", show_label=True)
|
1645 |
+
phash_plot = gr.Plot(label="pHash", show_label=True)
|
1646 |
+
|
1647 |
+
with gr.Row():
|
1648 |
+
color_plot = gr.Plot(label="Color Histogram", show_label=True)
|
1649 |
+
sharpness_plot = gr.Plot(label="Sharpness", show_label=True)
|
1650 |
|
1651 |
# Connect examples to auto-loading
|
1652 |
if example_pairs:
|
|
|
1778 |
gr.Row(visible=False), # info_section
|
1779 |
)
|
1780 |
|
1781 |
+
# Enhanced auto-load function with debouncing to prevent multiple rapid calls
|
1782 |
+
last_processed_pair = {"video1": None, "video2": None}
|
1783 |
+
|
1784 |
def enhanced_auto_load(video1, video2):
|
1785 |
print(f"DEBUG: Input change detected! video1={video1}, video2={video2}")
|
1786 |
+
|
1787 |
+
# Simple debouncing: skip if same video pair was just processed
|
1788 |
+
if (
|
1789 |
+
last_processed_pair["video1"] == video1
|
1790 |
+
and last_processed_pair["video2"] == video2
|
1791 |
+
):
|
1792 |
+
print("DEBUG: Same video pair already processed, skipping...")
|
1793 |
+
# Return current state without recomputing
|
1794 |
+
return (
|
1795 |
+
gr.update(),
|
1796 |
+
gr.update(),
|
1797 |
+
gr.update(),
|
1798 |
+
gr.update(),
|
1799 |
+
gr.update(),
|
1800 |
+
gr.update(),
|
1801 |
+
gr.update(),
|
1802 |
+
gr.update(),
|
1803 |
+
gr.update(),
|
1804 |
+
gr.update(),
|
1805 |
+
gr.update(),
|
1806 |
+
gr.update(),
|
1807 |
+
gr.update(),
|
1808 |
+
gr.update(),
|
1809 |
+
gr.update(),
|
1810 |
+
gr.update(),
|
1811 |
+
)
|
1812 |
+
|
1813 |
+
last_processed_pair["video1"] = video1
|
1814 |
+
last_processed_pair["video2"] = video2
|
1815 |
+
|
1816 |
return auto_load_when_examples_change(video1, video2)
|
1817 |
|
1818 |
# Auto-load when both video inputs change (triggered by examples)
|