langvio 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. langvio/__init__.py +132 -0
  2. langvio/cli.py +120 -0
  3. langvio/config.py +190 -0
  4. langvio/core/__init__.py +0 -0
  5. langvio/core/base.py +46 -0
  6. langvio/core/pipeline.py +154 -0
  7. langvio/core/processor_manager.py +140 -0
  8. langvio/core/registry.py +130 -0
  9. langvio/core/visualization_manager.py +392 -0
  10. langvio/default_config.yaml +107 -0
  11. langvio/llm/__init__.py +12 -0
  12. langvio/llm/base.py +279 -0
  13. langvio/llm/factory.py +101 -0
  14. langvio/llm/google.py +74 -0
  15. langvio/llm/openai.py +73 -0
  16. langvio/media/__init__.py +0 -0
  17. langvio/media/image_visualizer.py +181 -0
  18. langvio/media/processor.py +90 -0
  19. langvio/media/video_visualizer.py +282 -0
  20. langvio/prompts/__init__.py +11 -0
  21. langvio/prompts/constants.py +32 -0
  22. langvio/prompts/templates.py +122 -0
  23. langvio/utils/__init__.py +0 -0
  24. langvio/utils/detection/__init__.py +28 -0
  25. langvio/utils/detection/compression.py +109 -0
  26. langvio/utils/detection/extraction.py +280 -0
  27. langvio/utils/file_utils.py +123 -0
  28. langvio/utils/llm_utils.py +935 -0
  29. langvio/utils/logging.py +53 -0
  30. langvio/utils/spatial/__init__.py +16 -0
  31. langvio/utils/spatial/relationships.py +179 -0
  32. langvio/vision/__init__.py +0 -0
  33. langvio/vision/base.py +159 -0
  34. langvio/vision/color_detection.py +371 -0
  35. langvio/vision/utils.py +299 -0
  36. langvio/vision/yolo/__init__.py +0 -0
  37. langvio/vision/yolo/detector.py +139 -0
  38. langvio/vision/yolo/image_processor.py +169 -0
  39. langvio/vision/yolo/result_formatter.py +368 -0
  40. langvio/vision/yolo/video_processor.py +452 -0
  41. langvio/vision/yolo/yolo11_utils.py +165 -0
  42. langvio-0.0.1.dist-info/METADATA +445 -0
  43. langvio-0.0.1.dist-info/RECORD +47 -0
  44. langvio-0.0.1.dist-info/WHEEL +5 -0
  45. langvio-0.0.1.dist-info/entry_points.txt +2 -0
  46. langvio-0.0.1.dist-info/licenses/LICENSE +0 -0
  47. langvio-0.0.1.dist-info/top_level.txt +1 -0
langvio/__init__.py ADDED
@@ -0,0 +1,132 @@
1
+ """
2
+ langvio: Connect language models to vision models for natural language visual analysis
3
+ """
4
+
5
+ __version__ = "0.0.1"
6
+
7
+ # === Imports ===
8
+
9
+ # Standard library
10
+ import sys
11
+
12
+ # Third-party
13
+ import cv2
14
+ import torch
15
+ from dotenv import load_dotenv
16
+
17
+ from langvio.core.pipeline import Pipeline
18
+
19
+ # langvio modules
20
+ from langvio.core.registry import ModelRegistry
21
+ from langvio.llm.base import BaseLLMProcessor
22
+ from langvio.llm.factory import register_llm_processors
23
+ from langvio.vision.base import BaseVisionProcessor
24
+ from langvio.vision.yolo.detector import YOLOProcessor
25
+
26
+ # === Initialization ===
27
+
28
+ # Load environment variables
29
+ load_dotenv()
30
+
31
+ # OpenCV optimizations
32
+ cv2.setNumThreads(4)
33
+ cv2.setUseOptimized(True)
34
+
35
+ # PyTorch optimizations
36
+ if torch.cuda.is_available():
37
+ torch.backends.cudnn.benchmark = True
38
+
39
+ # Initialize model registry
40
+ registry = ModelRegistry()
41
+
42
+ # Register YOLO and YOLOe vision processors
43
+ registry.register_vision_processor(
44
+ "yolo", YOLOProcessor, model_path="yolo11n.pt", confidence=0.5
45
+ )
46
+
47
+ registry.register_vision_processor(
48
+ "yoloe",
49
+ YOLOProcessor,
50
+ model_path="yoloe-11s-seg-pf.pt",
51
+ confidence=0.5,
52
+ model_type="yoloe",
53
+ )
54
+
55
+ registry.register_vision_processor(
56
+ "yoloe_medium",
57
+ YOLOProcessor,
58
+ model_path="yoloe-11m-seg-pf.pt",
59
+ confidence=0.5,
60
+ model_type="yoloe",
61
+ )
62
+
63
+ registry.register_vision_processor(
64
+ "yoloe_large",
65
+ YOLOProcessor,
66
+ model_path="yoloe-11l-seg-pf.pt",
67
+ confidence=0.5,
68
+ model_type="yoloe",
69
+ )
70
+
71
+ # Register LLM processors
72
+ register_llm_processors(registry)
73
+
74
+
75
+ # === Pipeline Creator ===
76
+
77
+
78
+ def create_pipeline(config_path=None, llm_name=None, vision_name=None):
79
+ """
80
+ Create a pipeline with optional configuration.
81
+
82
+ Args:
83
+ config_path: Path to a configuration file
84
+ llm_name: Name of LLM processor to use
85
+ vision_name: Name of vision processor to use (default: "yoloe")
86
+
87
+ Returns:
88
+ A configured Pipeline instance
89
+ """
90
+ pipeline = Pipeline(config_path)
91
+
92
+ if vision_name:
93
+ pipeline.set_vision_processor(vision_name)
94
+ else:
95
+ try:
96
+ pipeline.set_vision_processor("yoloe_large")
97
+ except Exception:
98
+ pipeline.set_vision_processor("yolo")
99
+
100
+ if llm_name:
101
+ pipeline.set_llm_processor(llm_name)
102
+ else:
103
+ try:
104
+ default_llm = pipeline.config.config["llm"]["default"]
105
+ pipeline.set_llm_processor(default_llm)
106
+ except Exception:
107
+ if len(registry.list_llm_processors()) == 0:
108
+ error_msg = (
109
+ "ERROR: No LLM providers are installed. "
110
+ "Please install at least one provider:\n"
111
+ "- For OpenAI: pip install langvio[openai]\n"
112
+ "- For Google Gemini: pip install langvio[google]\n"
113
+ "- For all providers: pip install langvio[all-llm]"
114
+ )
115
+ print(error_msg, file=sys.stderr)
116
+ sys.exit(1)
117
+ else:
118
+ available_llm = next(iter(registry.list_llm_processors()))
119
+ pipeline.set_llm_processor(available_llm)
120
+
121
+ return pipeline
122
+
123
+
124
+ # === Public Exports ===
125
+
126
+ __all__ = [
127
+ "Pipeline",
128
+ "create_pipeline",
129
+ "registry",
130
+ "BaseLLMProcessor",
131
+ "BaseVisionProcessor",
132
+ ]
langvio/cli.py ADDED
@@ -0,0 +1,120 @@
1
+ """
2
+ Command-line interface for langvio
3
+ """
4
+
5
+ import argparse
6
+ import logging
7
+ import os
8
+ import sys
9
+
10
+ from langvio import create_pipeline
11
+ from langvio.utils.file_utils import is_image_file, is_video_file
12
+ from langvio.utils.logging import setup_logging
13
+
14
+
15
+ def main():
16
+ """Main entry point for the langvio CLI"""
17
+ # Parse arguments
18
+ parser = argparse.ArgumentParser(
19
+ description="langvio: Connect LLMs with video models"
20
+ )
21
+
22
+ # Required arguments
23
+ parser.add_argument("--query", "-q", required=True, help="Natural language query")
24
+ parser.add_argument(
25
+ "--media", "-m", required=True, help="Path to image or video file"
26
+ )
27
+
28
+ # Optional arguments
29
+ parser.add_argument("--config", "-c", help="Path to configuration file")
30
+ parser.add_argument("--llm", "-l", help="LLM processor to use")
31
+ parser.add_argument("--vision", "-v", help="Vision processor to use")
32
+ parser.add_argument("--output", "-o", help="Output directory for processed media")
33
+ parser.add_argument(
34
+ "--log-level",
35
+ choices=["DEBUG", "INFO", "WARNING", "ERROR"],
36
+ default="INFO",
37
+ help="Logging level",
38
+ )
39
+ parser.add_argument("--log-file", help="Path to log file")
40
+ parser.add_argument(
41
+ "--list-models", action="store_true", help="List available models and exit"
42
+ )
43
+
44
+ args = parser.parse_args()
45
+
46
+ # Set up logging
47
+ setup_logging({"level": args.log_level, "file": args.log_file})
48
+
49
+ logger = logging.getLogger(__name__)
50
+
51
+ # List models if requested
52
+ if args.list_models:
53
+ list_available_models()
54
+ return 0
55
+
56
+ # Check if media file exists
57
+ if not os.path.exists(args.media):
58
+ logger.error("Media file not found: %s", args.media)
59
+ return 1
60
+
61
+ # Check if media file is supported
62
+ if not is_image_file(args.media) and not is_video_file(args.media):
63
+ logger.error("Unsupported media file format: %s", args.media)
64
+ return 1
65
+
66
+ try:
67
+ # Create pipeline
68
+ pipeline = create_pipeline(
69
+ config_path=args.config, llm_name=args.llm, vision_name=args.vision
70
+ )
71
+
72
+ # Update output directory if specified
73
+ if args.output:
74
+ pipeline.config.config["media"]["output_dir"] = args.output
75
+ os.makedirs(args.output, exist_ok=True)
76
+
77
+ # Process query
78
+ result = pipeline.process(args.query, args.media)
79
+
80
+ # Print results
81
+ print("\n===== langvio Results =====")
82
+ print("Query: {}".format(result["query"]))
83
+ print("Media: {} ({})".format(result["media_path"], result["media_type"]))
84
+ print("Output: {}".format(result["output_path"]))
85
+ print("\nExplanation: {}".format(result["explanation"]))
86
+ print("\nDetection summary:")
87
+
88
+ # Count detections by label
89
+ counts = {}
90
+ for frame_dets in result["detections"].values():
91
+ for det in frame_dets:
92
+ label = det["label"]
93
+ counts[label] = counts.get(label, 0) + 1
94
+
95
+ for label, count in counts.items():
96
+ print("- {}: {}".format(label, count))
97
+
98
+ print("\nProcessing complete!")
99
+ return 0
100
+
101
+ except Exception as e:
102
+ logger.error("Error processing query: %s", e, exc_info=True)
103
+ return 1
104
+
105
+
106
+ def list_available_models():
107
+ """List all available models in the registry"""
108
+ from langvio import registry
109
+
110
+ print("\n===== Available LLM Processors =====")
111
+ for name, cls in registry.list_llm_processors().items():
112
+ print("- {}".format(name))
113
+
114
+ print("\n===== Available Vision Processors =====")
115
+ for name, cls in registry.list_vision_processors().items():
116
+ print("- {}".format(name))
117
+
118
+
119
+ if __name__ == "__main__":
120
+ sys.exit(main())
langvio/config.py ADDED
@@ -0,0 +1,190 @@
1
+ """
2
+ Configuration management for langvio
3
+ """
4
+
5
+ import os
6
+ from typing import Any, Dict, Optional
7
+
8
+ import yaml
9
+ from dotenv import load_dotenv
10
+
11
+ load_dotenv() # Load environment variables from .env
12
+
13
+
14
+ class Config:
15
+ """Configuration manager for langvio"""
16
+
17
+ DEFAULT_CONFIG_PATH = os.path.join(os.path.dirname(__file__), "default_config.yaml")
18
+
19
+ def __init__(self, config_path: Optional[str] = None):
20
+ """
21
+ Initialize configuration.
22
+
23
+ Args:
24
+ config_path: Path to a YAML configuration file
25
+ """
26
+ # Initialize empty config
27
+ self.config = {}
28
+
29
+ # First load default config
30
+ self._load_default_config()
31
+
32
+ # Then load user config if provided
33
+ if config_path and os.path.exists(config_path):
34
+ self.load_config(config_path)
35
+
36
+ def _load_default_config(self) -> None:
37
+ """Load the default configuration from default_config.yaml"""
38
+ try:
39
+ if os.path.exists(self.DEFAULT_CONFIG_PATH):
40
+ with open(self.DEFAULT_CONFIG_PATH, "r") as f:
41
+ self.config = yaml.safe_load(f)
42
+ if self.config is None: # Handle empty file case
43
+ self.config = {}
44
+ else:
45
+ # Fallback default configuration if file doesn't exist
46
+ self.config = {
47
+ "llm": {
48
+ "default": "gemini",
49
+ "models": {
50
+ "gemini": {
51
+ "model_name": "gemini-pro",
52
+ "model_kwargs": {"temperature": 0.2},
53
+ },
54
+ "gpt": {
55
+ "model_name": "gpt-3.5-turbo",
56
+ "model_kwargs": {"temperature": 0.0},
57
+ },
58
+ },
59
+ },
60
+ "vision": {
61
+ "default": "yolo",
62
+ "models": {
63
+ "yolo": {
64
+ "type": "yolo",
65
+ "model_path": "yolov11n.pt",
66
+ "confidence": 0.25,
67
+ }
68
+ },
69
+ },
70
+ "media": {
71
+ "output_dir": "./output",
72
+ "temp_dir": "./temp",
73
+ "visualization": {
74
+ "box_color": [0, 255, 0],
75
+ "text_color": [255, 255, 255],
76
+ "line_thickness": 2,
77
+ },
78
+ },
79
+ "logging": {"level": "INFO", "file": None},
80
+ }
81
+ except Exception as e:
82
+ raise ValueError(f"Error loading default configuration: {e}")
83
+
84
+ def load_config(self, config_path: str) -> None:
85
+ """
86
+ Load configuration from a YAML file.
87
+
88
+ Args:
89
+ config_path: Path to a YAML configuration file
90
+ """
91
+ try:
92
+ with open(config_path, "r") as f:
93
+ user_config = yaml.safe_load(f)
94
+ if user_config is None: # Handle empty file case
95
+ return
96
+
97
+ # Update configuration
98
+ self._update_config(self.config, user_config)
99
+ except Exception as e:
100
+ raise ValueError(f"Error loading configuration from {config_path}: {e}")
101
+
102
+ def _update_config(
103
+ self, base_config: Dict[str, Any], new_config: Dict[str, Any]
104
+ ) -> None:
105
+ """Recursively update base config with new config."""
106
+ for key, value in new_config.items():
107
+ if (
108
+ isinstance(value, dict)
109
+ and key in base_config
110
+ and isinstance(base_config[key], dict)
111
+ ):
112
+ self._update_config(base_config[key], value)
113
+ else:
114
+ base_config[key] = value
115
+
116
+ def get_llm_config(self, model_name: Optional[str] = None) -> Dict[str, Any]:
117
+ """
118
+ Get configuration for an LLM model.
119
+
120
+ Args:
121
+ model_name: Name of the model to get config for
122
+
123
+ Returns:
124
+ Model configuration dictionary
125
+ """
126
+ if not model_name:
127
+ model_name = self.config["llm"]["default"]
128
+
129
+ if model_name not in self.config["llm"]["models"]:
130
+ raise ValueError(f"LLM model '{model_name}' not found in configuration")
131
+
132
+ return self.config["llm"]["models"][model_name]
133
+
134
+ def get_vision_config(self, model_name: Optional[str] = None) -> Dict[str, Any]:
135
+ """
136
+ Get configuration for a vision model.
137
+
138
+ Args:
139
+ model_name: Name of the model to get config for
140
+
141
+ Returns:
142
+ Model configuration dictionary
143
+ """
144
+ if not model_name:
145
+ model_name = self.config["vision"]["default"]
146
+
147
+ if model_name not in self.config["vision"]["models"]:
148
+ raise ValueError(f"Vision model '{model_name}' not found in configuration")
149
+
150
+ return self.config["vision"]["models"][model_name]
151
+
152
+ def get_media_config(self) -> Dict[str, Any]:
153
+ """
154
+ Get media processing configuration.
155
+
156
+ Returns:
157
+ Media configuration dictionary
158
+ """
159
+ return self.config["media"]
160
+
161
+ def get_logging_config(self) -> Dict[str, Any]:
162
+ """
163
+ Get logging configuration.
164
+
165
+ Returns:
166
+ Logging configuration dictionary
167
+ """
168
+ return self.config["logging"]
169
+
170
+ def get_langsmith_config(self) -> Dict[str, Any]:
171
+ """
172
+ Get LangSmith configuration if available.
173
+
174
+ Returns:
175
+ LangSmith configuration dictionary
176
+ """
177
+ return self.config.get("langsmith", {})
178
+
179
+ def save_config(self, config_path: str) -> None:
180
+ """
181
+ Save current configuration to a YAML file.
182
+
183
+ Args:
184
+ config_path: Path to save the configuration
185
+ """
186
+ try:
187
+ with open(config_path, "w") as f:
188
+ yaml.dump(self.config, f, default_flow_style=False)
189
+ except Exception as e:
190
+ raise ValueError(f"Error saving configuration to {config_path}: {e}")
File without changes
langvio/core/base.py ADDED
@@ -0,0 +1,46 @@
1
+ """
2
+ Base classes for langvio components
3
+ """
4
+
5
+ from abc import ABC, abstractmethod
6
+ from typing import Any, Dict, Optional
7
+
8
+
9
+ class Processor(ABC):
10
+ """Base class for all processors in langvio"""
11
+
12
+ def __init__(self, name: str, config: Optional[Dict[str, Any]] = None):
13
+ """
14
+ Initialize processor.
15
+
16
+ Args:
17
+ name: Processor name
18
+ config: Configuration parameters
19
+ """
20
+ self.name = name
21
+ self.config = config or {}
22
+
23
+ @abstractmethod
24
+ def initialize(self) -> bool:
25
+ """
26
+ Initialize the processor with its configuration.
27
+
28
+ Returns:
29
+ True if initialization was successful
30
+ """
31
+
32
+ @classmethod
33
+ def from_config(cls, name: str, config: Dict[str, Any]) -> "Processor":
34
+ """
35
+ Create a processor from configuration.
36
+
37
+ Args:
38
+ name: Processor name
39
+ config: Configuration parameters
40
+
41
+ Returns:
42
+ Initialized processor
43
+ """
44
+ processor = cls(name, config)
45
+ processor.initialize()
46
+ return processor
@@ -0,0 +1,154 @@
1
+ """
2
+ Enhanced core pipeline for connecting LLMs with vision models - refactored
3
+ """
4
+
5
+ import logging
6
+ import os
7
+ from typing import Any, Dict, Optional
8
+
9
+ from langvio.config import Config
10
+ from langvio.core.processor_manager import ProcessorManager
11
+ from langvio.core.visualization_manager import VisualizationManager
12
+ from langvio.utils.file_utils import is_video_file
13
+ from langvio.utils.logging import setup_logging
14
+
15
+
16
+ class Pipeline:
17
+ """Main pipeline for processing queries with LLMs and vision models"""
18
+
19
+ def __init__(self, config_path: Optional[str] = None):
20
+ """Initialize pipeline"""
21
+ # Initialize configuration
22
+ self.config = Config(config_path)
23
+
24
+ # Set up logging
25
+ setup_logging(self.config.get_logging_config())
26
+ self.logger = logging.getLogger(__name__)
27
+
28
+ # Initialize managers
29
+ self.processor_manager = ProcessorManager(self.config)
30
+ self.visualization_manager = VisualizationManager(self.config)
31
+
32
+ self.logger.info("Enhanced Pipeline initialized")
33
+
34
+ def load_config(self, config_path: str) -> None:
35
+ """Load configuration from file"""
36
+ self.config.load_config(config_path)
37
+ self.logger.info(f"Loaded configuration from {config_path}")
38
+
39
+ # Update managers with new config
40
+ self.processor_manager.config = self.config
41
+ self.visualization_manager.config = self.config
42
+
43
+ def set_llm_processor(self, processor_name: str) -> None:
44
+ """Set the LLM processor"""
45
+ self.processor_manager.set_llm_processor(processor_name)
46
+
47
+ def set_vision_processor(self, processor_name: str) -> None:
48
+ """Set the vision processor"""
49
+ self.processor_manager.set_vision_processor(processor_name)
50
+
51
+ def process(self, query: str, media_path: str) -> Dict[str, Any]:
52
+ """Process a query on media with enhanced capabilities"""
53
+ self.logger.info(f"Processing query: {query}")
54
+
55
+ # Check if processors are set
56
+ if not self.processor_manager.has_processors():
57
+ if not self.processor_manager.llm_processor:
58
+ error_msg = "ERROR: LLM processor not set"
59
+ self.logger.error(error_msg)
60
+ raise ValueError(error_msg)
61
+
62
+ if not self.processor_manager.vision_processor:
63
+ error_msg = "ERROR: Vision processor not set"
64
+ self.logger.error(error_msg)
65
+ raise ValueError(error_msg)
66
+
67
+ # Check if media file exists
68
+ if not os.path.exists(media_path):
69
+ error_msg = f"ERROR: Media file not found: {media_path}"
70
+ self.logger.error(error_msg)
71
+ raise FileNotFoundError(error_msg)
72
+
73
+ # Check media type
74
+ is_video = is_video_file(media_path)
75
+ media_type = "video" if is_video else "image"
76
+
77
+ try:
78
+ # 1. Parse query with LLM to get structured parameters
79
+ query_params = self.processor_manager.parse_query(query)
80
+ self.logger.info(f"Parsed query params: {query_params}")
81
+
82
+ # 2. Run detection with vision processor
83
+ all_detections = self.processor_manager.process_media(
84
+ media_path, query_params
85
+ )
86
+
87
+ # Log detection results to JSON
88
+ self._log_detections_to_json(all_detections, query, media_path)
89
+
90
+ # 3. Generate explanation using all detected objects and metrics
91
+ explanation = self.processor_manager.generate_explanation(
92
+ query, all_detections
93
+ )
94
+
95
+ # 4. Get highlighted objects from the LLM processor
96
+ highlighted_objects = self.processor_manager.get_highlighted_objects()
97
+
98
+ # 5. Create visualization with highlighted objects
99
+ output_path = self.visualization_manager.create_visualization(
100
+ media_path, all_detections, highlighted_objects, query_params
101
+ )
102
+
103
+ # Prepare result
104
+ result = {
105
+ "query": query,
106
+ "media_path": media_path,
107
+ "media_type": media_type,
108
+ "output_path": output_path,
109
+ "explanation": explanation,
110
+ "detections": all_detections,
111
+ "query_params": query_params,
112
+ "highlighted_objects": highlighted_objects,
113
+ }
114
+
115
+ self.logger.info("Processed query successfully")
116
+ return result
117
+
118
+ except Exception as e:
119
+ self.logger.error(f"Error processing query: {e}")
120
+ raise
121
+
122
+ def _log_detections_to_json(
123
+ self, all_detections: Dict[str, Any], query: str, media_path: str
124
+ ):
125
+ """Log detection results to JSON file"""
126
+ import json
127
+ import os
128
+ from datetime import datetime
129
+
130
+ # Create logs directory if it doesn't exist
131
+ logs_dir = "logs"
132
+ os.makedirs(logs_dir, exist_ok=True)
133
+
134
+ # Create filename with timestamp
135
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
136
+ media_name = os.path.splitext(os.path.basename(media_path))[0]
137
+ filename = f"detections_{media_name}_{timestamp}.json"
138
+ filepath = os.path.join(logs_dir, filename)
139
+
140
+ # Prepare detection log data
141
+ detection_log = {
142
+ "query": query,
143
+ "media_path": media_path,
144
+ "timestamp": datetime.now().isoformat(),
145
+ "detections": all_detections,
146
+ }
147
+
148
+ # Write to JSON file
149
+ try:
150
+ with open(filepath, "w", encoding="utf-8") as f:
151
+ json.dump(detection_log, f, indent=2, ensure_ascii=False)
152
+ self.logger.info(f"Detection results logged to: {filepath}")
153
+ except Exception as e:
154
+ self.logger.error(f"Failed to log detection results to file: {e}")