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
« 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
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.
20 @param app application, if None, creates one
21 @param params parameters sent to @see cl MachineLearningPost
22 @return app
24 .. exref::
25 :title: Query a REST API with features
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:
31 ::
33 start_mlrestapi --name=dummy
35 And then query it with:
37 ::
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())
46 It should return:
48 ::
50 {'Y': [[0.4994216179, 0.4514893599, 0.0490890222]]}
51 """
52 from sklearn import datasets
53 from sklearn.linear_model import LogisticRegression
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)
61 if app is None:
62 app = falcon.API()
64 route = MachineLearningPost(
65 lambda clf=clf: clf, LogisticRegression.predict_proba, ccall='multi', **params)
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")
73 app.add_route('/', route)
74 return app
77def _distance_img(img1, img2, arr1=None):
78 """
79 Computes the distance between two images.
80 The function uses :epkg:`Pillow`.
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
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')
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
106def _distance_img_b64(img1, img2, arr1=None):
107 """
108 Calls @see fn _distance_img on an image encoded with :epkg:`*pyf:base64`.
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)
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*.
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
136 .. exref::
137 :title: Query a REST API with an image
139 This example shows how to query a REST API
140 by sending an image.
141 You can start it by running:
143 ::
145 start_mlrestapi --name=dummyimg
147 And then query it with:
149 ::
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())
161 It should return something like:
163 ::
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')
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
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`.
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
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>`_.
215 @param app application, if None, creates one
216 @param params parameters sent to @see cl MachineLearningPost
217 @return app
219 .. exref::
220 :title: Query a REST API with an image and get neighbors
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:
227 ::
229 start_mlrestapi --name=dummyknn
231 And then query it with:
233 ::
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())
242 It should return:
244 ::
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)
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
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
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).
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
283 You can start it by running:
285 ::
287 start_mlrestapi --name=dummyknnimg
289 And then query it with:
291 ::
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())
303 It should return:
305 ::
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')
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
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
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.
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
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
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)
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)])
365 route = MachineLearningPost(
366 lambda clf=clf: clf, LogisticRegression.predict_proba, ccall='multi', **params)
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")
374 app.add_route('/', route)
375 return app
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.
385 @param app application, if None, creates one
386 @param params parameters sent to @see cl MLStoragePost
387 @return app
388 """
390 if app is None:
391 app = falcon.API()
393 route = MLStoragePost(**params)
395 app.add_route('/', route)
396 return app