Skip to content

Commit

Permalink
Merge branch 'main' into update_detector
Browse files Browse the repository at this point in the history
  • Loading branch information
brandon-groundlight committed Oct 1, 2024
2 parents 6fa05f5 + 3e377f5 commit ef641c9
Show file tree
Hide file tree
Showing 15 changed files with 193 additions and 125 deletions.
205 changes: 93 additions & 112 deletions docs/package-lock.json

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions generated/.openapi-generator/FILES
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,4 @@ setup.cfg
setup.py
test-requirements.txt
test/__init__.py
test/test_detector_reset_api.py
test/test_patched_detector_request.py
tox.ini
1 change: 1 addition & 0 deletions generated/docs/ImageQuery.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Name | Type | Description | Notes
**patience_time** | **float** | How long to wait for a confident response. | [readonly]
**confidence_threshold** | **float** | Min confidence needed to accept the response of the image query. | [readonly]
**rois** | [**[ROI], none_type**](ROI.md) | An array of regions of interest (bounding boxes) collected on image | [readonly]
**text** | **str, none_type** | A text field on image query. | [readonly]
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]

[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
Expand Down
1 change: 1 addition & 0 deletions generated/docs/LabelValue.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Name | Type | Description | Notes
**created_at** | **datetime** | | [readonly]
**detector_id** | **int, none_type** | | [readonly]
**source** | **bool, date, datetime, dict, float, int, list, str, none_type** | | [readonly]
**text** | **str, none_type** | Text annotations | [readonly]
**rois** | [**[ROI], none_type**](ROI.md) | | [optional]
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]

Expand Down
2 changes: 1 addition & 1 deletion generated/docs/ResultTypeEnum.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**value** | **str** | | must be one of ["binary_classification", "counting", ]
**value** | **str** | | must be one of ["binary_classification", "counting", "multi_classification", ]

[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

Expand Down
9 changes: 9 additions & 0 deletions generated/groundlight_openapi_client/model/image_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,10 @@ def openapi_types():
[ROI],
none_type,
), # noqa: E501
"text": (
str,
none_type,
), # noqa: E501
}

@cached_property
Expand All @@ -169,6 +173,7 @@ def discriminator():
"patience_time": "patience_time", # noqa: E501
"confidence_threshold": "confidence_threshold", # noqa: E501
"rois": "rois", # noqa: E501
"text": "text", # noqa: E501
}

read_only_vars = {
Expand All @@ -183,6 +188,7 @@ def discriminator():
"patience_time", # noqa: E501
"confidence_threshold", # noqa: E501
"rois", # noqa: E501
"text", # noqa: E501
}

_composed_schemas = {}
Expand All @@ -202,6 +208,7 @@ def _from_openapi_data(
patience_time,
confidence_threshold,
rois,
text,
*args,
**kwargs,
): # noqa: E501
Expand All @@ -219,6 +226,7 @@ def _from_openapi_data(
patience_time (float): How long to wait for a confident response.
confidence_threshold (float): Min confidence needed to accept the response of the image query.
rois ([ROI], none_type): An array of regions of interest (bounding boxes) collected on image
text (str, none_type): A text field on image query.
Keyword Args:
_check_type (bool): if True, values for parameters in openapi_types
Expand Down Expand Up @@ -290,6 +298,7 @@ def _from_openapi_data(
self.patience_time = patience_time
self.confidence_threshold = confidence_threshold
self.rois = rois
self.text = text
for var_name, var_value in kwargs.items():
if (
var_name not in self.attribute_map
Expand Down
10 changes: 9 additions & 1 deletion generated/groundlight_openapi_client/model/label_value.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ def openapi_types():
str,
none_type,
), # noqa: E501
"text": (
str,
none_type,
), # noqa: E501
"rois": (
[ROI],
none_type,
Expand All @@ -142,6 +146,7 @@ def discriminator():
"created_at": "created_at", # noqa: E501
"detector_id": "detector_id", # noqa: E501
"source": "source", # noqa: E501
"text": "text", # noqa: E501
"rois": "rois", # noqa: E501
}

Expand All @@ -152,14 +157,15 @@ def discriminator():
"created_at", # noqa: E501
"detector_id", # noqa: E501
"source", # noqa: E501
"text", # noqa: E501
}

_composed_schemas = {}

@classmethod
@convert_js_args_to_python_args
def _from_openapi_data(
cls, confidence, class_name, annotations_requested, created_at, detector_id, source, *args, **kwargs
cls, confidence, class_name, annotations_requested, created_at, detector_id, source, text, *args, **kwargs
): # noqa: E501
"""LabelValue - a model defined in OpenAPI
Expand All @@ -170,6 +176,7 @@ def _from_openapi_data(
created_at (datetime):
detector_id (int, none_type):
source (bool, date, datetime, dict, float, int, list, str, none_type):
text (str, none_type): Text annotations
Keyword Args:
_check_type (bool): if True, values for parameters in openapi_types
Expand Down Expand Up @@ -237,6 +244,7 @@ def _from_openapi_data(
self.created_at = created_at
self.detector_id = detector_id
self.source = source
self.text = text
for var_name, var_value in kwargs.items():
if (
var_name not in self.attribute_map
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class ResultTypeEnum(ModelSimple):
("value",): {
"BINARY_CLASSIFICATION": "binary_classification",
"COUNTING": "counting",
"MULTI_CLASSIFICATION": "multi_classification",
},
}

Expand Down Expand Up @@ -102,10 +103,10 @@ def __init__(self, *args, **kwargs):
Note that value can be passed either in args or in kwargs, but not in both.
Args:
args[0] (str):, must be one of ["binary_classification", "counting", ] # noqa: E501
args[0] (str):, must be one of ["binary_classification", "counting", "multi_classification", ] # noqa: E501
Keyword Args:
value (str):, must be one of ["binary_classification", "counting", ] # noqa: E501
value (str):, must be one of ["binary_classification", "counting", "multi_classification", ] # noqa: E501
_check_type (bool): if True, values for parameters in openapi_types
will be type checked and a TypeError will be
raised if the wrong type is input.
Expand Down Expand Up @@ -194,10 +195,10 @@ def _from_openapi_data(cls, *args, **kwargs):
Note that value can be passed either in args or in kwargs, but not in both.
Args:
args[0] (str):, must be one of ["binary_classification", "counting", ] # noqa: E501
args[0] (str):, must be one of ["binary_classification", "counting", "multi_classification", ] # noqa: E501
Keyword Args:
value (str):, must be one of ["binary_classification", "counting", ] # noqa: E501
value (str):, must be one of ["binary_classification", "counting", "multi_classification", ] # noqa: E501
_check_type (bool): if True, values for parameters in openapi_types
will be type checked and a TypeError will be
raised if the wrong type is input.
Expand Down
5 changes: 4 additions & 1 deletion generated/model.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# generated by datamodel-codegen:
# filename: public-api.yaml
# timestamp: 2024-08-27T20:38:52+00:00
# timestamp: 2024-10-01T16:56:32+00:00

from __future__ import annotations

Expand Down Expand Up @@ -124,6 +124,7 @@ class ROIRequest(BaseModel):
class ResultTypeEnum(Enum):
binary_classification = "binary_classification"
counting = "counting"
multi_classification = "multi_classification"


class SnoozeTimeUnitEnum(Enum):
Expand Down Expand Up @@ -322,6 +323,7 @@ class ImageQuery(BaseModel):
rois: Optional[List[ROI]] = Field(
..., description="An array of regions of interest (bounding boxes) collected on image"
)
text: Optional[str] = Field(..., description="A text field on image query.")


class LabelValue(BaseModel):
Expand All @@ -334,6 +336,7 @@ class LabelValue(BaseModel):
created_at: datetime
detector_id: Optional[int] = Field(...)
source: SourceEnum
text: Optional[str] = Field(..., description="Text annotations")


class LabelValueRequest(BaseModel):
Expand Down
2 changes: 0 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ packages = [
{include = "**/*.py", from = "src"},
]
readme = "README.md"
version = "0.17.4"
version = "0.17.6"

[tool.poetry.dependencies]
# For certifi, use ">=" instead of "^" since it upgrades its "major version" every year, not really following semver
Expand Down
13 changes: 13 additions & 0 deletions spec/public-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -931,6 +931,11 @@ components:
nullable: true
description: An array of regions of interest (bounding boxes) collected
on image
text:
type: string
nullable: true
readOnly: true
description: A text field on image query.
required:
- confidence_threshold
- created_at
Expand All @@ -942,6 +947,7 @@ components:
- result
- result_type
- rois
- text
- type
x-internal: true
ImageQueryTypeEnum:
Expand Down Expand Up @@ -989,13 +995,19 @@ components:
allOf:
- $ref: '#/components/schemas/SourceEnum'
readOnly: true
text:
type: string
readOnly: true
nullable: true
description: Text annotations
required:
- annotations_requested
- class_name
- confidence
- created_at
- detector_id
- source
- text
LabelValueRequest:
type: object
properties:
Expand Down Expand Up @@ -1188,6 +1200,7 @@ components:
enum:
- binary_classification
- counting
- multi_classification
type: string
Rule:
type: object
Expand Down
14 changes: 13 additions & 1 deletion src/groundlight/experimental_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import requests
from groundlight_openapi_client.api.actions_api import ActionsApi
from groundlight_openapi_client.api.detector_groups_api import DetectorGroupsApi
from groundlight_openapi_client.api.detector_reset_api import DetectorResetApi
from groundlight_openapi_client.api.image_queries_api import ImageQueriesApi
from groundlight_openapi_client.api.notes_api import NotesApi
from groundlight_openapi_client.model.action_request import ActionRequest
Expand Down Expand Up @@ -48,6 +49,7 @@ def __init__(self, endpoint: Union[str, None] = None, api_token: Union[str, None
self.images_api = ImageQueriesApi(self.api_client)
self.notes_api = NotesApi(self.api_client)
self.detector_group_api = DetectorGroupsApi(self.api_client)
self.detector_reset_api = DetectorResetApi(self.api_client)

ITEMS_PER_PAGE = 100

Expand Down Expand Up @@ -210,7 +212,7 @@ def create_note(
data = {"content": note}
params = {"detector_id": det_id}
headers = {"x-api-token": self.configuration.api_key["ApiToken"]}
requests.post(url, headers=headers, data=data, files=files, params=params, timeout=60) # type: ignore
requests.post(url, headers=headers, data=data, files=files, params=params) # type: ignore

def create_detector_group(self, name: str) -> DetectorGroup:
"""
Expand Down Expand Up @@ -297,6 +299,16 @@ def add_label(
request_params = LabelValueRequest(label=api_label, image_query_id=image_query_id, rois=roi_requests)
self.labels_api.create_label(request_params)

def reset_detector(self, detector: Union[str, Detector]) -> None:
"""
Removes all image queries for the given detector
:param detector_id: the id of the detector to reset
"""
if isinstance(detector, Detector):
detector = detector.id
self.detector_reset_api.reset_detector(detector)

def update_detector_name(self, detector: Union[str, Detector], name: str) -> None:
"""
Updates the name of the given detector
Expand Down
9 changes: 9 additions & 0 deletions test/integration/test_groundlight.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,15 @@ def test_submit_image_query_returns_yes(gl: Groundlight):
assert image_query.result.label == Label.YES


def test_submit_image_query_returns_text(gl: Groundlight):
# We use the "never-review" pipeline to guarantee a confident "yes" answer.
detector = gl.get_or_create_detector(
name="Always same text", query="Is there a dog?", pipeline_config="constant-text"
)
image_query = gl.submit_image_query(detector=detector, image="test/assets/dog.jpeg", wait=10, human_review="NEVER")
assert isinstance(image_query.text, str)


def test_submit_image_query_filename(gl: Groundlight, detector: Detector):
_image_query = gl.submit_image_query(detector=detector.id, image="test/assets/dog.jpeg", human_review="NEVER")
assert str(_image_query)
Expand Down
34 changes: 34 additions & 0 deletions test/unit/test_detector_reset.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import time
from datetime import datetime

import pytest
from groundlight import ExperimentalApi
from groundlight_openapi_client.exceptions import NotFoundException


def test_reset_retry(gl_experimental: ExperimentalApi):
# Reset the detector, retrying in case the reset is still ongoing
det = gl_experimental.create_detector(f"Test {datetime.utcnow()}", "test_query")
iq = gl_experimental.submit_image_query(det, "test/assets/cat.jpeg")
gl_experimental.reset_detector(det.id)
success = False
for _ in range(60):
try:
gl_experimental.get_image_query(iq.id)
except NotFoundException:
with pytest.raises(NotFoundException):
gl_experimental.get_image_query(iq.id)
success = True
break
time.sleep(10)
if not success:
raise Exception("Failed to reset detector")


def test_reset_training(gl_experimental: ExperimentalApi):
# If we reset a detector, we should have low confidence after the reset
low_confidence_threshold = 0.6
det = gl_experimental.create_detector(f"Test {datetime.utcnow()}", "is this a cat")
gl_experimental.reset_detector(det.id)
iq = gl_experimental.submit_image_query(det, "test/assets/cat.jpeg", human_review="NEVER")
assert iq.result.confidence < low_confidence_threshold

0 comments on commit ef641c9

Please sign in to comment.