Skip to content
This repository was archived by the owner on Nov 8, 2021. It is now read-only.

Commit 9f0a1dc

Browse files
authored
Python SDK update for BUILD 2017 release. (#23)
* requirements: add pillow, reorder alphabetically. * Add start, top parameters for list persons. * More robust response parser. * Sample update: - Support more face attributes. - Fix image rotation bug. - Minor enhancement and bug fix. * Bump version to 1.3.0. * Minor change in result shown. * Update list person description.
1 parent 6156abf commit 9f0a1dc

File tree

8 files changed

+175
-88
lines changed

8 files changed

+175
-88
lines changed

cognitive_face/person.py

+12-5
Original file line numberDiff line numberDiff line change
@@ -145,20 +145,27 @@ def get_face(person_group_id, person_id, persisted_face_id):
145145
return util.request('GET', url)
146146

147147

148-
def lists(person_group_id):
149-
"""List all persons in a person group, and retrieve person information
150-
(including `person_id`, `name`, `user_data` and `persisited_face_ids` of
151-
registered faces of the person).
148+
def lists(person_group_id, start=None, top=None):
149+
"""List `top` persons in a person group with `person_id` greater than
150+
`start`, and retrieve person information (including `person_id`, `name`,
151+
`user_data` and `persisited_face_ids` of registered faces of the person).
152152
153153
Args:
154154
person_group_id: `person_group_id` of the target person group.
155+
start: List persons from the least `person_id` greater than this.
156+
top: The number of persons to list, rangeing in [1, 1000]. Default is
157+
1000;
155158
156159
Returns:
157160
An array of person information that belong to the person group.
158161
"""
159162
url = 'persongroups/{}/persons'.format(person_group_id)
163+
params = {
164+
'start': start,
165+
'top': top,
166+
}
160167

161-
return util.request('GET', url)
168+
return util.request('GET', url, params=params)
162169

163170

164171
def update(person_group_id, person_id, name=None, user_data=None):

cognitive_face/util.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,13 @@ def request(method, url, data=None, json=None, headers=None, params=None):
7575
result = None
7676
# `person_group.train` return 202 status code for success.
7777
if response.status_code not in (200, 202):
78-
print('status_code: {}'.format(response.status_code))
79-
print('response: {}'.format(response.text))
80-
error_msg = response.json()['error']
78+
try:
79+
error_msg = response.json()['error']
80+
except:
81+
raise CognitiveFaceException(
82+
response.status_code,
83+
response.status_code,
84+
response.text)
8185
raise CognitiveFaceException(
8286
response.status_code,
8387
error_msg.get('code'),

requirements.txt

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
requests
21
pep8
2+
pillow
33
pyflakes
4-
pylint
4+
pylint
5+
requests

sample/model/face.py

+66-25
Original file line numberDiff line numberDiff line change
@@ -4,49 +4,90 @@
44
File: face.py
55
Description: Face model for Python SDK Sample.
66
"""
7-
87
import wx
98

109
import util
1110

1211

12+
class Rect(object):
13+
"""Face Rectangle."""
14+
def __init__(self, rect):
15+
super(Rect, self).__init__()
16+
self.set_rect(rect)
17+
18+
def set_rect(self, rect):
19+
"""docstring for set_rect"""
20+
self.left = int(rect['left'])
21+
self.top = int(rect['top'])
22+
self.width = int(rect['width'])
23+
self.height = int(rect['height'])
24+
25+
26+
class Attribute(object):
27+
"""Attributes for face."""
28+
def __init__(self, attr):
29+
super(Attribute, self).__init__()
30+
self.set_attr(attr)
31+
32+
def set_attr(self, attr):
33+
"""Set the attribute value."""
34+
self.gender = attr['gender']
35+
self.age = int(attr['age'])
36+
if not attr['hair']['hairColor']:
37+
if attr['hair']['invisible']:
38+
self.hair = 'Invisible'
39+
else:
40+
self.hair = 'Bald'
41+
else:
42+
self.hair = max(
43+
attr['hair']['hairColor'],
44+
key=lambda x: x['confidence']
45+
)['color']
46+
self.facial_hair = sum(attr['facialHair'].values()) > 0 and 'Yes' \
47+
or 'No'
48+
self.makeup = any(attr['makeup'].values())
49+
self.emotion = util.key_with_max_value(attr['emotion'])
50+
self.occlusion = any(attr['occlusion'].values())
51+
self.exposure = attr['exposure']['exposureLevel']
52+
self.head_pose = "Pitch: {}, Roll:{}, Yaw:{}".format(
53+
attr['headPose']['pitch'],
54+
attr['headPose']['roll'],
55+
attr['headPose']['yaw']
56+
)
57+
if not attr['accessories']:
58+
self.accessories = 'NoAccessories'
59+
else:
60+
self.accessories = ' '.join(
61+
[str(x['type']) for x in attr['accessories']]
62+
)
63+
64+
1365
class Face(object):
1466
"""Face Model for each face."""
1567
def __init__(self, res, path, size=util.MAX_THUMBNAIL_SIZE):
1668
super(Face, self).__init__()
1769
self.path = path
18-
self.bmp = wx.Bitmap(path)
70+
img = util.rotate_image(path)
71+
self.bmp = img.ConvertToBitmap()
1972
self.name = None
2073
if res.get('faceId'):
2174
self.id = res['faceId']
2275
if res.get('persistedFaceId'):
2376
self.persisted_id = res['persistedFaceId']
2477
if res.get('faceRectangle'):
25-
rect = res['faceRectangle']
26-
self.left = int(rect['left'])
27-
self.top = int(rect['top'])
28-
self.width = int(rect['width'])
29-
self.height = int(rect['height'])
78+
self.rect = Rect(res['faceRectangle'])
3079
self.bmp = self.bmp.GetSubBitmap(wx.Rect(
31-
self.left, self.top, self.width, self.height))
80+
self.rect.left,
81+
self.rect.top,
82+
self.rect.width,
83+
self.rect.height,
84+
))
3285
if res.get('faceAttributes'):
33-
attr = res['faceAttributes']
34-
self.age = int(attr['age'])
35-
self.gender = attr['gender']
36-
self.head_pose = "Pitch: {}, Roll:{}, Yaw:{}".format(
37-
attr['headPose']['pitch'],
38-
attr['headPose']['roll'],
39-
attr['headPose']['yaw']
40-
)
41-
self.smile = float(attr['smile']) > 0 and 'Smile' or 'Not Smile'
42-
self.facial_hair = sum(attr['facialHair'].values()) > 0 and 'Yes' \
43-
or 'No'
44-
self.glasses = attr['glasses']
45-
self.emotion = max(
46-
attr['emotion'],
47-
key=lambda key: attr['emotion'][key]
48-
)
49-
self.bmp = util.scale_bitmap(self.bmp, size)
86+
self.attr = Attribute(res['faceAttributes'])
87+
self.bmp = util.scale_image(
88+
self.bmp.ConvertToImage(),
89+
size=size,
90+
).ConvertToBitmap()
5091

5192
def set_name(self, name):
5293
"""Set the name for the face."""

sample/util.py

+47-11
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
"""
77

88
from threading import Thread
9+
import operator
910
import os.path
1011

12+
from PIL import Image
1113
import wx
1214

1315
try:
@@ -25,14 +27,21 @@
2527
MAX_THUMBNAIL_SIZE = 75
2628
STYLE = wx.SIMPLE_BORDER
2729
SUBSCRIPTION_KEY_FILENAME = 'Subscription.txt'
30+
ORIENTATION_TAG = 274
2831

2932
LOG_FACE_LIST_REQUEST = (
3033
'Request: Face List {} will be used for build person database. '
3134
'Checking whether group exists.'
3235
)
3336
LOG_FACE_LIST_NOT_EXIST = 'Response: Face List {} does not exist before.'
3437
LOG_FACE_LIST_EXIST = 'Response: Face List {} exists.'
35-
LABEL_FACE = '{} years old, {}\n{}\n{}, {}\nFacial Hair: {}\nEmotion: {}\n'
38+
LABEL_FACE = (
39+
'{}, {} years old\n'
40+
'Hair: {}, Facial Hair: {}\n'
41+
'Makeup: {}, Emotion: {}\n'
42+
'Occluded: {}, Exposure: {}\n'
43+
'{}\n{}\n'
44+
)
3645

3746

3847
class SubscriptionKey(object):
@@ -69,9 +78,8 @@ def delete(cls):
6978
CF.Key.set(cls.key)
7079

7180

72-
def scale_bitmap(bitmap, size=MAX_IMAGE_SIZE):
73-
"""Scale the image."""
74-
img = bitmap.ConvertToImage()
81+
def scale_image(img, size=MAX_IMAGE_SIZE):
82+
"""Scale the wx.Image."""
7583
width = img.GetWidth()
7684
height = img.GetHeight()
7785
if width > height:
@@ -81,7 +89,23 @@ def scale_bitmap(bitmap, size=MAX_IMAGE_SIZE):
8189
new_height = size
8290
new_width = size * width / height
8391
img = img.Scale(new_width, new_height)
84-
return wx.BitmapFromImage(img)
92+
return img
93+
94+
95+
def rotate_image(path):
96+
"""Rotate the image from path and return wx.Image."""
97+
img = Image.open(path)
98+
try:
99+
exif = img._getexif()
100+
if exif[ORIENTATION_TAG] == 3:
101+
img = img.rotate(180, expand=True)
102+
elif exif[ORIENTATION_TAG] == 6:
103+
img = img.rotate(270, expand=True)
104+
elif exif[ORIENTATION_TAG] == 8:
105+
img = img.rotate(90, expand=True)
106+
except:
107+
pass
108+
return pil_image_to_wx_image(img)
85109

86110

87111
def draw_bitmap_rectangle(bitmap, faces):
@@ -98,20 +122,32 @@ def draw_bitmap_rectangle(bitmap, faces):
98122
wx.FONTWEIGHT_BOLD))
99123
for face in faces:
100124
dc.DrawRectangle(
101-
face.left * bitmap.scale,
102-
face.top * bitmap.scale,
103-
face.width * bitmap.scale,
104-
face.height * bitmap.scale,
125+
face.rect.left * bitmap.scale,
126+
face.rect.top * bitmap.scale,
127+
face.rect.width * bitmap.scale,
128+
face.rect.height * bitmap.scale,
105129
)
106130
if face.name:
107131
text_width, text_height = dc.GetTextExtent(face.name)
108132
dc.DrawText(face.name,
109-
face.left * bitmap.scale,
110-
face.top * bitmap.scale - text_height)
133+
face.rect.left * bitmap.scale,
134+
face.rect.top * bitmap.scale - text_height)
111135
dc.SelectObject(wx.NullBitmap)
112136
bitmap.bitmap.SetBitmap(bitmap.bmp)
113137

114138

139+
def pil_image_to_wx_image(pil_image):
140+
"""Convert from PIL image to wx image."""
141+
wx_image = wx.EmptyImage(pil_image.width, pil_image.height)
142+
wx_image.SetData(pil_image.convert("RGB").tobytes())
143+
return wx_image
144+
145+
146+
def key_with_max_value(item):
147+
"""Get the key with maximum value in a dict."""
148+
return max(item.iteritems(), key=operator.itemgetter(1))[0]
149+
150+
115151
def async(func):
116152
"""Async wrapper."""
117153
def wrapper(*args, **kwargs):

sample/view/base.py

+20-30
Original file line numberDiff line numberDiff line change
@@ -37,42 +37,30 @@ def __init__(self, parent, bitmap=wx.NullBitmap, size=util.MAX_IMAGE_SIZE):
3737

3838
def set_path(self, path):
3939
"""Set the image path."""
40-
bitmap = wx.Bitmap(path)
41-
self.bmp = util.scale_bitmap(bitmap, size=self.size)
42-
43-
width = bitmap.GetWidth()
44-
new_width = self.bmp.GetWidth()
45-
46-
self.scale = 1.0 * new_width / width
47-
48-
self.bitmap.SetBitmap(self.bmp)
49-
self.sizer.Layout()
50-
51-
def set_bmp(self, bitmap):
52-
"""Set the image bitmap."""
53-
self.bmp = util.scale_bitmap(bitmap, size=self.size)
54-
width = bitmap.GetWidth()
55-
new_width = self.bmp.GetWidth()
56-
40+
img = util.rotate_image(path)
41+
width = img.GetWidth()
42+
img = util.scale_image(img, size=self.size)
43+
new_width = img.GetWidth()
5744
self.scale = 1.0 * new_width / width
58-
45+
self.bmp = img.ConvertToBitmap()
5946
self.bitmap.SetBitmap(self.bmp)
6047
self.sizer.Layout()
6148

6249

6350
class MyGridStaticBitmap(wx.Panel):
6451
"""Base Grid StaticBitmap."""
6552
def __init__(self, parent, rows=1, cols=0, vgap=0, hgap=0,
66-
size=util.MAX_IMAGE_SIZE):
53+
size=util.MAX_THUMBNAIL_SIZE):
6754
super(MyGridStaticBitmap, self).__init__(parent)
6855
self.sizer = wx.GridSizer(rows, cols, vgap, hgap)
6956
self.SetSizer(self.sizer)
57+
self.size = size
7058

7159
def set_paths(self, paths):
7260
"""Set the paths for the images."""
7361
self.sizer.Clear(True)
7462
for path in paths:
75-
bitmap = MyStaticBitmap(self, size=util.MAX_THUMBNAIL_SIZE)
63+
bitmap = MyStaticBitmap(self, size=self.size)
7664
bitmap.set_path(path)
7765
self.sizer.Add(bitmap)
7866
self.SetSizerAndFit(self.sizer)
@@ -82,8 +70,7 @@ def set_faces(self, faces):
8270
"""Set the faces."""
8371
self.sizer.Clear(True)
8472
for face in faces:
85-
bitmap = MyStaticBitmap(self, bitmap=face.bmp,
86-
size=util.MAX_THUMBNAIL_SIZE)
73+
bitmap = MyStaticBitmap(self, bitmap=face.bmp, size=self.size)
8774
self.sizer.Add(bitmap)
8875
self.SetSizerAndFit(self.sizer)
8976
self.sizer.Layout()
@@ -231,7 +218,7 @@ def OnMeasureItem(self, index):
231218
"""OnMeasureItem for Layout."""
232219
face = self.faces[index]
233220
bmp_height = face.bmp.GetHeight() + 4
234-
label_height = self.GetTextExtent(face.glasses)[1] * 4 + 8
221+
label_height = self.GetTextExtent(face.attr.gender)[1] * 6
235222
return max(bmp_height, label_height)
236223

237224
def OnDrawItem(self, dc, rect, index):
@@ -243,13 +230,16 @@ def OnDrawItem(self, dc, rect, index):
243230
textx = rect.x + 2 + face.bmp.GetWidth() + 2
244231
label_rect = wx.Rect(textx, rect.y, rect.width - textx, rect.height)
245232
label = util.LABEL_FACE.format(
246-
face.age,
247-
face.gender,
248-
face.head_pose,
249-
face.smile,
250-
face.glasses,
251-
face.facial_hair,
252-
face.emotion
233+
face.attr.gender,
234+
face.attr.age,
235+
face.attr.hair,
236+
face.attr.facial_hair,
237+
face.attr.makeup,
238+
face.attr.emotion,
239+
face.attr.occlusion,
240+
face.attr.exposure,
241+
face.attr.head_pose,
242+
face.attr.accessories
253243
)
254244
dc.DrawLabel(label, label_rect, wx.ALIGN_LEFT | wx.ALIGN_TOP)
255245

0 commit comments

Comments
 (0)