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"
|