From 8f443369128b3d6140c795c5ffbd82fbb0db485d Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 18 Aug 2026 21:39:06 +0000 Subject: [PATCH] Slim core vlmrun dependencies and promote openai to base install Move heavy optional packages (pandas, opencv-python, IPython) out of core into extras, with DependencyError helpers pointing at pip install vlmrun[all] or vlmrun[video]. Promote openai to core so vlmrun gw chat works out of the box with pip install vlmrun or uvx vlmrun. Remove redundant openai and cli extras. Co-authored-by: Sudeep Pillai --- AGENTS.md | 8 +-- README.md | 13 ++--- pyproject.toml | 17 ++---- requirements/requirements.txt | 6 +- tests/common/test_dependencies.py | 95 ++++++++++++++++++++----------- tests/test_gateway.py | 6 +- vlmrun/cli/README.md | 10 +--- vlmrun/client/agent.py | 20 +------ vlmrun/client/gateway.py | 24 +++----- vlmrun/client/types.py | 22 +++++-- vlmrun/common/dependencies.py | 75 ++++++++++++++++++++++++ vlmrun/common/pdf.py | 3 +- vlmrun/common/video.py | 63 +++++++++++--------- vlmrun/common/viz.py | 16 ++++-- 14 files changed, 233 insertions(+), 145 deletions(-) create mode 100644 vlmrun/common/dependencies.py diff --git a/AGENTS.md b/AGENTS.md index f553a5e..0e4aba4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -79,11 +79,11 @@ Environment variables: ## Optional Dependencies Install based on needed functionality: -- `pip install vlmrun[cli]` - CLI with Typer/Rich -- `pip install vlmrun[video]` - Video processing (numpy) +- `pip install vlmrun[video]` - Video processing (numpy, opencv-python) - `pip install vlmrun[doc]` - PDF processing (pypdfium2) -- `pip install vlmrun[openai]` - OpenAI SDK for chat completions API -- `pip install vlmrun[all]` - All optional dependencies +- `pip install vlmrun[all]` - All optional dependencies (video, doc, pandas, IPython) + +The CLI, OpenAI SDK, and gateway chat commands are included in the base `pip install vlmrun` install. ## Testing diff --git a/README.md b/README.md index ceedac4..fb8927e 100644 --- a/README.md +++ b/README.md @@ -30,11 +30,6 @@ pip install vlmrun The package provides optional features that can be installed based on your needs: -- Chat with Orion via the CLI (see `vlmrun chat`) - ```bash - pip install "vlmrun[cli]" - ``` - - Video processing features (numpy, opencv-python): ```bash pip install "vlmrun[video]" @@ -45,9 +40,9 @@ The package provides optional features that can be installed based on your needs pip install "vlmrun[doc]" ``` -- OpenAI SDK integration (for chat completions API): +- Visualization and notebook helpers (pandas, IPython): ```bash - pip install "vlmrun[openai]" + pip install "vlmrun[all]" ``` - All optional features: @@ -55,6 +50,8 @@ The package provides optional features that can be installed based on your needs pip install "vlmrun[all]" ``` +The CLI and OpenAI-compatible gateway (`vlmrun gw chat`, `vlmrun chat`) work out of the box with `pip install vlmrun`. + ### Basic Usage ```python @@ -120,8 +117,6 @@ async def main(): asyncio.run(main()) ``` -**Installation**: Install with OpenAI support using `pip install vlmrun[openai]` - ### CLI Chat with Skills The `vlmrun chat` command supports **skills** — local directories containing a `SKILL.md` and optional assets that give the agent domain-specific expertise. Skills are sent inline with each request (no server-side upload required). diff --git a/pyproject.toml b/pyproject.toml index 4b56e85..55c5c58 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,26 +31,21 @@ license = {text = "Apache-2.0"} dynamic = ["version", "dependencies"] [project.optional-dependencies] -test = ["pytest", "openai", "pre-commit"] +test = ["pytest", "pre-commit"] build = ["twine", "build"] -openai = ["openai>=1.0.0"] video = [ "numpy>=1.24.0", + "opencv-python>=4.8.0", ] doc = [ - "pypdfium2>=4.30.0" -] -cli = [ - "typer>=0.9.0", - "rich>=13.0.0", - "openai>=1.0.0", + "pypdfium2>=4.30.0", ] all = [ "numpy>=1.24.0", + "opencv-python>=4.8.0", "pypdfium2>=4.30.0", - "openai>=1.0.0", - "typer>=0.9.0", - "rich>=13.0.0", + "pandas", + "ipython", ] [tool.setuptools.dynamic] diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 7ea077a..dd984e5 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -1,15 +1,11 @@ cachetools -IPython loguru -opencv-python>=4.8.0 -pandas +openai>=1.0.0 Pillow>=10.2.0 pydantic>=2.5,<3 pydantic_core>=2.23.4 requests rich -tabulate tenacity -tqdm typer>=0.9.0 vlmrun-hub>=0.1.28 diff --git a/tests/common/test_dependencies.py b/tests/common/test_dependencies.py index 3023642..670f785 100644 --- a/tests/common/test_dependencies.py +++ b/tests/common/test_dependencies.py @@ -1,46 +1,77 @@ -"""Tests for verifying correct installation of optional dependencies.""" +"""Tests for verifying optional dependency handling.""" + +from __future__ import annotations + +import builtins +import sys import pytest +from vlmrun.client.exceptions import DependencyError +from vlmrun.common import dependencies + + +def _block_import(monkeypatch, module_name: str) -> None: + for key in list(sys.modules): + if key == module_name or key.startswith(f"{module_name}."): + monkeypatch.delitem(sys.modules, key, raising=False) -@pytest.mark.skip(reason="Temporarily skipped as requested") -def test_base_dependencies(): - """Verify base installation has no optional dependencies.""" - with pytest.raises(ImportError): - import cv2 # noqa: F401 + real_import = builtins.__import__ - with pytest.raises(ImportError): - import pypdfium2 # noqa: F401 + def mock_import(name, globals=None, locals=None, fromlist=(), level=0): + blocked = ( + name == module_name + or name.startswith(f"{module_name}.") + or (fromlist and module_name in fromlist) + ) + if blocked: + raise ImportError(f"No module named '{module_name}'") + return real_import(name, globals, locals, fromlist, level) + monkeypatch.setattr(builtins, "__import__", mock_import) -@pytest.mark.skip(reason="Temporarily skipped as requested") -def test_video_dependencies(): - """Verify video dependencies are available.""" - import cv2 # noqa: F401 - import numpy as np # noqa: F401 - # Verify we can import and get versions - assert cv2.__version__, "cv2 version should be available" - assert np.__version__, "numpy version should be available" +def test_require_openai_suggestion(monkeypatch): + """OpenAI is a core dependency; errors should point at base install.""" + _block_import(monkeypatch, "openai") + with pytest.raises(DependencyError) as exc_info: + dependencies.require_openai() + assert "pip install vlmrun" in exc_info.value.suggestion + assert "[openai]" not in exc_info.value.suggestion -@pytest.mark.skip(reason="Temporarily skipped as requested") -def test_doc_dependencies(): - """Verify doc dependencies are available.""" - import pypdfium2 # noqa: F401 +@pytest.mark.parametrize( + ("require_fn", "module_name", "extra"), + [ + (dependencies.require_pandas, "pandas", "all"), + (dependencies.require_numpy, "numpy", "video"), + (dependencies.require_cv2, "cv2", "video"), + (dependencies.require_ipython_html, "IPython", "all"), + (dependencies.require_pypdfium2, "pypdfium2", "doc"), + ], +) +def test_optional_dependency_errors(require_fn, module_name, extra, monkeypatch): + """Missing optional deps should raise DependencyError with install hints.""" + _block_import(monkeypatch, module_name) + with pytest.raises(DependencyError) as exc_info: + require_fn() + assert f"vlmrun[{extra}]" in exc_info.value.suggestion - # Verify we can import and get version - assert pypdfium2.__version__, "pypdfium2 version should be available" +def test_markdown_table_to_dataframe_requires_pandas(monkeypatch): + """MarkdownTable.to_dataframe should lazy-load pandas.""" + def _raise_pandas(): + raise DependencyError( + message="pandas is not installed", + suggestion="Install it with `pip install vlmrun[all]`", + ) -@pytest.mark.skip(reason="Temporarily skipped as requested") -def test_all_dependencies(): - """Verify all dependencies are available.""" - import cv2 # noqa: F401 - import numpy as np # noqa: F401 - import pypdfium2 # noqa: F401 + monkeypatch.setattr("vlmrun.client.types.require_pandas", _raise_pandas) + from vlmrun.client.types import MarkdownTable, TableHeader - # Verify we can import and get versions - assert cv2.__version__, "cv2 version should be available" - assert np.__version__, "numpy version should be available" - assert pypdfium2.__version__, "pypdfium2 version should be available" + table = MarkdownTable( + headers=[TableHeader(id="col1", column=0, name="Column 1")], + data=[{"col1": "value"}], + ) + with pytest.raises(DependencyError): + table.to_dataframe() diff --git a/tests/test_gateway.py b/tests/test_gateway.py index 811ba21..1502465 100644 --- a/tests/test_gateway.py +++ b/tests/test_gateway.py @@ -308,7 +308,7 @@ class _Resp: status_code = 200 is_success = True - monkeypatch.setattr("httpx.get", lambda *a, **k: _Resp()) + monkeypatch.setattr("requests.get", lambda *a, **k: _Resp()) assert g.health() is True def test_health_falls_back_to_models_on_404(self, monkeypatch): @@ -318,7 +318,7 @@ class _Resp: status_code = 404 is_success = False - monkeypatch.setattr("httpx.get", lambda *a, **k: _Resp()) + monkeypatch.setattr("requests.get", lambda *a, **k: _Resp()) class _Models: def list(self): @@ -336,7 +336,7 @@ def test_health_false_on_connection_error(self, monkeypatch): def _boom(*a, **k): raise RuntimeError("no network") - monkeypatch.setattr("httpx.get", _boom) + monkeypatch.setattr("requests.get", _boom) class _Models: def list(self): diff --git a/vlmrun/cli/README.md b/vlmrun/cli/README.md index 239f94e..e351b22 100644 --- a/vlmrun/cli/README.md +++ b/vlmrun/cli/README.md @@ -4,16 +4,12 @@ Visual AI from your terminal. Chat with VLM Run's Orion visual AI agent to proce ## Installation -The CLI is included as an extra in the vlmrun package: - ```bash -# Install vlmrun with CLI support -pip install "vlmrun[cli]" - -# Or with uv -uv pip install "vlmrun[cli]" +pip install vlmrun ``` +The CLI and OpenAI-compatible gateway work out of the box with the base install. + ## Quick Start 1. **Get your API key** at [app.vlm.run](https://app.vlm.run) diff --git a/vlmrun/client/agent.py b/vlmrun/client/agent.py index a1ad669..3fbeaf0 100644 --- a/vlmrun/client/agent.py +++ b/vlmrun/client/agent.py @@ -19,7 +19,7 @@ AgentCreationResponse, AgentToolset, ) -from vlmrun.client.exceptions import DependencyError +from openai import AsyncOpenAI, OpenAI # VLM Run-specific kwargs accepted by the agent API that are not part of the # standard OpenAI chat completions signature. They are forwarded to the server @@ -307,15 +307,6 @@ def completions(self): Returns: OpenAI Completions object configured for VLMRun agent endpoint """ - try: - from openai import OpenAI - except ImportError: - raise DependencyError( - message="OpenAI SDK is not installed", - suggestion="Install it with `pip install vlmrun[openai]` or `pip install openai`", - error_type="missing_dependency", - ) - base_url = f"{self._client.base_url}/openai" openai_client = OpenAI( api_key=self._client.api_key, @@ -361,15 +352,6 @@ async def main(): Returns: OpenAI AsyncCompletions object configured for VLMRun agent endpoint """ - try: - from openai import AsyncOpenAI - except ImportError: - raise DependencyError( - message="OpenAI SDK is not installed", - suggestion="Install it with `pip install vlmrun[openai]` or `pip install openai`", - error_type="missing_dependency", - ) - base_url = f"{self._client.base_url}/openai" async_openai_client = AsyncOpenAI( api_key=self._client.api_key, diff --git a/vlmrun/client/gateway.py b/vlmrun/client/gateway.py index 7b37120..21d76d9 100644 --- a/vlmrun/client/gateway.py +++ b/vlmrun/client/gateway.py @@ -17,21 +17,11 @@ from typing import Any, List, Optional from vlmrun.constants import DEFAULT_GATEWAY_URL -from vlmrun.client.exceptions import DependencyError +from vlmrun.common.dependencies import require_openai from vlmrun.types.abstract import VLMRunProtocol - -def _require_openai(): - """Import the OpenAI SDK or raise a helpful :class:`DependencyError`.""" - try: - import openai # noqa: F401 - except ImportError as e: - raise DependencyError( - message="OpenAI SDK is not installed", - suggestion="Install it with `pip install vlmrun[openai]` or `pip install openai`", - error_type="missing_dependency", - ) from e - return openai +# Re-export for CLI/tests that patch gateway._require_openai. +_require_openai = require_openai class Gateway: @@ -217,13 +207,13 @@ def health(self) -> bool: Returns: True if the gateway is reachable and authenticated, else False. """ - # httpx is a hard dependency of the openai SDK, so it is always - # available whenever the gateway is usable. - import httpx + import requests headers = {"Authorization": f"Bearer {self._client.api_key}"} try: - resp = httpx.get(f"{self.base_url}/health", headers=headers, timeout=30.0) + resp = requests.get( + f"{self.base_url}/health", headers=headers, timeout=30.0 + ) except Exception: # No dedicated health route reachable — fall back to a real call. try: diff --git a/vlmrun/client/types.py b/vlmrun/client/types.py index 8b6931c..ad0f095 100644 --- a/vlmrun/client/types.py +++ b/vlmrun/client/types.py @@ -3,14 +3,17 @@ from __future__ import annotations from pathlib import Path -from typing import Dict, Any, Literal, Optional, Type, List, Tuple +from typing import Dict, Any, Literal, Optional, Type, List, Tuple, TYPE_CHECKING from pydantic import BaseModel, Field, model_validator from pydantic.dataclasses import dataclass from datetime import datetime from vlmrun.hub.utils import jsonschema_to_model +from vlmrun.common.dependencies import require_pandas import math -import pandas as pd + +if TYPE_CHECKING: + import pandas as pd JobStatus = Literal["enqueued", "pending", "running", "completed", "failed", "paused"] @@ -787,7 +790,9 @@ class MarkdownTable(BaseModel): def __str__(self): """Return a string representation of the markdown table.""" - return self.to_dataframe(header="name").to_markdown() + if self.content: + return self.content + return self.render() @model_validator(mode="after") def validate_metadata(self): @@ -798,8 +803,9 @@ def validate_metadata(self): def to_dataframe( self, header: Literal["id", "name", "none"] = "id" - ) -> pd.DataFrame: + ) -> "pd.DataFrame": """Convert the table to a pandas DataFrame.""" + pd = require_pandas() try: self.data = replace_nan_recursive_fast(self.data) @@ -824,7 +830,13 @@ def to_dataframe( def render(self) -> str: """Render the table as a markdown table.""" - return self.to_dataframe(header="name").to_markdown() + if self.content: + return self.content + df = self.to_dataframe(header="name") + try: + return df.to_markdown() + except ImportError: + return df.to_string(index=False) class MarkdownFigure(BaseModel): diff --git a/vlmrun/common/dependencies.py b/vlmrun/common/dependencies.py new file mode 100644 index 0000000..603a040 --- /dev/null +++ b/vlmrun/common/dependencies.py @@ -0,0 +1,75 @@ +"""Helpers for optional third-party dependencies.""" + +from __future__ import annotations + +from vlmrun.client.exceptions import DependencyError + + +def _dependency_error(package: str, *, extra: str | None = None) -> DependencyError: + """Build a :class:`DependencyError` with pip install guidance.""" + if extra: + suggestion = ( + f"Install it with `pip install vlmrun[{extra}]` or `pip install {package}`" + ) + else: + suggestion = f"Install it with `pip install vlmrun` or `pip install {package}`" + + return DependencyError( + message=f"{package} is not installed", + suggestion=suggestion, + error_type="missing_dependency", + ) + + +def require_openai(): + """Import the OpenAI SDK or raise :class:`DependencyError`.""" + try: + import openai + except ImportError as e: + raise _dependency_error("openai") from e + return openai + + +def require_pandas(): + """Import pandas or raise :class:`DependencyError`.""" + try: + import pandas as pd + except ImportError as e: + raise _dependency_error("pandas", extra="all") from e + return pd + + +def require_numpy(): + """Import numpy or raise :class:`DependencyError`.""" + try: + import numpy as np + except ImportError as e: + raise _dependency_error("numpy", extra="video") from e + return np + + +def require_cv2(): + """Import OpenCV or raise :class:`DependencyError`.""" + try: + import cv2 + except ImportError as e: + raise _dependency_error("opencv-python", extra="video") from e + return cv2 + + +def require_ipython_html(): + """Import IPython's HTML display helper or raise :class:`DependencyError`.""" + try: + from IPython.display import HTML + except ImportError as e: + raise _dependency_error("ipython", extra="all") from e + return HTML + + +def require_pypdfium2(): + """Import pypdfium2 or raise :class:`DependencyError`.""" + try: + import pypdfium2 as pdfium + except ImportError as e: + raise _dependency_error("pypdfium2", extra="doc") from e + return pdfium diff --git a/vlmrun/common/pdf.py b/vlmrun/common/pdf.py index 7884c37..33b50fc 100644 --- a/vlmrun/common/pdf.py +++ b/vlmrun/common/pdf.py @@ -5,6 +5,7 @@ from PIL import Image from vlmrun.common.logging import logger +from vlmrun.common.dependencies import require_pypdfium2 @dataclass @@ -32,7 +33,7 @@ def pdf_images( ) if backend == "pypdfium2": - import pypdfium2 as pdfium + pdfium = require_pypdfium2() logger.debug(f"Opening PDF document [path={path}]") doc = pdfium.PdfDocument(str(path)) diff --git a/vlmrun/common/video.py b/vlmrun/common/video.py index d9fe028..4a2f17a 100644 --- a/vlmrun/common/video.py +++ b/vlmrun/common/video.py @@ -1,14 +1,19 @@ """Video utilities for reading and writing video files using OpenCV.""" +from __future__ import annotations + from abc import ABC, abstractmethod from pathlib import Path -from typing import Callable, Iterator, List, Optional, Union +from typing import TYPE_CHECKING, Any, Callable, Iterator, List, Optional, Union -import cv2 -import numpy as np +from vlmrun.common.dependencies import require_cv2, require_numpy +if TYPE_CHECKING: + import numpy as np -T = np.ndarray + Frame = np.ndarray +else: + Frame = Any class BaseVideoReader(ABC): @@ -41,20 +46,20 @@ def __len__(self) -> int: raise NotImplementedError() @abstractmethod - def __iter__(self) -> Iterator[T]: + def __iter__(self) -> Iterator[Frame]: """Return an iterator over the video frames. Returns: - Iterator[T]: An iterator over the video frames. + Iterator[Frame]: An iterator over the video frames. """ raise NotImplementedError() @abstractmethod - def __next__(self) -> T: + def __next__(self) -> Frame: """Return the next frame in the video. Returns: - T: The next frame in the video. + Frame: The next frame in the video. Raises: StopIteration: If there are no more frames in the video. @@ -62,14 +67,14 @@ def __next__(self) -> T: raise NotImplementedError() @abstractmethod - def __getitem__(self, idx: Union[int, List[int]]) -> Union[T, List[T]]: + def __getitem__(self, idx: Union[int, List[int]]) -> Union[Frame, List[Frame]]: """Return the frame(s) at the given index/indices. Args: idx (Union[int, List[int]]): The index or list of indices to retrieve. Returns: - Union[T, List[T]]: The frame or list of frames at the given index/indices. + Union[Frame, List[Frame]]: The frame or list of frames at the given index/indices. Raises: IndexError: If any index is out of bounds. @@ -145,6 +150,8 @@ def __init__( super().__init__(filename) if not self.filename.exists(): raise FileNotFoundError(f"{self.filename} does not exist") + self._cv2 = require_cv2() + require_numpy() self.transform = transform self._video = self.open() @@ -156,21 +163,21 @@ def __len__(self) -> int: """ if self._video is None: return 0 - return int(self._video.get(cv2.CAP_PROP_FRAME_COUNT)) + return int(self._video.get(self._cv2.CAP_PROP_FRAME_COUNT)) - def __iter__(self) -> Iterator[T]: + def __iter__(self) -> Iterator[Frame]: """Return an iterator over the video frames. Returns: - Iterator[T]: An iterator over the video frames. + Iterator[Frame]: An iterator over the video frames. """ return self - def __next__(self) -> T: + def __next__(self) -> Frame: """Return the next frame in the video. Returns: - T: The next frame in the video. + Frame: The next frame in the video. Raises: StopIteration: If there are no more frames in the video. @@ -181,19 +188,19 @@ def __next__(self) -> T: ret, frame = self._video.read() if not ret: raise StopIteration() - frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + frame = self._cv2.cvtColor(frame, self._cv2.COLOR_BGR2RGB) if self.transform: frame = self.transform(frame) return frame - def __getitem__(self, idx: Union[int, List[int]]) -> Union[T, List[T]]: + def __getitem__(self, idx: Union[int, List[int]]) -> Union[Frame, List[Frame]]: """Return the frame(s) at the given index/indices. Args: idx (Union[int, List[int]]): The index or list of indices to retrieve. Returns: - Union[T, List[T]]: The frame or list of frames at the given index/indices. + Union[Frame, List[Frame]]: The frame or list of frames at the given index/indices. Raises: IndexError: If any index is out of bounds. @@ -210,16 +217,16 @@ def __getitem__(self, idx: Union[int, List[int]]) -> Union[T, List[T]]: else: raise TypeError(f"Unsupported index type: {type(idx)}") - def open(self) -> cv2.VideoCapture: + def open(self): """Open the video file. Returns: - cv2.VideoCapture: The opened video capture object. + The opened video capture object. Raises: RuntimeError: If the video file cannot be opened. """ - video = cv2.VideoCapture(str(self.filename)) + video = self._cv2.VideoCapture(str(self.filename)) if not video.isOpened(): raise RuntimeError(f"Failed to open video file: {self.filename}") return video @@ -239,7 +246,7 @@ def pos(self) -> Optional[int]: if self._video is None: return None try: - return int(self._video.get(cv2.CAP_PROP_POS_FRAMES)) + return int(self._video.get(self._cv2.CAP_PROP_POS_FRAMES)) except Exception: return None @@ -257,7 +264,7 @@ def seek(self, idx: int) -> None: raise RuntimeError("Video is not opened") if idx < 0 or idx >= len(self): raise IndexError(f"Frame index out of bounds: {idx}") - self._video.set(cv2.CAP_PROP_POS_FRAMES, idx) + self._video.set(self._cv2.CAP_PROP_POS_FRAMES, idx) class VideoWriter: @@ -276,19 +283,21 @@ def __init__(self, filename: Union[str, Path], fps: float = 30.0): self.filename = Path(str(filename)) if self.filename.exists(): raise FileExistsError(f"Output file already exists: {self.filename}") + self._cv2 = require_cv2() + require_numpy() self.fps = fps self.writer = None - def write(self, frame: np.ndarray) -> None: + def write(self, frame: Frame) -> None: """Write a frame to the video. Args: - frame (np.ndarray): The frame to write. Should be an RGB image. + frame: The frame to write. Should be an RGB image. """ if self.writer is None: height, width = frame.shape[:2] - fourcc = cv2.VideoWriter_fourcc(*"mp4v") - self.writer = cv2.VideoWriter( + fourcc = self._cv2.VideoWriter_fourcc(*"mp4v") + self.writer = self._cv2.VideoWriter( str(self.filename), fourcc, self.fps, (width, height), frame.ndim == 3 ) diff --git a/vlmrun/common/viz.py b/vlmrun/common/viz.py index 6cda51f..555b1dc 100644 --- a/vlmrun/common/viz.py +++ b/vlmrun/common/viz.py @@ -1,15 +1,17 @@ from typing import Union, List, Dict, Any, Optional, Tuple, Literal from PIL import Image -import pandas as pd -from IPython.display import HTML import json from pydantic import BaseModel -import cv2 -import numpy as np import io import base64 from pathlib import Path from vlmrun.common.image import _open_image_with_exif +from vlmrun.common.dependencies import ( + require_cv2, + require_ipython_html, + require_numpy, + require_pandas, +) DEFAULT_BOX_COLOR = (255, 0, 0) DEFAULT_BOX_THICKNESS = 2 @@ -238,6 +240,8 @@ def render_bbox_image( image = ensure_image(image) response_dict = to_dict(response) boxes = get_boxes_from_response(response_dict) + cv2 = require_cv2() + np = require_numpy() # Convert PIL to cv2 image img = np.array(image) @@ -413,7 +417,7 @@ def show_results( table_style: Optional[str] = None, show_content: bool = False, show_confidence: bool = False, -) -> HTML: +): """Display VLM Run results with images in a tabular format. This function renders VLM Run results alongside their corresponding images in a @@ -624,6 +628,8 @@ def to_dict(obj: Union[Dict, BaseModel]) -> Dict: data.append(row) + pd = require_pandas() + HTML = require_ipython_html() df = pd.DataFrame(data) pd.set_option("display.max_colwidth", None)