# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
import base64
import logging
import json
from PIL import Image
from typing import List, Literal, Tuple
from urllib.parse import urlparse
from camel.agents import ChatAgent
from camel.configs import ChatGPTConfig
from camel.toolkits.base import BaseToolkit
from camel.toolkits import FunctionTool, CodeExecutionToolkit
from camel.types import ModelType, ModelPlatformType
from camel.models import ModelFactory, OpenAIModel
from camel.messages import BaseMessage
logger = logging.getLogger(__name__)
class ImageAnalysisToolkit(BaseToolkit):
r"""A class representing a toolkit for image comprehension operations.
This class provides methods for understanding images, such as identifying
objects, text in images.
"""
def __init__(self, model: Literal['gpt-4o', 'gpt-4o-mini'] = 'gpt-4o'):
self.model_type = ModelType.GPT_4O
if model == 'gpt-4o':
self.model_type = ModelType.GPT_4O
elif model == 'gpt-4o-mini':
self.model_type = ModelType.GPT_4O_MINI
else:
raise ValueError(f"Invalid model type: {model}")
def _construct_image_url(self, image_path: str) -> str:
parsed_url = urlparse(image_path)
is_url = all([parsed_url.scheme, parsed_url.netloc])
image_url = image_path
if not is_url:
image_url = (
f"data:image/jpeg;base64,{self._encode_image(image_path)}"
)
return image_url
def _encode_image(self, image_path: str):
r"""Encode an image by its image path.
Arg:
image_path (str): The path to the image file."""
with open(image_path, "rb") as image_file:
return base64.b64encode(image_file.read()).decode("utf-8")
def _judge_if_write_code(self, question: str, image_path: str) -> Tuple[bool, str]:
_image_url = self._construct_image_url(image_path)
prompt = f"""
Given the question {question}, do you think it is suitable to write python code (using libraries like cv2) to process the image to get the answer?
Your output should be in json format (```json ```) including the following fields:
- `image_caption`: str, A detailed caption about the image. If it is suitable for writing code, it should contains helpful instructions and necessary informations for how to writing code.
- `if_write_code`: bool, True if it is suitable to write code to process the image, False otherwise.
"""
messages = [
{
"role": "system",
"content": "You are a helpful assistant for image relevant tasks, and can judge whether \
the given image is suitable for writing code to process or not. "
},
{
"role": "user",
"content": [
{'type': 'text', 'text': prompt},
{
'type': 'image_url',
'image_url': {
'url': _image_url,
},
},
],
},
]
LLM = OpenAIModel(model_type=self.model_type)
resp = LLM.run(messages)
result_str = resp.choices[0].message.content.lower()
result_str = result_str.replace("```json", "").replace("```", "").strip()
result_dict = json.loads(result_str)
if_write_code = result_dict.get("if_write_code", False)
image_caption = result_dict.get("image_caption", "")
return if_write_code, image_caption
def _get_image_caption(self, image_path: str) -> str:
_image_url = self._construct_image_url(image_path)
prompt = f"""
Please make a detailed description about the image.
"""
messages = [
{
"role": "user",
"content": [
{'type': 'text', 'text': prompt},
{
'type': 'image_url',
'image_url': {
'url': _image_url,
},
},
],
},
]
LLM = OpenAIModel(model_type=self.model_type)
resp = LLM.run(messages)
return resp.choices[0].message.content
def ask_question_about_image(self, image_path: str, question: str) -> str:
r"""Ask a question about the image based on the image path.
Args:
image_path (str): The path to the image file.
question (str): The question to ask about the image.
Returns:
str: The answer to the question based on the image.
"""
logger.debug(
f"Calling ask_question_about_image with question: `{question}` and \
image_path: `{image_path}`"
)
parsed_url = urlparse(image_path)
is_url = all([parsed_url.scheme, parsed_url.netloc])
if not (
image_path.endswith(".jpg") or \
image_path.endswith(".jpeg") or \
image_path.endswith(".png")
):
logger.warning(
f"The image path `{image_path}` is not a valid image path. "
f"Please provide a valid image path."
)
return f"The image path `{image_path}` is not a valid image path."
# _image_url = image_path
# if not is_url:
# _image_url = (
# f"data:image/jpeg;base64,{self._encode_image(image_path)}"
# )
model = ModelFactory.create(
model_platform=ModelPlatformType.OPENAI,
model_type=self.model_type,
)
code_model = ModelFactory.create(
model_platform=ModelPlatformType.OPENAI,
model_type=ModelType.O3_MINI,
)
code_execution_toolkit = CodeExecutionToolkit(require_confirm=False, sandbox="subprocess", verbose=True)
image_agent = ChatAgent(
"You are a helpful assistant for image relevant tasks. Given a question related to the image, you can carefully check the image in detail and answer the question.",
model,
)
code_agent = ChatAgent(
"You are an expert of writing code to process special images leveraging libraries like cv2.",
code_model,
tools=code_execution_toolkit.get_tools(),
)
if not is_url:
image_object = Image.open(image_path)
else:
import requests
from io import BytesIO
url_image = requests.get(image_path)
image_object = Image.open(BytesIO(url_image.content))
# if_write_code, image_caption = self._judge_if_write_code(question, image_path)
# if if_write_code:
# prompt = f"""
# Please write and execute python code (for example, using cv2 library) to process the image and complete the task: {question}
# Here are the image path you need to process: {image_path}
# Here are the caption about the image: {image_caption}
# """
# message = BaseMessage.make_user_message(
# role_name='user',
# content=prompt,
# )
# resp = code_agent.step(message)
# return resp.msgs[0].content
# else:
prompt = question
message = BaseMessage.make_user_message(
role_name='user',
content=prompt,
image_list=[image_object]
)
resp = image_agent.step(message)
return resp.msgs[0].content
def get_tools(self) -> List[FunctionTool]:
r"""Returns a list of FunctionTool objects representing the functions
in the toolkit.
Returns:
List[FunctionTool]: A list of FunctionTool objects representing the
functions in the toolkit.
"""
return [
FunctionTool(self.ask_question_about_image),
]