-
Notifications
You must be signed in to change notification settings - Fork 525
feat: image_compare
#4216
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
Haleshot
wants to merge
4
commits into
marimo-team:main
Choose a base branch
from
Haleshot:haleshot/image-comparison
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
feat: image_compare
#4216
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
#!/usr/bin/env python3 | ||
# Copyright 2024 Marimo. All rights reserved. | ||
|
||
""" | ||
Image comparison demo using Marimo's slider functionality | ||
""" | ||
|
||
import marimo | ||
|
||
__generated_with = "0.11.25" | ||
app = marimo.App() | ||
|
||
|
||
@app.cell(hide_code=True) | ||
def _(mo): | ||
mo.md( | ||
""" | ||
# Image Comparison Demo | ||
|
||
This demo showcases the `mo.image_compare` feature, which allows for side-by-side comparison of images. | ||
|
||
## Basic Usage - Horizontal Comparison | ||
|
||
The default orientation is horizontal, where you can slide left and right to compare images: | ||
""" | ||
) | ||
return | ||
|
||
|
||
@app.cell | ||
def _(): | ||
before_image_path = "before.jpg" | ||
after_image_path = "after.jpg" | ||
return after_image_path, before_image_path | ||
|
||
|
||
@app.cell | ||
def _(after_image_path, before_image_path, mo): | ||
# Basic horizontal comparison with default settings | ||
mo.image_compare(before_image=before_image_path, after_image=after_image_path) | ||
return | ||
|
||
|
||
@app.cell(hide_code=True) | ||
def _(mo): | ||
mo.md( | ||
r""" | ||
## Custom Labels | ||
|
||
You can add custom labels to each image: | ||
""" | ||
) | ||
return | ||
|
||
|
||
@app.cell | ||
def _(after_image_path, before_image_path, mo): | ||
mo.image_compare( | ||
before_image=before_image_path, | ||
after_image=after_image_path, | ||
direction="horizontal", | ||
show_labels=True, | ||
before_label="Before", | ||
after_label="After", | ||
height=1000, | ||
) | ||
return | ||
|
||
|
||
@app.cell(hide_code=True) | ||
def _(mo): | ||
mo.md( | ||
r""" | ||
## Vertical Comparison | ||
|
||
You can also use a vertical comparison slider: | ||
""" | ||
) | ||
return | ||
|
||
|
||
@app.cell | ||
def _(after_image_path, before_image_path, mo): | ||
mo.image_compare( | ||
before_image=before_image_path, | ||
after_image=after_image_path, | ||
direction="vertical", | ||
show_labels=True, | ||
before_label="Before", | ||
after_label="After", | ||
height=400, | ||
) | ||
return | ||
|
||
|
||
@app.cell | ||
def _(): | ||
import marimo as mo | ||
return (mo,) | ||
|
||
|
||
if __name__ == "__main__": | ||
app.run() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
# Copyright 2024 Marimo. All rights reserved. | ||
from __future__ import annotations | ||
|
||
import io | ||
import os | ||
from pathlib import Path | ||
from typing import Literal, Optional, Union | ||
|
||
import marimo._output.data.data as mo_data | ||
from marimo._output.hypertext import Html | ||
from marimo._output.rich_help import mddoc | ||
from marimo._output.utils import normalize_dimension | ||
from marimo._plugins.core.media import io_to_data_url | ||
from marimo._plugins.stateless.image import ImageLike, _normalize_image | ||
|
||
|
||
@mddoc | ||
def image_compare( | ||
before_image: ImageLike, | ||
after_image: ImageLike, | ||
value: float = 50, | ||
direction: Literal["horizontal", "vertical"] = "horizontal", | ||
show_labels: bool = False, | ||
before_label: str = "Before", | ||
after_label: str = "After", | ||
width: Optional[Union[int, str]] = None, | ||
height: Optional[Union[int, str]] = None, | ||
) -> Html: | ||
"""Render an image comparison slider to compare two images side by side. | ||
|
||
Examples: | ||
```python3 | ||
# Basic usage with two images | ||
mo.image_compare(before_image="before.jpg", after_image="after.jpg") | ||
``` | ||
|
||
```python3 | ||
# With custom settings | ||
mo.image_compare( | ||
before_image="original.png", | ||
after_image="processed.png", | ||
value=30, # Initial slider position at 30% | ||
direction="vertical", | ||
show_labels=True, | ||
before_label="Original", | ||
after_label="Processed", | ||
width=500, | ||
height=400, | ||
) | ||
``` | ||
|
||
Args: | ||
before_image: The "before" image to show in the comparison slider. | ||
Can be a path, URL, or array-like object. | ||
after_image: The "after" image to show in the comparison slider. | ||
Can be a path, URL, or array-like object. | ||
value: Initial position of the slider (0-100), defaults to 50. | ||
direction: Orientation of the slider, either "horizontal" or "vertical". | ||
Defaults to "horizontal". | ||
show_labels: Whether to show labels on the images, defaults to False. | ||
before_label: Label for the "before" image, defaults to "Before". | ||
after_label: Label for the "after" image, defaults to "After". | ||
width: Width of the component in pixels or CSS units. | ||
height: Height of the component in pixels or CSS units. | ||
|
||
Returns: | ||
`Html` object with the image comparison slider. | ||
""" | ||
# Process the before and after images | ||
before_src = _process_image_to_url(before_image) | ||
after_src = _process_image_to_url(after_image) | ||
|
||
normalized_value = max(0, min(100, float(value))) | ||
|
||
# Create container styles | ||
container_styles = {} | ||
if width is not None: | ||
container_styles["width"] = normalize_dimension(width) | ||
if height is not None: | ||
container_styles["height"] = normalize_dimension(height) | ||
|
||
if direction == "vertical" and "height" not in container_styles: | ||
container_styles["height"] = "400px" | ||
|
||
# Determine slots based on direction | ||
# In vertical mode we need to swap slots for correct display | ||
first_slot = "second" if direction == "vertical" else "first" | ||
second_slot = "first" if direction == "vertical" else "second" | ||
|
||
# Create HTML content | ||
html_content = f""" | ||
<script defer src="https://unpkg.com/img-comparison-slider@7/dist/index.js"></script> | ||
<link rel="stylesheet" href="https://unpkg.com/img-comparison-slider@7/dist/styles.css" /> | ||
<style> | ||
img-comparison-slider {{ | ||
--divider-width: 2px; | ||
--divider-color: white; | ||
--default-handle-opacity: 1; | ||
{f"width: {container_styles.get('width', '100%')};" if width is not None else ""} | ||
{f"height: {container_styles.get('height', 'auto')};" if height is not None else ""} | ||
max-width: 100%; | ||
display: block; | ||
}} | ||
</style> | ||
<img-comparison-slider value="{normalized_value}" direction="{direction}" class="img-comparison-slider"> | ||
<img slot="{first_slot}" src="{before_src}" /> | ||
<img slot="{second_slot}" src="{after_src}" /> | ||
{f'<div slot="{first_slot}-label" class="label">{before_label}</div>' if show_labels else ""} | ||
{f'<div slot="{second_slot}-label" class="label">{after_label}</div>' if show_labels else ""} | ||
Comment on lines
+108
to
+109
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The labels feature isn't working correctly yet (doesn't seem to show up at all). |
||
</img-comparison-slider> | ||
""" | ||
|
||
return Html(html_content) | ||
|
||
|
||
def _process_image_to_url(src: ImageLike) -> str: | ||
"""Process an image-like object to a URL that can be used in an <img> tag. | ||
|
||
Args: | ||
src: An image-like object. | ||
|
||
Returns: | ||
A string URL that can be used in an <img> tag. | ||
""" | ||
try: | ||
src = _normalize_image(src) | ||
|
||
# different types handling | ||
if isinstance(src, io.BufferedReader) or isinstance(src, io.BytesIO): | ||
src.seek(0) | ||
return mo_data.image(src.read()).url | ||
elif isinstance(src, bytes): | ||
return mo_data.image(src).url | ||
elif isinstance(src, Path): | ||
return mo_data.image(src.read_bytes(), ext=src.suffix).url | ||
elif isinstance(src, str) and os.path.isfile( | ||
expanded_path := os.path.expanduser(src) | ||
): | ||
path = Path(expanded_path) | ||
return mo_data.image(path.read_bytes(), ext=path.suffix).url | ||
else: | ||
# If it's a URL or other string, try to use it directly | ||
return io_to_data_url(src, fallback_mime_type="image/png") | ||
except Exception as e: | ||
# return an error message otherwise | ||
error_message = f"Error processing image: {str(e)}" | ||
# Using a comment instead of print for logging | ||
# print(f"Warning: {error_message}") | ||
return f"data:text/plain,{error_message}" |
Empty file.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this can be a library in our
package.json
so that this plugin can work offline.we can create a stateless plugin
ImageComparison
. you can see how we do this withJsonOutputPlugin
orAccordionPlugin
.since
img-comparison-slider
also may not be common. we should lazy-load the library. you can look at SliderPlugin to VegaPlugin for some exampelsThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure; will look into this. Thanks for the feedback.