-
Notifications
You must be signed in to change notification settings - Fork 0
Feature/cluster representative images #42
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
Open
NetZissou
wants to merge
8
commits into
main
Choose a base branch
from
feature/cluster-representative-images
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.
Open
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
b282592
Add per-cluster representative images: shared util + concurrent fetch
NetZissou 2495a80
Merge remote-tracking branch 'origin/main' into feature/cluster-repre…
NetZissou 806f557
Use (+URL) crawler convention in image-fetch User-Agent
NetZissou 741eb35
Single-source the pkg version in pypropject.toml
NetZissou a9a0220
Address Copilot review comments on PR #42
NetZissou d770c10
Document the oversampled representatives contract
NetZissou 4059c2b
Use a per-thread requests.Session for image fetching
NetZissou 1ed6e5e
Restore multi-column URL fallback for record images
NetZissou 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
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
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,76 @@ | ||
| """Shared renderer for per-cluster representative images. | ||
|
|
||
| Both apps surface representative images differently: | ||
| - embed_explore resolves a local image file path. | ||
| - precalculated fetches a remote image URL (which can fail). | ||
|
|
||
| This renderer is source-agnostic: the caller passes a `resolve_image(idx)` | ||
| callable that returns something `st.image` can display (a PIL image, a path, | ||
| or bytes) or `None` when the image is unavailable. The renderer walks each | ||
| cluster's ranked candidate indices and collects up to `n_per_cluster` | ||
| successful images, skipping any that resolve to `None` — the shared fallback. | ||
| """ | ||
|
|
||
| from typing import Any, Callable, Dict, List, Optional | ||
|
|
||
| import streamlit as st | ||
|
|
||
| from shared.utils.logging_config import get_logger | ||
|
|
||
| logger = get_logger(__name__) | ||
|
|
||
|
|
||
| def _sorted_cluster_ids(representatives: Dict[object, List[int]]) -> List[object]: | ||
| """Sort cluster ids numerically when possible, else as strings.""" | ||
| keys = list(representatives.keys()) | ||
| try: | ||
| return sorted(keys, key=lambda k: int(k)) | ||
| except (ValueError, TypeError): | ||
| return sorted(keys, key=str) | ||
|
|
||
|
|
||
| def render_representative_images( | ||
| representatives: Dict[object, List[int]], | ||
| resolve_image: Callable[[int], Optional[Any]], | ||
| n_per_cluster: int = 3, | ||
| caption_fn: Optional[Callable[[int], Optional[str]]] = None, | ||
| columns: int = 3, | ||
| ) -> None: | ||
| """Render up to `n_per_cluster` representative images per cluster. | ||
|
|
||
| Args: | ||
| representatives: {cluster_id: [ranked candidate global indices]}, as | ||
| returned by `find_cluster_representatives`. | ||
| resolve_image: idx -> displayable (PIL image / path / bytes) or None. | ||
| None means "unavailable" and the renderer falls back to the next | ||
| candidate. | ||
| n_per_cluster: number of images to show per cluster. | ||
| caption_fn: optional idx -> caption string, or None for no caption. | ||
| columns: images per row. | ||
| """ | ||
| for cluster_id in _sorted_cluster_ids(representatives): | ||
| candidates = representatives[cluster_id] | ||
| st.markdown(f"**Cluster {cluster_id}**") | ||
|
|
||
| # Walk ranked candidates, collecting successful resolutions until we | ||
| # have n_per_cluster (or run out of candidates). | ||
| shown: List[tuple] = [] # (displayable, caption) | ||
| for idx in candidates: | ||
| if len(shown) >= n_per_cluster: | ||
| break | ||
| try: | ||
| img = resolve_image(idx) | ||
| except Exception as e: # never let one bad image break the panel | ||
| logger.debug(f"resolve_image({idx}) raised: {e}") | ||
| img = None | ||
| if img is not None: | ||
| caption = caption_fn(idx) if caption_fn else None | ||
| shown.append((img, caption)) | ||
|
|
||
| if not shown: | ||
| st.caption("No images available for this cluster.") | ||
| continue | ||
|
|
||
| cols = st.columns(min(columns, len(shown))) | ||
| for i, (img, caption) in enumerate(shown): | ||
| cols[i % len(cols)].image(img, caption=caption, width="stretch") |
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
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.