From 0faf1324a4143b89c2d9ef0946ab494498a63e0a Mon Sep 17 00:00:00 2001 From: lext Date: Sat, 23 Nov 2019 21:40:38 +0200 Subject: [PATCH] added socketio, logging etc. Added old frontend for further modifications. --- deploy.sh | 1 + docker/docker-compose-cpu.yml | 13 +++++-- docker/docker-compose-gpu.yml | 8 +++-- ouludeepknee/inference/app.py | 58 +++++++++++++++++++++--------- ouludeepknee/inference/pipeline.py | 22 ++++++++---- 5 files changed, 73 insertions(+), 29 deletions(-) diff --git a/deploy.sh b/deploy.sh index ffd3e52..0ebb53f 100644 --- a/deploy.sh +++ b/deploy.sh @@ -10,6 +10,7 @@ fi if [ ! -d "snapshots_knee_grading" ]; then sh ./fetch_snapshots.sh fi +mkdir -p logs docker-compose -f ./docker/docker-compose-$1.yml down docker-compose -f ./docker/docker-compose-$1.yml build diff --git a/docker/docker-compose-cpu.yml b/docker/docker-compose-cpu.yml index 7c1fb81..8848bba 100644 --- a/docker/docker-compose-cpu.yml +++ b/docker/docker-compose-cpu.yml @@ -9,11 +9,14 @@ services: source: ../snapshots_release_kneel # The snapshots are stored in the root directory target: /snapshots/ read_only: true + - type: bind + source: ../logs + target: /logs/ entrypoint: ["python", "-u", "-m", "kneel.inference.app", "--lc_snapshot_path", "/snapshots/lext-devbox_2019_07_14_16_04_41", "--hc_snapshot_path", "/snapshots/lext-devbox_2019_07_14_19_25_40", "--refine", "True", "--mean_std_path", "/snapshots/mean_std.npy", - "--deploy", "True", "--device", "cpu", "--port", "5000"] + "--deploy", "True", "--device", "cpu", "--port", "5000", "--logs", "/logs/kneel-cpu.log"] deepknee-backend: depends_on: - kneel @@ -27,8 +30,12 @@ services: source: ../snapshots_knee_grading/ # The snapshots are stored in the root directory target: /snapshots/ read_only: true + - type: bind + source: ../logs + target: /logs/ environment: - KNEEL_ADDR=http://kneel:5000 entrypoint: ["python", "-m", "ouludeepknee.inference.app", - "--snapshots_path", "/snapshots/", - "--device", "cpu", "--deploy", "True"] + "--snapshots_path", "/snapshots/", "--device", "cpu", "--deploy", "True", + "--port", "5001", + "--logs", "/logs/deepknee-cpu.log"] diff --git a/docker/docker-compose-gpu.yml b/docker/docker-compose-gpu.yml index 62f4136..f503eaa 100644 --- a/docker/docker-compose-gpu.yml +++ b/docker/docker-compose-gpu.yml @@ -10,11 +10,14 @@ services: source: ../snapshots_release_kneel # The snapshots are stored in the root directory target: /snapshots/ read_only: true + - type: bind + source: ../logs + target: /logs/ entrypoint: ["python", "-u", "-m", "kneel.inference.app", "--lc_snapshot_path", "/snapshots/lext-devbox_2019_07_14_16_04_41", "--hc_snapshot_path", "/snapshots/lext-devbox_2019_07_14_19_25_40", "--refine", "True", "--mean_std_path", "/snapshots/mean_std.npy", - "--deploy", "True", "--device", "cuda", "--port", "5000"] + "--deploy", "True", "--device", "cuda", "--port", "5000", "--logs", "/logs/kneel-gpu.log"] deepknee-backend: runtime: nvidia depends_on: @@ -33,4 +36,5 @@ services: - KNEEL_ADDR=http://kneel:5000 entrypoint: ["python", "-m", "ouludeepknee.inference.app", "--snapshots_path", "/snapshots/", - "--device", "cuda", "--deploy", "True"] + "--device", "cuda", "--deploy", "True", + "--port", "5001", "--logs", "/logs/deepknee-cpu.log"] diff --git a/ouludeepknee/inference/app.py b/ouludeepknee/inference/app.py index 6ee805a..ff60b0f 100644 --- a/ouludeepknee/inference/app.py +++ b/ouludeepknee/inference/app.py @@ -13,10 +13,15 @@ from flask import Flask, request from flask import jsonify, make_response from gevent.pywsgi import WSGIServer +import socketio +import eventlet + import logging from ouludeepknee.inference.pipeline import KneeNetEnsemble app = Flask(__name__) +# Wrap Flask application with socketio's middleware +sio = socketio.Server(ping_timeout=120, ping_interval=120) def numpy2base64(img): @@ -24,14 +29,10 @@ def numpy2base64(img): return 'data:image/png;base64,' + base64.b64encode(buffer).decode('ascii') -# curl -F dicom=@01 -X POST http://127.0.0.1:5001/predict/bilateral -@app.route('/predict/bilateral', methods=['POST']) -def analyze_knee(): - if os.environ['KNEEL_ADDR'] == '': - return make_response(jsonify({'msg': 'KNEEL microservice is not defined'}), 500) - dicom_raw = request.files['dicom'].read() +def call_pipeline(dicom_raw, landmarks=None): # Localization of ROIs and their conversion into 8-bit 140x140mm images - res_bilateral = net.predict_draw_bilateral(dicom_raw, args.sizemm, args.pad) + res_bilateral = net.predict_draw_bilateral(dicom_raw, args.sizemm, args.pad, + kneel_addr=os.environ['KNEEL_ADDR'], landmarks=landmarks) if res_bilateral is None: return make_response(jsonify({'msg': 'Could not localize the landmarks'}), 400) @@ -46,17 +47,33 @@ def analyze_knee(): 'preds_bar': numpy2base64(preds_bar_r), 'kl': str(pred_r)}, 'msg': 'Finished!'} + return response + +# curl -F dicom=@01 -X POST http://127.0.0.1:5001/deepknee/predict/bilateral +@app.route('/deepknee/predict/bilateral', methods=['POST']) +def analyze_knee(): + logger = logging.getLogger(f'deepknee-backend:app') + dicom_raw = request.files['dicom'].read() + logger.log(logging.INFO, f'Received DICOM') + + if os.environ['KNEEL_ADDR'] == '': + return make_response(jsonify({'msg': 'KNEEL microservice is not defined'}), 500) + + response = call_pipeline(dicom_raw) return make_response(response, 200) -@app.route('/predict/single', methods=['POST']) -def analyze_single_knee(): - """ - Runs prediction for a single cropped right knee X-ray. +@sio.on('dicom_submission', namespace='/deepknee/sockets') +def on_dicom_submission(sid, data): + sio.emit('dicom_received', dict(), room=sid, namespace='/deepknee/sockets') + logger.info(f'Sent a message back to {sid}') + sio.sleep(0) - """ - raise NotImplementedError + tmp = data['file_blob'].split(',', 1)[1] + response = call_pipeline(base64.b64decode(tmp)) + # Send out the results + sio.emit('dicom_processed', response, room=sid, namespace='/deepknee/sockets') if __name__ == '__main__': @@ -64,20 +81,27 @@ def analyze_single_knee(): parser.add_argument('--snapshots_path', default='') parser.add_argument('--deploy_addr', default='0.0.0.0') parser.add_argument('--device', default='cuda') + parser.add_argument('--port', type=int, default=5001) parser.add_argument('--sizemm', type=int, default=140) parser.add_argument('--pad', type=int, default=300) parser.add_argument('--deploy', type=bool, default=False) parser.add_argument('--logs', type=str, default='/tmp/deepknee.log') args = parser.parse_args() - logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.DEBUG) + logging.basicConfig(filename=args.logs, filemode='a', + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.DEBUG) + logger = logging.getLogger(f'deepknee-backend:app') net = KneeNetEnsemble(glob.glob(os.path.join(args.snapshots_path, "*", '*.pth')), mean_std_path=os.path.join(args.snapshots_path, 'mean_std.npy'), device=args.device) + app = socketio.WSGIApp(sio, app, socketio_path='/deepknee/sockets/socket.io') + if args.deploy: - http_server = WSGIServer((args.deploy_addr, 5001), app, log=logger) - http_server.serve_forever() + # Deploy as an eventlet WSGI server + eventlet.wsgi.server(eventlet.listen((args.deploy_addr, args.port)), app, log=logger) + # http_server = WSGIServer((args.deploy_addr, 5001), app, log=logger) + # http_server.serve_forever() else: - app.run(host=args.deploy_addr, port=5001, debug=True) + app.run(host=args.deploy_addr, port=args.port, debug=True) diff --git a/ouludeepknee/inference/pipeline.py b/ouludeepknee/inference/pipeline.py index 168889a..cfa813c 100644 --- a/ouludeepknee/inference/pipeline.py +++ b/ouludeepknee/inference/pipeline.py @@ -338,14 +338,23 @@ def predict_draw(self, fileobj_in, nbits=16, fname_suffix=None, path_dir_out=Non self.logger.log(logging.INFO, f'Sending the results back to the user') return img, img_overlayed, probs_bar, probs.squeeze().argmax() - def localize_bilateral(self, dicom_raw, sizemm, pad): - files = {'dicom': dicom_raw} + def request_landmarks(self, kneel_addr, file): self.logger.log(logging.INFO, f'Sending the image to KNEEL: {os.environ["KNEEL_ADDR"]}') - response = requests.post(f'{os.environ["KNEEL_ADDR"]}/predict/bilateral', files=files) + if kneel_addr is None: + kneel_addr = os.environ["KNEEL_ADDR"] + + response = requests.post(f'{kneel_addr}/kneel/predict/bilateral', files=file) landmarks = response.json() + return landmarks + + def localize_bilateral(self, dicom_raw, sizemm, pad, kneel_addr=None, landmarks=None): + if landmarks is None: + landmarks = self.request_landmarks(kneel_addr, {'dicom': dicom_raw}) + if landmarks['R'] is None: - self.logger.log(logging.INFO, f'Landmarks have not been localized. Returning None') + self.logger.log(logging.INFO, f'Landmarks are not found. Returning None') return None + self.logger.log(logging.INFO, f'Image decoding and pre-processing started') raw = DicomBytesIO(dicom_raw) dicom_data = dcmread(raw) @@ -373,9 +382,8 @@ def localize_bilateral(self, dicom_raw, sizemm, pad): self.logger.log(logging.INFO, f'Returning localized left and right knees') return img_left, img_right - def predict_draw_bilateral(self, dicom_raw, sizemm, pad): - self.logger.log(logging.INFO, f'Received DICOM') - res_landmarks = self.localize_bilateral(dicom_raw, sizemm, pad) + def predict_draw_bilateral(self, dicom_raw, sizemm, pad, kneel_addr=None, landmarks=None): + res_landmarks = self.localize_bilateral(dicom_raw, sizemm, pad, kneel_addr, landmarks) if res_landmarks is None: return None