app.py 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977
  1. # copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. from flask import Flask, request, render_template, send_from_directory, jsonify, session, send_file
  15. from werkzeug.utils import secure_filename
  16. from flask_cors import CORS
  17. import argparse
  18. from os import path as osp
  19. import os
  20. import time
  21. import json
  22. import sys
  23. import multiprocessing as mp
  24. from . import workspace_pb2 as w
  25. from .utils import CustomEncoder, ShareData, is_pic, get_logger, TaskStatus, get_ip
  26. from paddlex_restful.restful.dataset.utils import get_encoding
  27. import numpy as np
  28. import pickle
  29. app = Flask(__name__)
  30. CORS(app, supports_credentials=True)
  31. SESSION_TYPE = 'filesystem'
  32. app.config.from_object(__name__)
  33. SD = ShareData()
  34. def init(dirname, logger):
  35. #初始化工作空间
  36. from .workspace import init_workspace
  37. from .system import get_system_info
  38. SD.workspace = w.Workspace(path=dirname)
  39. init_workspace(SD.workspace, dirname, logger)
  40. SD.workspace_dir = dirname
  41. get_system_info(SD.machine_info)
  42. @app.errorhandler(Exception)
  43. def handle_exception(e):
  44. ret = {"status": -1, 'message': repr(e)}
  45. return ret
  46. @app.route('/workspace', methods=['GET', 'PUT'])
  47. def workspace():
  48. """
  49. methods=='GET':获取工作目录中项目、数据集、任务的属性
  50. Args:
  51. struct(str):结构类型,可以是'dataset', 'project'或'task',
  52. id(str):结构类型对应的id
  53. attr_list(list):需要获取的属性的列表
  54. Return:
  55. attr(dict):key为属性,value为属性的值,
  56. status
  57. methods=='PUT':修改工作目录中项目、数据集、任务的属性
  58. Args:
  59. struct(str):结构类型,可以是'dataset', 'project'或'task',
  60. id(str):结构类型对应的id
  61. attr_dict(dict):key:需要修改的属性,value:需要修改属性的值
  62. Return:
  63. status
  64. """
  65. data = request.get_json()
  66. if data is None:
  67. data = request.args
  68. if request.method == 'GET':
  69. if data:
  70. from .workspace import get_attr
  71. ret = get_attr(data, SD.workspace)
  72. return ret
  73. return {'status': 1, 'dirname': SD.workspace_dir}
  74. if request.method == 'PUT':
  75. from .workspace import set_attr
  76. ret = set_attr(data, SD.workspace)
  77. return ret
  78. @app.route('/dataset', methods=['GET', 'POST', 'PUT', 'DELETE'])
  79. def dataset():
  80. """
  81. methods=='GET':获取所有数据集或者单个数据集的信息
  82. Args:
  83. did(str, optional):数据集id(可选),如果存在就返回数据集id对应数据集的信息
  84. Ruturn:
  85. status
  86. if 'did' in Args:
  87. id(str):数据集id,
  88. dataset_status(int):数据集状态(DatasetStatus)枚举变量的值
  89. message(str):数据集状态信息
  90. attr(dict):数据集属性
  91. else:
  92. datasets(list):所有数据集属性的列表
  93. methods=='POST':创建一个新的数据集
  94. Args:
  95. name(str):数据集名字
  96. desc(str):数据集描述
  97. dataset_type(str):数据集类型,可以是['classification', 'detection', 'segmentation','instance_segmentation','remote_segmentation']
  98. Return:
  99. did(str):数据集id
  100. status
  101. methods=='PUT':异步,向数据集导入数据,支持分类、检测、语义分割、实例分割、摇杆分割数据集类型
  102. Args:
  103. did(str):数据集id
  104. path(str):数据集路径
  105. Return:
  106. status
  107. methods=='DELETE':删除已有的某个数据集
  108. Args:
  109. did(str):数据集id
  110. Return:
  111. status
  112. """
  113. data = request.get_json()
  114. if data is None:
  115. data = request.args
  116. if request.method == 'GET':
  117. if 'did' in data:
  118. from .dataset.dataset import get_dataset_status
  119. ret = get_dataset_status(data, SD.workspace)
  120. return ret
  121. from .dataset.dataset import list_datasets
  122. ret = list_datasets(SD.workspace)
  123. return ret
  124. if request.method == 'POST':
  125. from .dataset.dataset import create_dataset
  126. ret = create_dataset(data, SD.workspace)
  127. return ret
  128. if request.method == 'PUT':
  129. from .dataset.dataset import import_dataset
  130. ret = import_dataset(data, SD.workspace, SD.monitored_processes,
  131. SD.load_demo_proc_dict)
  132. return ret
  133. if request.method == 'DELETE':
  134. from .dataset.dataset import delete_dataset
  135. ret = delete_dataset(data, SD.workspace)
  136. return ret
  137. @app.route('/dataset/details', methods=['GET'])
  138. def dataset_details():
  139. """
  140. methods=='GET':获取某个数据集的详细信息
  141. Args:
  142. did(str):数据集id
  143. Return:
  144. details(dict):数据集详细信息,
  145. status
  146. """
  147. data = request.get_json()
  148. if data is None:
  149. data = request.args
  150. if request.method == 'GET':
  151. from .dataset.dataset import get_dataset_details
  152. ret = get_dataset_details(data, SD.workspace)
  153. return ret
  154. @app.route('/dataset/split', methods=['PUT'])
  155. def dataset_split():
  156. """
  157. Args:
  158. did(str):数据集id
  159. val_split(float): 验证集比例
  160. test_split(float): 测试集比例
  161. Return:
  162. status
  163. """
  164. data = request.get_json()
  165. if request.method == 'PUT':
  166. from .dataset.dataset import split_dataset
  167. ret = split_dataset(data, SD.workspace)
  168. return ret
  169. @app.route('/dataset/image', methods=['GET'])
  170. def dataset_img_base64():
  171. """
  172. Args:
  173. GET: 获取图片base64数据,参数:'path' 图片绝对路径
  174. """
  175. data = request.get_json()
  176. if request.method == 'GET':
  177. from .dataset.dataset import img_base64
  178. ret = img_base64(data)
  179. return ret
  180. @app.route('/dataset/file', methods=['GET'])
  181. def get_image_file():
  182. """
  183. Args:
  184. GET: 获取文件数据,参数:'path' 文件绝对路径
  185. """
  186. data = request.get_json()
  187. if request.method == 'GET':
  188. ret = data['path']
  189. assert os.path.abspath(ret).startswith(
  190. os.path.abspath(SD.workspace_dir)
  191. ) and ".." not in ret, "Illegal path {}.".format(ret)
  192. return send_file(ret)
  193. @app.route('/dataset/npy', methods=['GET'])
  194. def get_npyfile():
  195. """
  196. Args:
  197. GET: 获取文件数据,参数:'path' npy文件绝对路径
  198. """
  199. data = request.get_json()
  200. if request.method == 'GET':
  201. npy = np.load(data['path'], allow_pickle=True).tolist()
  202. npy['gt_bbox'] = npy['gt_bbox'].tolist()
  203. return npy
  204. @app.route('/file', methods=['GET'])
  205. def get_file():
  206. """
  207. Args:
  208. path'(str):文件在服务端的路径
  209. Return:
  210. #数据为图片
  211. img_data(str): base64图片数据
  212. status
  213. #数据为xml文件
  214. ret:数据流
  215. #数据为log文件
  216. ret:json数据
  217. """
  218. data = request.get_json()
  219. if data is None:
  220. data = request.args
  221. if request.method == 'GET':
  222. path = data['path']
  223. if not os.path.exists(path):
  224. return {'status': -1}
  225. if is_pic(path):
  226. from .dataset.dataset import img_base64
  227. ret = img_base64(data, SD.workspace)
  228. return ret
  229. file_type = path[(path.rfind('.') + 1):]
  230. if file_type in ['xml', 'npy', 'log']:
  231. return send_file(path)
  232. else:
  233. pass
  234. @app.route('/project', methods=['GET', 'POST', 'DELETE'])
  235. def project():
  236. """
  237. methods=='GET':获取指定项目id的信息
  238. Args:
  239. 'id'(str, optional):项目id,可选,如果存在就返回项目id对应项目的信息
  240. Return:
  241. status,
  242. if 'id' in Args:
  243. attr(dict):项目属性
  244. else:
  245. projects(list):所有项目属性
  246. methods=='POST':创建一个项目
  247. Args:
  248. name(str): 项目名
  249. desc(str):项目描述
  250. project_type(str):项目类型
  251. Return:
  252. pid(str):项目id
  253. status
  254. methods=='DELETE':删除一个项目,以及项目相关的task
  255. Args:
  256. pid(str):项目id
  257. Return:
  258. status
  259. """
  260. data = request.get_json()
  261. if data is None:
  262. data = request.args
  263. if request.method == 'GET':
  264. from .project.project import list_projects
  265. from .project.project import get_project
  266. if 'id' in data:
  267. ret = get_project(data, SD.workspace)
  268. return ret
  269. ret = list_projects(SD.workspace)
  270. return ret
  271. if request.method == 'POST':
  272. from .project.project import create_project
  273. ret = create_project(data, SD.workspace)
  274. return ret
  275. if request.method == 'DELETE':
  276. from .project.project import delete_project
  277. ret = delete_project(data, SD.workspace)
  278. return ret
  279. @app.route('/project/task', methods=['GET', 'POST', 'DELETE'])
  280. def task():
  281. """
  282. methods=='GET':#获取某个任务的信息或者所有任务的信息
  283. Args:
  284. tid(str, optional):任务id,可选,若存在即返回id对应任务的信息
  285. resume(str, optional):获取是否可以恢复训练的状态,可选,需在存在tid的情况下才生效
  286. pid(str, optional):项目id,可选,若存在即返回该项目id下所有任务信息
  287. Return:
  288. status
  289. if 'tid' in Args:
  290. task_status(int):任务状态(TaskStatus)枚举变量的值
  291. message(str):任务状态信息
  292. type:任务类型包括{'classification', 'detection', 'segmentation', 'instance_segmentation'}
  293. resumable(bool):仅Args中存在resume时返回,任务训练是否可以恢复
  294. max_saved_epochs(int):仅Args中存在resume时返回,当前训练模型保存的最大epoch
  295. else:
  296. tasks(list):所有任务属性
  297. methods=='POST':#创建任务(训练或者裁剪)
  298. Args:
  299. pid(str):项目id
  300. train(dict):训练参数
  301. desc(str, optional):任务描述,可选
  302. parent_id(str, optional):可选,若存在即表示新建的任务为裁剪任务,parent_id的值为裁剪任务对应的训练任务id
  303. Return:
  304. tid(str):任务id
  305. status
  306. methods=='DELETE':#删除任务
  307. Args:
  308. tid(str):任务id
  309. Return:
  310. status
  311. """
  312. data = request.get_json()
  313. if data is None:
  314. data = request.args
  315. if request.method == 'GET':
  316. if data:
  317. if 'pid' not in data:
  318. from .project.task import get_task_status
  319. ret = get_task_status(data, SD.workspace)
  320. return ret
  321. from .project.task import list_tasks
  322. ret = list_tasks(data, SD.workspace)
  323. return ret
  324. if request.method == 'POST':
  325. from .project.task import create_task
  326. ret = create_task(data, SD.workspace)
  327. return ret
  328. if request.method == 'DELETE':
  329. from .project.task import delete_task
  330. ret = delete_task(data, SD.workspace)
  331. return ret
  332. @app.route('/project/task/params', methods=['GET', 'POST'])
  333. def task_params():
  334. """
  335. methods=='GET':#获取任务id对应的参数,或者获取项目默认参数
  336. Args:
  337. tid(str, optional):获取任务对应的参数
  338. pid(str,optional):获取项目对应的默认参数
  339. model_type(str,optional):pid存在下有效,对应项目下获取指定模型的默认参数
  340. gpu_list(list,optional):pid存在下有效,默认值为[0],使用指定的gpu并获取相应的默认参数
  341. Return:
  342. train(dict):训练或者裁剪的参数
  343. status
  344. methods=='POST':#设置任务参数,将前端用户设置训练参数dict保存在后端的pkl文件中
  345. Args:
  346. tid(str):任务id
  347. train(dict):训练参数
  348. Return:
  349. status
  350. """
  351. data = request.get_json()
  352. if data is None:
  353. data = request.args
  354. if request.method == 'GET':
  355. if 'tid' in data:
  356. from .project.task import get_task_params
  357. ret = get_task_params(data, SD.workspace)
  358. ret['train'] = CustomEncoder().encode(ret['train'])
  359. ret['train'] = json.loads(ret['train'])
  360. return ret
  361. if 'pid' in data:
  362. from .project.task import get_default_params
  363. ret = get_default_params(data, SD.workspace, SD.machine_info)
  364. return ret
  365. if request.method == 'POST':
  366. from .project.task import set_task_params
  367. ret = set_task_params(data, SD.workspace)
  368. return ret
  369. @app.route('/project/task/metrics', methods=['GET'])
  370. def task_metrics():
  371. """
  372. methods=='GET':#获取日志数据
  373. Args:
  374. tid(str):任务id
  375. type(str):可以获取日志的类型,[train,eval,sensitivities,prune],包括训练,评估,敏感度与模型裁剪率关系图,裁剪的日志
  376. Return:
  377. status
  378. if type == 'train':
  379. train_log(dict): 训练日志
  380. elif type == 'eval':
  381. eval_metrics(dict): 评估结果
  382. elif type == 'sensitivities':
  383. sensitivities_loss_img(dict): 敏感度与模型裁剪率关系图
  384. elif type == 'prune':
  385. prune_log(dict):裁剪日志
  386. """
  387. data = request.get_json()
  388. if data is None:
  389. data = request.args
  390. if request.method == 'GET':
  391. if data['type'] == 'train':
  392. from .project.task import get_train_metrics
  393. ret = get_train_metrics(data, SD.workspace)
  394. return ret
  395. if data['type'] == 'eval':
  396. from .project.task import get_eval_metrics
  397. ret = get_eval_metrics(data, SD.workspace)
  398. return ret
  399. if data['type'] == 'eval_all':
  400. from .project.task import get_eval_all_metrics
  401. ret = get_eval_all_metrics(data, SD.workspace)
  402. return ret
  403. if data['type'] == 'sensitivities':
  404. from .project.task import get_sensitivities_loss_img
  405. ret = get_sensitivities_loss_img(data, SD.workspace)
  406. return ret
  407. if data['type'] == 'prune':
  408. from .project.task import get_prune_metrics
  409. ret = get_prune_metrics(data, SD.workspace)
  410. return ret
  411. @app.route('/project/task/train', methods=['POST', 'PUT'])
  412. def task_train():
  413. """
  414. methods=='POST':#异步,启动训练或者裁剪任务
  415. Args:
  416. tid(str):任务id
  417. eval_metric_loss(int,optional):可选,裁剪任务时可用,裁剪任务所需的评估loss
  418. Return:
  419. status
  420. methods=='PUT':#改变任务训练的状态,即终止训练或者恢复训练
  421. Args:
  422. tid(str):任务id
  423. act(str):[stop,resume]暂停或者恢复
  424. epoch(int):(resume下可以设置)恢复训练的起始轮数
  425. Return:
  426. status
  427. """
  428. data = request.get_json()
  429. if request.method == 'POST':
  430. from .project.task import start_train_task
  431. ret = start_train_task(data, SD.workspace, SD.monitored_processes)
  432. return ret
  433. if request.method == 'PUT':
  434. if data['act'] == 'resume':
  435. from .project.task import resume_train_task
  436. ret = resume_train_task(data, SD.workspace, SD.monitored_processes)
  437. return ret
  438. if data['act'] == 'stop':
  439. from .project.task import stop_train_task
  440. ret = stop_train_task(data, SD.workspace)
  441. return ret
  442. @app.route('/project/task/train/file', methods=['GET'])
  443. def log_file():
  444. data = request.get_json()
  445. if request.method == 'GET':
  446. path = data['path']
  447. if not os.path.exists(path):
  448. return {'status': -1}
  449. logs = open(path, encoding='utf-8').readlines()
  450. if len(logs) < 50:
  451. return {'status': 1, 'log': logs}
  452. else:
  453. logs = logs[-50:]
  454. return {'status': 1, 'log': logs}
  455. @app.route('/project/task/prune', methods=['GET', 'POST', 'PUT'])
  456. def task_prune():
  457. """
  458. methods=='GET':#获取裁剪任务的状态
  459. Args:
  460. tid(str):任务id
  461. Return:
  462. prune_status(int): 裁剪任务状态(PruneStatus)枚举变量的值
  463. status
  464. methods=='POST':#异步,创建一个裁剪分析,对于启动裁剪任务前需要先启动裁剪分析
  465. Args:
  466. tid(str):任务id
  467. Return:
  468. status
  469. methods=='PUT':#改变裁剪分析任务的状态
  470. Args:
  471. tid(str):任务id
  472. act(str):[stop],目前仅支持停止一个裁剪分析任务
  473. Return
  474. status
  475. """
  476. data = request.get_json()
  477. if data is None:
  478. data = request.args
  479. if request.method == 'GET':
  480. from .project.task import get_prune_status
  481. ret = get_prune_status(data, SD.workspace)
  482. return ret
  483. if request.method == 'POST':
  484. from .project.task import start_prune_analysis
  485. ret = start_prune_analysis(data, SD.workspace, SD.monitored_processes)
  486. return ret
  487. if request.method == 'PUT':
  488. if data['act'] == 'stop':
  489. from .project.task import stop_prune_analysis
  490. ret = stop_prune_analysis(data, SD.workspace)
  491. return ret
  492. @app.route('/project/task/evaluate', methods=['GET', 'POST'])
  493. def task_evaluate():
  494. '''
  495. methods=='GET':#获取模型评估的结果
  496. Args:
  497. tid(str):任务id
  498. Return:
  499. evaluate_status(int): 任务状态(TaskStatus)枚举变量的值
  500. message(str):描述评估任务的信息
  501. result(dict):如果评估成功,返回评估结果的dict,否则为None
  502. status
  503. methods=='POST':#异步,创建一个评估任务
  504. Args:
  505. tid(str):任务id
  506. epoch(int,optional):需要评估的epoch,如果为None则会评估训练时指标最好的epoch
  507. topk(int,optional):分类任务topk指标,如果为None默认输入为5
  508. score_thresh(float):检测任务类别的score threshhold值,如果为None默认输入为0.5
  509. overlap_thresh(float):实例分割任务IOU threshhold值,如果为None默认输入为0.3
  510. Return:
  511. status
  512. '''
  513. data = request.get_json()
  514. if data is None:
  515. data = request.args
  516. if request.method == 'GET':
  517. from .project.task import get_evaluate_result
  518. ret = get_evaluate_result(data, SD.workspace)
  519. if ret['evaluate_status'] == TaskStatus.XEVALUATED and ret[
  520. 'result'] is not None:
  521. if 'Confusion_Matrix' in ret['result']:
  522. ret['result']['Confusion_Matrix'] = ret['result'][
  523. 'Confusion_Matrix']
  524. ret['result'] = CustomEncoder().encode(ret['result'])
  525. ret['result'] = json.loads(ret['result'])
  526. ret['evaluate_status'] = ret['evaluate_status'].value
  527. return ret
  528. if request.method == 'POST':
  529. from .project.task import evaluate_model
  530. ret = evaluate_model(data, SD.workspace, SD.monitored_processes)
  531. return ret
  532. @app.route('/project/task/evaluate/file', methods=['GET'])
  533. def task_evaluate_file():
  534. data = request.get_json()
  535. if request.method == 'GET':
  536. if 'path' in data:
  537. ret = data['path']
  538. assert os.path.abspath(ret).startswith(
  539. os.path.abspath(SD.workspace_dir)
  540. ) and ".." not in ret, "Illegal path {}.".format(ret)
  541. return pickle.load(open(os.path.abspath(ret), 'rb'))
  542. else:
  543. from .project.task import get_evaluate_result
  544. from .project.task import import_evaluate_excel
  545. ret = get_evaluate_result(data, SD.workspace)
  546. if ret['evaluate_status'] == TaskStatus.XEVALUATED and ret[
  547. 'result'] is not None:
  548. result = ret['result']
  549. excel_ret = dict()
  550. excel_ret = import_evaluate_excel(data, result, SD.workspace)
  551. return excel_ret
  552. else:
  553. excel_ret = dict()
  554. excel_ret['path'] = None
  555. excel_ret['status'] = -1
  556. excel_ret['message'] = "评估尚未完成或评估失败"
  557. return excel_ret
  558. @app.route('/project/task/predict', methods=['GET', 'POST', 'PUT'])
  559. def task_predict():
  560. '''
  561. methods=='GET':#获取预测状态
  562. Args:
  563. tid(str):任务id
  564. Return:
  565. predict_status(int): 预测任务状态(PredictStatus)枚举变量的值
  566. message(str): 预测信息
  567. status
  568. methods=='POST':#创建预测任务,目前仅支持单张图片的预测
  569. Args:
  570. tid(str):任务id
  571. image_data(str):base64编码的image数据
  572. score_thresh(float,optional):可选,检测任务时有效,检测类别的score threashold值默认是0.5
  573. epoch(int,float,optional):可选,选择需要做预测的ephoch,默认为评估指标最好的那一个epoch
  574. Return:
  575. path(str):服务器上保存预测结果图片的路径
  576. status
  577. '''
  578. data = request.get_json()
  579. if data is None:
  580. data = request.args
  581. if request.method == 'GET':
  582. from .project.task import get_predict_status
  583. ret = get_predict_status(data, SD.workspace)
  584. return ret
  585. if request.method == 'POST':
  586. from .project.task import predict_test_pics
  587. ret = predict_test_pics(data, SD.workspace, SD.monitored_processes)
  588. if 'img_list' in data:
  589. del ret['path']
  590. return ret
  591. return ret
  592. if request.method == 'PUT':
  593. from .project.task import stop_predict_task
  594. ret = stop_predict_task(data, SD.workspace)
  595. return ret
  596. @app.route('/project/task/export', methods=['GET', 'POST', 'PUT'])
  597. def task_export():
  598. '''
  599. methods=='GET':#获取导出模型的状态
  600. Args:
  601. tid(str):任务id
  602. quant(str,optional)可选,[log,result],导出量模型导出状态,若值为log则返回量化的日志;若值为result则返回量化的结果
  603. Return:
  604. status
  605. if quant == 'log':
  606. quant_log(dict):量化日志
  607. if quant == 'result'
  608. quant_result(dict):量化结果
  609. if quant not in Args:
  610. export_status(int):模型导出状态(PredictStatus)枚举变量的值
  611. message(str):模型导出提示信息
  612. methods=='POST':#导出inference模型或者导出lite模型
  613. Args:
  614. tid(str):任务id
  615. type(str):保存模型的类别[infer,lite],支持inference模型导出和lite的模型导出
  616. save_dir(str):保存模型的路径
  617. epoch(str,optional)可选,指定导出的epoch数默认为评估效果最好的epoch
  618. quant(bool,optional)可选,type为infer有效,是否导出量化后的模型,默认为False
  619. model_path(str,optional)可选,type为lite时有效,inference模型的地址
  620. Return:
  621. status
  622. if type == 'infer':
  623. save_dir:模型保存路径
  624. if type == 'lite':
  625. message:模型保存信息
  626. methods=='PUT':#停止导出模型
  627. Args:
  628. tid(str):任务id
  629. Return:
  630. export_status(int):模型导出状态(PredictStatus)枚举变量的值
  631. message(str):停止模型导出提示信息
  632. status
  633. '''
  634. data = request.get_json()
  635. if data is None:
  636. data = request.args
  637. if request.method == 'GET':
  638. if 'quant' in data:
  639. if data['quant'] == 'log':
  640. from .project.task import get_quant_progress
  641. ret = get_quant_progress(data, SD.workspace)
  642. return ret
  643. if data['quant'] == 'result':
  644. from .project.task import get_quant_result
  645. ret = get_quant_result(data, SD.workspace)
  646. return ret
  647. from .project.task import get_export_status
  648. ret = get_export_status(data, SD.workspace)
  649. ret['export_status'] = ret['export_status'].value
  650. return ret
  651. if request.method == 'POST':
  652. if data['type'] == 'infer':
  653. from .project.task import export_infer_model
  654. ret = export_infer_model(data, SD.workspace,
  655. SD.monitored_processes)
  656. return ret
  657. if data['type'] == 'lite':
  658. from .project.task import export_lite_model
  659. ret = export_lite_model(data, SD.workspace)
  660. return ret
  661. if request.method == 'PUT':
  662. from .project.task import stop_export_task
  663. stop_export_task(data, SD.workspace)
  664. return ret
  665. @app.route('/project/task/vdl', methods=['GET'])
  666. def task_vdl():
  667. '''
  668. methods=='GET':#打开某个任务的可视化分析工具(VisualDL)
  669. Args:
  670. tid(str):任务id
  671. Return:
  672. url(str):vdl地址
  673. status
  674. '''
  675. data = request.get_json()
  676. if data is None:
  677. data = request.args
  678. if request.method == 'GET':
  679. from .project.task import open_vdl
  680. ret = open_vdl(data, SD.workspace, SD.current_port,
  681. SD.monitored_processes, SD.running_boards)
  682. return ret
  683. @app.route('/system', methods=['GET', 'DELETE'])
  684. def system():
  685. '''
  686. methods=='GET':#获取系统GPU、CPU信息
  687. Args:
  688. type(str):[machine_info,gpu_memory_size]选择需要获取的系统信息
  689. Return:
  690. status
  691. if type=='machine_info'
  692. info(dict):服务端信息
  693. if type=='gpu_memory_size'
  694. gpu_mem_infos(list):GPU内存信息
  695. '''
  696. data = request.get_json()
  697. if data is None:
  698. data = request.args
  699. if request.method == 'GET':
  700. if data['type'] == 'machine_info':
  701. '''if 'path' not in data:
  702. data['path'] = None
  703. from .system import get_machine_info
  704. ret = get_machine_info(data, SD.machine_info)'''
  705. from .system import get_system_info
  706. ret = get_system_info(SD.machine_info)
  707. return ret
  708. if data['type'] == 'gpu_memory_size':
  709. #from .system import get_gpu_memory_size
  710. from .system import get_gpu_memory_info
  711. ret = get_gpu_memory_info(SD.machine_info)
  712. return ret
  713. if request.method == 'DELETE':
  714. from .system import exit_system
  715. ret = exit_system(SD.monitored_processes)
  716. return ret
  717. @app.route('/demo', methods=['GET', 'POST', 'PUT'])
  718. def demo():
  719. '''
  720. methods=='GET':#获取demo下载进度
  721. Args:
  722. prj_type(int):项目类型ProjectType枚举变量的int值
  723. Return:
  724. status
  725. attr(dict):demo下载信息
  726. methods=='POST':#下载或创建demo工程
  727. Args:
  728. type(str):{download,load}下载或者创建样例
  729. prj_type(int):项目类型ProjectType枚举变量的int值
  730. Return:
  731. status
  732. if type=='load':
  733. did:数据集id
  734. pid:项目id
  735. methods=='PUT':#停止下载或创建demo工程
  736. Args:
  737. prj_type(int):项目类型ProjectType枚举变量的int值
  738. Return:
  739. status
  740. '''
  741. data = request.get_json()
  742. if data is None:
  743. data = request.args
  744. if request.method == 'GET':
  745. from .demo import get_download_demo_progress
  746. ret = get_download_demo_progress(data, SD.workspace)
  747. return ret
  748. if request.method == 'POST':
  749. if data['type'] == 'download':
  750. from .demo import download_demo_dataset
  751. ret = download_demo_dataset(data, SD.workspace,
  752. SD.load_demo_proc_dict)
  753. return ret
  754. if data['type'] == 'load':
  755. from .demo import load_demo_project
  756. ret = load_demo_project(data, SD.workspace, SD.monitored_processes,
  757. SD.load_demo_proj_data_dict,
  758. SD.load_demo_proc_dict)
  759. return ret
  760. if request.method == 'PUT':
  761. from .demo import stop_import_demo
  762. ret = stop_import_demo(data, SD.workspace, SD.load_demo_proc_dict,
  763. SD.load_demo_proj_data_dict)
  764. return ret
  765. @app.route('/model', methods=['GET', 'POST', 'DELETE'])
  766. def model():
  767. '''
  768. methods=='GET':#获取一个或者所有模型的信息
  769. Args:
  770. mid(str,optional)可选,若存在则返回某个模型的信息
  771. type(str,optional)可选,[pretrained,exported].若存在则返回对应类型下所有的模型信息
  772. Return:
  773. status
  774. if mid in Args:
  775. dataset_attr(dict):数据集属性
  776. task_params(dict):模型训练参数
  777. eval_result(dict):模型评估结果
  778. if type in Args and type == 'pretrained':
  779. pretrained_models(list):所有预训练模型信息
  780. if type in Args and type == 'exported':
  781. exported_models(list):所有inference模型的信息
  782. methods=='POST':#创建一个模型
  783. Args:
  784. pid(str):项目id
  785. tid(str):任务id
  786. name(str):模型名字
  787. type(str):创建模型的类型,[pretrained,exported],pretrained代表创建预训练模型、exported代表创建inference或者lite模型
  788. source_path(str):仅type为pretrained时有效,训练好的模型的路径
  789. path(str):仅type为exported时有效,inference或者lite模型的路径
  790. exported_type(int):0为inference模型,1为lite模型
  791. eval_results(dict,optional):可选,仅type为pretrained时有效,模型评估的指标
  792. Return:
  793. status
  794. if type == 'pretrained':
  795. pmid(str):预训练模型id
  796. if type == 'exported':
  797. emid(str):inference模型id
  798. methods=='DELETE':删除一个模型
  799. Args:
  800. type(str):删除模型的类型,[pretrained,exported],pretrained代表创建预训练模型、exported代表创建inference或者lite模型
  801. if type='pretrained':
  802. pmid:预训练模型id
  803. if type='exported':
  804. emid:inference或者lite模型id
  805. Return:
  806. status
  807. '''
  808. data = request.get_json()
  809. if data is None:
  810. data = request.args
  811. if request.method == 'GET':
  812. if 'type' in data:
  813. if data['type'] == 'pretrained':
  814. from .model import list_pretrained_models
  815. ret = list_pretrained_models(SD.workspace)
  816. return ret
  817. if data['type'] == 'exported':
  818. from .model import list_exported_models
  819. ret = list_exported_models(SD.workspace)
  820. return ret
  821. from .model import get_model_details
  822. ret = get_model_details(data, SD.workspace)
  823. ret['eval_result']['Confusion_Matrix'] = ret['eval_result'][
  824. 'Confusion_Matrix'].tolist()
  825. ret['eval_result'] = CustomEncoder().encode(ret['eval_result'])
  826. ret['task_params'] = CustomEncoder().encode(ret['task_params'])
  827. return ret
  828. if request.method == 'POST':
  829. if data['type'] == 'pretrained':
  830. if 'eval_results' in data:
  831. data['eval_results']['Confusion_Matrix'] = np.array(data[
  832. 'eval_results']['Confusion_Matrix'])
  833. from .model import create_pretrained_model
  834. ret = create_pretrained_model(data, SD.workspace,
  835. SD.monitored_processes)
  836. return ret
  837. if data['type'] == 'exported':
  838. from .model import create_exported_model
  839. ret = create_exported_model(data, SD.workspace)
  840. return ret
  841. if request.method == 'DELETE':
  842. if data['type'] == 'pretrained':
  843. from .model import delete_pretrained_model
  844. ret = delete_pretrained_model(data, SD.workspace)
  845. return ret
  846. if data['type'] == 'exported':
  847. from .model import delete_exported_model
  848. ret = delete_exported_model(data, SD.workspace)
  849. return ret
  850. @app.route('/model/file', methods=['GET'])
  851. def model_file():
  852. data = request.get_json()
  853. if request.method == 'GET':
  854. ret = data['path']
  855. assert os.path.abspath(ret).startswith(
  856. os.path.abspath(SD.workspace_dir)
  857. ) and ".." not in ret, "Illegal path {}.".format(ret)
  858. return send_file(ret)
  859. @app.route('/', methods=['GET'])
  860. def gui():
  861. if request.method == 'GET':
  862. file_path = osp.join(
  863. osp.dirname(__file__), 'templates', 'paddlex_restful_demo.html')
  864. ip = get_ip()
  865. url = 'var str_srv_url = "http://' + ip + ':' + str(SD.port) + '";'
  866. f = open(file_path, 'r+', encoding=get_encoding(file_path))
  867. lines = f.readlines()
  868. for i, line in enumerate(lines):
  869. if '0.0.0.0:8080' in line:
  870. lines[i] = url
  871. break
  872. f.close()
  873. f = open(file_path, 'w+', encoding=get_encoding(file_path))
  874. f.writelines(lines)
  875. f.close()
  876. return render_template('/paddlex_restful_demo.html')
  877. def run(port, workspace_dir):
  878. if workspace_dir is None:
  879. user_home = os.path.expanduser('~')
  880. dirname = osp.join(user_home, "paddlex_workspace")
  881. else:
  882. dirname = workspace_dir
  883. if not osp.exists(dirname):
  884. os.makedirs(dirname)
  885. else:
  886. if not osp.isdir(dirname):
  887. os.remove(dirname)
  888. os.makedirs(dirname)
  889. logger = get_logger(osp.join(dirname, "mcessages.log"))
  890. init(dirname, logger)
  891. SD.port = port
  892. ip = get_ip()
  893. url = ip + ':' + str(port)
  894. try:
  895. logger.info("RESTful服务启动成功后,您可以在浏览器打开 {} 使用WEB版本GUI".format(url))
  896. app.run(host='0.0.0.0', port=port, threaded=True)
  897. except:
  898. print("服务启动不成功,请确保端口号:{}未被防火墙限制".format(port))