From bc42cb66761ba68d65bbeab913076c414a152123 Mon Sep 17 00:00:00 2001 From: Leo Ueno Date: Sat, 30 Nov 2024 19:09:34 -0800 Subject: [PATCH 1/2] Add Image Details endpoint support --- roboflow/core/project.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/roboflow/core/project.py b/roboflow/core/project.py index b7e66c0f..13d83aba 100644 --- a/roboflow/core/project.py +++ b/roboflow/core/project.py @@ -767,3 +767,37 @@ def __str__(self): json_str = {"name": self.name, "type": self.type, "workspace": self.__workspace} return json.dumps(json_str, indent=2) + + def image(self, image_id: str) -> Dict: + """ + Fetch the details of a specific image from the Roboflow API. + + Args: + image_id (str): The ID of the image to fetch. + + Returns: + Dict: A dictionary containing the image details. + + Example: + >>> import roboflow + + >>> rf = roboflow.Roboflow(api_key="YOUR_API_KEY") + + >>> project = rf.workspace().project("PROJECT_ID") + + >>> image_details = project.image("image-id") + """ + url = f"{API_URL}/{self.__workspace}/{self.__project_name}/images/{image_id}" f"?api_key={self.__api_key}" + + data = requests.get(url).json() + + if "error" in data: + raise RuntimeError(data["error"]) + + if "image" not in data: + print(data, image_id) + raise RuntimeError("Image not found") + + image_details = data["image"] + + return image_details From 3c3f87728ee19901d8d7385fe7298d95bb28d878 Mon Sep 17 00:00:00 2001 From: Leo Ueno Date: Sat, 30 Nov 2024 19:12:30 -0800 Subject: [PATCH 2/2] Add test --- roboflow/core/version.py | 7 ++--- roboflow/deployment.py | 2 +- tests/test_project.py | 64 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 66 insertions(+), 7 deletions(-) diff --git a/roboflow/core/version.py b/roboflow/core/version.py index 069ffd35..c0b9da52 100644 --- a/roboflow/core/version.py +++ b/roboflow/core/version.py @@ -818,11 +818,8 @@ def __download_zip(self, link, location, format): def bar_progress(current, total, width=80): progress_message = ( - "Downloading Dataset Version Zip in " - + location - + " to " - + format - + ": %d%% [%d / %d] bytes" % (current / total * 100, current, total) + f"Downloading Dataset Version Zip in {location} to {format}: " + f"{current / total * 100:.0f}% [{current} / {total}] bytes" ) sys.stdout.write("\r" + progress_message) sys.stdout.flush() diff --git a/roboflow/deployment.py b/roboflow/deployment.py index 84fa792e..c9ac213f 100644 --- a/roboflow/deployment.py +++ b/roboflow/deployment.py @@ -10,7 +10,7 @@ def is_valid_ISO8601_timestamp(ts): try: datetime.fromisoformat(ts) return True - except: + except ValueError: return False diff --git a/tests/test_project.py b/tests/test_project.py index 84b99f96..a7cd55de 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -1,9 +1,10 @@ +import requests import responses from roboflow import API_URL from roboflow.adapters.rfapi import AnnotationSaveError, ImageUploadError from roboflow.config import DEFAULT_BATCH_NAME -from tests import PROJECT_NAME, ROBOFLOW_API_KEY, RoboflowTest +from tests import PROJECT_NAME, ROBOFLOW_API_KEY, WORKSPACE_NAME, RoboflowTest class TestProject(RoboflowTest): @@ -82,3 +83,64 @@ def test_upload_raises_upload_annotation_error(self): ) self.assertEqual(str(error.exception), "Image was already annotated.") + + def test_image_success(self): + image_id = "test-image-id" + expected_url = f"{API_URL}/{WORKSPACE_NAME}/{PROJECT_NAME}/images/{image_id}?api_key={ROBOFLOW_API_KEY}" + mock_response = { + "image": { + "id": image_id, + "name": "test_image.jpg", + "annotation": { + "key": "some-key", + "width": 640, + "height": 480, + "boxes": [{"label": "person", "x": 100, "y": 150, "width": 50, "height": 80}], + }, + "labels": ["person"], + "split": "train", + "tags": ["tag1", "tag2"], + "created": 1616161616, + "urls": { + "original": "https://example.com/image.jpg", + "thumb": "https://example.com/thumb.jpg", + "annotation": "https://example.com/annotation.json", + }, + "embedding": [0.1, 0.2, 0.3], + } + } + + responses.add(responses.GET, expected_url, json=mock_response, status=200) + + image_details = self.project.image(image_id) + + self.assertIsInstance(image_details, dict) + self.assertEqual(image_details["id"], image_id) + self.assertEqual(image_details["name"], "test_image.jpg") + self.assertIn("annotation", image_details) + self.assertIn("labels", image_details) + self.assertEqual(image_details["split"], "train") + + def test_image_not_found(self): + image_id = "nonexistent-image-id" + expected_url = f"{API_URL}/{WORKSPACE_NAME}/{PROJECT_NAME}/images/{image_id}?api_key={ROBOFLOW_API_KEY}" + mock_response = {"error": "Image not found."} + + responses.add(responses.GET, expected_url, json=mock_response, status=404) + + with self.assertRaises(RuntimeError) as context: + self.project.image(image_id) + + self.assertIn("HTTP error occurred while fetching image details", str(context.exception)) + + def test_image_invalid_json_response(self): + image_id = "invalid-json-image-id" + expected_url = f"{API_URL}/{WORKSPACE_NAME}/{PROJECT_NAME}/images/{image_id}?api_key={ROBOFLOW_API_KEY}" + invalid_json = "Invalid JSON response" + + responses.add(responses.GET, expected_url, body=invalid_json, status=200) + + with self.assertRaises(requests.exceptions.JSONDecodeError) as context: + self.project.image(image_id) + + self.assertIn("Expecting value", str(context.exception))