Skip to content
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

feat: text renderer, artifact manager, image vstack #41

Merged
merged 3 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions camtools/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from . import artifact
from . import camera
from . import colmap
from . import colormap
Expand Down
48 changes: 48 additions & 0 deletions camtools/artifact.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import requests
from pathlib import Path


class ArtifactManager:
BASE_URL = "https://github.com/yxlao/camtools-artifacts/releases/download"

def __init__(self):
self.cache_dir = Path.home() / ".camtools"

def get_path(self, artifact_key: str, verbose: bool = False):
"""
Checks if the artifact is locally available, and if not, attempts to download it.
"""
artifact_path = self.cache_dir / artifact_key
if artifact_path.exists():
if verbose:
print(f"Found local artifact: {artifact_path}.")
else:
url = f"{self.BASE_URL}/{artifact_key}"
self._try_download_artifact(url, artifact_path)
return artifact_path

def _try_download_artifact(self, url, artifact_path):
"""
Attempts to download the artifact from the provided URL.
"""
try:
print(f"Attempting to download from {url}...")
response = requests.get(url)
if response.status_code == 200:
artifact_path.parent.mkdir(parents=True, exist_ok=True)
artifact_path.write_bytes(response.content)
print(f"Downloaded to {artifact_path}.")
else:
raise RuntimeError(
f"Artifact download failed. Please check the URL {url}"
)
except requests.exceptions.RequestException as e:
print(f"An error occurred while downloading the artifact: {e}")


def get_artifact_path(artifact_key: str):
"""
Checks if the artifact is locally available, and if not, attempts to download it.
"""
artifact_manager = ArtifactManager()
return artifact_manager.get_path(artifact_key)
60 changes: 60 additions & 0 deletions camtools/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -731,3 +731,63 @@ def get_scales(im_height, max_lines, font, line_text_h_ratio):
assert im_corres.min() >= 0.0 and im_corres.max() <= 1.0

return im_corres


def vstack_images(
ims: List[np.ndarray],
alignment: str = "left",
background_color: Tuple[float, float, float] = (1.0, 1.0, 1.0),
) -> np.ndarray:
"""
Vertically stacks images, aligning them to "left", "center", or "right",
with a specified background color.

Args:
ims (List[np.ndarray]): List of float32/float64 images with shape
(H, W, 3) and pixel values in [0.0, 1.0].
alignment (str): How to align images ("left", "center", "right").
Defaults to "left".
background_color (Tuple[float, float, float]): The background color of
the stacked image, specified as a tuple of three floats (R, G, B)
in the range [0.0, 1.0]. Defaults to white (1.0, 1.0, 1.0).

Returns:
np.ndarray: Stacked image as float32.
"""
for im in ims:
if im.ndim != 3 or im.shape[2] != 3:
raise ValueError("Each image must be 3D with 3 channels.")
if im.dtype not in [np.float32, np.float64]:
raise ValueError("Image dtype must be float32/float64.")
if im.min() < 0.0 or im.max() > 1.0:
raise ValueError("Pixels must be in [0.0, 1.0].")
if not all(0 <= c <= 1 for c in background_color):
raise ValueError(
f"background_color must be 3 floats in the range [0, 1], "
f"but got {background_color}."
)
valid_alignments = ["left", "center", "right"]
if alignment not in valid_alignments:
raise ValueError(
f"Invalid alignment: '{alignment}', must be one of {valid_alignments}."
)

max_width = max(im.shape[1] for im in ims)
total_height = sum(im.shape[0] for im in ims)

im_stacked = np.ones((total_height, max_width, 3), dtype=np.float32)
im_stacked = im_stacked * np.array(background_color).reshape(1, 1, 3)

curr_row = 0
for im in ims:
offset = (
(max_width - im.shape[1]) // 2
if alignment == "center"
else max_width - im.shape[1] if alignment == "right" else 0
)
im_stacked[curr_row : curr_row + im.shape[0], offset : offset + im.shape[1]] = (
im
)
curr_row += im.shape[0]

return im_stacked
Loading
Loading