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

Accept numpy image in batch as base64 encoded string #187

Merged
merged 2 commits into from
Dec 13, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
115 changes: 84 additions & 31 deletions docs/quickstart/http_inference.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,19 +106,19 @@ You can send a URL with an image, a NumPy array, or a base64-encoded image to an
img_str = img_str.decode("ascii")

infer_payload = {
"model_id": f"{project_id}/{model_version}",
"image": {
"type": "base64",
"value": img_str,
},
"confidence": confidence,
"iou_threshold": iou_thresh,
"api_key": api_key,
"model_id": f"{project_id}/{model_version}",
"image": {
"type": "base64",
"value": img_str,
},
"confidence": confidence,
"iou_threshold": iou_thresh,
"api_key": api_key,
}

res = requests.post(
f"http://localhost:9001/infer/{task}",
json=infer_payload,
f"http://localhost:9001/infer/{task}",
json=infer_payload,
)

predictions = res.json()
Expand Down Expand Up @@ -167,29 +167,29 @@ You can send a URL with an image, a NumPy array, or a base64-encoded image to an
img_str = img_str.decode("ascii")

infer_payload = {
"model_id": f"{project_id}/{model_version}",
"image": [
{
"type": "base64",
"value": img_str,
},
{
"type": "base64",
"value": img_str,
},
{
"type": "base64",
"value": img_str,
}
],
"confidence": confidence,
"iou_threshold": iou_thresh,
"api_key": api_key,
"model_id": f"{project_id}/{model_version}",
"image": [
{
"type": "base64",
"value": img_str,
},
{
"type": "base64",
"value": img_str,
},
{
"type": "base64",
"value": img_str,
}
],
"confidence": confidence,
"iou_threshold": iou_thresh,
"api_key": api_key,
}

res = requests.post(
f"http://localhost:9001/infer/{task}",
json=infer_payload,
f"http://localhost:9001/infer/{task}",
json=infer_payload,
)

predictions = res.json()
Expand All @@ -212,7 +212,60 @@ You can send a URL with an image, a NumPy array, or a base64-encoded image to an

=== "Numpy Array"

Numpy array inputs are currently not supported by V2 routes.
Create a new Python file and add the following code:

```python
import requests
import base64
from PIL import Image
from io import BytesIO

project_id = "soccer-players-5fuqs"
model_version = 1
task = "object_detection"
confidence = 0.5
iou_thresh = 0.5
api_key = "YOUR ROBOFLOW API KEY"
file_name = "path/to/local/image.jpg"

image = cv2.imread(file_name)
numpy_data = pickle.dumps(image)
img_str = base64.b64encode(numpy_data)
img_str = img_str.decode("ascii")

infer_payload = {
"model_id": f"{project_id}/{model_version}",
"image": {
"type": "base64",
sberan marked this conversation as resolved.
Show resolved Hide resolved
"value": img_str,
},
"confidence": confidence,
"iou_threshold": iou_thresh,
"api_key": api_key,
}

res = requests.post(
f"http://localhost:9001/infer/{task}",
json=infer_payload,
)

predictions = res.json()
print(predictions)
```

Above, specify:

1. `project_id`, `model_version`: Your project ID and model version number. [Learn how to retrieve your project ID and model version number](https://docs.roboflow.com/api-reference/workspace-and-project-ids).
2. `confidence`: The confidence threshold for predictions. Predictions with a confidence score below this threshold will be filtered out.
3. `api_key`: Your Roboflow API key. [Learn how to retrieve your Roboflow API key](https://docs.roboflow.com/api-reference/authentication#retrieve-an-api-key).
4. `task`: The type of task you want to run. Choose from `object_detection`, `classification`, or `segmentation`.
5. `filename`: The path to the image you want to run inference on.

Then, run the Python script:

```
python app.py
```

### V1 Routes

Expand Down
9 changes: 6 additions & 3 deletions inference/core/utils/image_utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import binascii
import os
import pickle
import re
Expand Down Expand Up @@ -222,11 +223,11 @@ def load_image_from_buffer(
return result


def load_image_from_numpy_str(value: bytes) -> np.ndarray:
def load_image_from_numpy_str(value: Union[bytes, str]) -> np.ndarray:
"""Loads an image from a numpy array string.

Args:
value (str): String representing the numpy array of the image.
value (Union[bytes, str]): Base64 string or byte sequence representing the pickled numpy array of the image.

Returns:
Image.Image: The loaded PIL image.
Expand All @@ -235,8 +236,10 @@ def load_image_from_numpy_str(value: bytes) -> np.ndarray:
InvalidNumpyInput: If the numpy data is invalid.
"""
try:
if isinstance(value, str):
value = pybase64.b64decode(value)
data = pickle.loads(value)
except (EOFError, TypeError, pickle.UnpicklingError) as error:
except (EOFError, TypeError, pickle.UnpicklingError, binascii.Error) as error:
raise InvalidNumpyInput(
f"Could not unpickle image data. Cause: {error}"
) from error
Expand Down
6 changes: 6 additions & 0 deletions tests/inference/unit_tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ def image_as_pickled_bytes() -> bytes:
image = np.zeros((128, 128, 3), dtype=np.uint8)
return pickle.dumps(image)

@fixture(scope="function")
def image_as_base64_encoded_pickled_bytes() -> bytes:
image = np.zeros((128, 128, 3), dtype=np.uint8)
bytes = pickle.dumps(image)
return base64.b64encode(bytes).decode()


@fixture(scope="function")
def image_as_pickled_bytes_rgba() -> bytes:
Expand Down
3 changes: 3 additions & 0 deletions tests/inference/unit_tests/core/utils/test_image_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@ def test_load_image_from_encoded_bytes_when_decoding_should_fail() -> None:
"image_as_png_bytes",
"image_as_jpeg_bytes",
"image_as_pickled_bytes",
"image_as_base64_encoded_pickled_bytes",
],
)
def test_attempt_loading_image_from_string_when_parsing_should_be_successful(
Expand Down Expand Up @@ -505,6 +506,7 @@ def test_extract_image_payload_and_type_when_value_is_request_and_type_is_not_re
("image_as_local_path", ImageType.FILE, True),
("image_as_buffer", ImageType.MULTIPART, True),
("image_as_pickled_bytes", ImageType.NUMPY, True),
("image_as_base64_encoded_pickled_bytes", ImageType.NUMPY, True),
("image_as_pillow", ImageType.PILLOW, False),
],
)
Expand Down Expand Up @@ -626,6 +628,7 @@ def test_convert_gray_image_to_bgr_when_2d_input_submitted(
("image_as_rgba_buffer", True), # works due to cv load flags
("image_as_gray_buffer", True),
("image_as_pickled_bytes", True),
("image_as_base64_encoded_pickled_bytes", True),
("image_as_pickled_bytes_gray", True),
("image_as_pillow", False),
],
Expand Down
Loading