Spaces:
Running
Running
Commit
·
a94c604
1
Parent(s):
89dc802
Add image comparison slider functionality using gradio-imageslider. Update layout for frame display and enhance overall quality metric description. Update project dependencies in pyproject.toml and uv.lock.
Browse files- app.py +45 -19
- pyproject.toml +1 -0
- uv.lock +16 -1
app.py
CHANGED
|
@@ -6,6 +6,7 @@ import gradio as gr
|
|
| 6 |
import imagehash
|
| 7 |
import numpy as np
|
| 8 |
import plotly.graph_objects as go
|
|
|
|
| 9 |
from PIL import Image
|
| 10 |
from scipy.stats import pearsonr
|
| 11 |
from skimage.metrics import mean_squared_error as mse_skimage
|
|
@@ -1532,13 +1533,28 @@ def create_app():
|
|
| 1532 |
with gr.Column():
|
| 1533 |
gr.Markdown("### Video 1 - Current Frame")
|
| 1534 |
frame1_output = gr.Image(
|
| 1535 |
-
label="Video 1 Frame",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1536 |
)
|
| 1537 |
|
| 1538 |
with gr.Column():
|
| 1539 |
gr.Markdown("### Video 2 - Current Frame")
|
| 1540 |
frame2_output = gr.Image(
|
| 1541 |
-
label="Video 2 Frame",
|
|
|
|
|
|
|
|
|
|
| 1542 |
)
|
| 1543 |
|
| 1544 |
# Frame navigation (initially hidden) - moved underneath frames
|
|
@@ -1586,7 +1602,7 @@ def create_app():
|
|
| 1586 |
- **pHash**: Perceptual Hash similarity (1.0 = visually identical)
|
| 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
|
| 1590 |
""")
|
| 1591 |
with gr.Column() as info_section:
|
| 1592 |
status_output = gr.Textbox(
|
|
@@ -1691,6 +1707,7 @@ def create_app():
|
|
| 1691 |
status, # status_output
|
| 1692 |
slider_update, # frame_slider
|
| 1693 |
frame1, # frame1_output
|
|
|
|
| 1694 |
frame2, # frame2_output
|
| 1695 |
info, # frame_info
|
| 1696 |
ssim_fig, # ssim_plot
|
|
@@ -1709,6 +1726,7 @@ def create_app():
|
|
| 1709 |
def update_frames(frame_index):
|
| 1710 |
if comparator.max_frames == 0:
|
| 1711 |
return (
|
|
|
|
| 1712 |
None,
|
| 1713 |
None,
|
| 1714 |
"No videos loaded",
|
|
@@ -1718,6 +1736,7 @@ def create_app():
|
|
| 1718 |
None,
|
| 1719 |
None,
|
| 1720 |
None,
|
|
|
|
| 1721 |
)
|
| 1722 |
|
| 1723 |
frame1, frame2 = comparator.get_frames_at_index(frame_index)
|
|
@@ -1735,6 +1754,7 @@ def create_app():
|
|
| 1735 |
|
| 1736 |
return (
|
| 1737 |
frame1,
|
|
|
|
| 1738 |
frame2,
|
| 1739 |
info,
|
| 1740 |
ssim_fig,
|
|
@@ -1763,6 +1783,7 @@ def create_app():
|
|
| 1763 |
minimum=0, maximum=0, step=1, value=0, interactive=False
|
| 1764 |
), # frame_slider
|
| 1765 |
None, # frame1_output
|
|
|
|
| 1766 |
None, # frame2_output
|
| 1767 |
"", # frame_info
|
| 1768 |
None, # ssim_plot
|
|
@@ -1792,22 +1813,23 @@ def create_app():
|
|
| 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
|
|
@@ -1823,6 +1845,7 @@ def create_app():
|
|
| 1823 |
status_output,
|
| 1824 |
frame_slider,
|
| 1825 |
frame1_output,
|
|
|
|
| 1826 |
frame2_output,
|
| 1827 |
frame_info,
|
| 1828 |
ssim_plot,
|
|
@@ -1846,6 +1869,7 @@ def create_app():
|
|
| 1846 |
status_output,
|
| 1847 |
frame_slider,
|
| 1848 |
frame1_output,
|
|
|
|
| 1849 |
frame2_output,
|
| 1850 |
frame_info,
|
| 1851 |
ssim_plot,
|
|
@@ -1874,6 +1898,7 @@ def create_app():
|
|
| 1874 |
status_output,
|
| 1875 |
frame_slider,
|
| 1876 |
frame1_output,
|
|
|
|
| 1877 |
frame2_output,
|
| 1878 |
frame_info,
|
| 1879 |
ssim_plot,
|
|
@@ -1895,6 +1920,7 @@ def create_app():
|
|
| 1895 |
inputs=[frame_slider],
|
| 1896 |
outputs=[
|
| 1897 |
frame1_output,
|
|
|
|
| 1898 |
frame2_output,
|
| 1899 |
frame_info,
|
| 1900 |
ssim_plot,
|
|
|
|
| 6 |
import imagehash
|
| 7 |
import numpy as np
|
| 8 |
import plotly.graph_objects as go
|
| 9 |
+
from gradio_imageslider import ImageSlider
|
| 10 |
from PIL import Image
|
| 11 |
from scipy.stats import pearsonr
|
| 12 |
from skimage.metrics import mean_squared_error as mse_skimage
|
|
|
|
| 1533 |
with gr.Column():
|
| 1534 |
gr.Markdown("### Video 1 - Current Frame")
|
| 1535 |
frame1_output = gr.Image(
|
| 1536 |
+
label="Video 1 Frame",
|
| 1537 |
+
type="numpy",
|
| 1538 |
+
interactive=False,
|
| 1539 |
+
# height=400,
|
| 1540 |
+
)
|
| 1541 |
+
|
| 1542 |
+
with gr.Column():
|
| 1543 |
+
gr.Markdown("### Frame Comparison Slider")
|
| 1544 |
+
image_slider = ImageSlider(
|
| 1545 |
+
label="Drag to compare frames",
|
| 1546 |
+
type="numpy",
|
| 1547 |
+
interactive=True,
|
| 1548 |
+
# height=400,
|
| 1549 |
)
|
| 1550 |
|
| 1551 |
with gr.Column():
|
| 1552 |
gr.Markdown("### Video 2 - Current Frame")
|
| 1553 |
frame2_output = gr.Image(
|
| 1554 |
+
label="Video 2 Frame",
|
| 1555 |
+
type="numpy",
|
| 1556 |
+
interactive=False,
|
| 1557 |
+
# height=400,
|
| 1558 |
)
|
| 1559 |
|
| 1560 |
# Frame navigation (initially hidden) - moved underneath frames
|
|
|
|
| 1602 |
- **pHash**: Perceptual Hash similarity (1.0 = visually identical)
|
| 1603 |
- **Color Histogram**: Color distribution correlation (1.0 = identical color patterns)
|
| 1604 |
- **Sharpness**: Laplacian variance per video (higher = sharper/more detailed images)
|
| 1605 |
+
- **Overall Quality**: Combined metric averaging SSIM, min-max normalized PSNR, and pHash
|
| 1606 |
""")
|
| 1607 |
with gr.Column() as info_section:
|
| 1608 |
status_output = gr.Textbox(
|
|
|
|
| 1707 |
status, # status_output
|
| 1708 |
slider_update, # frame_slider
|
| 1709 |
frame1, # frame1_output
|
| 1710 |
+
(frame1, frame2), # image_slider
|
| 1711 |
frame2, # frame2_output
|
| 1712 |
info, # frame_info
|
| 1713 |
ssim_fig, # ssim_plot
|
|
|
|
| 1726 |
def update_frames(frame_index):
|
| 1727 |
if comparator.max_frames == 0:
|
| 1728 |
return (
|
| 1729 |
+
None,
|
| 1730 |
None,
|
| 1731 |
None,
|
| 1732 |
"No videos loaded",
|
|
|
|
| 1736 |
None,
|
| 1737 |
None,
|
| 1738 |
None,
|
| 1739 |
+
None,
|
| 1740 |
)
|
| 1741 |
|
| 1742 |
frame1, frame2 = comparator.get_frames_at_index(frame_index)
|
|
|
|
| 1754 |
|
| 1755 |
return (
|
| 1756 |
frame1,
|
| 1757 |
+
(frame1, frame2),
|
| 1758 |
frame2,
|
| 1759 |
info,
|
| 1760 |
ssim_fig,
|
|
|
|
| 1783 |
minimum=0, maximum=0, step=1, value=0, interactive=False
|
| 1784 |
), # frame_slider
|
| 1785 |
None, # frame1_output
|
| 1786 |
+
(None, None), # image_slider
|
| 1787 |
None, # frame2_output
|
| 1788 |
"", # frame_info
|
| 1789 |
None, # ssim_plot
|
|
|
|
| 1813 |
print("DEBUG: Same video pair already processed, skipping...")
|
| 1814 |
# Return current state without recomputing
|
| 1815 |
return (
|
| 1816 |
+
gr.update(), # status_output
|
| 1817 |
+
gr.update(), # frame_slider
|
| 1818 |
+
gr.update(), # frame1_output
|
| 1819 |
+
gr.update(), # image_slider
|
| 1820 |
+
gr.update(), # frame2_output
|
| 1821 |
+
gr.update(), # frame_info
|
| 1822 |
+
gr.update(), # ssim_plot
|
| 1823 |
+
gr.update(), # psnr_plot
|
| 1824 |
+
gr.update(), # mse_plot
|
| 1825 |
+
gr.update(), # phash_plot
|
| 1826 |
+
gr.update(), # color_plot
|
| 1827 |
+
gr.update(), # sharpness_plot
|
| 1828 |
+
gr.update(), # overall_plot
|
| 1829 |
+
gr.update(), # frame_controls
|
| 1830 |
+
gr.update(), # frame_display
|
| 1831 |
+
gr.update(), # metrics_section
|
| 1832 |
+
gr.update(), # info_section
|
| 1833 |
)
|
| 1834 |
|
| 1835 |
last_processed_pair["video1"] = video1
|
|
|
|
| 1845 |
status_output,
|
| 1846 |
frame_slider,
|
| 1847 |
frame1_output,
|
| 1848 |
+
image_slider,
|
| 1849 |
frame2_output,
|
| 1850 |
frame_info,
|
| 1851 |
ssim_plot,
|
|
|
|
| 1869 |
status_output,
|
| 1870 |
frame_slider,
|
| 1871 |
frame1_output,
|
| 1872 |
+
image_slider,
|
| 1873 |
frame2_output,
|
| 1874 |
frame_info,
|
| 1875 |
ssim_plot,
|
|
|
|
| 1898 |
status_output,
|
| 1899 |
frame_slider,
|
| 1900 |
frame1_output,
|
| 1901 |
+
image_slider,
|
| 1902 |
frame2_output,
|
| 1903 |
frame_info,
|
| 1904 |
ssim_plot,
|
|
|
|
| 1920 |
inputs=[frame_slider],
|
| 1921 |
outputs=[
|
| 1922 |
frame1_output,
|
| 1923 |
+
image_slider,
|
| 1924 |
frame2_output,
|
| 1925 |
frame_info,
|
| 1926 |
ssim_plot,
|
pyproject.toml
CHANGED
|
@@ -13,4 +13,5 @@ dependencies = [
|
|
| 13 |
"plotly>=5.17.0",
|
| 14 |
"imagehash>=4.3.1",
|
| 15 |
"scipy>=1.11.0",
|
|
|
|
| 16 |
]
|
|
|
|
| 13 |
"plotly>=5.17.0",
|
| 14 |
"imagehash>=4.3.1",
|
| 15 |
"scipy>=1.11.0",
|
| 16 |
+
"gradio-imageslider>=0.0.20",
|
| 17 |
]
|
uv.lock
CHANGED
|
@@ -217,11 +217,12 @@ wheels = [
|
|
| 217 |
]
|
| 218 |
|
| 219 |
[[package]]
|
| 220 |
-
name = "
|
| 221 |
version = "0.1.0"
|
| 222 |
source = { virtual = "." }
|
| 223 |
dependencies = [
|
| 224 |
{ name = "gradio" },
|
|
|
|
| 225 |
{ name = "imagehash" },
|
| 226 |
{ name = "numpy" },
|
| 227 |
{ name = "opencv-python" },
|
|
@@ -234,6 +235,7 @@ dependencies = [
|
|
| 234 |
[package.metadata]
|
| 235 |
requires-dist = [
|
| 236 |
{ name = "gradio", specifier = ">=5.38.2" },
|
|
|
|
| 237 |
{ name = "imagehash", specifier = ">=4.3.1" },
|
| 238 |
{ name = "numpy", specifier = ">=1.24.0" },
|
| 239 |
{ name = "opencv-python", specifier = ">=4.8.0" },
|
|
@@ -310,6 +312,19 @@ wheels = [
|
|
| 310 |
{ url = "https://files.pythonhosted.org/packages/e0/38/7f50ae95de8fa419276742230f57a34e8c0f47231da0ad54479dd0088972/gradio_client-1.11.0-py3-none-any.whl", hash = "sha256:afb714aea50224f6f04679fe2ce79c1be75011012d0dc3b3ee575610a0dc8eb2", size = 324452 },
|
| 311 |
]
|
| 312 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 313 |
[[package]]
|
| 314 |
name = "groovy"
|
| 315 |
version = "0.1.2"
|
|
|
|
| 217 |
]
|
| 218 |
|
| 219 |
[[package]]
|
| 220 |
+
name = "framearena"
|
| 221 |
version = "0.1.0"
|
| 222 |
source = { virtual = "." }
|
| 223 |
dependencies = [
|
| 224 |
{ name = "gradio" },
|
| 225 |
+
{ name = "gradio-imageslider" },
|
| 226 |
{ name = "imagehash" },
|
| 227 |
{ name = "numpy" },
|
| 228 |
{ name = "opencv-python" },
|
|
|
|
| 235 |
[package.metadata]
|
| 236 |
requires-dist = [
|
| 237 |
{ name = "gradio", specifier = ">=5.38.2" },
|
| 238 |
+
{ name = "gradio-imageslider", specifier = ">=0.0.20" },
|
| 239 |
{ name = "imagehash", specifier = ">=4.3.1" },
|
| 240 |
{ name = "numpy", specifier = ">=1.24.0" },
|
| 241 |
{ name = "opencv-python", specifier = ">=4.8.0" },
|
|
|
|
| 312 |
{ url = "https://files.pythonhosted.org/packages/e0/38/7f50ae95de8fa419276742230f57a34e8c0f47231da0ad54479dd0088972/gradio_client-1.11.0-py3-none-any.whl", hash = "sha256:afb714aea50224f6f04679fe2ce79c1be75011012d0dc3b3ee575610a0dc8eb2", size = 324452 },
|
| 313 |
]
|
| 314 |
|
| 315 |
+
[[package]]
|
| 316 |
+
name = "gradio-imageslider"
|
| 317 |
+
version = "0.0.20"
|
| 318 |
+
source = { registry = "https://pypi.org/simple" }
|
| 319 |
+
dependencies = [
|
| 320 |
+
{ name = "gradio" },
|
| 321 |
+
{ name = "pillow" },
|
| 322 |
+
]
|
| 323 |
+
sdist = { url = "https://files.pythonhosted.org/packages/4e/20/aadd2089f4b45abb8f8a407ad124c6a0bda35298bb44af56cc160ec6d945/gradio_imageslider-0.0.20.tar.gz", hash = "sha256:a1421c3239cce2a01160852cdc0962292230418384a0cff3b0307a95a451643f", size = 256548 }
|
| 324 |
+
wheels = [
|
| 325 |
+
{ url = "https://files.pythonhosted.org/packages/38/e5/c33f36a4fd0bd8207bdb0761513387bfbd28b54f51d31265e93fff2d1b1b/gradio_imageslider-0.0.20-py3-none-any.whl", hash = "sha256:c73d155ce14a63f3fceb7547e8238e93c12c9acaecf7445cf8f281aa880cac79", size = 101455 },
|
| 326 |
+
]
|
| 327 |
+
|
| 328 |
[[package]]
|
| 329 |
name = "groovy"
|
| 330 |
version = "0.1.2"
|