Skip to content

Commit 2fa5401

Browse files
Merge pull request #1036 from adamtheturtle/simple-query
Add the initial query interface
2 parents cebb6de + 2bb3b04 commit 2fa5401

File tree

5 files changed

+171
-2
lines changed

5 files changed

+171
-2
lines changed

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
requests==2.22.0
22
timeout-decorator==0.4.1
3+
urllib3==1.25.3

src/vws/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
"""
44

55
from ._version import get_versions
6+
from .query import CloudRecoService
67
from .vws import VWS
78

89
__all__ = [
10+
'CloudRecoService',
911
'VWS',
1012
]
1113

src/vws/query.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
"""
2+
Tools for interacting with the Vuforia Cloud Recognition Web APIs.
3+
"""
4+
5+
import io
6+
from typing import Any, Dict, List
7+
from urllib.parse import urljoin
8+
9+
import requests
10+
from urllib3.filepost import encode_multipart_formdata
11+
12+
from ._authorization import authorization_header, rfc_1123_date
13+
14+
15+
class CloudRecoService:
16+
"""
17+
An interface to the Vuforia Cloud Recognition Web APIs.
18+
"""
19+
20+
def __init__(
21+
self,
22+
client_access_key: str,
23+
client_secret_key: str,
24+
) -> None:
25+
"""
26+
Args:
27+
client_access_key: A VWS client access key.
28+
client_secret_key: A VWS client secret key.
29+
"""
30+
self._client_access_key = client_access_key.encode()
31+
self._client_secret_key = client_secret_key.encode()
32+
33+
def query(self, image: io.BytesIO) -> List[Dict[str, Any]]:
34+
"""
35+
Use the Vuforia Web Query API to make an Image Recognition Query.
36+
37+
See
38+
https://library.vuforia.com/articles/Solution/How-To-Perform-an-Image-Recognition-Query
39+
for parameter details.
40+
41+
Args:
42+
image: The image to make a query against.
43+
44+
Returns:
45+
An ordered list of target details of matching targets.
46+
"""
47+
image_content = image.getvalue()
48+
body = {
49+
'image': ('image.jpeg', image_content, 'image/jpeg'),
50+
}
51+
date = rfc_1123_date()
52+
request_path = '/v1/query'
53+
content, content_type_header = encode_multipart_formdata(body)
54+
method = 'POST'
55+
56+
authorization_string = authorization_header(
57+
access_key=self._client_access_key,
58+
secret_key=self._client_secret_key,
59+
method=method,
60+
content=content,
61+
# Note that this is not the actual Content-Type header value sent.
62+
content_type='multipart/form-data',
63+
date=date,
64+
request_path=request_path,
65+
)
66+
67+
headers = {
68+
'Authorization': authorization_string,
69+
'Date': date,
70+
'Content-Type': content_type_header,
71+
}
72+
73+
base_vwq_url = 'https://cloudreco.vuforia.com'
74+
response = requests.request(
75+
method=method,
76+
url=urljoin(base=base_vwq_url, url=request_path),
77+
headers=headers,
78+
data=content,
79+
)
80+
81+
return list(response.json()['results'])

tests/conftest.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from mock_vws import MockVWS
99
from mock_vws.database import VuforiaDatabase
1010

11-
from vws import VWS
11+
from vws import VWS, CloudRecoService
1212

1313
pytest_plugins = [ # pylint: disable=invalid-name
1414
'tests.fixtures.images',
@@ -29,9 +29,21 @@ def _mock_database() -> Iterator[VuforiaDatabase]:
2929
@pytest.fixture()
3030
def vws_client(_mock_database: VuforiaDatabase) -> Iterator[VWS]:
3131
"""
32-
Yield a VWS client which connects to a mock.
32+
Yield a VWS client which connects to a mock database.
3333
"""
3434
yield VWS(
3535
server_access_key=_mock_database.server_access_key,
3636
server_secret_key=_mock_database.server_secret_key,
3737
)
38+
39+
40+
@pytest.fixture()
41+
def cloud_reco_client(_mock_database: VuforiaDatabase,
42+
) -> Iterator[CloudRecoService]:
43+
"""
44+
Yield a ``CloudRecoService`` client which connects to a mock database.
45+
"""
46+
yield CloudRecoService(
47+
client_access_key=_mock_database.client_access_key,
48+
client_secret_key=_mock_database.client_secret_key,
49+
)

tests/test_query.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
"""
2+
Tests for the ``CloudRecoService`` querying functionality.
3+
"""
4+
5+
import io
6+
import uuid
7+
8+
from vws import VWS, CloudRecoService
9+
10+
11+
class TestQuery:
12+
"""
13+
Tests for making image queries.
14+
"""
15+
16+
def test_no_matches(
17+
self,
18+
cloud_reco_client: CloudRecoService,
19+
high_quality_image: io.BytesIO,
20+
) -> None:
21+
"""
22+
An empty list is returned if there are no matches.
23+
"""
24+
result = cloud_reco_client.query(image=high_quality_image)
25+
assert result == []
26+
27+
def test_match(
28+
self,
29+
vws_client: VWS,
30+
cloud_reco_client: CloudRecoService,
31+
high_quality_image: io.BytesIO,
32+
) -> None:
33+
"""
34+
Details of matching targets are returned.
35+
"""
36+
target_id = vws_client.add_target(
37+
name='x',
38+
width=1,
39+
image=high_quality_image,
40+
)
41+
vws_client.wait_for_target_processed(target_id=target_id)
42+
[matching_target] = cloud_reco_client.query(image=high_quality_image)
43+
assert matching_target['target_id'] == target_id
44+
45+
46+
class TestMaxNumResults:
47+
"""
48+
Tests for the ``max_num_results`` parameter of ``query``.
49+
"""
50+
51+
def test_default(
52+
self,
53+
vws_client: VWS,
54+
cloud_reco_client: CloudRecoService,
55+
high_quality_image: io.BytesIO,
56+
) -> None:
57+
"""
58+
By default the maximum number of results is 1.
59+
"""
60+
target_id = vws_client.add_target(
61+
name=uuid.uuid4().hex,
62+
width=1,
63+
image=high_quality_image,
64+
)
65+
target_id_2 = vws_client.add_target(
66+
name=uuid.uuid4().hex,
67+
width=1,
68+
image=high_quality_image,
69+
)
70+
vws_client.wait_for_target_processed(target_id=target_id)
71+
vws_client.wait_for_target_processed(target_id=target_id_2)
72+
matches = cloud_reco_client.query(image=high_quality_image)
73+
assert len(matches) == 1

0 commit comments

Comments
 (0)