-
Notifications
You must be signed in to change notification settings - Fork 6
Wait for confident result (Blocking submit) #18
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
Changes from all commits
c2d7293
b51b9c1
a56538a
6419962
f589822
a312dda
36b01cf
2880146
6bd2030
2a38ecf
cbc528b
a4d0b59
96d7053
a16d2da
2ce94a2
6fb665b
83d1bd6
2534d92
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,23 +2,49 @@ | |
|
||
Groundlight makes it simple to understand images. You can easily create computer vision detectors just by describing what you want to know using natural language. | ||
|
||
## Computer vision made simple | ||
|
||
How to build a working computer vision system in just 5 lines of python code: | ||
|
||
```Python | ||
from groundlight import Groundlight | ||
gl = Groundlight() | ||
d = gl.create_detector("door", query="Is the door open?") # define with natural language | ||
image_query = gl.submit_image_query(detector=d, image=jpeg_img) # send in an image | ||
print(f"The answer is {image_query.result}") # get the result | ||
``` | ||
|
||
**How does it work?** Your images are first analyzed by machine learning (ML) models which are automatically trained on your data. If those models have high enough confidence, that's your answer. But if the models are unsure, then the images are progressively escalated to more resource-intensive analysis methods up to real-time human review. So what you get is a computer vision system that starts working right away without even needing to first gather and label a dataset. At first it will operate with high latency, because people need to review the image queries. But over time, the ML systems will learn and improve so queries come back faster with higher confidence. | ||
|
||
*Note: The SDK is currently in "beta" phase. Interfaces are subject to change in future versions.* | ||
|
||
|
||
## Simple Example | ||
## Managing confidence levels and latency | ||
|
||
How to build a computer vision system in 5 lines of python code: | ||
Groundlight gives you a simple way to control the trade-off of latency against accuracy. The longer you can wait for an answer to your image query, the better accuracy you can get. In particular, if the ML models are unsure of the best response, they will escalate the image query to more intensive analysis with more complex models and real-time human monitors as needed. Your code can easily wait for this delayed response. Either way, these new results are automatically trained into your models so your next queries will get better results faster. | ||
|
||
The desired confidence level is set as the escalation threshold on your detector. This determines what is the minimum confidence score for the ML system to provide before the image query is escalated. | ||
|
||
For example, say you want to set your desired confidence level to 0.95, but that you're willing to wait up to 60 seconds to get a confident response. | ||
|
||
```Python | ||
from groundlight import Groundlight | ||
gl = Groundlight() | ||
d = gl.create_detector("door", query="Is the door open?") # define with natural language | ||
image_query = gl.submit_image_query(detector=d, image="path/filename.jpeg") # send an image | ||
print(f"The answer is {image_query.result}") # get the result | ||
d = gl.create_detector("trash", query="Is the trash can full?", confidence=0.95) | ||
image_query = gl.submit_image_query(detector=d, image=jpeg_img, wait=60) | ||
# This will wait until either 30 seconds have passed or the confidence reaches 0.95 | ||
print(f"The answer is {image_query.result}") | ||
``` | ||
|
||
Or if you want to run as fast as possible, set `wait=0`. This way you will only get the ML results, without waiting for escalation. Image queries which are below the desired confidence level still be escalated for further analysis, and the results are incorporated as training data to improve your ML model, but your code will not wait for that to happen. | ||
|
||
```Python | ||
image_query = gl.submit_image_query(detector=d, image=jpeg_img, wait=0) | ||
``` | ||
|
||
You can see the confidence score returned for the image query: | ||
|
||
```Python | ||
print(f"The confidence is {image_query.result.confidence}") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the None result here will be confusing... |
||
``` | ||
|
||
## Getting Started | ||
|
||
|
@@ -45,6 +71,7 @@ $ python3 glapp.py | |
``` | ||
|
||
|
||
|
||
## Prerequisites | ||
|
||
### Using Groundlight SDK on Ubuntu 18.04 | ||
|
@@ -125,6 +152,7 @@ gl = Groundlight() | |
try: | ||
detectors = gl.list_detectors() | ||
except ApiException as e: | ||
# Many fields available to describe the error | ||
print(e) | ||
print(e.args) | ||
print(e.body) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
Code samples | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
"""Example of how to wait for a confident result | ||
""" | ||
import logging | ||
|
||
logging.basicConfig(level=logging.DEBUG) | ||
|
||
from groundlight import Groundlight | ||
|
||
gl = Groundlight() | ||
|
||
d = gl.get_or_create_detector(name="dog", query="is there a dog in the picture?") | ||
|
||
print(f"Submitting image query") | ||
iq = gl.submit_image_query(d, image="../test/assets/dog.jpeg", wait=30) | ||
print(iq) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,7 @@ | ||
import os | ||
from io import BufferedReader, BytesIO | ||
import logging | ||
import os | ||
import time | ||
from typing import Optional, Union | ||
|
||
from model import Detector, ImageQuery, PaginatedDetectorList, PaginatedImageQueryList | ||
|
@@ -15,6 +17,8 @@ | |
|
||
GROUNDLIGHT_ENDPOINT = os.environ.get("GROUNDLIGHT_ENDPOINT", "https://api.groundlight.ai/device-api") | ||
|
||
logger = logging.getLogger("groundlight") | ||
|
||
|
||
class ApiTokenError(Exception): | ||
pass | ||
|
@@ -57,7 +61,10 @@ def __init__(self, endpoint: str = GROUNDLIGHT_ENDPOINT, api_token: str = None): | |
self.detectors_api = DetectorsApi(ApiClient(configuration)) | ||
self.image_queries_api = ImageQueriesApi(ApiClient(configuration)) | ||
|
||
def get_detector(self, id: str) -> Detector: | ||
def get_detector(self, id: Union[str, Detector]) -> Detector: | ||
if isinstance(id, Detector): | ||
# Short-circuit | ||
return id | ||
obj = self.detectors_api.get_detector(id=id) | ||
return Detector.parse_obj(obj.to_dict()) | ||
|
||
|
@@ -107,19 +114,22 @@ def submit_image_query( | |
self, | ||
detector: Union[Detector, str], | ||
image: Union[str, bytes, BytesIO, BufferedReader], | ||
wait: float = 0, | ||
) -> ImageQuery: | ||
"""Evaluates an image with Groundlight. | ||
:param detector: the Detector object, or string id of a detector like `det_12345` | ||
:param image: The image, in several possible formats: | ||
- a filename (string) of a jpeg file | ||
- a byte array or BytesIO with jpeg bytes | ||
- a numpy array in the 0-255 range (gets converted to jpeg) | ||
:param wait: How long to wait (in seconds) for a confident answer | ||
""" | ||
if isinstance(detector, Detector): | ||
detector_id = detector.id | ||
else: | ||
detector_id = detector | ||
image_bytesio: Union[BytesIO, BufferedReader] | ||
# TODO: support PIL Images | ||
if isinstance(image, str): | ||
# Assume it is a filename | ||
image_bytesio = buffer_from_jpeg_file(image) | ||
|
@@ -134,5 +144,29 @@ def submit_image_query( | |
"Unsupported type for image. We only support JPEG images specified through a filename, bytes, BytesIO, or BufferedReader object." | ||
) | ||
|
||
obj = self.image_queries_api.submit_image_query(detector_id=detector_id, body=image_bytesio) | ||
return ImageQuery.parse_obj(obj.to_dict()) | ||
raw_img_query = self.image_queries_api.submit_image_query(detector_id=detector_id, body=image_bytesio) | ||
img_query = ImageQuery.parse_obj(raw_img_query.to_dict()) | ||
if wait: | ||
threshold = self.get_detector(detector).confidence_threshold | ||
img_query = self._poll_for_confident_result(img_query, wait, threshold) | ||
return img_query | ||
|
||
def _poll_for_confident_result(self, img_query: ImageQuery, wait: float, threshold: float) -> ImageQuery: | ||
"""Polls on an image query waiting for the result to reach the specified confidence.""" | ||
start_time = time.time() | ||
delay = 0.1 | ||
while time.time() - start_time < wait: | ||
current_confidence = img_query.result.confidence | ||
if current_confidence is None: | ||
logging.debug(f"Image query with None confidence implies human label (for now)") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is really confusing and needs to be documented further up as well. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Completely agree. In fact, we should write a wrapper which hides this. |
||
break | ||
if current_confidence >= threshold: | ||
logging.debug(f"Image query confidence {current_confidence:.3f} above {threshold:.3f}") | ||
break | ||
logger.debug( | ||
f"Polling for updated image_query because confidence {current_confidence:.3f} < {threshold:.3f}" | ||
) | ||
time.sleep(delay) | ||
delay *= 1.4 # slow exponential backoff | ||
img_query = self.get_image_query(img_query.id) | ||
return img_query |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
technically its just one line of code to build the model :) the other line is to use the model you already built.