Coverage for src/botadi/mokadi/mokadi_action_emotion.py: 34%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

96 statements  

1# -*- coding: utf-8 -*- 

2""" 

3@file 

4@brief Defines an action for Mokadi. 

5""" 

6import datetime 

7import os 

8from PIL import Image, ImageDraw 

9from .mokadi_action import MokadiAction 

10from .mokadi_info import MokadiInfo 

11from .mokadi_exceptions import MokadiException 

12from .cognitive_services_helper import call_api_emotions 

13from .mokadi_picture import take_picture 

14 

15 

16class MokadiActionEmotion(MokadiAction): 

17 """ 

18 Action. Emotion. Takes a picture and analyses it. 

19 """ 

20 

21 def __init__(self, subkey, folder, fLOG=None): 

22 """ 

23 Constructor. 

24 

25 @param subkey subscription key 

26 @param folder where to save the picture (must exist) 

27 @param fLOG logging function 

28 """ 

29 MokadiAction.__init__(self, fLOG=fLOG) 

30 self._subkey = subkey 

31 self._folder = folder 

32 

33 if not os.path.exists(folder): 

34 raise FileNotFoundError(folder) 

35 

36 def can_do(self, interpreted, message): 

37 """ 

38 Tells if the class can process the message. 

39 

40 @param interpreted interpreted message 

41 @param message message 

42 @return true if the class can process the message 

43 """ 

44 if len(interpreted) < 2: 

45 return False 

46 for word in interpreted: 

47 if word[1] == ":emotion:": 

48 return True 

49 return False 

50 

51 def get_picture_name(self): 

52 """ 

53 return a picture name 

54 """ 

55 dt = datetime.datetime.now() 

56 name = "camera-%04d-%02d-%02dT%02d-%02d-%02d" % (dt.year, dt.month, dt.day, 

57 dt.hour, dt.minute, dt.second) 

58 final = os.path.join(self._folder, name + ".png") 

59 i = 0 

60 while os.path.exists(final): 

61 i += 1 

62 final = os.path.join(self._folder, "%s.%d.png" % (name, i)) 

63 return final 

64 

65 def process_interpreted_message(self, interpretation, message): 

66 """ 

67 Processes the interpreted message. 

68 

69 @param interpretation interpretation 

70 @param message original message 

71 @return iterator on Info 

72 """ 

73 filename = self.get_picture_name() 

74 self.fLOG( 

75 "[MokadiActionEmotion.process_interpreted_message] create ", filename) 

76 take_picture(filename) 

77 if not os.path.exists(filename): 

78 yield MokadiInfo("error", "", "Aucune photo n'a pas pu être prise. Je ne peux pas en dire plus.") 

79 done = False 

80 res = call_api_emotions(self._subkey, filename) 

81 self.fLOG("[MokadiActionEmotion.process_interpreted_message] ", res) 

82 if len(res) == 0 or res.get('statusCode', 200) == 404: 

83 yield MokadiInfo("error", "", "Aucun résultat. Veuillez recommencer.") 

84 done = True 

85 elif "error" in res: 

86 yield MokadiInfo("error", "", res.get("message", "no message")) 

87 done = True 

88 else: 

89 img = Image.open(filename) 

90 draw = ImageDraw.Draw(img) 

91 new_filename = os.path.splitext(filename)[0] + ".emotion.png" 

92 # [{'faceRectangle': {'height': 198, 'left': 268, 'top': 191, 'width': 198}, 

93 done = True 

94 if len(res) > 1: 

95 yield MokadiInfo("ok", "Vous êtes plusieurs...") 

96 done = False 

97 

98 for i, el in enumerate(res): 

99 score, emotion = self.analyse_emotion(el["scores"]) 

100 rect = el["faceRectangle"] 

101 self.fLOG( 

102 "[MokadiActionEmotion.process_interpreted_message] ", el) 

103 draw.rectangle([rect["left"], rect["top"], rect["left"] + rect["width"], rect["top"] + rect["height"]], 

104 outline=(255, 255, 0)) 

105 if i == 0: 

106 numero = "Le premier" 

107 else: 

108 numero = "Le suivant" 

109 if score == 0: 

110 yield MokadiInfo("ok", numero + " " + "Je ne sais pas.") 

111 elif score < 0.5: 

112 yield MokadiInfo("ok", "Dur dur. Dans le doute, " + numero + " " + emotion) 

113 elif score < 0.8: 

114 yield MokadiInfo("ok", "J'hésite. " + numero + " " + emotion) 

115 else: 

116 yield MokadiInfo("ok", numero + " " + emotion) 

117 

118 else: 

119 for el in res: 

120 score, emotion = self.analyse_emotion(el["scores"]) 

121 rect = el["faceRectangle"] 

122 self.fLOG( 

123 "[MokadiActionEmotion.process_interpreted_message] ", el) 

124 draw.rectangle([rect["left"], rect["top"], rect["left"] + rect["width"], rect["top"] + rect["height"]], 

125 outline=(255, 255, 0)) 

126 if score == 0: 

127 yield MokadiInfo("ok", "Je ne sais pas.") 

128 elif score < 0.5: 

129 yield MokadiInfo("ok", "Dur dur. Dans le doute, tu " + emotion) 

130 elif score < 0.8: 

131 yield MokadiInfo("ok", "J'hésite. Tu " + emotion) 

132 else: 

133 yield MokadiInfo("ok", "Tu " + emotion) 

134 

135 img.save(new_filename) 

136 yield MokadiInfo("ok", image=new_filename) 

137 

138 if not done: 

139 raise MokadiException( 

140 "Unable to interpret '{0}'\n{1}".format(interpretation, message)) 

141 

142 def analyse_emotion(self, scores): 

143 """ 

144 Returns the emotion as text. 

145 

146 @param scores dictionary 

147 @return string 

148 

149 Example for *scores*: 

150 

151 :: 

152 

153 'scores': {'anger': 0.000243422386, 'contempt': 0.00207486819, 

154 'disgust': 2.390567e-05, 'fear': 2.512976e-07, 

155 'happiness': 3.5321933e-05, 'neutral': 0.9925101, 

156 'sadness': 0.00510106329, 'surprise': 1.10992869e-05}}] 

157 

158 """ 

159 traduction = {'anger': 'est en colère', 

160 'contempt': 'est content', 

161 'disgust': "est dégoûté", 

162 'fear': "a peur", 

163 'happiness': 'est heureux', 

164 'neutral': 'est difficile à lire', 

165 'sadness': 'est triste', 

166 'surprise': 'est surpris'} 

167 strong = [traduction[k] for k, v in scores.items() if v > 0.8] 

168 weak = [traduction[k] for k, v in scores.items() if v > 0.5] 

169 if len(strong) > 0: 

170 return 0.8, " et ".join(strong) 

171 elif len(weak) > 0: 

172 return 0.5, " et ".join(weak) 

173 else: 

174 tri = [(v, k) for k, v in scores.items()] 

175 ans = max(tri) 

176 return ans[0], traduction[ans[1]]