Coverage for src/lightmlrestapi/testing/dummy_applications.py: 92%

127 statements  

« prev     ^ index     » next       coverage.py v6.4.1, created at 2022-06-06 07:16 +0200

1""" 

2@file 

3@brief Machine Learning Post request 

4""" 

5import os 

6import falcon 

7import numpy 

8from .data import get_wiki_img 

9from ..mlapp import MachineLearningPost, AuthMiddleware, MLStoragePost 

10from ..args import base642image, image2array, encrypt_password 

11 

12 

13def dummy_application(app=None, **params): 

14 """ 

15 Defines a dummy application using this API. 

16 It returns a score produced by a model trained 

17 on `Iris datasets <http://scikit-learn.org/stable/auto_examples/datasets/plot_iris_dataset.html>`_ 

18 and two features. 

19 

20 @param app application, if None, creates one 

21 @param params parameters sent to @see cl MachineLearningPost 

22 @return app 

23 

24 .. exref:: 

25 :title: Query a REST API with features 

26 

27 This example shows how to query a :epkg:`REST API` 

28 by sending a vector of features. 

29 You can start it by running: 

30 

31 :: 

32 

33 start_mlrestapi --name=dummy 

34 

35 And then query it with: 

36 

37 :: 

38 

39 import requests 

40 import ujson 

41 features = ujson.dumps({'X': [0.1, 0.2]}) 

42 r = requests.post('http://127.0.0.1:8081', data=features) 

43 print(r) 

44 print(r.json()) 

45 

46 It should return: 

47 

48 :: 

49 

50 {'Y': [[0.4994216179, 0.4514893599, 0.0490890222]]} 

51 """ 

52 from sklearn import datasets 

53 from sklearn.linear_model import LogisticRegression 

54 

55 iris = datasets.load_iris() 

56 X = iris.data[:, :2] # we only take the first two features. 

57 y = iris.target 

58 clf = LogisticRegression() 

59 clf.fit(X, y) 

60 

61 if app is None: 

62 app = falcon.API() 

63 

64 route = MachineLearningPost( 

65 lambda clf=clf: clf, LogisticRegression.predict_proba, ccall='multi', **params) 

66 

67 # Let's check it works. 

68 one = clf.predict_proba(X[:1]) 

69 two = route.check_single(X[0]) 

70 if type(one) != type(two): # pylint: disable=C0123 

71 raise RuntimeError("Type mismath") 

72 

73 app.add_route('/', route) 

74 return app 

75 

76 

77def _distance_img(img1, img2, arr1=None): 

78 """ 

79 Computes the distance between two images. 

80 The function uses :epkg:`Pillow`. 

81 

82 @param img1 reference :epkg:`PIL:Image.Image` 

83 @param img2 new image :epkg:`PIL:Image.Image` 

84 @param arr1 img1 as an array if available (to avoid converting 

85 the same image multiple times) 

86 @return distance (in [0, 1]) or list of distances 

87 """ 

88 arr1 = image2array(img1) if arr1 is None else arr1 

89 

90 if isinstance(img2, list): 

91 return [_distance_img(img1, im2, arr1) for im2 in img2] 

92 else: 

93 im2 = img2 

94 if img1.size != im2.size: 

95 im2 = im2.resize(img1.size) 

96 if im2.mode != 'RGB': 

97 im2 = im2.convert('RGB') 

98 

99 arr2 = image2array(im2) 

100 # raise Exception("THIS {0}-{1}-{2}".format(im2.size, img1.size, arr1 is None)) 

101 diff = arr1.ravel() - arr2.ravel() 

102 total = numpy.abs(diff) 

103 return total.sum() / float(len(total)) / 255 

104 

105 

106def _distance_img_b64(img1, img2, arr1=None): 

107 """ 

108 Calls @see fn _distance_img on an image encoded with :epkg:`*pyf:base64`. 

109 

110 @param img1 reference :epkg:`PIL:Image.Image` 

111 @param img2 new image or list of images encoded with :epkg:`*pyf:base64` 

112 @param arr1 img1 as an array if available (to avoid converting 

113 the same image multiple times) 

114 @return distance (in [0, 1]) or list of distances 

115 """ 

116 if isinstance(img2, list): 

117 img2 = [base642image(img) for img in img2] 

118 else: 

119 img2 = base642image(img2) 

120 return _distance_img(img1, img2, arr1) 

121 

122 

123def dummy_application_image(app=None, options=None, **params): 

124 """ 

125 Defines a dummy application using this API 

126 and processing one image. The API ingests 

127 an image, resizes it to 224x224 and returns 

128 a distance to an original image from 

129 subfolder *data*. 

130 

131 @param app application, if None, creates one 

132 @param options if not empty, path to an image 

133 @param params parameters sent to @see cl MachineLearningPost 

134 @return app 

135 

136 .. exref:: 

137 :title: Query a REST API with an image 

138 

139 This example shows how to query a REST API 

140 by sending an image. 

141 You can start it by running: 

142 

143 :: 

144 

145 start_mlrestapi --name=dummyimg 

146 

147 And then query it with: 

148 

149 :: 

150 

151 import requests 

152 import ujson 

153 from lightmlrestapi.args import image2base64 

154 img = "path_to_image" 

155 b64 = image2base64(img)[1] 

156 features = ujson.dumps({'X': b64}, reject_bytes=False) 

157 r = requests.post('http://127.0.0.1:8081', data=features) 

158 print(r) 

159 print(r.json()) 

160 

161 It should return something like: 

162 

163 :: 

164 

165 {'distance': [0.21]} 

166 """ 

167 if options is None or not isinstance(options, str) or len(options) == 0: 

168 options = get_wiki_img() 

169 if not os.path.exists(options): 

170 raise FileNotFoundError("Unable to find image '{0}'.".format(options)) 

171 from PIL import Image 

172 img_base = Image.open(get_wiki_img()) 

173 if img_base.size != (224, 224): 

174 img_base = img_base.resize((224, 224)) 

175 if img_base.mode != 'RGB': 

176 img_base = img_base.convert('RGB') 

177 

178 if app is None: 

179 app = falcon.API() 

180 app.add_route( 

181 '/', MachineLearningPost(lambda: None, lambda model, X: _distance_img_b64(img_base, X), **params)) 

182 return app 

183 

184 

185def dummy_application_fct(restapi_load, restapi_predict, users=None, algo='sha224', app=None, **params): 

186 """ 

187 Defines an application as defined in the tutorial 

188 :ref:`l-dummy-function-application`. 

189 

190 @param restapi_load function to load a model 

191 @param restapi_predict predict function 

192 @param params parameters sent to @see cl MachineLearningPost 

193 @param users restrict to authenticated users, 

194 @see fn load_passwords 

195 @param algo algorithm used to encrypt password 

196 @param app application, if None, creates one 

197 """ 

198 if app is None: 

199 if users: 

200 middleware = [AuthMiddleware(users, algo=algo)] 

201 app = falcon.API(middleware=middleware) 

202 else: 

203 app = falcon.API() 

204 app.add_route( 

205 '/', MachineLearningPost(restapi_load, restapi_predict, **params)) 

206 return app 

207 

208 

209def dummy_application_neighbors(app=None, **params): 

210 """ 

211 Defines a dummy application using this API. 

212 It returns a list of neighbors with a score 

213 on `Iris datasets <http://scikit-learn.org/stable/auto_examples/datasets/plot_iris_dataset.html>`_. 

214 

215 @param app application, if None, creates one 

216 @param params parameters sent to @see cl MachineLearningPost 

217 @return app 

218 

219 .. exref:: 

220 :title: Query a REST API with an image and get neighbors 

221 

222 A previous example shows how to send an image, 

223 this one illustrates a result which is a classifier result 

224 neither a regressor one but neighbors. 

225 You can start it by running: 

226 

227 :: 

228 

229 start_mlrestapi --name=dummyknn 

230 

231 And then query it with: 

232 

233 :: 

234 

235 import requests 

236 import ujson 

237 features = ujson.dumps({'X': [0.1, 0.2]}) 

238 r = requests.post('http://127.0.0.1:8081', data=features) 

239 print(r) 

240 print(r.json()) 

241 

242 It should return: 

243 

244 :: 

245 

246 {'Y': [[[41, 4.8754486973], [13, 5.0477717856], [8, 5.0774009099], [38, 5.1312766443], [60, 5.2201532545]]]} 

247 """ 

248 from sklearn import datasets 

249 from sklearn.neighbors import NearestNeighbors 

250 iris = datasets.load_iris() 

251 X = iris.data[:, :2] # we only take the first two features. 

252 knn = NearestNeighbors(n_neighbors=5) 

253 knn.fit(X) 

254 

255 def to_serie(knn, x): 

256 "converts into series" 

257 dist, ind = knn.kneighbors(x) 

258 res = [] 

259 for i in range(0, len(x)): 

260 res.append([(int(j), float(s)) 

261 for j, s in zip(ind[i, :], dist[i, :])]) 

262 return res 

263 

264 if app is None: 

265 app = falcon.API() 

266 app.add_route( 

267 '/', MachineLearningPost(lambda: knn, lambda knn, x: to_serie(knn, x), 

268 ccall='multi', **params)) 

269 return app 

270 

271 

272def dummy_application_neighbors_image(app=None, options=None, **params): 

273 """ 

274 Defines a dummy application using this API. 

275 It returns a list of one neighbor for an image 

276 and metadata (random). 

277 

278 @param app application, if None, creates one 

279 @param options if not empty, path to an image 

280 @param params parameters sent to @see cl MachineLearningPost 

281 @return app 

282 

283 You can start it by running: 

284 

285 :: 

286 

287 start_mlrestapi --name=dummyknnimg 

288 

289 And then query it with: 

290 

291 :: 

292 

293 import requests 

294 import ujson 

295 from lightmlrestapi.args import image2base64 

296 img = "path_to_image" 

297 b64 = image2base64(img)[1] 

298 features = ujson.dumps({'X': b64}, reject_bytes=False) 

299 r = requests.post('http://127.0.0.1:8081', data=features) 

300 print(r) 

301 print(r.json()) 

302 

303 It should return: 

304 

305 :: 

306 

307 {'Y': [[[41, 4.8754486973, {'name': 'wiki.png', description='something'}]]]} 

308 """ 

309 if options is None or not isinstance(options, str) or len(options) == 0: 

310 options = get_wiki_img() 

311 if not os.path.exists(options): 

312 raise FileNotFoundError("Unable to find image '{0}'.".format(options)) 

313 from PIL import Image 

314 img_base = Image.open(get_wiki_img()) 

315 if img_base.size != (224, 224): 

316 img_base = img_base.resize((224, 224)) 

317 if img_base.mode != 'RGB': 

318 img_base = img_base.convert('RGB') 

319 

320 def mypredict(img_base, X): 

321 "overwrites predict" 

322 res = _distance_img_b64(img_base, X) 

323 final = [] 

324 for r, x in zip(res, X): 

325 final.append([(0, r, dict(name=os.path.split(options)[1], 

326 description="image from wikipedia: {0}".format(len(x))))]) 

327 return final 

328 

329 if app is None: 

330 app = falcon.API() 

331 app.add_route( 

332 '/', MachineLearningPost(lambda: img_base, mypredict, ccall='multi', **params)) 

333 return app 

334 

335 

336def dummy_application_auth(app=None, algo="sha224", **params): 

337 """ 

338 Defines a dummy application using this API 

339 including authentification. 

340 It returns a score produced by a model trained 

341 on `Iris datasets <http://scikit-learn.org/stable/auto_examples/datasets/plot_iris_dataset.html>`_ 

342 and two features. 

343 

344 @param app application, if None, creates one 

345 @param algo algorithm used to encrypt the passwords 

346 @param params parameters sent to @see cl MachineLearningPost 

347 @return app 

348 

349 It adds one users with the login 'me' and the password 'dummy'. 

350 """ 

351 from sklearn import datasets 

352 from sklearn.linear_model import LogisticRegression 

353 

354 iris = datasets.load_iris() 

355 X = iris.data[:, :2] # we only take the first two features. 

356 y = iris.target 

357 clf = LogisticRegression() 

358 clf.fit(X, y) 

359 

360 if app is None: 

361 zoo = encrypt_password("dummy", algo=algo) 

362 data = "login,pwd\nme,{0}".format(zoo) 

363 app = falcon.API(middleware=[AuthMiddleware(data, algo=algo)]) 

364 

365 route = MachineLearningPost( 

366 lambda clf=clf: clf, LogisticRegression.predict_proba, ccall='multi', **params) 

367 

368 # Let's check it works. 

369 one = clf.predict_proba(X[:1]) 

370 two = route.check_single(X[0]) 

371 if type(one) != type(two): # pylint: disable=C0123 

372 raise RuntimeError("Type mismath") 

373 

374 app.add_route('/', route) 

375 return app 

376 

377 

378def dummy_mlstorage(app=None, **params): 

379 """ 

380 Defines a dummy application using this API. 

381 It stores a model and it returns a score produced by a model trained 

382 on `Iris datasets <http://scikit-learn.org/stable/auto_examples/datasets/plot_iris_dataset.html>`_ 

383 and two features. 

384 

385 @param app application, if None, creates one 

386 @param params parameters sent to @see cl MLStoragePost 

387 @return app 

388 """ 

389 

390 if app is None: 

391 app = falcon.API() 

392 

393 route = MLStoragePost(**params) 

394 

395 app.add_route('/', route) 

396 return app