Skip to content

Commit ff6da32

Browse files
Tyler RomeroAuto-format Botsunildkumar
authored
Improve the plumbing and documentation for some of the ask* methods (#125)
Address a some documentation papercuts for the new ask_* methods. Plus, improve plumbing of arguments to submit_image_query and to wait_for_confident_result. --------- Co-authored-by: Auto-format Bot <[email protected]> Co-authored-by: Sunil Kumar <[email protected]>
1 parent 3360381 commit ff6da32

File tree

3 files changed

+65
-23
lines changed

3 files changed

+65
-23
lines changed

docs/docs/building-applications/5-async-queries.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ from time import sleep
1616

1717
detector = gl.get_or_create_detector(name="your_detector_name", query="your_query")
1818

19-
cam = cv2.VideoCapture(0) # Initialize camera (0 is the default index)
19+
cam = cv2.VideoCapture(0) # Initialize camera (0 is the default index)
2020

2121
while True:
2222
_, image = cam.read() # Capture one frame from the camera
@@ -35,7 +35,7 @@ from groundlight import Groundlight
3535

3636
detector = gl.get_or_create_detector(name="your_detector_name", query="your_query")
3737

38-
image_query_id = db.get_next_image_query_id()
38+
image_query_id = db.get_next_image_query_id()
3939

4040
while image_query_id is not None:
4141
image_query = gl.get_image_query(id=image_query_id) # retrieve the image query from Groundlight
@@ -67,4 +67,7 @@ result = image_query.result # This will always be 'None' as you asked asynchron
6767

6868
image_query = gl.get_image_query(id=image_query.id) # Immediately retrieve the image query from Groundlight
6969
result = image_query.result # This will likely be 'UNCLEAR' as Groundlight is still processing your query
70+
71+
image_query = gl.wait_for_confident_result(id=image_query.id) # Poll for a confident result from Groundlight
72+
result = image_query.result
7073
```

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ packages = [
99
{include = "**/*.py", from = "src"},
1010
]
1111
readme = "README.md"
12-
version = "0.12.0"
12+
version = "0.12.1"
1313

1414
[tool.poetry.dependencies]
1515
# For certifi, use ">=" instead of "^" since it upgrades its "major version" every year, not really following semver

src/groundlight/client.py

Lines changed: 59 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import logging
22
import os
33
import time
4+
from functools import partial
45
from io import BufferedReader, BytesIO
56
from typing import Callable, Optional, Union
67

@@ -304,9 +305,20 @@ def submit_image_query( # noqa: PLR0913 # pylint: disable=too-many-arguments, t
304305
Any pixel format will get converted to JPEG at high quality before sending to service.
305306
:type image: str or bytes or Image.Image or BytesIO or BufferedReader or np.ndarray
306307
307-
:param wait: How long to wait (in seconds) for a confident answer.
308+
:param wait: How long to poll (in seconds) for a confident answer. This is a client-side timeout.
308309
:type wait: float
309310
311+
:param patience_time: How long to wait (in seconds) for a confident answer for this image query.
312+
The longer the patience_time, the more likely Groundlight will arrive at a confident answer.
313+
Within patience_time, Groundlight will update ML predictions based on stronger findings,
314+
and, additionally, Groundlight will prioritize human review of the image query if necessary.
315+
This is a soft server-side timeout. If not set, use the detector's patience_time.
316+
:type patience_time: float
317+
318+
:param confidence_threshold: The confidence threshold to wait for.
319+
If not set, use the detector's confidence threshold.
320+
:type confidence_threshold: float
321+
310322
:param human_review: If `None` or `DEFAULT`, send the image query for human review
311323
only if the ML prediction is not confident.
312324
If set to `ALWAYS`, always send the image query for human review.
@@ -375,8 +387,10 @@ def ask_confident(
375387
confidence_threshold: Optional[float] = None,
376388
wait: Optional[float] = None,
377389
) -> ImageQuery:
378-
"""Evaluates an image with Groundlight waiting until an answer above the confidence threshold
379-
of the detector is reached or the wait period has passed.
390+
"""
391+
Evaluates an image with Groundlight waiting until an answer above the confidence threshold
392+
of the detector is reached or the wait period has passed.
393+
380394
:param detector: the Detector object, or string id of a detector like `det_12345`
381395
:type detector: Detector or str
382396
@@ -405,6 +419,8 @@ def ask_confident(
405419
image,
406420
confidence_threshold=confidence_threshold,
407421
wait=wait,
422+
patience_time=wait,
423+
human_review=None,
408424
)
409425

410426
def ask_ml(
@@ -413,7 +429,9 @@ def ask_ml(
413429
image: Union[str, bytes, Image.Image, BytesIO, BufferedReader, np.ndarray],
414430
wait: Optional[float] = None,
415431
) -> ImageQuery:
416-
"""Evaluates an image with Groundlight, getting the first answer Groundlight can provide.
432+
"""
433+
Evaluates an image with Groundlight, getting the first answer Groundlight can provide.
434+
417435
:param detector: the Detector object, or string id of a detector like `det_12345`
418436
:type detector: Detector or str
419437
@@ -443,12 +461,13 @@ def ask_ml(
443461
wait = self.DEFAULT_WAIT if wait is None else wait
444462
return self.wait_for_ml_result(iq, timeout_sec=wait)
445463

446-
def ask_async(
464+
def ask_async( # noqa: PLR0913 # pylint: disable=too-many-arguments
447465
self,
448466
detector: Union[Detector, str],
449467
image: Union[str, bytes, Image.Image, BytesIO, BufferedReader, np.ndarray],
468+
patience_time: Optional[float] = None,
469+
confidence_threshold: Optional[float] = None,
450470
human_review: Optional[str] = None,
451-
inspection_id: Optional[str] = None,
452471
) -> ImageQuery:
453472
"""
454473
Convenience method for submitting an `ImageQuery` asynchronously. This is equivalent to calling
@@ -469,6 +488,17 @@ def ask_async(
469488
470489
:type image: str or bytes or Image.Image or BytesIO or BufferedReader or np.ndarray
471490
491+
:param patience_time: How long to wait (in seconds) for a confident answer for this image query.
492+
The longer the patience_time, the more likely Groundlight will arrive at a confident answer.
493+
Within patience_time, Groundlight will update ML predictions based on stronger findings,
494+
and, additionally, Groundlight will prioritize human review of the image query if necessary.
495+
This is a soft server-side timeout. If not set, use the detector's patience_time.
496+
:type patience_time: float
497+
498+
:param confidence_threshold: The confidence threshold to wait for.
499+
If not set, use the detector's confidence threshold.
500+
:type confidence_threshold: float
501+
472502
:param human_review: If `None` or `DEFAULT`, send the image query for human review
473503
only if the ML prediction is not confident.
474504
If set to `ALWAYS`, always send the image query for human review.
@@ -500,26 +530,34 @@ def ask_async(
500530
assert image_query.id is not None
501531
502532
# Do not attempt to access the result of this query as the result for all async queries
503-
# will be None. Your result is being computed asynchronously and will be available
504-
# later
533+
# will be None. Your result is being computed asynchronously and will be available later
505534
assert image_query.result is None
506535
507-
# retrieve the result later or on another machine by calling gl.get_image_query()
508-
# with the id of the image_query above
509-
image_query = gl.get_image_query(image_query.id)
536+
# retrieve the result later or on another machine by calling gl.wait_for_confident_result()
537+
# with the id of the image_query above. This will block until the result is available.
538+
image_query = gl.wait_for_confident_result(image_query.id)
510539
511540
# now the result will be available for your use
512541
assert image_query.result is not None
513542
543+
# alternatively, you can check if the result is available (without blocking) by calling
544+
# gl.get_image_query() with the id of the image_query above.
545+
image_query = gl.get_image_query(image_query.id)
514546
"""
515547
return self.submit_image_query(
516-
detector, image, wait=0, human_review=human_review, want_async=True, inspection_id=inspection_id
548+
detector,
549+
image,
550+
wait=0,
551+
patience_time=patience_time,
552+
confidence_threshold=confidence_threshold,
553+
human_review=human_review,
554+
want_async=True,
517555
)
518556

519557
def wait_for_confident_result(
520558
self,
521559
image_query: Union[ImageQuery, str],
522-
confidence_threshold: float,
560+
confidence_threshold: Optional[float] = None,
523561
timeout_sec: float = 30.0,
524562
) -> ImageQuery:
525563
"""
@@ -529,7 +567,8 @@ def wait_for_confident_result(
529567
:param image_query: An ImageQuery object to poll
530568
:type image_query: ImageQuery or str
531569
532-
:param confidence_threshold: The minimum confidence level required to return before the timeout.
570+
:param confidence_threshold: The confidence threshold to wait for.
571+
If not set, use the detector's confidence threshold.
533572
:type confidence_threshold: float
534573
535574
:param timeout_sec: The maximum number of seconds to wait.
@@ -538,10 +577,12 @@ def wait_for_confident_result(
538577
:return: ImageQuery
539578
:rtype: ImageQuery
540579
"""
580+
if confidence_threshold is None:
581+
if isinstance(image_query, str):
582+
image_query = self.get_image_query(image_query)
583+
confidence_threshold = self.get_detector(image_query.detector_id).confidence_threshold
541584

542-
def confidence_above_thresh(iq):
543-
return iq_is_confident(iq, confidence_threshold=confidence_threshold)
544-
585+
confidence_above_thresh = partial(iq_is_confident, confidence_threshold=confidence_threshold)
545586
return self._wait_for_result(image_query, condition=confidence_above_thresh, timeout_sec=timeout_sec)
546587

547588
def wait_for_ml_result(self, image_query: Union[ImageQuery, str], timeout_sec: float = 30.0) -> ImageQuery:
@@ -551,9 +592,6 @@ def wait_for_ml_result(self, image_query: Union[ImageQuery, str], timeout_sec: f
551592
:param image_query: An ImageQuery object to poll
552593
:type image_query: ImageQuery or str
553594
554-
:param confidence_threshold: The minimum confidence level required to return before the timeout.
555-
:type confidence_threshold: float
556-
557595
:param timeout_sec: The maximum number of seconds to wait.
558596
:type timeout_sec: float
559597
@@ -623,6 +661,7 @@ def add_label(self, image_query: Union[ImageQuery, str], label: Union[Label, str
623661
else:
624662
image_query_id = str(image_query)
625663
# Some old imagequery id's started with "chk_"
664+
# TODO: handle iqe_ for image_queries returned from edge endpoints
626665
if not image_query_id.startswith(("chk_", "iq_")):
627666
raise ValueError(f"Invalid image query id {image_query_id}")
628667
api_label = convert_display_label_to_internal(image_query_id, label)

0 commit comments

Comments
 (0)