Skip to content

Commit

Permalink
feat: adding ub.label_gallery() tool for data labelling
Browse files Browse the repository at this point in the history
  • Loading branch information
trojblue committed Nov 17, 2024
1 parent 1381b9a commit 0fdac23
Show file tree
Hide file tree
Showing 5 changed files with 751 additions and 2 deletions.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,23 @@ images_to_resize = resizer.get_resize_jobs()
resizer.execute_resize_jobs(images_to_resize)
```

<br>

**view and label images within jupyter notebook**:

```python
import unibox as ub

uris = ["https://cdn.donmai.us/180x180/8e/ea/8eea944690c0c0b27e303420cb1e65bd.jpg"] * 9
labels = ['Image 1', 'Image 2', 'Image 3'] * 3

# label data interactively
ub.label_gallery(uris, labels)

# or: view images only
# ub.gallery(uris, labels)
```



## Install
Expand Down
573 changes: 573 additions & 0 deletions noteooks/test_widget.ipynb

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "unibox"
version = "0.4.12"
version = "0.4.13"
description = "Unibox provides unified interface for common file operations."
authors = ["yada <trojblue@gmail.com>"]
license = "MIT"
Expand Down
8 changes: 7 additions & 1 deletion unibox/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,17 @@
# from .utils.ipython_utils import gallery
try:
from .utils.ipython_utils import gallery
from .utils.ipython_utils import label_gallery
except (ImportError, ModuleNotFoundError):
print("IPython is not available. Gallery function will not work.")
def gallery(*args, **kwargs):

def gallery(paths: list[str], labels: list[str] = [],
row_height='300px', num_workers=32, debug_print=True, thumbnail_size: int = 512):
print("IPython is not available. Gallery function will not work.")

def label_gallery(paths: list[str], labels: list[str] = [],
row_height='150px', num_workers=32, debug_print=True, thumbnail_size: int = 512):
print("IPython is not available. Gallery function will not work.")


def loads(file_path: str | Path, debug_print=True) -> any:
Expand Down
153 changes: 153 additions & 0 deletions unibox/utils/ipython_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,156 @@ def gallery(paths: list[str], labels: list[str] = [],
{''.join(figures)}
</div>
''')


from IPython.display import HTML, display
from PIL import Image as PILImage
from io import BytesIO
import base64

def label_gallery(paths: list[str], labels: list[str] = [],
row_height='150px', num_workers=32, debug_print=True, thumbnail_size: int = 512):
"""Displays images in a gallery with JavaScript-based selection.
Parameters
----------
paths: list of str
Paths to images to display. Can be local paths or URLs.
labels: list of str
Labels for each image. Defaults to the filename if not provided.
row_height: str
CSS height value to assign to all images.
num_workers: int
Number of concurrent workers to load images.
debug_print: bool
Whether to print debug information or not.
thumbnail_size: int
If provided, resize images to this size using PIL's thumbnail method.
"""
if len(paths) > 1000:
raise ValueError("Too many images to display.")

if len(labels) > 0 and len(labels) != len(paths):
raise ValueError("Number of labels must match number of paths.")

# Load images using ub.concurrent_loads()
images = concurrent_loads(paths, num_workers=num_workers) # list of PIL images

# Process images: resize and handle None
processed_images = []
for img in images:
if img is not None:
if thumbnail_size > 0:
img.thumbnail((thumbnail_size, thumbnail_size))
processed_images.append(img)
else:
processed_images.append(None) # Keep None for images that failed to load

figures = []
for i, image in enumerate(processed_images):
if image is not None:
try:
buffered = BytesIO()
image.save(buffered, format="JPEG")
img_data = base64.b64encode(buffered.getvalue()).decode()
src = f"data:image/jpeg;base64,{img_data}"

if len(labels) > 0:
caption_str = labels[i]
else:
caption_str = paths[i].split("/")[-1]

figures.append(f'''
<figure style="margin: 5px !important; width: auto; text-align: center;">
<img src="{src}" style="height: {row_height}; cursor: pointer; box-sizing: border-box;" onclick="selectImage({i}, this)">
<figcaption style="font-size: 0.6em">{caption_str}</figcaption>
</figure>
''')
except Exception as e:
figures.append(f'''
<figure style="margin: 5px !important;">
<figcaption style="font-size: 0.6em; color: red;">Error processing image {i}: {e}</figcaption>
</figure>
''')
else:
figures.append(f'''
<figure style="margin: 5px !important;">
<figcaption style="font-size: 0.6em; color: red;">Error loading image {i}</figcaption>
</figure>
''')

html_content = f'''
<div id="gallery" style="display: flex; flex-wrap: wrap; text-align: center;">
{''.join(figures)}
</div>
<button onclick="copySelection()" style="margin-top: 10px;">Copy Selection</button>
<script>
var selectedIndices = [];
function updateOutput() {{
var outputDiv = document.getElementById('output');
if (selectedIndices.length > 0) {{
var indicesList = '[' + selectedIndices.sort((a, b) => a - b).join(', ') + ']';
outputDiv.innerHTML = 'Selected indices: ' + indicesList;
}} else {{
outputDiv.innerHTML = 'Selected indices: []';
}}
}}
function selectImage(index, imgElement) {{
var idx = selectedIndices.indexOf(index);
if (idx > -1) {{
// Deselect
selectedIndices.splice(idx, 1);
imgElement.style.outline = '';
}} else {{
// Select
selectedIndices.push(index);
imgElement.style.outline = '4px solid blue';
}}
updateOutput();
}}
function copySelection() {{
if (selectedIndices.length > 0) {{
var indicesList = '[' + selectedIndices.sort((a, b) => a - b).join(', ') + ']';
if (navigator.clipboard && navigator.clipboard.writeText) {{
navigator.clipboard.writeText(indicesList).then(function() {{
alert('Selected indices copied to clipboard.');
}}, function(err) {{
// Fallback method
fallbackCopyTextToClipboard(indicesList);
}});
}} else {{
// Fallback method
fallbackCopyTextToClipboard(indicesList);
}}
}} else {{
alert('No images selected.');
}}
}}
function fallbackCopyTextToClipboard(text) {{
var textArea = document.createElement("textarea");
textArea.value = text;
textArea.style.position = "fixed"; // Avoid scrolling to bottom
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {{
var successful = document.execCommand('copy');
if (successful) {{
alert('Selected indices copied to clipboard.');
}} else {{
alert('Could not copy text.');
}}
}} catch (err) {{
alert('Could not copy text: ', err);
}}
document.body.removeChild(textArea);
}}
</script>
'''

display(HTML(html_content))

0 comments on commit 0fdac23

Please sign in to comment.